From b394a294a412c8b07b7e0c47d2fb46aedc7db49d Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 13 Sep 2025 19:45:33 -0500 Subject: [PATCH 01/32] init(graphql): adds Functor, fold --- examples/sandbox/src/graphql.ts | 385 ++++++++++++++++++++++++++++++++ examples/sandbox/src/main.tsx | 113 ++++++++++ 2 files changed, 498 insertions(+) create mode 100644 examples/sandbox/src/graphql.ts diff --git a/examples/sandbox/src/graphql.ts b/examples/sandbox/src/graphql.ts new file mode 100644 index 00000000..eb20711d --- /dev/null +++ b/examples/sandbox/src/graphql.ts @@ -0,0 +1,385 @@ +import type * as T from '@traversable/registry' +import { fn, has } from '@traversable/registry' + +// interface Query { +// kind: 'OperationDefinition' +// operation: 'query' +// name: Name +// variableDefinitions: readonly VariableDefinition[], +// directives: readonly T[] +// selectionSet: { +// kind: 'SelectionSet' +// selections: [ +// ], +// } +// } + +export interface Name { + kind: 'Name' + value: Value +} + +export interface Named { + name: Name +} + +/** + * ## {@link Ref `Ref`} + * + * A {@link Ref `Ref`} is a named type that is not one of the built-in types. + */ +interface Ref { + kind: 'NamedType' + name: Name +} + +export interface Document { + kind: 'Document' + definitions: readonly T[] +} + +export interface InputValue { + kind: 'InputValueDefinition' + name: Name + type: T + directives: readonly T[] +} + +export interface InputObject { + kind: 'InputObjectTypeDefinition' + name: Name + fields: readonly T[] + directives: readonly T[] +} + +export interface Variable { + kind: 'Variable' + name: Name +} + +export interface VariableDefinition { + kind: 'VariableDefinition' + variable: Variable + type: T + directives: readonly T[] +} + +export type Nullary = + | Boolean + | Int + | Number + | Float + | String + | ID + | Scalar + | Enum + +export interface Scalar { + kind: 'ScalarTypeDefinition' + name: Name +} + + +export interface Boolean { + kind: 'NamedType' + name: Name<'Boolean'> +} + +export interface Int { + kind: 'NamedType' + name: Name<'Int'> +} + +export interface Number { + kind: 'NamedType' + name: Name<'Number'> +} + +export interface Float { + kind: 'NamedType' + name: Name<'Float'> +} + +export interface String { + kind: 'NamedType' + name: Name<'String'> +} + +export interface ID { + kind: 'NamedType' + name: Name<'ID'> +} + +export interface EnumValue { + kind: 'EnumValueDefinition' + name: Name +} + +export interface Enum { + kind: 'EnumTypeDefinition' + name: Name + values: readonly EnumValue[] +} + +export interface NonNull { + kind: 'NonNullType' + type: T +} + +export interface List { + kind: 'ListType' + type: T +} + +export interface Field { + kind: 'FieldDefinition' + name: Name + type: T + defaultValue?: unknown + arguments: readonly T[] + directives: readonly T[] +} + +export interface Object { + kind: 'ObjectTypeDefinition' + name: Name + fields: readonly T[] + interfaces: readonly T[] + directives: readonly T[] +} + +export interface Interface { + kind: 'InterfaceTypeDefinition' + name: Name + fields: readonly T[] + interfaces: readonly T[] + directives: readonly T[] +} + +export interface Union { + kind: 'UnionTypeDefinition' + name: Name + types: readonly T[] + directives: readonly T[] +} + +export type Unary = + | NonNull + | List + | Field + | Object + | Interface + | Union + | InputValue + | InputObject + | Document + +export type Fixpoint = + | Nullary + | Ref + | NonNull + | List + | Field + | Object + | Interface + | Union + | InputValue + | InputObject + | Document + +export type F = + | Nullary + | Ref + | Unary + +export function isScalar(x: unknown): x is Scalar { + return has('kind', (kind) => kind === 'ScalarTypeDefinition')(x) +} + +export function isBoolean(x: unknown): x is Boolean { + return has('name', 'value', (value) => value === 'Boolean')(x) +} + +export function isInt(x: unknown): x is Int { + return has('name', 'value', (value) => value === 'Int')(x) +} + +export function isNumber(x: unknown): x is Number { + return has('name', 'value', (value) => value === 'Number')(x) +} + +export function isFloat(x: unknown): x is Float { + return has('name', 'value', (value) => value === 'Float')(x) +} + +export function isString(x: unknown): x is String { + return has('name', 'value', (value) => value === 'String')(x) +} + +export function isID(x: unknown): x is ID { + return has('name', 'value', (value) => value === 'ID')(x) +} + +export function isEnum(x: unknown): x is Enum { + return has('name', 'value', (value) => value === 'ID')(x) +} + +export function isNonNull(x: unknown): x is NonNull { + return has('kind', (kind) => kind === 'NonNullType')(x) +} + +export function isList(x: unknown): x is List { + return has('kind', (kind) => kind === 'ListType')(x) +} + +export function isField(x: unknown): x is Field { + return has('kind', (kind) => kind === 'FieldDefinition')(x) +} + +export function isObject(x: unknown): x is Object { + return has('kind', (kind) => kind === 'ObjectTypeDefinition')(x) +} + +export function isInterface(x: unknown): x is Interface { + return has('kind', (kind) => kind === 'InterfaceTypeDefinition')(x) +} + +export function isUnion(x: unknown): x is Union { + return has('kind', (kind) => kind === 'UnionTypeDefinition')(x) +} + +export function isInputValue(x: unknown): x is InputValue { + return has('kind', (kind) => kind === 'InputValueDefinition')(x) +} + +export function isInputObject(x: unknown): x is InputObject { + return has('kind', (kind) => kind === 'InputObjectTypeDefinition')(x) +} + +export function isDocument(x: unknown): x is Document { + return has('kind', (kind) => kind === 'Document')(x) +} + +export function isNullary(x: unknown): x is Nullary { + return isScalar(x) + || isBoolean(x) + || isInt(x) + || isNumber(x) + || isFloat(x) + || isString(x) + || isID(x) + || isEnum(x) +} + +export function isNamed(x: unknown): x is Named { + return has('name', 'value', (value) => typeof value === 'string')(x) +} + +export function isRef(x: unknown): x is Ref { + return has('kind', (kind) => kind === 'NamedType')(x) + && has('name', 'value', (value) => typeof value === 'string')(x) + && !isNullary(x) +} + +export function isUnary(x: unknown): x is Unary { + return isNonNull(x) + || isList(x) + || isObject(x) + || isInterface(x) + || isUnion(x) + || isInputObject(x) +} + +export const defaultIndex = { isNonNull: false } satisfies Index + +export interface Index { + isNonNull: boolean +} + +export type Algebra = { + (src: F, ix?: Index): T + (src: Fixpoint, ix?: Index): T + (src: F, ix?: Index): T +} + +export type Fold = (g: (src: F, ix: Index, x: Fixpoint) => T) => Algebra + +export interface Functor extends T.HKT { [-1]: F } + +export declare namespace Functor { + export { Index } +} + +export const Functor: T.Functor.Ix = { + map(g) { + return (x) => { + switch (true) { + default: return x + case isRef(x): return x + case isNullary(x): return x + case isNonNull(x): return { ...x, type: g(x.type) } + case isList(x): return { ...x, type: g(x.type) } + case isUnion(x): return { ...x, directives: x.directives.map(g), types: x.types.map(g) } + case isField(x): return { ...x, type: g(x.type), arguments: x.arguments.map(g), directives: x.directives.map(g) } + case isObject(x): return { ...x, directives: x.directives.map(g), interfaces: x.interfaces.map(g), fields: x.fields.map(g) } + case isInterface(x): return { ...x, directives: x.directives.map(g), interfaces: x.interfaces.map(g), fields: x.fields.map(g) } + case isInputValue(x): return { ...x, type: g(x.type), directives: x.directives.map(g) } + case isInputObject(x): return { ...x, directives: x.directives.map(g), fields: x.fields.map(g) } + case isDocument(x): return { ...x, definitions: x.definitions.map(g) } + } + } + }, + mapWithIndex(g) { + return (x, _ix) => { + const ix = isNonNull(x) ? { isNonNull: true } satisfies Index : defaultIndex + switch (true) { + default: return x + case isRef(x): return x + case isNullary(x): return x + case isNonNull(x): return { ...x, type: g(x.type, ix, x) } + case isList(x): return { ...x, type: g(x.type, ix, x) } + case isUnion(x): return { + ...x, + directives: x.directives.map((_) => g(_, ix, x)), + types: x.types.map((_) => g(_, ix, x)) + } + case isField(x): { + return { + ...x, + type: g(x.type, isNonNull(x.type) ? { isNonNull: true } : ix, x), + arguments: x.arguments.map((_) => g(_, ix, x)), + directives: x.directives.map((_) => g(_, ix, x)), + } + } + case isObject(x): return { + ...x, + directives: x.directives.map((_) => g(_, ix, x)), + fields: x.fields.map((_) => g(_, ix, x)), + interfaces: x.interfaces.map((_) => g(_, ix, x)), + } + case isInterface(x): return { + ...x, + directives: x.directives.map((_) => g(_, ix, x)), + fields: x.fields.map((_) => g(_, ix, x)), + interfaces: x.interfaces.map((_) => g(_, ix, x)), + } + case isInputValue(x): return { + ...x, + type: g(x.type, ix, x), + directives: x.directives.map((_) => g(_, ix, x)), + } + case isInputObject(x): return { + ...x, + directives: x.directives.map((_) => g(_, ix, x)), + fields: x.fields.map((_) => g(_, ix, x)), + } + case isDocument(x): return { + ...x, + definitions: x.definitions.map((_) => g(_, ix, x)), + } + } + } + } +} + +export const fold: Fold = fn.catamorphism(Functor, defaultIndex) as never diff --git a/examples/sandbox/src/main.tsx b/examples/sandbox/src/main.tsx index e2d8b76a..e4c743b1 100644 --- a/examples/sandbox/src/main.tsx +++ b/examples/sandbox/src/main.tsx @@ -2,6 +2,119 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' import { Sandbox } from './sandbox.tsx' +import { has, parseKey } from '@traversable/registry' + +import * as AST from './graphql.ts' + +const test = { + "kind": "Document", + "definitions": [ + { + "kind": "ObjectTypeDefinition", + "name": { + "kind": "Name", + "value": "Pet", + "loc": { + "start": 133, + "end": 136 + } + }, + "interfaces": [], + "directives": [], + "fields": [ + { + "kind": "FieldDefinition", + "name": { + "kind": "Name", + "value": "name", + "loc": { + "start": 141, + "end": 145 + } + }, + "arguments": [], + "type": { + "kind": "NamedType", + "name": { + "kind": "Name", + "value": "String", + "loc": { + "start": 147, + "end": 153 + } + }, + "loc": { + "start": 147, + "end": 153 + } + }, + "directives": [], + "loc": { + "start": 141, + "end": 153 + } + } + ], + "loc": { + "start": 128, + "end": 155 + } + } + ], + "loc": { + "start": 0, + "end": 156 + } +} as const + +const algebra = AST.fold((x, ix, original) => { + switch (true) { + default: return x satisfies never + case AST.isDocument(x): throw Error('Nesting documents is not allowed') + case AST.isInputValue(x): throw Error('Input values cannot be converted to a type') + case AST.isInputObject(x): throw Error('Input objects cannot be converted to a type') + case AST.isRef(x): return x.name.value + case AST.isScalar(x): return x.name.value + case AST.isBoolean(x): return 'boolean' + case AST.isInt(x): return 'number' + case AST.isNumber(x): return 'number' + case AST.isFloat(x): return 'number' + case AST.isString(x): return 'string' + case AST.isID(x): return 'string' + case AST.isEnum(x): return ( + x.values.length === 0 ? 'never' + : x.values.length === 1 ? JSON.stringify(x.values[0]) + : `(${x.values.map((v) => JSON.stringify(v)).join(' | ')})` + ) + case AST.isNonNull(x): return `${x.type}!` + case AST.isUnion(x): return ( + x.types.length === 0 ? 'never' + : x.types.length === 1 ? JSON.stringify(x.types[0]) + : `(${x.types.map((v) => JSON.stringify(v)).join(' | ')})` + ) + case AST.isList(x): { + if (!AST.isList(original)) throw Error('Illegal state') + const isNonNull = x.type.endsWith('!') + const TYPE = isNonNull ? x.type.slice(0, -1) : `${x.type} | null` + return `Array<${TYPE}>` + } + case AST.isField(x): { + const isNonNull = x.type.endsWith('!') + const VALUE = isNonNull ? x.type.slice(0, -1) : x.type + return `${parseKey(x.name.value)}${isNonNull ? '' : '?'}: ${VALUE}` + } + case AST.isObject(x): { return x.fields.length === 0 ? `{}` : `{ ${x.fields.join(', ')} }` } + case AST.isInterface(x): { return x.fields.length === 0 ? `{}` : `{ ${x.fields.join(', ')} }` } + } +}) + +function toType(doc: AST.Document) { + const types = doc.definitions.map((def, i) => `type ${AST.isNamed(def) ? def.name.value : `Type${i}`} = ${AST.fold(algebra)(def)}`) + return types.join('\n') +} + +console.log('\n\n') +console.log(toType(test)) createRoot(document.getElementById('root')!).render( From cd8d3859ecaa7adf3ae30105f608578db30f9ff9 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Wed, 17 Sep 2025 04:33:01 -0500 Subject: [PATCH 02/32] init(graphql,graphql-test,graphql-types): initializes `@traversable/graphql`, `@traverable/graphql-test`, `@traversable/graphql-types` --- bin/workspace.ts | 4 +- config/__generated__/package-list.ts | 3 + examples/sandbox/src/graphql.ts | 385 ----------------- examples/sandbox/src/main.tsx | 113 ----- packages/graphql-test/README.md | 40 ++ packages/graphql-test/package.json | 46 +++ .../src/__generated__/__manifest__.ts | 46 +++ packages/graphql-test/src/exports.ts | 1 + packages/graphql-test/src/index.ts | 1 + packages/graphql-test/src/version.ts | 3 + packages/graphql-test/test/version.test.ts | 10 + packages/graphql-test/tsconfig.build.json | 11 + packages/graphql-test/tsconfig.json | 8 + packages/graphql-test/tsconfig.src.json | 11 + packages/graphql-test/tsconfig.test.json | 15 + packages/graphql-test/vite.config.ts | 6 + packages/graphql-types/README.md | 40 ++ packages/graphql-types/package.json | 58 +++ .../src/__generated__/__manifest__.ts | 54 +++ packages/graphql-types/src/exports.ts | 35 ++ packages/graphql-types/src/functor.ts | 387 ++++++++++++++++++ packages/graphql-types/src/index.ts | 1 + packages/graphql-types/src/version.ts | 3 + packages/graphql-types/test/functor.test.ts | 150 +++++++ packages/graphql-types/test/version.test.ts | 10 + packages/graphql-types/tsconfig.build.json | 11 + packages/graphql-types/tsconfig.json | 8 + packages/graphql-types/tsconfig.src.json | 11 + packages/graphql-types/tsconfig.test.json | 14 + packages/graphql-types/vite.config.ts | 6 + packages/graphql/README.md | 40 ++ packages/graphql/package.json | 46 +++ .../graphql/src/__generated__/__manifest__.ts | 46 +++ packages/graphql/src/exports.ts | 1 + packages/graphql/src/index.ts | 1 + packages/graphql/src/version.ts | 3 + packages/graphql/test/version.test.ts | 10 + packages/graphql/tsconfig.build.json | 11 + packages/graphql/tsconfig.json | 8 + packages/graphql/tsconfig.src.json | 11 + packages/graphql/tsconfig.test.json | 15 + packages/graphql/vite.config.ts | 6 + pnpm-lock.yaml | 30 ++ tsconfig.base.json | 6 + tsconfig.build.json | 3 + tsconfig.json | 3 + 46 files changed, 1232 insertions(+), 499 deletions(-) delete mode 100644 examples/sandbox/src/graphql.ts create mode 100644 packages/graphql-test/README.md create mode 100644 packages/graphql-test/package.json create mode 100644 packages/graphql-test/src/__generated__/__manifest__.ts create mode 100644 packages/graphql-test/src/exports.ts create mode 100644 packages/graphql-test/src/index.ts create mode 100644 packages/graphql-test/src/version.ts create mode 100644 packages/graphql-test/test/version.test.ts create mode 100644 packages/graphql-test/tsconfig.build.json create mode 100644 packages/graphql-test/tsconfig.json create mode 100644 packages/graphql-test/tsconfig.src.json create mode 100644 packages/graphql-test/tsconfig.test.json create mode 100644 packages/graphql-test/vite.config.ts create mode 100644 packages/graphql-types/README.md create mode 100644 packages/graphql-types/package.json create mode 100644 packages/graphql-types/src/__generated__/__manifest__.ts create mode 100644 packages/graphql-types/src/exports.ts create mode 100644 packages/graphql-types/src/functor.ts create mode 100644 packages/graphql-types/src/index.ts create mode 100644 packages/graphql-types/src/version.ts create mode 100644 packages/graphql-types/test/functor.test.ts create mode 100644 packages/graphql-types/test/version.test.ts create mode 100644 packages/graphql-types/tsconfig.build.json create mode 100644 packages/graphql-types/tsconfig.json create mode 100644 packages/graphql-types/tsconfig.src.json create mode 100644 packages/graphql-types/tsconfig.test.json create mode 100644 packages/graphql-types/vite.config.ts create mode 100644 packages/graphql/README.md create mode 100644 packages/graphql/package.json create mode 100644 packages/graphql/src/__generated__/__manifest__.ts create mode 100644 packages/graphql/src/exports.ts create mode 100644 packages/graphql/src/index.ts create mode 100644 packages/graphql/src/version.ts create mode 100644 packages/graphql/test/version.test.ts create mode 100644 packages/graphql/tsconfig.build.json create mode 100644 packages/graphql/tsconfig.json create mode 100644 packages/graphql/tsconfig.src.json create mode 100644 packages/graphql/tsconfig.test.json create mode 100644 packages/graphql/vite.config.ts diff --git a/bin/workspace.ts b/bin/workspace.ts index c554938f..1a80850c 100755 --- a/bin/workspace.ts +++ b/bin/workspace.ts @@ -430,6 +430,7 @@ namespace write { ($) => pipe( [ `export * from './exports.js'`, + null ].join('\n'), $.dryRun ? tap(`\n\n[CREATE #10]: workspaceIndex\n`, globalThis.String) : fs.writeString(path.join(PATH.packages, $.pkgName, 'src', 'index.ts')), @@ -443,7 +444,8 @@ namespace write { export const workspaceSrcExports = defineEffect( ($) => pipe( [ - `export * from './version.js'`, + `export { VERSION } from './version.js'`, + null ].join('\n'), $.dryRun ? tap(`\n\n[CREATE #11]: workspaceSrcExports\n`, globalThis.String) : fs.writeString(path.join(PATH.packages, $.pkgName, 'src', 'exports.ts')), diff --git a/config/__generated__/package-list.ts b/config/__generated__/package-list.ts index 9aee010d..dee58135 100644 --- a/config/__generated__/package-list.ts +++ b/config/__generated__/package-list.ts @@ -2,6 +2,9 @@ export const PACKAGES = [ "packages/arktype", "packages/arktype-test", "packages/arktype-types", + "packages/graphql", + "packages/graphql-test", + "packages/graphql-types", "packages/json", "packages/json-schema", "packages/json-schema-test", diff --git a/examples/sandbox/src/graphql.ts b/examples/sandbox/src/graphql.ts deleted file mode 100644 index eb20711d..00000000 --- a/examples/sandbox/src/graphql.ts +++ /dev/null @@ -1,385 +0,0 @@ -import type * as T from '@traversable/registry' -import { fn, has } from '@traversable/registry' - -// interface Query { -// kind: 'OperationDefinition' -// operation: 'query' -// name: Name -// variableDefinitions: readonly VariableDefinition[], -// directives: readonly T[] -// selectionSet: { -// kind: 'SelectionSet' -// selections: [ -// ], -// } -// } - -export interface Name { - kind: 'Name' - value: Value -} - -export interface Named { - name: Name -} - -/** - * ## {@link Ref `Ref`} - * - * A {@link Ref `Ref`} is a named type that is not one of the built-in types. - */ -interface Ref { - kind: 'NamedType' - name: Name -} - -export interface Document { - kind: 'Document' - definitions: readonly T[] -} - -export interface InputValue { - kind: 'InputValueDefinition' - name: Name - type: T - directives: readonly T[] -} - -export interface InputObject { - kind: 'InputObjectTypeDefinition' - name: Name - fields: readonly T[] - directives: readonly T[] -} - -export interface Variable { - kind: 'Variable' - name: Name -} - -export interface VariableDefinition { - kind: 'VariableDefinition' - variable: Variable - type: T - directives: readonly T[] -} - -export type Nullary = - | Boolean - | Int - | Number - | Float - | String - | ID - | Scalar - | Enum - -export interface Scalar { - kind: 'ScalarTypeDefinition' - name: Name -} - - -export interface Boolean { - kind: 'NamedType' - name: Name<'Boolean'> -} - -export interface Int { - kind: 'NamedType' - name: Name<'Int'> -} - -export interface Number { - kind: 'NamedType' - name: Name<'Number'> -} - -export interface Float { - kind: 'NamedType' - name: Name<'Float'> -} - -export interface String { - kind: 'NamedType' - name: Name<'String'> -} - -export interface ID { - kind: 'NamedType' - name: Name<'ID'> -} - -export interface EnumValue { - kind: 'EnumValueDefinition' - name: Name -} - -export interface Enum { - kind: 'EnumTypeDefinition' - name: Name - values: readonly EnumValue[] -} - -export interface NonNull { - kind: 'NonNullType' - type: T -} - -export interface List { - kind: 'ListType' - type: T -} - -export interface Field { - kind: 'FieldDefinition' - name: Name - type: T - defaultValue?: unknown - arguments: readonly T[] - directives: readonly T[] -} - -export interface Object { - kind: 'ObjectTypeDefinition' - name: Name - fields: readonly T[] - interfaces: readonly T[] - directives: readonly T[] -} - -export interface Interface { - kind: 'InterfaceTypeDefinition' - name: Name - fields: readonly T[] - interfaces: readonly T[] - directives: readonly T[] -} - -export interface Union { - kind: 'UnionTypeDefinition' - name: Name - types: readonly T[] - directives: readonly T[] -} - -export type Unary = - | NonNull - | List - | Field - | Object - | Interface - | Union - | InputValue - | InputObject - | Document - -export type Fixpoint = - | Nullary - | Ref - | NonNull - | List - | Field - | Object - | Interface - | Union - | InputValue - | InputObject - | Document - -export type F = - | Nullary - | Ref - | Unary - -export function isScalar(x: unknown): x is Scalar { - return has('kind', (kind) => kind === 'ScalarTypeDefinition')(x) -} - -export function isBoolean(x: unknown): x is Boolean { - return has('name', 'value', (value) => value === 'Boolean')(x) -} - -export function isInt(x: unknown): x is Int { - return has('name', 'value', (value) => value === 'Int')(x) -} - -export function isNumber(x: unknown): x is Number { - return has('name', 'value', (value) => value === 'Number')(x) -} - -export function isFloat(x: unknown): x is Float { - return has('name', 'value', (value) => value === 'Float')(x) -} - -export function isString(x: unknown): x is String { - return has('name', 'value', (value) => value === 'String')(x) -} - -export function isID(x: unknown): x is ID { - return has('name', 'value', (value) => value === 'ID')(x) -} - -export function isEnum(x: unknown): x is Enum { - return has('name', 'value', (value) => value === 'ID')(x) -} - -export function isNonNull(x: unknown): x is NonNull { - return has('kind', (kind) => kind === 'NonNullType')(x) -} - -export function isList(x: unknown): x is List { - return has('kind', (kind) => kind === 'ListType')(x) -} - -export function isField(x: unknown): x is Field { - return has('kind', (kind) => kind === 'FieldDefinition')(x) -} - -export function isObject(x: unknown): x is Object { - return has('kind', (kind) => kind === 'ObjectTypeDefinition')(x) -} - -export function isInterface(x: unknown): x is Interface { - return has('kind', (kind) => kind === 'InterfaceTypeDefinition')(x) -} - -export function isUnion(x: unknown): x is Union { - return has('kind', (kind) => kind === 'UnionTypeDefinition')(x) -} - -export function isInputValue(x: unknown): x is InputValue { - return has('kind', (kind) => kind === 'InputValueDefinition')(x) -} - -export function isInputObject(x: unknown): x is InputObject { - return has('kind', (kind) => kind === 'InputObjectTypeDefinition')(x) -} - -export function isDocument(x: unknown): x is Document { - return has('kind', (kind) => kind === 'Document')(x) -} - -export function isNullary(x: unknown): x is Nullary { - return isScalar(x) - || isBoolean(x) - || isInt(x) - || isNumber(x) - || isFloat(x) - || isString(x) - || isID(x) - || isEnum(x) -} - -export function isNamed(x: unknown): x is Named { - return has('name', 'value', (value) => typeof value === 'string')(x) -} - -export function isRef(x: unknown): x is Ref { - return has('kind', (kind) => kind === 'NamedType')(x) - && has('name', 'value', (value) => typeof value === 'string')(x) - && !isNullary(x) -} - -export function isUnary(x: unknown): x is Unary { - return isNonNull(x) - || isList(x) - || isObject(x) - || isInterface(x) - || isUnion(x) - || isInputObject(x) -} - -export const defaultIndex = { isNonNull: false } satisfies Index - -export interface Index { - isNonNull: boolean -} - -export type Algebra = { - (src: F, ix?: Index): T - (src: Fixpoint, ix?: Index): T - (src: F, ix?: Index): T -} - -export type Fold = (g: (src: F, ix: Index, x: Fixpoint) => T) => Algebra - -export interface Functor extends T.HKT { [-1]: F } - -export declare namespace Functor { - export { Index } -} - -export const Functor: T.Functor.Ix = { - map(g) { - return (x) => { - switch (true) { - default: return x - case isRef(x): return x - case isNullary(x): return x - case isNonNull(x): return { ...x, type: g(x.type) } - case isList(x): return { ...x, type: g(x.type) } - case isUnion(x): return { ...x, directives: x.directives.map(g), types: x.types.map(g) } - case isField(x): return { ...x, type: g(x.type), arguments: x.arguments.map(g), directives: x.directives.map(g) } - case isObject(x): return { ...x, directives: x.directives.map(g), interfaces: x.interfaces.map(g), fields: x.fields.map(g) } - case isInterface(x): return { ...x, directives: x.directives.map(g), interfaces: x.interfaces.map(g), fields: x.fields.map(g) } - case isInputValue(x): return { ...x, type: g(x.type), directives: x.directives.map(g) } - case isInputObject(x): return { ...x, directives: x.directives.map(g), fields: x.fields.map(g) } - case isDocument(x): return { ...x, definitions: x.definitions.map(g) } - } - } - }, - mapWithIndex(g) { - return (x, _ix) => { - const ix = isNonNull(x) ? { isNonNull: true } satisfies Index : defaultIndex - switch (true) { - default: return x - case isRef(x): return x - case isNullary(x): return x - case isNonNull(x): return { ...x, type: g(x.type, ix, x) } - case isList(x): return { ...x, type: g(x.type, ix, x) } - case isUnion(x): return { - ...x, - directives: x.directives.map((_) => g(_, ix, x)), - types: x.types.map((_) => g(_, ix, x)) - } - case isField(x): { - return { - ...x, - type: g(x.type, isNonNull(x.type) ? { isNonNull: true } : ix, x), - arguments: x.arguments.map((_) => g(_, ix, x)), - directives: x.directives.map((_) => g(_, ix, x)), - } - } - case isObject(x): return { - ...x, - directives: x.directives.map((_) => g(_, ix, x)), - fields: x.fields.map((_) => g(_, ix, x)), - interfaces: x.interfaces.map((_) => g(_, ix, x)), - } - case isInterface(x): return { - ...x, - directives: x.directives.map((_) => g(_, ix, x)), - fields: x.fields.map((_) => g(_, ix, x)), - interfaces: x.interfaces.map((_) => g(_, ix, x)), - } - case isInputValue(x): return { - ...x, - type: g(x.type, ix, x), - directives: x.directives.map((_) => g(_, ix, x)), - } - case isInputObject(x): return { - ...x, - directives: x.directives.map((_) => g(_, ix, x)), - fields: x.fields.map((_) => g(_, ix, x)), - } - case isDocument(x): return { - ...x, - definitions: x.definitions.map((_) => g(_, ix, x)), - } - } - } - } -} - -export const fold: Fold = fn.catamorphism(Functor, defaultIndex) as never diff --git a/examples/sandbox/src/main.tsx b/examples/sandbox/src/main.tsx index e4c743b1..e2d8b76a 100644 --- a/examples/sandbox/src/main.tsx +++ b/examples/sandbox/src/main.tsx @@ -2,119 +2,6 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' import { Sandbox } from './sandbox.tsx' -import { has, parseKey } from '@traversable/registry' - -import * as AST from './graphql.ts' - -const test = { - "kind": "Document", - "definitions": [ - { - "kind": "ObjectTypeDefinition", - "name": { - "kind": "Name", - "value": "Pet", - "loc": { - "start": 133, - "end": 136 - } - }, - "interfaces": [], - "directives": [], - "fields": [ - { - "kind": "FieldDefinition", - "name": { - "kind": "Name", - "value": "name", - "loc": { - "start": 141, - "end": 145 - } - }, - "arguments": [], - "type": { - "kind": "NamedType", - "name": { - "kind": "Name", - "value": "String", - "loc": { - "start": 147, - "end": 153 - } - }, - "loc": { - "start": 147, - "end": 153 - } - }, - "directives": [], - "loc": { - "start": 141, - "end": 153 - } - } - ], - "loc": { - "start": 128, - "end": 155 - } - } - ], - "loc": { - "start": 0, - "end": 156 - } -} as const - -const algebra = AST.fold((x, ix, original) => { - switch (true) { - default: return x satisfies never - case AST.isDocument(x): throw Error('Nesting documents is not allowed') - case AST.isInputValue(x): throw Error('Input values cannot be converted to a type') - case AST.isInputObject(x): throw Error('Input objects cannot be converted to a type') - case AST.isRef(x): return x.name.value - case AST.isScalar(x): return x.name.value - case AST.isBoolean(x): return 'boolean' - case AST.isInt(x): return 'number' - case AST.isNumber(x): return 'number' - case AST.isFloat(x): return 'number' - case AST.isString(x): return 'string' - case AST.isID(x): return 'string' - case AST.isEnum(x): return ( - x.values.length === 0 ? 'never' - : x.values.length === 1 ? JSON.stringify(x.values[0]) - : `(${x.values.map((v) => JSON.stringify(v)).join(' | ')})` - ) - case AST.isNonNull(x): return `${x.type}!` - case AST.isUnion(x): return ( - x.types.length === 0 ? 'never' - : x.types.length === 1 ? JSON.stringify(x.types[0]) - : `(${x.types.map((v) => JSON.stringify(v)).join(' | ')})` - ) - case AST.isList(x): { - if (!AST.isList(original)) throw Error('Illegal state') - const isNonNull = x.type.endsWith('!') - const TYPE = isNonNull ? x.type.slice(0, -1) : `${x.type} | null` - return `Array<${TYPE}>` - } - case AST.isField(x): { - const isNonNull = x.type.endsWith('!') - const VALUE = isNonNull ? x.type.slice(0, -1) : x.type - return `${parseKey(x.name.value)}${isNonNull ? '' : '?'}: ${VALUE}` - } - case AST.isObject(x): { return x.fields.length === 0 ? `{}` : `{ ${x.fields.join(', ')} }` } - case AST.isInterface(x): { return x.fields.length === 0 ? `{}` : `{ ${x.fields.join(', ')} }` } - } -}) - -function toType(doc: AST.Document) { - const types = doc.definitions.map((def, i) => `type ${AST.isNamed(def) ? def.name.value : `Type${i}`} = ${AST.fold(algebra)(def)}`) - return types.join('\n') -} - -console.log('\n\n') -console.log(toType(test)) createRoot(document.getElementById('root')!).render( diff --git a/packages/graphql-test/README.md b/packages/graphql-test/README.md new file mode 100644 index 00000000..fcbae809 --- /dev/null +++ b/packages/graphql-test/README.md @@ -0,0 +1,40 @@ +
+

ᯓ𝘁𝗿𝗮𝘃𝗲𝗿𝘀𝗮𝗯𝗹𝗲/𝗴𝗿𝗮𝗽𝗵𝗾𝗹-𝘁𝗲𝘀𝘁

+
+ +

+ TODO: write me +

+ +
+ NPM Version +   + TypeScript +   + License +   + npm +   +
+ +
+ + Static Badge +   + Static Badge +   + Static Badge +   +
+ +
+ Demo (StackBlitz) +   •   + TypeScript Playground +   •   + npm +
+
+
+
diff --git a/packages/graphql-test/package.json b/packages/graphql-test/package.json new file mode 100644 index 00000000..f8b786fc --- /dev/null +++ b/packages/graphql-test/package.json @@ -0,0 +1,46 @@ +{ + "name": "@traversable/graphql-test", + "type": "module", + "version": "0.0.0", + "private": false, + "description": "", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/traversable/schema.git", + "directory": "packages/graphql-test" + }, + "bugs": { + "url": "https://github.com/traversable/schema/issues", + "email": "ahrjarrett@gmail.com" + }, + "@traversable": { + "generateExports": { "include": ["**/*.ts"] }, + "generateIndex": { "include": ["**/*.ts"] } + }, + "publishConfig": { + "access": "public", + "directory": "dist", + "registry": "https://registry.npmjs.org" + }, + "scripts": { + "bench": "echo NOTHING TO BENCH", + "build": "pnpm build:esm && pnpm build:cjs && pnpm build:annotate", + "build:annotate": "babel build --plugins annotate-pure-calls --out-dir build --source-maps", + "build:esm": "tsc -b tsconfig.build.json", + "build:cjs": "babel build/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir build/cjs --source-maps", + "check": "tsc -b tsconfig.json", + "clean": "pnpm run \"/^clean:.*/\"", + "clean:build": "rm -rf .tsbuildinfo dist build", + "clean:deps": "rm -rf node_modules", + "test": "vitest" + }, + "peerDependencies": { + "@traversable/graphql-types": "workspace:^", + "@traversable/registry": "workspace:^" + }, + "devDependencies": { + "@traversable/graphql-types": "workspace:^", + "@traversable/registry": "workspace:^" + } +} diff --git a/packages/graphql-test/src/__generated__/__manifest__.ts b/packages/graphql-test/src/__generated__/__manifest__.ts new file mode 100644 index 00000000..c578a8d7 --- /dev/null +++ b/packages/graphql-test/src/__generated__/__manifest__.ts @@ -0,0 +1,46 @@ +export default { + "name": "@traversable/graphql-test", + "type": "module", + "version": "0.0.0", + "private": false, + "description": "", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/traversable/schema.git", + "directory": "packages/graphql-test" + }, + "bugs": { + "url": "https://github.com/traversable/schema/issues", + "email": "ahrjarrett@gmail.com" + }, + "@traversable": { + "generateExports": { "include": ["**/*.ts"] }, + "generateIndex": { "include": ["**/*.ts"] } + }, + "publishConfig": { + "access": "public", + "directory": "dist", + "registry": "https://registry.npmjs.org" + }, + "scripts": { + "bench": "echo NOTHING TO BENCH", + "build": "pnpm build:esm && pnpm build:cjs && pnpm build:annotate", + "build:annotate": "babel build --plugins annotate-pure-calls --out-dir build --source-maps", + "build:esm": "tsc -b tsconfig.build.json", + "build:cjs": "babel build/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir build/cjs --source-maps", + "check": "tsc -b tsconfig.json", + "clean": "pnpm run \"/^clean:.*/\"", + "clean:build": "rm -rf .tsbuildinfo dist build", + "clean:deps": "rm -rf node_modules", + "test": "vitest" + }, + "peerDependencies": { + "@traversable/graphql-types": "workspace:^", + "@traversable/registry": "workspace:^" + }, + "devDependencies": { + "@traversable/graphql-types": "workspace:^", + "@traversable/registry": "workspace:^" + } +} as const \ No newline at end of file diff --git a/packages/graphql-test/src/exports.ts b/packages/graphql-test/src/exports.ts new file mode 100644 index 00000000..04783bce --- /dev/null +++ b/packages/graphql-test/src/exports.ts @@ -0,0 +1 @@ +export * from './version.js' \ No newline at end of file diff --git a/packages/graphql-test/src/index.ts b/packages/graphql-test/src/index.ts new file mode 100644 index 00000000..9fd152fb --- /dev/null +++ b/packages/graphql-test/src/index.ts @@ -0,0 +1 @@ +export * from './exports.js' \ No newline at end of file diff --git a/packages/graphql-test/src/version.ts b/packages/graphql-test/src/version.ts new file mode 100644 index 00000000..660ff1ca --- /dev/null +++ b/packages/graphql-test/src/version.ts @@ -0,0 +1,3 @@ +import pkg from './__generated__/__manifest__.js' +export const VERSION = `${pkg.name}@${pkg.version}` as const +export type VERSION = typeof VERSION diff --git a/packages/graphql-test/test/version.test.ts b/packages/graphql-test/test/version.test.ts new file mode 100644 index 00000000..5494d934 --- /dev/null +++ b/packages/graphql-test/test/version.test.ts @@ -0,0 +1,10 @@ +import * as vi from 'vitest' +import pkg from '../package.json' with { type: 'json' } +import { VERSION } from '@traversable/graphql-test' + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/graphql-test❳', () => { + vi.test('〖⛳️〗› ❲VERSION❳', () => { + const expected = `${pkg.name}@${pkg.version}` + vi.assert.equal(VERSION, expected) + }) +}) diff --git a/packages/graphql-test/tsconfig.build.json b/packages/graphql-test/tsconfig.build.json new file mode 100644 index 00000000..16c1c0e4 --- /dev/null +++ b/packages/graphql-test/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.src.json", + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/build.tsbuildinfo", + "types": ["node"], + "declarationDir": "build/dts", + "outDir": "build/esm", + "stripInternal": true + }, + "references": [{ "path": "../graphql-types" }, { "path": "../registry" }] +} diff --git a/packages/graphql-test/tsconfig.json b/packages/graphql-test/tsconfig.json new file mode 100644 index 00000000..2c291d21 --- /dev/null +++ b/packages/graphql-test/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "include": [], + "references": [ + { "path": "tsconfig.src.json" }, + { "path": "tsconfig.test.json" } + ] +} diff --git a/packages/graphql-test/tsconfig.src.json b/packages/graphql-test/tsconfig.src.json new file mode 100644 index 00000000..c110a318 --- /dev/null +++ b/packages/graphql-test/tsconfig.src.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/src.tsbuildinfo", + "rootDir": "src", + "types": ["node"], + "outDir": "build/src" + }, + "references": [{ "path": "../graphql-types" }, { "path": "../registry" }], + "include": ["src"] +} diff --git a/packages/graphql-test/tsconfig.test.json b/packages/graphql-test/tsconfig.test.json new file mode 100644 index 00000000..e318a1ea --- /dev/null +++ b/packages/graphql-test/tsconfig.test.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/test.tsbuildinfo", + "rootDir": "test", + "types": ["node"], + "noEmit": true + }, + "references": [ + { "path": "tsconfig.src.json" }, + { "path": "../graphql-types" }, + { "path": "../registry" } + ], + "include": ["test"] +} diff --git a/packages/graphql-test/vite.config.ts b/packages/graphql-test/vite.config.ts new file mode 100644 index 00000000..64dba4ad --- /dev/null +++ b/packages/graphql-test/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig, mergeConfig } from 'vitest/config' +import sharedConfig from '../../vite.config.js' + +const localConfig = defineConfig({}) + +export default mergeConfig(sharedConfig, localConfig) \ No newline at end of file diff --git a/packages/graphql-types/README.md b/packages/graphql-types/README.md new file mode 100644 index 00000000..9f380e9b --- /dev/null +++ b/packages/graphql-types/README.md @@ -0,0 +1,40 @@ +
+

ᯓ𝘁𝗿𝗮𝘃𝗲𝗿𝘀𝗮𝗯𝗹𝗲/𝗴𝗿𝗮𝗽𝗵𝗾𝗹-𝘁𝘆𝗽𝗲𝘀

+
+ +

+ "Low-level" primitives like types and predicates. Most of this package is re-exported by @traversable/graphql, so you probably won't need to use it directly. +

+ +
+ NPM Version +   + TypeScript +   + License +   + npm +   +
+ +
+ + Static Badge +   + Static Badge +   + Static Badge +   +
+ +
+ Demo (StackBlitz) +   •   + TypeScript Playground +   •   + npm +
+
+
+
diff --git a/packages/graphql-types/package.json b/packages/graphql-types/package.json new file mode 100644 index 00000000..c5e57232 --- /dev/null +++ b/packages/graphql-types/package.json @@ -0,0 +1,58 @@ +{ + "name": "@traversable/graphql-types", + "type": "module", + "version": "0.0.0", + "private": false, + "description": "", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/traversable/schema.git", + "directory": "packages/graphql-types" + }, + "bugs": { + "url": "https://github.com/traversable/schema/issues", + "email": "ahrjarrett@gmail.com" + }, + "@traversable": { + "generateExports": { + "include": [ + "**/*.ts" + ] + }, + "generateIndex": { + "include": [ + "**/*.ts" + ] + } + }, + "publishConfig": { + "access": "public", + "directory": "dist", + "registry": "https://registry.npmjs.org" + }, + "scripts": { + "bench": "echo NOTHING TO BENCH", + "build": "pnpm build:esm && pnpm build:cjs && pnpm build:annotate", + "build:annotate": "babel build --plugins annotate-pure-calls --out-dir build --source-maps", + "build:esm": "tsc -b tsconfig.build.json", + "build:cjs": "babel build/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir build/cjs --source-maps", + "check": "tsc -b tsconfig.json", + "clean": "pnpm run \"/^clean:.*/\"", + "clean:build": "rm -rf .tsbuildinfo dist build", + "clean:deps": "rm -rf node_modules", + "test": "vitest" + }, + "peerDependencies": { + "@traversable/registry": "workspace:^" + }, + "peerDependenciesMeta": { + "@traversable/registry": { + "optional": false + } + }, + "devDependencies": { + "@prettier/sync": "catalog:", + "@traversable/registry": "workspace:^" + } +} diff --git a/packages/graphql-types/src/__generated__/__manifest__.ts b/packages/graphql-types/src/__generated__/__manifest__.ts new file mode 100644 index 00000000..8746d00e --- /dev/null +++ b/packages/graphql-types/src/__generated__/__manifest__.ts @@ -0,0 +1,54 @@ +export default { + "name": "@traversable/graphql-types", + "type": "module", + "version": "0.0.0", + "private": false, + "description": "", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/traversable/schema.git", + "directory": "packages/graphql-types" + }, + "bugs": { + "url": "https://github.com/traversable/schema/issues", + "email": "ahrjarrett@gmail.com" + }, + "@traversable": { + "generateExports": { + "include": ["**/*.ts"] + }, + "generateIndex": { + "include": ["**/*.ts"] + } + }, + "publishConfig": { + "access": "public", + "directory": "dist", + "registry": "https://registry.npmjs.org" + }, + "scripts": { + "bench": "echo NOTHING TO BENCH", + "build": "pnpm build:esm && pnpm build:cjs && pnpm build:annotate", + "build:annotate": "babel build --plugins annotate-pure-calls --out-dir build --source-maps", + "build:esm": "tsc -b tsconfig.build.json", + "build:cjs": "babel build/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir build/cjs --source-maps", + "check": "tsc -b tsconfig.json", + "clean": "pnpm run \"/^clean:.*/\"", + "clean:build": "rm -rf .tsbuildinfo dist build", + "clean:deps": "rm -rf node_modules", + "test": "vitest" + }, + "peerDependencies": { + "@traversable/registry": "workspace:^" + }, + "peerDependenciesMeta": { + "@traversable/registry": { + "optional": false + } + }, + "devDependencies": { + "@prettier/sync": "catalog:", + "@traversable/registry": "workspace:^" + } +} as const \ No newline at end of file diff --git a/packages/graphql-types/src/exports.ts b/packages/graphql-types/src/exports.ts new file mode 100644 index 00000000..a0f4ef83 --- /dev/null +++ b/packages/graphql-types/src/exports.ts @@ -0,0 +1,35 @@ +export { VERSION } from './version.js' + +export type { + AST, + Algebra, + Fold, + Index, +} from './functor.js' + +export { + Functor, + defaultIndex, + fold, + isBooleanNode, + isDocumentNode, + isEnumNode, + isFieldNode, + isFloatNode, + isIDNode, + isInputObjectNode, + isInputValueNode, + isIntNode, + isInterfaceNode, + isListNode, + isNamedNode, + isNonNullNode, + isNullaryNode, + isNumberNode, + isObjectNode, + isRefNode, + isScalarNode, + isStringNode, + isUnaryNode, + isUnionNode, +} from './functor.js' diff --git a/packages/graphql-types/src/functor.ts b/packages/graphql-types/src/functor.ts new file mode 100644 index 00000000..62850f7f --- /dev/null +++ b/packages/graphql-types/src/functor.ts @@ -0,0 +1,387 @@ +import type * as T from '@traversable/registry' +import { fn, has } from '@traversable/registry' + +// interface Query { +// kind: 'OperationDefinition' +// operation: 'query' +// name: Name +// variableDefinitions: readonly VariableDefinition[], +// directives: readonly T[] +// selectionSet: { +// kind: 'SelectionSet' +// selections: [ +// ], +// } +// } + +export declare namespace AST { + export interface Name { + kind: 'Name' + value: Value + } + + export interface Named { + name: Name + } + + /** + * ## {@link Ref `Ref`} + * + * A {@link Ref `Ref`} is a named type that is not one of the built-in types. + */ + export interface Ref { + kind: 'NamedType' + name: Name + } + + export interface Document { + kind: 'Document' + definitions: readonly T[] + } + + export interface InputValue { + kind: 'InputValueDefinition' + name: Name + type: T + directives: readonly T[] + } + + export interface InputObject { + kind: 'InputObjectTypeDefinition' + name: Name + fields: readonly T[] + directives: readonly T[] + } + + export interface Variable { + kind: 'Variable' + name: Name + } + + export interface VariableDefinition { + kind: 'VariableDefinition' + variable: Variable + type: T + directives: readonly T[] + } + + export type Nullary = + | Boolean + | Int + | Number + | Float + | String + | ID + | Scalar + | Enum + + export interface Scalar { + kind: 'ScalarTypeDefinition' + name: Name + } + + + export interface Boolean { + kind: 'NamedType' + name: Name<'Boolean'> + } + + export interface Int { + kind: 'NamedType' + name: Name<'Int'> + } + + export interface Number { + kind: 'NamedType' + name: Name<'Number'> + } + + export interface Float { + kind: 'NamedType' + name: Name<'Float'> + } + + export interface String { + kind: 'NamedType' + name: Name<'String'> + } + + export interface ID { + kind: 'NamedType' + name: Name<'ID'> + } + + export interface EnumValue { + kind: 'EnumValueDefinition' + name: Name + } + + export interface Enum { + kind: 'EnumTypeDefinition' + name: Name + values: readonly EnumValue[] + } + + export interface NonNull { + kind: 'NonNullType' + type: T + } + + export interface List { + kind: 'ListType' + type: T + } + + export interface Field { + kind: 'FieldDefinition' + name: Name + type: T + defaultValue?: unknown + arguments: readonly T[] + directives: readonly T[] + } + + export interface Object { + kind: 'ObjectTypeDefinition' + name: Name + fields: readonly T[] + interfaces: readonly T[] + directives: readonly T[] + } + + export interface Interface { + kind: 'InterfaceTypeDefinition' + name: Name + fields: readonly T[] + interfaces: readonly T[] + directives: readonly T[] + } + + export interface Union { + kind: 'UnionTypeDefinition' + name: Name + types: readonly T[] + directives: readonly T[] + } + + export type Unary = + | NonNull + | List + | Field + | Object + | Interface + | Union + | InputValue + | InputObject + | Document + + export type Fixpoint = + | Nullary + | Ref + | NonNull + | List + | Field + | Object + | Interface + | Union + | InputValue + | InputObject + | Document + + export type F = + | Nullary + | Ref + | Unary +} + +export function isScalarNode(x: unknown): x is AST.Scalar { + return has('kind', (kind) => kind === 'ScalarTypeDefinition')(x) +} + +export function isBooleanNode(x: unknown): x is AST.Boolean { + return has('name', 'value', (value) => value === 'Boolean')(x) +} + +export function isIntNode(x: unknown): x is AST.Int { + return has('name', 'value', (value) => value === 'Int')(x) +} + +export function isNumberNode(x: unknown): x is AST.Number { + return has('name', 'value', (value) => value === 'Number')(x) +} + +export function isFloatNode(x: unknown): x is AST.Float { + return has('name', 'value', (value) => value === 'Float')(x) +} + +export function isStringNode(x: unknown): x is AST.String { + return has('name', 'value', (value) => value === 'String')(x) +} + +export function isIDNode(x: unknown): x is AST.ID { + return has('name', 'value', (value) => value === 'ID')(x) +} + +export function isEnumNode(x: unknown): x is AST.Enum { + return has('name', 'value', (value) => value === 'ID')(x) +} + +export function isNonNullNode(x: unknown): x is AST.NonNull { + return has('kind', (kind) => kind === 'NonNullType')(x) +} + +export function isListNode(x: unknown): x is AST.List { + return has('kind', (kind) => kind === 'ListType')(x) +} + +export function isFieldNode(x: unknown): x is AST.Field { + return has('kind', (kind) => kind === 'FieldDefinition')(x) +} + +export function isObjectNode(x: unknown): x is AST.Object { + return has('kind', (kind) => kind === 'ObjectTypeDefinition')(x) +} + +export function isInterfaceNode(x: unknown): x is AST.Interface { + return has('kind', (kind) => kind === 'InterfaceTypeDefinition')(x) +} + +export function isUnionNode(x: unknown): x is AST.Union { + return has('kind', (kind) => kind === 'UnionTypeDefinition')(x) +} + +export function isInputValueNode(x: unknown): x is AST.InputValue { + return has('kind', (kind) => kind === 'InputValueDefinition')(x) +} + +export function isInputObjectNode(x: unknown): x is AST.InputObject { + return has('kind', (kind) => kind === 'InputObjectTypeDefinition')(x) +} + +export function isDocumentNode(x: unknown): x is AST.Document { + return has('kind', (kind) => kind === 'Document')(x) +} + +export function isNullaryNode(x: unknown): x is AST.Nullary { + return isScalarNode(x) + || isBooleanNode(x) + || isIntNode(x) + || isNumberNode(x) + || isFloatNode(x) + || isStringNode(x) + || isIDNode(x) + || isEnumNode(x) +} + +export function isNamedNode(x: unknown): x is AST.Named { + return has('name', 'value', (value) => typeof value === 'string')(x) +} + +export function isRefNode(x: unknown): x is AST.Ref { + return has('kind', (kind) => kind === 'NamedType')(x) + && has('name', 'value', (value) => typeof value === 'string')(x) + && !isNullaryNode(x) +} + +export function isUnaryNode(x: unknown): x is AST.Unary { + return isNonNullNode(x) + || isListNode(x) + || isObjectNode(x) + || isInterfaceNode(x) + || isUnionNode(x) + || isInputObjectNode(x) +} + +export const defaultIndex = { isNonNull: false } satisfies Index + +export interface Index { + isNonNull: boolean +} + +export type Algebra = { + (src: AST.F, ix?: Index): T + (src: AST.Fixpoint, ix?: Index): T + (src: AST.F, ix?: Index): T +} + +export type Fold = (g: (src: AST.F, ix: Index, x: AST.Fixpoint) => T) => Algebra + +export interface Functor extends T.HKT { [-1]: AST.F } + +export declare namespace Functor { + export { Index } +} + +export const Functor: T.Functor.Ix = { + map(g) { + return (x) => { + switch (true) { + default: return x + case isRefNode(x): return x + case isNullaryNode(x): return x + case isNonNullNode(x): return { ...x, type: g(x.type) } + case isListNode(x): return { ...x, type: g(x.type) } + case isUnionNode(x): return { ...x, directives: x.directives.map(g), types: x.types.map(g) } + case isFieldNode(x): return { ...x, type: g(x.type), arguments: x.arguments.map(g), directives: x.directives.map(g) } + case isObjectNode(x): return { ...x, directives: x.directives.map(g), interfaces: x.interfaces.map(g), fields: x.fields.map(g) } + case isInterfaceNode(x): return { ...x, directives: x.directives.map(g), interfaces: x.interfaces.map(g), fields: x.fields.map(g) } + case isInputValueNode(x): return { ...x, type: g(x.type), directives: x.directives.map(g) } + case isInputObjectNode(x): return { ...x, directives: x.directives.map(g), fields: x.fields.map(g) } + case isDocumentNode(x): return { ...x, definitions: x.definitions.map(g) } + } + } + }, + mapWithIndex(g) { + return (x, _ix) => { + const ix = isNonNullNode(x) ? { isNonNull: true } satisfies Index : defaultIndex + switch (true) { + default: return x + case isRefNode(x): return x + case isNullaryNode(x): return x + case isNonNullNode(x): return { ...x, type: g(x.type, ix, x) } + case isListNode(x): return { ...x, type: g(x.type, ix, x) } + case isUnionNode(x): return { + ...x, + directives: x.directives.map((_) => g(_, ix, x)), + types: x.types.map((_) => g(_, ix, x)) + } + case isFieldNode(x): { + return { + ...x, + type: g(x.type, isNonNullNode(x.type) ? { isNonNull: true } : ix, x), + arguments: x.arguments.map((_) => g(_, ix, x)), + directives: x.directives.map((_) => g(_, ix, x)), + } + } + case isObjectNode(x): return { + ...x, + directives: x.directives.map((_) => g(_, ix, x)), + fields: x.fields.map((_) => g(_, ix, x)), + interfaces: x.interfaces.map((_) => g(_, ix, x)), + } + case isInterfaceNode(x): return { + ...x, + directives: x.directives.map((_) => g(_, ix, x)), + fields: x.fields.map((_) => g(_, ix, x)), + interfaces: x.interfaces.map((_) => g(_, ix, x)), + } + case isInputValueNode(x): return { + ...x, + type: g(x.type, ix, x), + directives: x.directives.map((_) => g(_, ix, x)), + } + case isInputObjectNode(x): return { + ...x, + directives: x.directives.map((_) => g(_, ix, x)), + fields: x.fields.map((_) => g(_, ix, x)), + } + case isDocumentNode(x): return { + ...x, + definitions: x.definitions.map((_) => g(_, ix, x)), + } + } + } + } +} + +export const fold: Fold = fn.catamorphism(Functor, defaultIndex) as never diff --git a/packages/graphql-types/src/index.ts b/packages/graphql-types/src/index.ts new file mode 100644 index 00000000..410a4bcb --- /dev/null +++ b/packages/graphql-types/src/index.ts @@ -0,0 +1 @@ +export * from './exports.js' diff --git a/packages/graphql-types/src/version.ts b/packages/graphql-types/src/version.ts new file mode 100644 index 00000000..660ff1ca --- /dev/null +++ b/packages/graphql-types/src/version.ts @@ -0,0 +1,3 @@ +import pkg from './__generated__/__manifest__.js' +export const VERSION = `${pkg.name}@${pkg.version}` as const +export type VERSION = typeof VERSION diff --git a/packages/graphql-types/test/functor.test.ts b/packages/graphql-types/test/functor.test.ts new file mode 100644 index 00000000..1dc9e089 --- /dev/null +++ b/packages/graphql-types/test/functor.test.ts @@ -0,0 +1,150 @@ +import * as vi from 'vitest' +import prettier from '@prettier/sync' +import { parseKey } from '@traversable/registry' +import type { AST } from '@traversable/graphql-types' +import { + fold, + isBooleanNode, + isDocumentNode, + isEnumNode, + isFieldNode, + isFloatNode, + isIDNode, + isInputObjectNode, + isInputValueNode, + isIntNode, + isInterfaceNode, + isListNode, + isNamedNode, + isNonNullNode, + isNumberNode, + isObjectNode, + isRefNode, + isScalarNode, + isStringNode, + isUnionNode, +} from '@traversable/graphql-types' + +const format = (src: string) => prettier.format( + src, + { parser: 'typescript', semi: false, printWidth: 60 } +) + +const test = { + "kind": "Document", + "definitions": [ + { + "kind": "ObjectTypeDefinition", + "name": { + "kind": "Name", + "value": "Pet", + "loc": { + "start": 133, + "end": 136 + } + }, + "interfaces": [], + "directives": [], + "fields": [ + { + "kind": "FieldDefinition", + "name": { + "kind": "Name", + "value": "name", + "loc": { + "start": 141, + "end": 145 + } + }, + "arguments": [], + "type": { + "kind": "NamedType", + "name": { + "kind": "Name", + "value": "String", + "loc": { + "start": 147, + "end": 153 + } + }, + "loc": { + "start": 147, + "end": 153 + } + }, + "directives": [], + "loc": { + "start": 141, + "end": 153 + } + } + ], + "loc": { + "start": 128, + "end": 155 + } + } + ], + "loc": { + "start": 0, + "end": 156 + } +} as const + +const algebra = fold((x, ix, original) => { + switch (true) { + default: return x satisfies never + case isDocumentNode(x): throw Error('Nesting documents is not allowed') + case isInputValueNode(x): throw Error('Input values cannot be converted to a type') + case isInputObjectNode(x): throw Error('Input objects cannot be converted to a type') + case isRefNode(x): return x.name.value + case isScalarNode(x): return x.name.value + case isBooleanNode(x): return 'boolean' + case isIntNode(x): return 'number' + case isNumberNode(x): return 'number' + case isFloatNode(x): return 'number' + case isStringNode(x): return 'string' + case isIDNode(x): return 'string' + case isEnumNode(x): return ( + x.values.length === 0 ? 'never' + : x.values.length === 1 ? JSON.stringify(x.values[0]) + : `(${x.values.map((v) => JSON.stringify(v)).join(' | ')})` + ) + case isNonNullNode(x): return `${x.type}!` + case isUnionNode(x): return ( + x.types.length === 0 ? 'never' + : x.types.length === 1 ? JSON.stringify(x.types[0]) + : `(${x.types.map((v) => JSON.stringify(v)).join(' | ')})` + ) + case isListNode(x): { + if (!isListNode(original)) throw Error('Illegal state') + const isNonNull = x.type.endsWith('!') + const TYPE = isNonNull ? x.type.slice(0, -1) : `${x.type} | null` + return `Array<${TYPE}>` + } + case isFieldNode(x): { + const isNonNull = x.type.endsWith('!') + const VALUE = isNonNull ? x.type.slice(0, -1) : x.type + return `${parseKey(x.name.value)}${isNonNull ? '' : '?'}: ${VALUE}` + } + case isObjectNode(x): { return x.fields.length === 0 ? `{}` : `{ ${x.fields.join(', ')} }` } + case isInterfaceNode(x): { return x.fields.length === 0 ? `{}` : `{ ${x.fields.join(', ')} }` } + } +}) + +function toType(doc: AST.Document) { + const types = doc.definitions.map((def, i) => `type ${isNamedNode(def) ? def.name.value : `Type${i}`} = ${fold(algebra)(def)}`) + return types.join('\n') +} + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/graphql-types❳', () => { + vi.test('〖⛳️〗› ❲Functor❳', () => { + vi.expect.soft(format( + toType({ + kind: 'Document', + definitions: [] + }) + )).toMatchInlineSnapshot + (`""`) + }) +}) diff --git a/packages/graphql-types/test/version.test.ts b/packages/graphql-types/test/version.test.ts new file mode 100644 index 00000000..17cbd6e9 --- /dev/null +++ b/packages/graphql-types/test/version.test.ts @@ -0,0 +1,10 @@ +import * as vi from 'vitest' +import pkg from '../package.json' with { type: 'json' } +import { VERSION } from '@traversable/graphql-types' + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/graphql-types❳', () => { + vi.test('〖⛳️〗› ❲VERSION❳', () => { + const expected = `${pkg.name}@${pkg.version}` + vi.assert.equal(VERSION, expected) + }) +}) diff --git a/packages/graphql-types/tsconfig.build.json b/packages/graphql-types/tsconfig.build.json new file mode 100644 index 00000000..aacae424 --- /dev/null +++ b/packages/graphql-types/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.src.json", + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/build.tsbuildinfo", + "types": ["node"], + "declarationDir": "build/dts", + "outDir": "build/esm", + "stripInternal": true + }, + "references": [{ "path": "../registry" }] +} diff --git a/packages/graphql-types/tsconfig.json b/packages/graphql-types/tsconfig.json new file mode 100644 index 00000000..2c291d21 --- /dev/null +++ b/packages/graphql-types/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "include": [], + "references": [ + { "path": "tsconfig.src.json" }, + { "path": "tsconfig.test.json" } + ] +} diff --git a/packages/graphql-types/tsconfig.src.json b/packages/graphql-types/tsconfig.src.json new file mode 100644 index 00000000..f70f4837 --- /dev/null +++ b/packages/graphql-types/tsconfig.src.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/src.tsbuildinfo", + "rootDir": "src", + "types": ["node"], + "outDir": "build/src" + }, + "references": [{ "path": "../registry" }], + "include": ["src"] +} diff --git a/packages/graphql-types/tsconfig.test.json b/packages/graphql-types/tsconfig.test.json new file mode 100644 index 00000000..1a25fb45 --- /dev/null +++ b/packages/graphql-types/tsconfig.test.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/test.tsbuildinfo", + "rootDir": "test", + "types": ["node"], + "noEmit": true + }, + "references": [ + { "path": "tsconfig.src.json" }, + { "path": "../registry" } + ], + "include": ["test"] +} diff --git a/packages/graphql-types/vite.config.ts b/packages/graphql-types/vite.config.ts new file mode 100644 index 00000000..64dba4ad --- /dev/null +++ b/packages/graphql-types/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig, mergeConfig } from 'vitest/config' +import sharedConfig from '../../vite.config.js' + +const localConfig = defineConfig({}) + +export default mergeConfig(sharedConfig, localConfig) \ No newline at end of file diff --git a/packages/graphql/README.md b/packages/graphql/README.md new file mode 100644 index 00000000..756177b6 --- /dev/null +++ b/packages/graphql/README.md @@ -0,0 +1,40 @@ +
+

ᯓ𝘁𝗿𝗮𝘃𝗲𝗿𝘀𝗮𝗯𝗹𝗲/𝗴𝗿𝗮𝗽𝗵𝗾𝗹

+
+ +

+ TODO: write me +

+ +
+ NPM Version +   + TypeScript +   + License +   + npm +   +
+ +
+ + Static Badge +   + Static Badge +   + Static Badge +   +
+ +
+ Demo (StackBlitz) +   •   + TypeScript Playground +   •   + npm +
+
+
+
diff --git a/packages/graphql/package.json b/packages/graphql/package.json new file mode 100644 index 00000000..f9f4ae4a --- /dev/null +++ b/packages/graphql/package.json @@ -0,0 +1,46 @@ +{ + "name": "@traversable/graphql", + "type": "module", + "version": "0.0.0", + "private": false, + "description": "", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/traversable/schema.git", + "directory": "packages/graphql" + }, + "bugs": { + "url": "https://github.com/traversable/schema/issues", + "email": "ahrjarrett@gmail.com" + }, + "@traversable": { + "generateExports": { "include": ["**/*.ts"] }, + "generateIndex": { "include": ["**/*.ts"] } + }, + "publishConfig": { + "access": "public", + "directory": "dist", + "registry": "https://registry.npmjs.org" + }, + "scripts": { + "bench": "echo NOTHING TO BENCH", + "build": "pnpm build:esm && pnpm build:cjs && pnpm build:annotate", + "build:annotate": "babel build --plugins annotate-pure-calls --out-dir build --source-maps", + "build:esm": "tsc -b tsconfig.build.json", + "build:cjs": "babel build/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir build/cjs --source-maps", + "check": "tsc -b tsconfig.json", + "clean": "pnpm run \"/^clean:.*/\"", + "clean:build": "rm -rf .tsbuildinfo dist build", + "clean:deps": "rm -rf node_modules", + "test": "vitest" + }, + "peerDependencies": { + "@traversable/graphql-types": "workspace:^", + "@traversable/registry": "workspace:^" + }, + "devDependencies": { + "@traversable/graphql-types": "workspace:^", + "@traversable/registry": "workspace:^" + } +} diff --git a/packages/graphql/src/__generated__/__manifest__.ts b/packages/graphql/src/__generated__/__manifest__.ts new file mode 100644 index 00000000..28612cc6 --- /dev/null +++ b/packages/graphql/src/__generated__/__manifest__.ts @@ -0,0 +1,46 @@ +export default { + "name": "@traversable/graphql", + "type": "module", + "version": "0.0.0", + "private": false, + "description": "", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/traversable/schema.git", + "directory": "packages/graphql" + }, + "bugs": { + "url": "https://github.com/traversable/schema/issues", + "email": "ahrjarrett@gmail.com" + }, + "@traversable": { + "generateExports": { "include": ["**/*.ts"] }, + "generateIndex": { "include": ["**/*.ts"] } + }, + "publishConfig": { + "access": "public", + "directory": "dist", + "registry": "https://registry.npmjs.org" + }, + "scripts": { + "bench": "echo NOTHING TO BENCH", + "build": "pnpm build:esm && pnpm build:cjs && pnpm build:annotate", + "build:annotate": "babel build --plugins annotate-pure-calls --out-dir build --source-maps", + "build:esm": "tsc -b tsconfig.build.json", + "build:cjs": "babel build/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir build/cjs --source-maps", + "check": "tsc -b tsconfig.json", + "clean": "pnpm run \"/^clean:.*/\"", + "clean:build": "rm -rf .tsbuildinfo dist build", + "clean:deps": "rm -rf node_modules", + "test": "vitest" + }, + "peerDependencies": { + "@traversable/graphql-types": "workspace:^", + "@traversable/registry": "workspace:^" + }, + "devDependencies": { + "@traversable/graphql-types": "workspace:^", + "@traversable/registry": "workspace:^" + } +} as const \ No newline at end of file diff --git a/packages/graphql/src/exports.ts b/packages/graphql/src/exports.ts new file mode 100644 index 00000000..c58b54ea --- /dev/null +++ b/packages/graphql/src/exports.ts @@ -0,0 +1 @@ +export { VERSION } from './version.js' diff --git a/packages/graphql/src/index.ts b/packages/graphql/src/index.ts new file mode 100644 index 00000000..410a4bcb --- /dev/null +++ b/packages/graphql/src/index.ts @@ -0,0 +1 @@ +export * from './exports.js' diff --git a/packages/graphql/src/version.ts b/packages/graphql/src/version.ts new file mode 100644 index 00000000..660ff1ca --- /dev/null +++ b/packages/graphql/src/version.ts @@ -0,0 +1,3 @@ +import pkg from './__generated__/__manifest__.js' +export const VERSION = `${pkg.name}@${pkg.version}` as const +export type VERSION = typeof VERSION diff --git a/packages/graphql/test/version.test.ts b/packages/graphql/test/version.test.ts new file mode 100644 index 00000000..48330e1b --- /dev/null +++ b/packages/graphql/test/version.test.ts @@ -0,0 +1,10 @@ +import * as vi from 'vitest' +import pkg from '../package.json' with { type: 'json' } +import { VERSION } from '@traversable/graphql' + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/graphql❳', () => { + vi.test('〖⛳️〗› ❲VERSION❳', () => { + const expected = `${pkg.name}@${pkg.version}` + vi.assert.equal(VERSION, expected) + }) +}) diff --git a/packages/graphql/tsconfig.build.json b/packages/graphql/tsconfig.build.json new file mode 100644 index 00000000..16c1c0e4 --- /dev/null +++ b/packages/graphql/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.src.json", + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/build.tsbuildinfo", + "types": ["node"], + "declarationDir": "build/dts", + "outDir": "build/esm", + "stripInternal": true + }, + "references": [{ "path": "../graphql-types" }, { "path": "../registry" }] +} diff --git a/packages/graphql/tsconfig.json b/packages/graphql/tsconfig.json new file mode 100644 index 00000000..2c291d21 --- /dev/null +++ b/packages/graphql/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "include": [], + "references": [ + { "path": "tsconfig.src.json" }, + { "path": "tsconfig.test.json" } + ] +} diff --git a/packages/graphql/tsconfig.src.json b/packages/graphql/tsconfig.src.json new file mode 100644 index 00000000..c110a318 --- /dev/null +++ b/packages/graphql/tsconfig.src.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/src.tsbuildinfo", + "rootDir": "src", + "types": ["node"], + "outDir": "build/src" + }, + "references": [{ "path": "../graphql-types" }, { "path": "../registry" }], + "include": ["src"] +} diff --git a/packages/graphql/tsconfig.test.json b/packages/graphql/tsconfig.test.json new file mode 100644 index 00000000..e318a1ea --- /dev/null +++ b/packages/graphql/tsconfig.test.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/test.tsbuildinfo", + "rootDir": "test", + "types": ["node"], + "noEmit": true + }, + "references": [ + { "path": "tsconfig.src.json" }, + { "path": "../graphql-types" }, + { "path": "../registry" } + ], + "include": ["test"] +} diff --git a/packages/graphql/vite.config.ts b/packages/graphql/vite.config.ts new file mode 100644 index 00000000..64dba4ad --- /dev/null +++ b/packages/graphql/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig, mergeConfig } from 'vitest/config' +import sharedConfig from '../../vite.config.js' + +const localConfig = defineConfig({}) + +export default mergeConfig(sharedConfig, localConfig) \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5737f9ce..e5fc25f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -288,6 +288,36 @@ importers: version: 2.1.20 publishDirectory: dist + packages/graphql: + devDependencies: + '@traversable/graphql-types': + specifier: workspace:^ + version: link:../graphql-types/dist + '@traversable/registry': + specifier: workspace:^ + version: link:../registry/dist + publishDirectory: dist + + packages/graphql-test: + devDependencies: + '@traversable/graphql-types': + specifier: workspace:^ + version: link:../graphql-types/dist + '@traversable/registry': + specifier: workspace:^ + version: link:../registry/dist + publishDirectory: dist + + packages/graphql-types: + devDependencies: + '@prettier/sync': + specifier: 'catalog:' + version: 0.5.5(prettier@3.6.2) + '@traversable/registry': + specifier: workspace:^ + version: link:../registry/dist + publishDirectory: dist + packages/json: dependencies: fast-check: diff --git a/tsconfig.base.json b/tsconfig.base.json index b4c61481..8f24b6d6 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -34,6 +34,12 @@ "@traversable/arktype-types": ["packages/arktype-types/src/index.js"], "@traversable/arktype-types/*": ["packages/arktype-types/*.js"], "@traversable/arktype/*": ["packages/arktype/*.js"], + "@traversable/graphql": ["packages/graphql/src/index.js"], + "@traversable/graphql-test": ["packages/graphql-test/src/index.js"], + "@traversable/graphql-test/*": ["packages/graphql-test/*.js"], + "@traversable/graphql-types": ["packages/graphql-types/src/index.js"], + "@traversable/graphql-types/*": ["packages/graphql-types/*.js"], + "@traversable/graphql/*": ["packages/graphql/*.js"], "@traversable/json": ["packages/json/src/index.js"], "@traversable/json-schema": ["packages/json-schema/src/index.js"], "@traversable/json-schema-test": [ diff --git a/tsconfig.build.json b/tsconfig.build.json index f3db1e87..fa2f292f 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -5,6 +5,9 @@ { "path": "packages/arktype-test/tsconfig.build.json" }, { "path": "packages/arktype-types/tsconfig.build.json" }, { "path": "packages/arktype/tsconfig.build.json" }, + { "path": "packages/graphql-test/tsconfig.build.json" }, + { "path": "packages/graphql-types/tsconfig.build.json" }, + { "path": "packages/graphql/tsconfig.build.json" }, { "path": "packages/json-schema-test/tsconfig.build.json" }, { "path": "packages/json-schema-types/tsconfig.build.json" }, { "path": "packages/json-schema/tsconfig.build.json" }, diff --git a/tsconfig.json b/tsconfig.json index bb6039f0..fe1a435d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,9 @@ { "path": "packages/arktype" }, { "path": "packages/arktype-test" }, { "path": "packages/arktype-types" }, + { "path": "packages/graphql" }, + { "path": "packages/graphql-test" }, + { "path": "packages/graphql-types" }, { "path": "packages/json" }, { "path": "packages/json-schema" }, { "path": "packages/json-schema-test" }, From a6452f55a6aae1aae2f7c569fbb91d5041aa15c7 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Wed, 17 Sep 2025 04:34:24 -0500 Subject: [PATCH 03/32] chore: commits changeset --- .changeset/fresh-years-lay.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/fresh-years-lay.md diff --git a/.changeset/fresh-years-lay.md b/.changeset/fresh-years-lay.md new file mode 100644 index 00000000..98008b9a --- /dev/null +++ b/.changeset/fresh-years-lay.md @@ -0,0 +1,7 @@ +--- +"@traversable/graphql-types": patch +"@traversable/graphql-test": patch +"@traversable/graphql": patch +--- + +initializes `@traversable/graphql`, `@traverable/graphql-test`, `@traversable/graphql-types` packages From 463e5a314c8978bb91ec44532f1ba54d23f0d45a Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Thu, 18 Sep 2025 21:09:38 -0500 Subject: [PATCH 04/32] feat(graphql): adds full AST support, `GQL.toType` --- packages/graphql-types/package.json | 3 +- packages/graphql-types/src/exports.ts | 27 +- packages/graphql-types/src/functor.ts | 842 +++++++++++++++----- packages/graphql-types/src/to-type.ts | 101 +++ packages/graphql-types/test/to-type.test.ts | 96 +++ pnpm-lock.yaml | 9 + 6 files changed, 866 insertions(+), 212 deletions(-) create mode 100644 packages/graphql-types/src/to-type.ts create mode 100644 packages/graphql-types/test/to-type.test.ts diff --git a/packages/graphql-types/package.json b/packages/graphql-types/package.json index c5e57232..68b08863 100644 --- a/packages/graphql-types/package.json +++ b/packages/graphql-types/package.json @@ -53,6 +53,7 @@ }, "devDependencies": { "@prettier/sync": "catalog:", - "@traversable/registry": "workspace:^" + "@traversable/registry": "workspace:^", + "graphql": "^16.11.0" } } diff --git a/packages/graphql-types/src/exports.ts b/packages/graphql-types/src/exports.ts index a0f4ef83..e20f0bff 100644 --- a/packages/graphql-types/src/exports.ts +++ b/packages/graphql-types/src/exports.ts @@ -9,27 +9,46 @@ export type { export { Functor, + Kind, + NamedType, defaultIndex, fold, isBooleanNode, + isBooleanValueNode, + isDirectiveNode, isDocumentNode, isEnumNode, + isEnumValueNode, isFieldNode, isFloatNode, + isFloatValueNode, + isFragmentDefinitionNode, + isFragmentSpreadNode, isIDNode, + isInlineFragmentNode, isInputObjectNode, isInputValueNode, - isIntNode, isInterfaceNode, + isIntNode, + isIntValueNode, isListNode, - isNamedNode, - isNonNullNode, + isListValueNode, + isNamedTypeNode, + isNonNullTypeNode, isNullaryNode, + isNullValueNode, isNumberNode, isObjectNode, + isObjectValueNode, isRefNode, - isScalarNode, + isScalarTypeDefinition, + isSelectionSetNode, isStringNode, + isStringValueNode, isUnaryNode, isUnionNode, + isValueNode, + isVariableNode, } from './functor.js' + +export { toType } from './to-type.js' diff --git a/packages/graphql-types/src/functor.ts b/packages/graphql-types/src/functor.ts index 62850f7f..e7a11e16 100644 --- a/packages/graphql-types/src/functor.ts +++ b/packages/graphql-types/src/functor.ts @@ -1,269 +1,582 @@ import type * as T from '@traversable/registry' import { fn, has } from '@traversable/registry' -// interface Query { -// kind: 'OperationDefinition' -// operation: 'query' -// name: Name -// variableDefinitions: readonly VariableDefinition[], -// directives: readonly T[] -// selectionSet: { -// kind: 'SelectionSet' -// selections: [ -// ], -// } -// } +/** + * ## {@link Kind `Kind`} + * + * [Reference](https://github.com/graphql/graphql-js/blob/16.x.x/src/language/kinds.ts#L4) + */ +export const Kind = { + Document: 'Document', + EnumTypeDefinition: 'EnumTypeDefinition', + EnumValueDefinition: 'EnumValueDefinition', + FieldDefinition: 'FieldDefinition', + InputObjectTypeDefinition: 'InputObjectTypeDefinition', + InputValueDefinition: 'InputValueDefinition', + InterfaceTypeDefinition: 'InterfaceTypeDefinition', + ListType: 'ListType', + Name: 'Name', + NamedType: 'NamedType', + NonNullType: 'NonNullType', + ObjectTypeDefinition: 'ObjectTypeDefinition', + OperationDefinition: 'OperationDefinition', + ScalarTypeDefinition: 'ScalarTypeDefinition', + SelectionSet: 'SelectionSet', + UnionTypeDefinition: 'UnionTypeDefinition', + Variable: 'Variable', + VariableDefinition: 'VariableDefinition', + // + FragmentSpread: 'FragmentSpread', + InlineFragment: 'InlineFragment', + FragmentDefinition: 'FragmentDefinition', + // + Argument: 'Argument', + Directive: 'Directive', + DirectiveDefinition: 'DirectiveDefinition', + EnumValue: 'EnumValue', + Field: 'Field', + FloatValue: 'FloatValue', + StringValue: 'StringValue', + BooleanValue: 'BooleanValue', + IntValue: 'IntValue', + ListValue: 'ListValue', + NullValue: 'NullValue', + ObjectValue: 'ObjectValue', + ObjectField: 'ObjectField', + SchemaDefinition: 'SchemaDefinition', + SchemaExtension: 'SchemaExtension', + OperationTypeDefinition: 'OperationTypeDefinition', +} as const + +export declare namespace Kind { + type Document = typeof Kind.Document + type EnumTypeDefinition = typeof Kind.EnumTypeDefinition + type EnumValueDefinition = typeof Kind.EnumValueDefinition + type FieldDefinition = typeof Kind.FieldDefinition + type InputObjectTypeDefinition = typeof Kind.InputObjectTypeDefinition + type InputValueDefinition = typeof Kind.InputValueDefinition + type InterfaceTypeDefinition = typeof Kind.InterfaceTypeDefinition + type ListType = typeof Kind.ListType + type Name = typeof Kind.Name + type NamedType = typeof Kind.NamedType + type NonNullType = typeof Kind.NonNullType + type ObjectTypeDefinition = typeof Kind.ObjectTypeDefinition + type OperationDefinition = typeof Kind.OperationDefinition + type ScalarTypeDefinition = typeof Kind.ScalarTypeDefinition + type SelectionSet = typeof Kind.SelectionSet + type UnionTypeDefinition = typeof Kind.UnionTypeDefinition + type Variable = typeof Kind.Variable + type VariableDefinition = typeof Kind.VariableDefinition + // + type FragmentSpread = typeof Kind.FragmentSpread + type InlineFragment = typeof Kind.InlineFragment + type FragmentDefinition = typeof Kind.FragmentDefinition + // + type Argument = typeof Kind.Argument + type Directive = typeof Kind.Directive + type DirectiveDefinition = typeof Kind.DirectiveDefinition + type EnumValue = typeof Kind.EnumValue + type Field = typeof Kind.Field + type FloatValue = typeof Kind.FloatValue + type StringValue = typeof Kind.StringValue + type BooleanValue = typeof Kind.BooleanValue + type IntValue = typeof Kind.IntValue + type ListValue = typeof Kind.ListValue + type NullValue = typeof Kind.NullValue + type ObjectValue = typeof Kind.ObjectValue + type ObjectField = typeof Kind.ObjectField + type SchemaDefinition = typeof Kind.SchemaDefinition + type SchemaExtension = typeof Kind.SchemaExtension + type OperationTypeDefinition = typeof Kind.OperationTypeDefinition +} + +/** + * ## {@link NamedType `NamedType`} + */ +export const NamedType = { + Boolean: 'Boolean', + Float: 'Float', + ID: 'ID', + Int: 'Int', + Number: 'Number', + String: 'String', +} as const + +export declare namespace NamedType { + type Boolean = typeof NamedType.Boolean + type Float = typeof NamedType.Float + type ID = typeof NamedType.ID + type Int = typeof NamedType.Int + type Number = typeof NamedType.Number + type String = typeof NamedType.String +} +/** + * ## {@link AST `AST`} + */ export declare namespace AST { - export interface Name { - kind: 'Name' + type Catalog = { [Node in F as Node['kind']]: Node } + interface Location { + start: number + end: number + } + + interface SelectionSetNode { + kind: Kind.SelectionSet + selections: readonly T[] + loc?: Location + } + + interface NameNode { + kind: Kind.Name value: Value + loc?: Location } - export interface Named { - name: Name + interface NamedTypeNode { + name: NameNode + loc?: Location } /** - * ## {@link Ref `Ref`} + * ## {@link RefNode `RefNode`} * - * A {@link Ref `Ref`} is a named type that is not one of the built-in types. + * A {@link RefNode `RefNode`} is a named type that is not one of the built-in types. */ - export interface Ref { - kind: 'NamedType' - name: Name + interface RefNode { + kind: Kind.NamedType + name: NameNode + loc?: Location } - export interface Document { - kind: 'Document' + interface DocumentNode { + kind: Kind.Document definitions: readonly T[] + loc?: Location } - export interface InputValue { - kind: 'InputValueDefinition' - name: Name + interface InputValueNode { + kind: Kind.InputValueDefinition + name: NameNode type: T - directives: readonly T[] + directives?: readonly T[] + loc?: Location } - export interface InputObject { - kind: 'InputObjectTypeDefinition' - name: Name + interface InputObjectNode { + kind: Kind.InputObjectTypeDefinition + name: NameNode fields: readonly T[] - directives: readonly T[] + directives?: readonly T[] + loc?: Location } - export interface Variable { - kind: 'Variable' - name: Name + interface VariableNode { + kind: Kind.Variable + name: NameNode + loc?: Location } - export interface VariableDefinition { - kind: 'VariableDefinition' - variable: Variable + interface VariableDefinitionNode { + kind: Kind.VariableDefinition + variable: VariableNode type: T - directives: readonly T[] + directives?: readonly T[] + loc?: Location } - export type Nullary = - | Boolean - | Int - | Number - | Float - | String - | ID - | Scalar - | Enum - - export interface Scalar { - kind: 'ScalarTypeDefinition' - name: Name + interface ScalarTypeDefinition { + kind: Kind.ScalarTypeDefinition + name: NameNode + loc?: Location } - - export interface Boolean { - kind: 'NamedType' - name: Name<'Boolean'> + interface Boolean { + kind: Kind.NamedType + name: NameNode + loc?: Location } - export interface Int { - kind: 'NamedType' - name: Name<'Int'> + interface Int { + kind: Kind.NamedType + name: NameNode + loc?: Location } - export interface Number { - kind: 'NamedType' - name: Name<'Number'> + interface Number { + kind: Kind.NamedType + name: NameNode + loc?: Location } - export interface Float { - kind: 'NamedType' - name: Name<'Float'> + interface Float { + kind: Kind.NamedType + name: NameNode + loc?: Location } - export interface String { - kind: 'NamedType' - name: Name<'String'> + interface String { + kind: Kind.NamedType + name: NameNode + loc?: Location } - export interface ID { - kind: 'NamedType' - name: Name<'ID'> + interface ID { + kind: Kind.NamedType + name: NameNode + loc?: Location } - export interface EnumValue { - kind: 'EnumValueDefinition' - name: Name + interface EnumValueNode { + kind: Kind.EnumValueDefinition + name: NameNode + loc?: Location } - export interface Enum { - kind: 'EnumTypeDefinition' - name: Name - values: readonly EnumValue[] + interface EnumNode { + kind: Kind.EnumTypeDefinition + name: NameNode + values: readonly EnumValueNode[] + loc?: Location } - export interface NonNull { - kind: 'NonNullType' + interface NonNullTypeNode { + kind: Kind.NonNullType type: T + loc?: Location } - export interface List { - kind: 'ListType' + interface ListNode { + kind: Kind.ListType type: T + loc?: Location } - export interface Field { - kind: 'FieldDefinition' - name: Name + interface FieldNode { + kind: Kind.FieldDefinition + name: NameNode type: T defaultValue?: unknown - arguments: readonly T[] - directives: readonly T[] + arguments?: readonly T[] + directives?: readonly T[] + loc?: Location } - export interface Object { - kind: 'ObjectTypeDefinition' - name: Name + interface ObjectNode { + kind: Kind.ObjectTypeDefinition + name: NameNode fields: readonly T[] interfaces: readonly T[] - directives: readonly T[] + directives?: readonly T[] + loc?: Location } - export interface Interface { - kind: 'InterfaceTypeDefinition' - name: Name + interface InterfaceNode { + kind: Kind.InterfaceTypeDefinition + name: NameNode fields: readonly T[] interfaces: readonly T[] - directives: readonly T[] + directives?: readonly T[] + loc?: Location } - export interface Union { - kind: 'UnionTypeDefinition' - name: Name + interface UnionNode { + kind: Kind.UnionTypeDefinition + name: NameNode types: readonly T[] - directives: readonly T[] + directives?: readonly T[] + loc?: Location } - export type Unary = - | NonNull - | List - | Field - | Object - | Interface - | Union - | InputValue - | InputObject - | Document + interface FragmentDefinitionNode { + kind: Kind.FragmentDefinition + name: NameNode + typeCondition: NamedTypeNode + directives?: readonly T[] + selectionSet: T + loc?: Location + } - export type Fixpoint = - | Nullary - | Ref - | NonNull - | List - | Field - | Object - | Interface - | Union - | InputValue - | InputObject - | Document - - export type F = + interface FragmentSpreadNode { + kind: Kind.FragmentSpread + name: NameNode + directives?: readonly T[] + loc?: Location + } + + interface InlineFragmentNode { + kind: Kind.InlineFragment + typeCondition?: NamedTypeNode + directives?: readonly T[] + selectionSet: T + loc?: Location + } + + interface IntValueNode { + kind: Kind.IntValue + value: string + loc?: Location + } + + interface FloatValueNode { + kind: Kind.FloatValue + value: string + loc?: Location + } + + interface StringValueNode { + kind: Kind.StringValue + value: string + loc?: Location + } + + interface BooleanValueNode { + kind: Kind.BooleanValue + value: boolean + loc?: Location + } + + interface NullValueNode { + kind: Kind.NullValue + loc?: Location + } + + interface ListValueNode { + kind: Kind.ListValue + values: readonly ValueNode[] + loc?: Location + } + + interface ObjectValueNode { + kind: Kind.ObjectValue + fields: readonly ObjectFieldNode[] + loc?: Location + } + + interface ObjectFieldNode { + kind: Kind.ObjectField + name: NameNode + value: ValueNode + loc?: Location + } + + interface DirectiveNode { + kind: Kind.Directive + name: NameNode + arguments?: readonly T[] + loc?: Location + } + + type ValueNode = + | VariableNode + | IntValueNode + | FloatValueNode + | StringValueNode + | BooleanValueNode + | NullValueNode + | EnumValueNode + | ListValueNode + | ObjectValueNode + + type Nullary = + | Boolean + | Int + | Number + | Float + | String + | ID + | ScalarTypeDefinition + | EnumNode + | ValueNode + + type Unary = + | NonNullTypeNode + | ListNode + | FieldNode + | ObjectNode + | InterfaceNode + | UnionNode + | InputValueNode + | InputObjectNode + | SelectionSetNode + | FragmentDefinitionNode + | FragmentSpreadNode + | InlineFragmentNode + | DirectiveNode + + type F = + | RefNode | Nullary - | Ref | Unary + | DocumentNode + + type Fixpoint = + | RefNode + | Nullary + | NonNullTypeNode + | ListNode + | FieldNode + | ObjectNode + | InterfaceNode + | UnionNode + | InputValueNode + | InputObjectNode + | SelectionSetNode + | FragmentDefinitionNode + | FragmentSpreadNode + | InlineFragmentNode + | DirectiveNode + | DocumentNode } -export function isScalarNode(x: unknown): x is AST.Scalar { - return has('kind', (kind) => kind === 'ScalarTypeDefinition')(x) +export function isScalarTypeDefinition(x: unknown): x is AST.ScalarTypeDefinition { + return has('kind', (kind) => kind === Kind.ScalarTypeDefinition)(x) } export function isBooleanNode(x: unknown): x is AST.Boolean { - return has('name', 'value', (value) => value === 'Boolean')(x) + return has('name', 'value', (value) => value === NamedType.Boolean)(x) } export function isIntNode(x: unknown): x is AST.Int { - return has('name', 'value', (value) => value === 'Int')(x) + return has('name', 'value', (value) => value === NamedType.Int)(x) } export function isNumberNode(x: unknown): x is AST.Number { - return has('name', 'value', (value) => value === 'Number')(x) + return has('name', 'value', (value) => value === NamedType.Number)(x) } export function isFloatNode(x: unknown): x is AST.Float { - return has('name', 'value', (value) => value === 'Float')(x) + return has('name', 'value', (value) => value === NamedType.Float)(x) } export function isStringNode(x: unknown): x is AST.String { - return has('name', 'value', (value) => value === 'String')(x) + return has('name', 'value', (value) => value === NamedType.String)(x) } export function isIDNode(x: unknown): x is AST.ID { - return has('name', 'value', (value) => value === 'ID')(x) + return has('name', 'value', (value) => value === NamedType.ID)(x) } -export function isEnumNode(x: unknown): x is AST.Enum { - return has('name', 'value', (value) => value === 'ID')(x) +export function isEnumNode(x: unknown): x is AST.EnumNode { + return has('kind', (kind) => kind === Kind.EnumTypeDefinition)(x) } -export function isNonNullNode(x: unknown): x is AST.NonNull { - return has('kind', (kind) => kind === 'NonNullType')(x) +export function isVariableNode(x: unknown): x is AST.VariableNode { + return has('kind', (kind) => kind === Kind.Variable)(x) } -export function isListNode(x: unknown): x is AST.List { - return has('kind', (kind) => kind === 'ListType')(x) +export function isBooleanValueNode(x: unknown): x is AST.BooleanValueNode { + return has('kind', (kind) => kind === Kind.BooleanValue)(x) } -export function isFieldNode(x: unknown): x is AST.Field { - return has('kind', (kind) => kind === 'FieldDefinition')(x) +export function isIntValueNode(x: unknown): x is AST.IntValueNode { + return has('kind', (kind) => kind === Kind.IntValue)(x) } -export function isObjectNode(x: unknown): x is AST.Object { - return has('kind', (kind) => kind === 'ObjectTypeDefinition')(x) +export function isFloatValueNode(x: unknown): x is AST.FloatValueNode { + return has('kind', (kind) => kind === Kind.FloatValue)(x) } -export function isInterfaceNode(x: unknown): x is AST.Interface { - return has('kind', (kind) => kind === 'InterfaceTypeDefinition')(x) +export function isStringValueNode(x: unknown): x is AST.StringValueNode { + return has('kind', (kind) => kind === Kind.StringValue)(x) +} + +export function isNullValueNode(x: unknown): x is AST.NullValueNode { + return has('kind', (kind) => kind === Kind.NullValue)(x) +} + +export function isEnumValueNode(x: unknown): x is AST.EnumValueNode { + return has('kind', (kind) => kind === Kind.EnumValue)(x) +} + +export function isListValueNode(x: unknown): x is AST.ListValueNode { + return has('kind', (kind) => kind === Kind.ListValue)(x) +} + +export function isObjectValueNode(x: unknown): x is AST.ObjectValueNode { + return has('kind', (kind) => kind === Kind.ObjectValue)(x) +} + +export function isNonNullTypeNode(x: unknown): x is AST.NonNullTypeNode { + return has('kind', (kind) => kind === Kind.NonNullType)(x) +} + +export function isListNode(x: unknown): x is AST.ListNode { + return has('kind', (kind) => kind === Kind.ListType)(x) +} + +export function isFieldNode(x: unknown): x is AST.FieldNode { + return has('kind', (kind) => kind === Kind.FieldDefinition)(x) +} + +export function isObjectNode(x: unknown): x is AST.ObjectNode { + return has('kind', (kind) => kind === Kind.ObjectTypeDefinition)(x) +} + +export function isInterfaceNode(x: unknown): x is AST.InterfaceNode { + return has('kind', (kind) => kind === Kind.InterfaceTypeDefinition)(x) +} + +export function isUnionNode(x: unknown): x is AST.UnionNode { + return has('kind', (kind) => kind === Kind.UnionTypeDefinition)(x) +} + +export function isInputValueNode(x: unknown): x is AST.InputValueNode { + return has('kind', (kind) => kind === Kind.InputValueDefinition)(x) +} + +export function isInputObjectNode(x: unknown): x is AST.InputObjectNode { + return has('kind', (kind) => kind === Kind.InputObjectTypeDefinition)(x) +} + +export function isNamedTypeNode(x: unknown): x is AST.NamedTypeNode { + return has('name', 'value', (value) => typeof value === 'string')(x) } -export function isUnionNode(x: unknown): x is AST.Union { - return has('kind', (kind) => kind === 'UnionTypeDefinition')(x) +export function isSelectionSetNode(x: unknown): x is AST.SelectionSetNode { + return has('kind', (kind) => kind === Kind.SelectionSet)(x) } -export function isInputValueNode(x: unknown): x is AST.InputValue { - return has('kind', (kind) => kind === 'InputValueDefinition')(x) +export function isFragmentDefinitionNode(x: unknown): x is AST.FragmentDefinitionNode { + return has('kind', (kind) => kind === Kind.FragmentDefinition)(x) } -export function isInputObjectNode(x: unknown): x is AST.InputObject { - return has('kind', (kind) => kind === 'InputObjectTypeDefinition')(x) +export function isFragmentSpreadNode(x: unknown): x is AST.FragmentSpreadNode { + return has('kind', (kind) => kind === Kind.FragmentSpread)(x) } -export function isDocumentNode(x: unknown): x is AST.Document { - return has('kind', (kind) => kind === 'Document')(x) +export function isInlineFragmentNode(x: unknown): x is AST.InlineFragmentNode { + return has('kind', (kind) => kind === Kind.InlineFragment)(x) +} + +export function isDirectiveNode(x: unknown): x is AST.DirectiveNode { + return has('kind', (kind) => kind === Kind.Directive)(x) +} + +export function isDocumentNode(x: unknown): x is AST.DocumentNode { + return has('kind', (kind) => kind === Kind.Document)(x) +} + +export function isRefNode(x: unknown): x is AST.RefNode { + return has('kind', (kind) => kind === Kind.NamedType)(x) + && has('name', 'value', (value) => typeof value === 'string')(x) + && !isNullaryNode(x) +} + +export function isValueNode(x: unknown): x is AST.ValueNode { + return isVariableNode(x) + || isIntValueNode(x) + || isFloatValueNode(x) + || isStringValueNode(x) + || isBooleanValueNode(x) + || isNullValueNode(x) + || isEnumValueNode(x) + || isListValueNode(x) + || isObjectValueNode(x) } export function isNullaryNode(x: unknown): x is AST.Nullary { - return isScalarNode(x) + return isScalarTypeDefinition(x) || isBooleanNode(x) || isIntNode(x) || isNumberNode(x) @@ -273,18 +586,8 @@ export function isNullaryNode(x: unknown): x is AST.Nullary { || isEnumNode(x) } -export function isNamedNode(x: unknown): x is AST.Named { - return has('name', 'value', (value) => typeof value === 'string')(x) -} - -export function isRefNode(x: unknown): x is AST.Ref { - return has('kind', (kind) => kind === 'NamedType')(x) - && has('name', 'value', (value) => typeof value === 'string')(x) - && !isNullaryNode(x) -} - export function isUnaryNode(x: unknown): x is AST.Unary { - return isNonNullNode(x) + return isNonNullTypeNode(x) || isListNode(x) || isObjectNode(x) || isInterfaceNode(x) @@ -292,12 +595,14 @@ export function isUnaryNode(x: unknown): x is AST.Unary { || isInputObjectNode(x) } -export const defaultIndex = { isNonNull: false } satisfies Index - export interface Index { isNonNull: boolean } +export const defaultIndex = { + isNonNull: false, +} satisfies Index + export type Algebra = { (src: AST.F, ix?: Index): T (src: AST.Fixpoint, ix?: Index): T @@ -309,79 +614,202 @@ export type Fold = (g: (src: AST.F, ix: Index, x: AST.Fixpoint) => T) => A export interface Functor extends T.HKT { [-1]: AST.F } export declare namespace Functor { - export { Index } + export { + Index, + } } -export const Functor: T.Functor.Ix = { +export const Functor: T.Functor.Ix = { map(g) { return (x) => { switch (true) { - default: return x + default: return fn.exhaustive(x) case isRefNode(x): return x case isNullaryNode(x): return x - case isNonNullNode(x): return { ...x, type: g(x.type) } case isListNode(x): return { ...x, type: g(x.type) } - case isUnionNode(x): return { ...x, directives: x.directives.map(g), types: x.types.map(g) } - case isFieldNode(x): return { ...x, type: g(x.type), arguments: x.arguments.map(g), directives: x.directives.map(g) } - case isObjectNode(x): return { ...x, directives: x.directives.map(g), interfaces: x.interfaces.map(g), fields: x.fields.map(g) } - case isInterfaceNode(x): return { ...x, directives: x.directives.map(g), interfaces: x.interfaces.map(g), fields: x.fields.map(g) } - case isInputValueNode(x): return { ...x, type: g(x.type), directives: x.directives.map(g) } - case isInputObjectNode(x): return { ...x, directives: x.directives.map(g), fields: x.fields.map(g) } + case isNonNullTypeNode(x): return { ...x, type: g(x.type) } + case isSelectionSetNode(x): return { ...x, selections: x.selections.map(g) } case isDocumentNode(x): return { ...x, definitions: x.definitions.map(g) } + case isUnionNode(x): { + const { directives, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map(g) }, + types: x.types.map(g), + } + } + case isFieldNode(x): { + const { arguments: args, directives, ...xs } = x + return { + ...xs, + ...args && { arguments: args.map(g) }, + ...directives && { directives: directives.map(g) }, + type: g(x.type), + } + } + case isObjectNode(x): { + const { directives, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map(g) }, + interfaces: x.interfaces.map(g), + fields: x.fields.map(g), + } + } + case isInterfaceNode(x): { + const { directives, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map(g) }, + interfaces: x.interfaces.map(g), + fields: x.fields.map(g), + } + } + case isInputValueNode(x): { + const { directives, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map(g) }, + type: g(x.type), + } + } + case isInputObjectNode(x): { + const { directives, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map(g) }, + fields: x.fields.map(g), + } + } + case isFragmentDefinitionNode(x): { + const { directives, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map(g) }, + selectionSet: g(x.selectionSet), + } + } + case isFragmentSpreadNode(x): { + const { directives, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map(g) }, + } + } + case isInlineFragmentNode(x): { + const { directives, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map(g) }, + selectionSet: g(x.selectionSet), + } + } + case isDirectiveNode(x): { + const { arguments: args, ...xs } = x + return { + ...xs, + ...args && { arguments: args.map(g) }, + } + } } } }, mapWithIndex(g) { return (x, _ix) => { - const ix = isNonNullNode(x) ? { isNonNull: true } satisfies Index : defaultIndex + const ix = isNonNullTypeNode(x) ? { isNonNull: true } satisfies Index : defaultIndex switch (true) { - default: return x + default: return fn.exhaustive(x) case isRefNode(x): return x case isNullaryNode(x): return x - case isNonNullNode(x): return { ...x, type: g(x.type, ix, x) } case isListNode(x): return { ...x, type: g(x.type, ix, x) } - case isUnionNode(x): return { - ...x, - directives: x.directives.map((_) => g(_, ix, x)), - types: x.types.map((_) => g(_, ix, x)) + case isNonNullTypeNode(x): return { ...x, type: g(x.type, ix, x) } + case isSelectionSetNode(x): return { ...x, selections: x.selections.map((_) => g(_, ix, x)) } + case isDocumentNode(x): return { ...x, definitions: x.definitions.map((_) => g(_, ix, x)) } + case isUnionNode(x): { + const { directives, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map((_) => g(_, ix, x)) }, + types: x.types.map((_) => g(_, ix, x)), + } } case isFieldNode(x): { + const { arguments: args, directives, ...xs } = x + return { + ...xs, + ...args && { arguments: args.map((_) => g(_, ix, x)) }, + ...directives && { directives: directives.map((_) => g(_, ix, x)) }, + type: g(x.type, isNonNullTypeNode(x.type) ? { isNonNull: true } : ix, x), + } + } + case isObjectNode(x): { + const { directives, ...xs } = x return { - ...x, - type: g(x.type, isNonNullNode(x.type) ? { isNonNull: true } : ix, x), - arguments: x.arguments.map((_) => g(_, ix, x)), - directives: x.directives.map((_) => g(_, ix, x)), + ...xs, + ...directives && { directives: directives.map((_) => g(_, ix, x)) }, + fields: x.fields.map((_) => g(_, ix, x)), + interfaces: x.interfaces.map((_) => g(_, ix, x)), } } - case isObjectNode(x): return { - ...x, - directives: x.directives.map((_) => g(_, ix, x)), - fields: x.fields.map((_) => g(_, ix, x)), - interfaces: x.interfaces.map((_) => g(_, ix, x)), + case isInterfaceNode(x): { + const { directives, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map((_) => g(_, ix, x)) }, + fields: x.fields.map((_) => g(_, ix, x)), + interfaces: x.interfaces.map((_) => g(_, ix, x)), + } + } + case isInputValueNode(x): { + const { directives, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map((_) => g(_, ix, x)) }, + type: g(x.type, ix, x), + } + } + case isInputObjectNode(x): { + const { directives, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map((_) => g(_, ix, x)) }, + fields: x.fields.map((_) => g(_, ix, x)), + } } - case isInterfaceNode(x): return { - ...x, - directives: x.directives.map((_) => g(_, ix, x)), - fields: x.fields.map((_) => g(_, ix, x)), - interfaces: x.interfaces.map((_) => g(_, ix, x)), + case isFragmentDefinitionNode(x): { + const { directives, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map((_) => g(_, ix, x)) }, + selectionSet: g(x.selectionSet, ix, x), + } } - case isInputValueNode(x): return { - ...x, - type: g(x.type, ix, x), - directives: x.directives.map((_) => g(_, ix, x)), + case isFragmentSpreadNode(x): { + const { directives, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map((_) => g(_, ix, x)) }, + } } - case isInputObjectNode(x): return { - ...x, - directives: x.directives.map((_) => g(_, ix, x)), - fields: x.fields.map((_) => g(_, ix, x)), + case isInlineFragmentNode(x): { + const { directives, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map((_) => g(_, ix, x)) }, + selectionSet: g(x.selectionSet, ix, x), + } } - case isDocumentNode(x): return { - ...x, - definitions: x.definitions.map((_) => g(_, ix, x)), + case isDirectiveNode(x): { + const { arguments: args, ...xs } = x + return { + ...xs, + ...args && { arguments: args.map((_) => g(_, ix, x)) }, + } } } } } + } export const fold: Fold = fn.catamorphism(Functor, defaultIndex) as never diff --git a/packages/graphql-types/src/to-type.ts b/packages/graphql-types/src/to-type.ts new file mode 100644 index 00000000..486695f3 --- /dev/null +++ b/packages/graphql-types/src/to-type.ts @@ -0,0 +1,101 @@ +import { fn, has, parseKey } from '@traversable/registry' +import * as GQL from './functor.js' +import type { AST } from './functor.js' + +const unsupported = [ + 'Directive', + 'FragmentDefinition', + 'FragmentSpread', + 'InlineFragment', + 'InputObjectTypeDefinition', + 'InputValueDefinition', + 'SelectionSet', +] as const satisfies typeof GQL.Kind[keyof typeof GQL.Kind][] + +type UnsupportedNodeMap = Pick +type UnsupportedNode = UnsupportedNodeMap[keyof UnsupportedNodeMap] + +function isUnsupportedNode(x: unknown): x is UnsupportedNode { + return unsupported.some( + (nope) => has('kind', (kind): kind is never => kind === nope)(x) + ) +} + +function valueNodeToString(x: AST.ValueNode): string { + switch (x.kind) { + default: return fn.exhaustive(x) + case 'NullValue': return 'null' + case 'BooleanValue': return `${x.value}` + case 'IntValue': return `${x.value}` + case 'FloatValue': return `${x.value}` + case 'StringValue': return `"${x.value}"` + case 'EnumValueDefinition': return `"${x.name.value}"` + case 'ListValue': return `[${x.values.map(valueNodeToString).join(', ')}]` + case 'ObjectValue': return '' + + '{ ' + + x.fields.map((node) => `${parseKey(node.name.value)}: ${valueNodeToString(node.value)}`).join(', ') + + ' }' + case 'Variable': return `${x.name.value}` + } +} + +const fold = GQL.fold((x, _, original) => { + switch (true) { + default: return fn.exhaustive(x) + case GQL.isRefNode(x): return x.name.value + case GQL.isValueNode(x): return valueNodeToString(x) + case GQL.isScalarTypeDefinition(x): return x.name.value + case GQL.isBooleanNode(x): return 'boolean' + case GQL.isIntNode(x): return 'number' + case GQL.isNumberNode(x): return 'number' + case GQL.isFloatNode(x): return 'number' + case GQL.isStringNode(x): return 'string' + case GQL.isIDNode(x): return 'string' + case GQL.isEnumNode(x): return ( + x.values.length === 0 ? 'never' + : x.values.length === 1 ? JSON.stringify(x.values[0]) + : `(${x.values.map((v) => JSON.stringify(v)).join(' | ')})` + ) + case GQL.isNonNullTypeNode(x): return `${x.type}!` + case GQL.isUnionNode(x): return ( + x.types.length === 0 ? 'never' + : x.types.length === 1 ? JSON.stringify(x.types[0]) + : `(${x.types.map((v) => JSON.stringify(v)).join(' | ')})` + ) + case GQL.isListNode(x): { + if (!GQL.isListNode(original)) throw Error('Illegal state') + const isNonNull = x.type.endsWith('!') + const TYPE = isNonNull ? x.type.slice(0, -1) : `${x.type} | null` + return `Array<${TYPE}>` + } + case GQL.isFieldNode(x): { + const isNonNull = x.type.endsWith('!') + const VALUE = isNonNull ? x.type.slice(0, -1) : x.type + return `${parseKey(x.name.value)}${isNonNull ? '' : '?'}: ${VALUE}` + } + case GQL.isObjectNode(x): { return x.fields.length === 0 ? `{}` : `{ ${x.fields.join(', ')} }` } + case GQL.isInterfaceNode(x): { return x.fields.length === 0 ? `{}` : `{ ${x.fields.join(', ')} }` } + case GQL.isDocumentNode(x): throw Error('[@traversable/graphql-types/to-type.js]: Nesting documents is not allowed') + case isUnsupportedNode(x): throw Error(`[@traversable/graphql-types/to-type.js]: Unsupported node: ${x.kind}`) + } +}) + +export declare namespace toType { + export type { + UnsupportedNode + } +} + +toType.unsupported = unsupported + +/** + * ## {@link toType `toType`} + * + * Convert a GraphQL AST into its corresponding TypeScript type. + */ +export function toType(doc: AST.DocumentNode) { + const types = doc.definitions.map( + (x, i) => `type ${GQL.isNamedTypeNode(x) ? x.name.value : `Type${i}`} = ${fold(x)}` + ) + return types.join('\n') +} diff --git a/packages/graphql-types/test/to-type.test.ts b/packages/graphql-types/test/to-type.test.ts new file mode 100644 index 00000000..fe6dc0dc --- /dev/null +++ b/packages/graphql-types/test/to-type.test.ts @@ -0,0 +1,96 @@ +import * as vi from 'vitest' +import { toType } from '@traversable/graphql-types' +import prettier from '@prettier/sync' + +const format = (src: string) => prettier.format( + src, + { parser: 'typescript', semi: false, printWidth: 50 } +) + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/graphql-types❳', () => { + vi.test('〖⛳️〗› ❲toType❳', () => { + vi.expect.soft(format( + toType({ + "kind": "Document", + "definitions": [ + { + "kind": "ObjectTypeDefinition", + "name": { + "kind": "Name", + "value": "Pet", + }, + "interfaces": [], + "directives": [], + "fields": [ + { + "kind": "FieldDefinition", + "name": { + "kind": "Name", + "value": "petName", + }, + "arguments": [], + "type": { + "kind": "NamedType", + "name": { + "kind": "Name", + "value": "String", + }, + }, + "directives": [], + } + ], + }, + { + "kind": "ObjectTypeDefinition", + "name": { + "kind": "Name", + "value": "Human", + }, + "interfaces": [], + "directives": [], + "fields": [ + { + "kind": "FieldDefinition", + "name": { + "kind": "Name", + "value": "humanName", + }, + "arguments": [], + "type": { + "kind": "NamedType", + "name": { + "kind": "Name", + "value": "String", + }, + }, + "directives": [], + }, + { + "kind": "FieldDefinition", + "name": { + "kind": "Name", + "value": "pet", + }, + "arguments": [], + "type": { + "kind": "NamedType", + "name": { + "kind": "Name", + "value": "Pet", + }, + }, + "directives": [], + } + ], + } + ], + }) + ) + ).toMatchInlineSnapshot + (` + "type Pet = { petName?: string } + type Human = { humanName?: string; pet?: Pet } + " + `) + }) +}) \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e5fc25f0..c7fffb34 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -316,6 +316,9 @@ importers: '@traversable/registry': specifier: workspace:^ version: link:../registry/dist + graphql: + specifier: ^16.11.0 + version: 16.11.0 publishDirectory: dist packages/json: @@ -2401,6 +2404,10 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + graphql@16.11.0: + resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -5421,6 +5428,8 @@ snapshots: graphemer@1.4.0: {} + graphql@16.11.0: {} + has-bigints@1.1.0: {} has-flag@4.0.0: {} From 4f5f316460109eff3527846def0c00f22bed0743 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Thu, 18 Sep 2025 23:15:06 -0500 Subject: [PATCH 05/32] deps(graphql-types): adds type-level dep on `graphql` --- packages/graphql-types/src/functor.ts | 112 ++++++++++++++++---- packages/graphql-types/src/to-type.ts | 6 +- packages/graphql-types/test/to-type.test.ts | 102 ++++-------------- 3 files changed, 115 insertions(+), 105 deletions(-) diff --git a/packages/graphql-types/src/functor.ts b/packages/graphql-types/src/functor.ts index e7a11e16..229944f8 100644 --- a/packages/graphql-types/src/functor.ts +++ b/packages/graphql-types/src/functor.ts @@ -1,5 +1,6 @@ import type * as T from '@traversable/registry' import { fn, has } from '@traversable/registry' +import type * as gql from 'graphql' /** * ## {@link Kind `Kind`} @@ -111,11 +112,24 @@ export declare namespace NamedType { type String = typeof NamedType.String } +export const OperationType = { + Query: 'query', + Mutation: 'mutation', + Subscription: 'subscription', +} as const + +export declare namespace OperationType { + type Query = typeof OperationType.Query + type Mutation = typeof OperationType.Mutation + type Subscription = typeof OperationType.Subscription +} + /** * ## {@link AST `AST`} */ export declare namespace AST { type Catalog = { [Node in F as Node['kind']]: Node } + interface Location { start: number end: number @@ -367,6 +381,36 @@ export declare namespace AST { loc?: Location } + interface QueryOperation { + kind: Kind.OperationDefinition + operation: OperationType.Query + name?: NameNode + variableDefinitions?: readonly T[] + directives?: readonly T[] + selectionSet: T + loc?: Location + } + + interface MutationOperation { + kind: Kind.OperationDefinition + operation: OperationType.Mutation + name?: NameNode + variableDefinitions?: readonly T[] + directives?: readonly T[] + selectionSet: T + loc?: Location + } + + interface SubscriptionOperation { + kind: Kind.OperationDefinition + operation: OperationType.Subscription + name?: NameNode + variableDefinitions?: readonly T[] + directives?: readonly T[] + selectionSet: T + loc?: Location + } + type ValueNode = | VariableNode | IntValueNode @@ -389,6 +433,11 @@ export declare namespace AST { | EnumNode | ValueNode + type OperationDefinitionNode = + | QueryOperation + | MutationOperation + | SubscriptionOperation + type Unary = | NonNullTypeNode | ListNode @@ -408,25 +457,8 @@ export declare namespace AST { | RefNode | Nullary | Unary + | OperationDefinitionNode | DocumentNode - - type Fixpoint = - | RefNode - | Nullary - | NonNullTypeNode - | ListNode - | FieldNode - | ObjectNode - | InterfaceNode - | UnionNode - | InputValueNode - | InputObjectNode - | SelectionSetNode - | FragmentDefinitionNode - | FragmentSpreadNode - | InlineFragmentNode - | DirectiveNode - | DocumentNode } export function isScalarTypeDefinition(x: unknown): x is AST.ScalarTypeDefinition { @@ -553,6 +585,24 @@ export function isDirectiveNode(x: unknown): x is AST.DirectiveNode { return has('kind', (kind) => kind === Kind.Directive)(x) } +export function isQueryOperation(x: unknown): x is AST.QueryOperation { + return has('kind', (kind) => kind === OperationType.Query)(x) +} + +export function isMutationOperation(x: unknown): x is AST.MutationOperation { + return has('kind', (kind) => kind === OperationType.Mutation)(x) +} + +export function isSubscriptionOperation(x: unknown): x is AST.SubscriptionOperation { + return has('kind', (kind) => kind === OperationType.Subscription)(x) +} + +export function isOperationDefinitionNode(x: unknown): x is AST.OperationDefinitionNode { + return isQueryOperation(x) + || isMutationOperation(x) + || isSubscriptionOperation(x) +} + export function isDocumentNode(x: unknown): x is AST.DocumentNode { return has('kind', (kind) => kind === Kind.Document)(x) } @@ -605,11 +655,11 @@ export const defaultIndex = { export type Algebra = { (src: AST.F, ix?: Index): T - (src: AST.Fixpoint, ix?: Index): T + (src: gql.ASTNode, ix?: Index): T (src: AST.F, ix?: Index): T } -export type Fold = (g: (src: AST.F, ix: Index, x: AST.Fixpoint) => T) => Algebra +export type Fold = (g: (src: AST.F, ix: Index, x: gql.ASTNode) => T) => Algebra export interface Functor extends T.HKT { [-1]: AST.F } @@ -619,7 +669,7 @@ export declare namespace Functor { } } -export const Functor: T.Functor.Ix = { +export const Functor: T.Functor.Ix = { map(g) { return (x) => { switch (true) { @@ -711,6 +761,16 @@ export const Functor: T.Functor.Ix = { ...args && { arguments: args.map(g) }, } } + + case isOperationDefinitionNode(x): { + const { directives, variableDefinitions: vars, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map(g) }, + ...vars && { variableDefinitions: vars.map(g) }, + selectionSet: g(x.selectionSet) + } + } } } }, @@ -806,10 +866,18 @@ export const Functor: T.Functor.Ix = { ...args && { arguments: args.map((_) => g(_, ix, x)) }, } } + case isOperationDefinitionNode(x): { + const { directives, variableDefinitions: vars, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map((_) => g(_, ix, x)) }, + ...vars && { variableDefinitions: vars.map((_) => g(_, ix, x)) }, + selectionSet: g(x.selectionSet, ix, x) + } + } } } } - } export const fold: Fold = fn.catamorphism(Functor, defaultIndex) as never diff --git a/packages/graphql-types/src/to-type.ts b/packages/graphql-types/src/to-type.ts index 486695f3..c9aa9c2e 100644 --- a/packages/graphql-types/src/to-type.ts +++ b/packages/graphql-types/src/to-type.ts @@ -1,5 +1,6 @@ import { fn, has, parseKey } from '@traversable/registry' import * as GQL from './functor.js' +import type * as gql from 'graphql' import type { AST } from './functor.js' const unsupported = [ @@ -10,7 +11,8 @@ const unsupported = [ 'InputObjectTypeDefinition', 'InputValueDefinition', 'SelectionSet', -] as const satisfies typeof GQL.Kind[keyof typeof GQL.Kind][] + 'OperationDefinition' +] as const satisfies Array type UnsupportedNodeMap = Pick type UnsupportedNode = UnsupportedNodeMap[keyof UnsupportedNodeMap] @@ -93,7 +95,7 @@ toType.unsupported = unsupported * * Convert a GraphQL AST into its corresponding TypeScript type. */ -export function toType(doc: AST.DocumentNode) { +export function toType(doc: gql.DocumentNode) { const types = doc.definitions.map( (x, i) => `type ${GQL.isNamedTypeNode(x) ? x.name.value : `Type${i}`} = ${fold(x)}` ) diff --git a/packages/graphql-types/test/to-type.test.ts b/packages/graphql-types/test/to-type.test.ts index fe6dc0dc..5d06f81b 100644 --- a/packages/graphql-types/test/to-type.test.ts +++ b/packages/graphql-types/test/to-type.test.ts @@ -1,96 +1,36 @@ import * as vi from 'vitest' import { toType } from '@traversable/graphql-types' +import * as graphql from 'graphql' import prettier from '@prettier/sync' const format = (src: string) => prettier.format( src, - { parser: 'typescript', semi: false, printWidth: 50 } + { parser: 'typescript', semi: false, printWidth: 35 } ) vi.describe('〖⛳️〗‹‹‹ ❲@traversable/graphql-types❳', () => { vi.test('〖⛳️〗› ❲toType❳', () => { vi.expect.soft(format( - toType({ - "kind": "Document", - "definitions": [ - { - "kind": "ObjectTypeDefinition", - "name": { - "kind": "Name", - "value": "Pet", - }, - "interfaces": [], - "directives": [], - "fields": [ - { - "kind": "FieldDefinition", - "name": { - "kind": "Name", - "value": "petName", - }, - "arguments": [], - "type": { - "kind": "NamedType", - "name": { - "kind": "Name", - "value": "String", - }, - }, - "directives": [], - } - ], - }, - { - "kind": "ObjectTypeDefinition", - "name": { - "kind": "Name", - "value": "Human", - }, - "interfaces": [], - "directives": [], - "fields": [ - { - "kind": "FieldDefinition", - "name": { - "kind": "Name", - "value": "humanName", - }, - "arguments": [], - "type": { - "kind": "NamedType", - "name": { - "kind": "Name", - "value": "String", - }, - }, - "directives": [], - }, - { - "kind": "FieldDefinition", - "name": { - "kind": "Name", - "value": "pet", - }, - "arguments": [], - "type": { - "kind": "NamedType", - "name": { - "kind": "Name", - "value": "Pet", - }, - }, - "directives": [], - } - ], - } - ], - }) - ) - ).toMatchInlineSnapshot + toType(graphql.parse(` + type Pet { + petName: [String!] + } + type Human { + humanName: String! + pet: Pet + } + ` + )) + )).toMatchInlineSnapshot (` - "type Pet = { petName?: string } - type Human = { humanName?: string; pet?: Pet } + "type Pet = { + petName?: Array + } + type Human = { + humanName: string + pet?: Pet + } " `) }) -}) \ No newline at end of file +}) From c7499a3e1d324f6e6f67e7e77614f6c82dc56331 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Thu, 18 Sep 2025 23:16:31 -0500 Subject: [PATCH 06/32] deps(graphql-types): moves `graphql` to `peerDependencies` --- packages/graphql-types/package.json | 6 +++++- packages/graphql-types/src/functor.ts | 13 ++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/graphql-types/package.json b/packages/graphql-types/package.json index 68b08863..cf97581a 100644 --- a/packages/graphql-types/package.json +++ b/packages/graphql-types/package.json @@ -44,11 +44,15 @@ "test": "vitest" }, "peerDependencies": { - "@traversable/registry": "workspace:^" + "@traversable/registry": "workspace:^", + "graphql": "^16.11.0" }, "peerDependenciesMeta": { "@traversable/registry": { "optional": false + }, + "graphql": { + "optional": false } }, "devDependencies": { diff --git a/packages/graphql-types/src/functor.ts b/packages/graphql-types/src/functor.ts index 229944f8..4e828bf7 100644 --- a/packages/graphql-types/src/functor.ts +++ b/packages/graphql-types/src/functor.ts @@ -597,12 +597,6 @@ export function isSubscriptionOperation(x: unknown): x is AST.SubscriptionOpe return has('kind', (kind) => kind === OperationType.Subscription)(x) } -export function isOperationDefinitionNode(x: unknown): x is AST.OperationDefinitionNode { - return isQueryOperation(x) - || isMutationOperation(x) - || isSubscriptionOperation(x) -} - export function isDocumentNode(x: unknown): x is AST.DocumentNode { return has('kind', (kind) => kind === Kind.Document)(x) } @@ -645,6 +639,12 @@ export function isUnaryNode(x: unknown): x is AST.Unary { || isInputObjectNode(x) } +export function isOperationDefinitionNode(x: unknown): x is AST.OperationDefinitionNode { + return isQueryOperation(x) + || isMutationOperation(x) + || isSubscriptionOperation(x) +} + export interface Index { isNonNull: boolean } @@ -761,7 +761,6 @@ export const Functor: T.Functor.Ix = { ...args && { arguments: args.map(g) }, } } - case isOperationDefinitionNode(x): { const { directives, variableDefinitions: vars, ...xs } = x return { From f7b55c8e1fff8d09aa6984f0a0949a57a60069ab Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Thu, 18 Sep 2025 23:17:36 -0500 Subject: [PATCH 07/32] chore: commits changeset --- .changeset/cruel-geese-buy.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/cruel-geese-buy.md diff --git a/.changeset/cruel-geese-buy.md b/.changeset/cruel-geese-buy.md new file mode 100644 index 00000000..4a546ad0 --- /dev/null +++ b/.changeset/cruel-geese-buy.md @@ -0,0 +1,7 @@ +--- +"@traversable/graphql-types": patch +"@traversable/graphql-test": patch +"@traversable/graphql": patch +--- + +feat(graphql,graphql-types): adds `graphql` to `peerDependencies` From 4047d3039d0141d9b03ff7af5f419cfd42e2663d Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Thu, 18 Sep 2025 23:22:28 -0500 Subject: [PATCH 08/32] test(graphql-types): deletes unused `functor.test.ts` files --- packages/graphql-types/test/functor.test.ts | 150 -------------------- 1 file changed, 150 deletions(-) delete mode 100644 packages/graphql-types/test/functor.test.ts diff --git a/packages/graphql-types/test/functor.test.ts b/packages/graphql-types/test/functor.test.ts deleted file mode 100644 index 1dc9e089..00000000 --- a/packages/graphql-types/test/functor.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -import * as vi from 'vitest' -import prettier from '@prettier/sync' -import { parseKey } from '@traversable/registry' -import type { AST } from '@traversable/graphql-types' -import { - fold, - isBooleanNode, - isDocumentNode, - isEnumNode, - isFieldNode, - isFloatNode, - isIDNode, - isInputObjectNode, - isInputValueNode, - isIntNode, - isInterfaceNode, - isListNode, - isNamedNode, - isNonNullNode, - isNumberNode, - isObjectNode, - isRefNode, - isScalarNode, - isStringNode, - isUnionNode, -} from '@traversable/graphql-types' - -const format = (src: string) => prettier.format( - src, - { parser: 'typescript', semi: false, printWidth: 60 } -) - -const test = { - "kind": "Document", - "definitions": [ - { - "kind": "ObjectTypeDefinition", - "name": { - "kind": "Name", - "value": "Pet", - "loc": { - "start": 133, - "end": 136 - } - }, - "interfaces": [], - "directives": [], - "fields": [ - { - "kind": "FieldDefinition", - "name": { - "kind": "Name", - "value": "name", - "loc": { - "start": 141, - "end": 145 - } - }, - "arguments": [], - "type": { - "kind": "NamedType", - "name": { - "kind": "Name", - "value": "String", - "loc": { - "start": 147, - "end": 153 - } - }, - "loc": { - "start": 147, - "end": 153 - } - }, - "directives": [], - "loc": { - "start": 141, - "end": 153 - } - } - ], - "loc": { - "start": 128, - "end": 155 - } - } - ], - "loc": { - "start": 0, - "end": 156 - } -} as const - -const algebra = fold((x, ix, original) => { - switch (true) { - default: return x satisfies never - case isDocumentNode(x): throw Error('Nesting documents is not allowed') - case isInputValueNode(x): throw Error('Input values cannot be converted to a type') - case isInputObjectNode(x): throw Error('Input objects cannot be converted to a type') - case isRefNode(x): return x.name.value - case isScalarNode(x): return x.name.value - case isBooleanNode(x): return 'boolean' - case isIntNode(x): return 'number' - case isNumberNode(x): return 'number' - case isFloatNode(x): return 'number' - case isStringNode(x): return 'string' - case isIDNode(x): return 'string' - case isEnumNode(x): return ( - x.values.length === 0 ? 'never' - : x.values.length === 1 ? JSON.stringify(x.values[0]) - : `(${x.values.map((v) => JSON.stringify(v)).join(' | ')})` - ) - case isNonNullNode(x): return `${x.type}!` - case isUnionNode(x): return ( - x.types.length === 0 ? 'never' - : x.types.length === 1 ? JSON.stringify(x.types[0]) - : `(${x.types.map((v) => JSON.stringify(v)).join(' | ')})` - ) - case isListNode(x): { - if (!isListNode(original)) throw Error('Illegal state') - const isNonNull = x.type.endsWith('!') - const TYPE = isNonNull ? x.type.slice(0, -1) : `${x.type} | null` - return `Array<${TYPE}>` - } - case isFieldNode(x): { - const isNonNull = x.type.endsWith('!') - const VALUE = isNonNull ? x.type.slice(0, -1) : x.type - return `${parseKey(x.name.value)}${isNonNull ? '' : '?'}: ${VALUE}` - } - case isObjectNode(x): { return x.fields.length === 0 ? `{}` : `{ ${x.fields.join(', ')} }` } - case isInterfaceNode(x): { return x.fields.length === 0 ? `{}` : `{ ${x.fields.join(', ')} }` } - } -}) - -function toType(doc: AST.Document) { - const types = doc.definitions.map((def, i) => `type ${isNamedNode(def) ? def.name.value : `Type${i}`} = ${fold(algebra)(def)}`) - return types.join('\n') -} - -vi.describe('〖⛳️〗‹‹‹ ❲@traversable/graphql-types❳', () => { - vi.test('〖⛳️〗› ❲Functor❳', () => { - vi.expect.soft(format( - toType({ - kind: 'Document', - definitions: [] - }) - )).toMatchInlineSnapshot - (`""`) - }) -}) From f64262fef66879830cecbc192b43ea774769695a Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Thu, 18 Sep 2025 23:30:46 -0500 Subject: [PATCH 09/32] deps(graphql): promotes `graphql-types` to being a proper dependency --- packages/graphql-types/src/__generated__/__manifest__.ts | 9 +++++++-- packages/graphql/package.json | 6 +----- packages/graphql/src/__generated__/__manifest__.ts | 6 +----- pnpm-lock.yaml | 2 +- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/packages/graphql-types/src/__generated__/__manifest__.ts b/packages/graphql-types/src/__generated__/__manifest__.ts index 8746d00e..90ca0075 100644 --- a/packages/graphql-types/src/__generated__/__manifest__.ts +++ b/packages/graphql-types/src/__generated__/__manifest__.ts @@ -40,15 +40,20 @@ export default { "test": "vitest" }, "peerDependencies": { - "@traversable/registry": "workspace:^" + "@traversable/registry": "workspace:^", + "graphql": "^16.11.0" }, "peerDependenciesMeta": { "@traversable/registry": { "optional": false + }, + "graphql": { + "optional": false } }, "devDependencies": { "@prettier/sync": "catalog:", - "@traversable/registry": "workspace:^" + "@traversable/registry": "workspace:^", + "graphql": "^16.11.0" } } as const \ No newline at end of file diff --git a/packages/graphql/package.json b/packages/graphql/package.json index f9f4ae4a..80af8949 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -35,11 +35,7 @@ "clean:deps": "rm -rf node_modules", "test": "vitest" }, - "peerDependencies": { - "@traversable/graphql-types": "workspace:^", - "@traversable/registry": "workspace:^" - }, - "devDependencies": { + "dependencies": { "@traversable/graphql-types": "workspace:^", "@traversable/registry": "workspace:^" } diff --git a/packages/graphql/src/__generated__/__manifest__.ts b/packages/graphql/src/__generated__/__manifest__.ts index 28612cc6..92c562e7 100644 --- a/packages/graphql/src/__generated__/__manifest__.ts +++ b/packages/graphql/src/__generated__/__manifest__.ts @@ -35,11 +35,7 @@ export default { "clean:deps": "rm -rf node_modules", "test": "vitest" }, - "peerDependencies": { - "@traversable/graphql-types": "workspace:^", - "@traversable/registry": "workspace:^" - }, - "devDependencies": { + "dependencies": { "@traversable/graphql-types": "workspace:^", "@traversable/registry": "workspace:^" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7fffb34..493f8210 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -289,7 +289,7 @@ importers: publishDirectory: dist packages/graphql: - devDependencies: + dependencies: '@traversable/graphql-types': specifier: workspace:^ version: link:../graphql-types/dist From 5cb3330b3104d2d42c0369ded5eb285ccb56f561 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Thu, 18 Sep 2025 23:32:07 -0500 Subject: [PATCH 10/32] docs(graphql): updates changelog --- .changeset/cruel-geese-buy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/cruel-geese-buy.md b/.changeset/cruel-geese-buy.md index 4a546ad0..ba12aac2 100644 --- a/.changeset/cruel-geese-buy.md +++ b/.changeset/cruel-geese-buy.md @@ -4,4 +4,4 @@ "@traversable/graphql": patch --- -feat(graphql,graphql-types): adds `graphql` to `peerDependencies` +feat(graphql): adds `graphql` to `dependencies` From 5587c5c2dca2bd0b67485b0a02b09862afcc5e0b Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Fri, 26 Sep 2025 08:15:59 -0500 Subject: [PATCH 11/32] feat(graphql): adds naive `toString` function, beginning of the GraphQL schema arbitrary --- packages/graphql-test/package.json | 23 +- packages/graphql-test/src/generator-bounds.ts | 222 +++++ .../graphql-test/src/generator-options.ts | 502 ++++++++++++ packages/graphql-test/src/generator-seed.ts | 412 ++++++++++ packages/graphql-test/src/generator.ts | 758 ++++++++++++++++++ packages/graphql-test/tsconfig.build.json | 5 +- packages/graphql-test/tsconfig.src.json | 5 +- packages/graphql-types/package.json | 10 +- packages/graphql-types/src/exports.ts | 10 +- packages/graphql-types/src/functor.ts | 27 +- packages/graphql-types/src/to-string.ts | 66 ++ packages/graphql-types/src/to-type.ts | 47 +- packages/graphql-types/test/to-string.test.ts | 38 + pnpm-lock.yaml | 331 ++++---- 14 files changed, 2242 insertions(+), 214 deletions(-) create mode 100644 packages/graphql-test/src/generator-bounds.ts create mode 100644 packages/graphql-test/src/generator-options.ts create mode 100644 packages/graphql-test/src/generator-seed.ts create mode 100644 packages/graphql-test/src/generator.ts create mode 100644 packages/graphql-types/src/to-string.ts create mode 100644 packages/graphql-types/test/to-string.test.ts diff --git a/packages/graphql-test/package.json b/packages/graphql-test/package.json index f8b786fc..5d3d2fed 100644 --- a/packages/graphql-test/package.json +++ b/packages/graphql-test/package.json @@ -15,8 +15,16 @@ "email": "ahrjarrett@gmail.com" }, "@traversable": { - "generateExports": { "include": ["**/*.ts"] }, - "generateIndex": { "include": ["**/*.ts"] } + "generateExports": { + "include": [ + "**/*.ts" + ] + }, + "generateIndex": { + "include": [ + "**/*.ts" + ] + } }, "publishConfig": { "access": "public", @@ -37,10 +45,17 @@ }, "peerDependencies": { "@traversable/graphql-types": "workspace:^", - "@traversable/registry": "workspace:^" + "@traversable/registry": "workspace:^", + "graphql": ">= 16" + }, + "peerDependenciesMeta": { + "@traversable/graphql-types": { "optional": false }, + "@traversable/registry": { "optional": false }, + "graphql": { "optional": true } }, "devDependencies": { "@traversable/graphql-types": "workspace:^", - "@traversable/registry": "workspace:^" + "@traversable/registry": "workspace:^", + "graphql": "^16.11.0" } } diff --git a/packages/graphql-test/src/generator-bounds.ts b/packages/graphql-test/src/generator-bounds.ts new file mode 100644 index 00000000..1fa992f9 --- /dev/null +++ b/packages/graphql-test/src/generator-bounds.ts @@ -0,0 +1,222 @@ +import * as fc from 'fast-check' + +import type { newtype } from '@traversable/registry' +import { fn, Number_isFinite, Number_isNatural, Number_isSafeInteger, Object_is } from '@traversable/registry' + +/** @internal */ +const nullable = (model: fc.Arbitrary) => fc.oneof(fc.constant(null), fc.constant(null), model) + +/** @internal */ +const isBigInt = (x: unknown) => typeof x === 'bigint' + +export const defaultDoubleConstraints = { + noNaN: true, + noDefaultInfinity: true, +} satisfies fc.DoubleConstraints + +const defaultIntBounds = [-0x1000, +0x1000, null] satisfies Bounds_int +const defaultBigIntBounds = [-0x1000000n, 0x1000000n, null] satisfies Bounds_bigint +const defaultNumberBounds = [-0x10000, +0x10000, null, false, false] satisfies Bounds_number +const defaultStringBounds = [0, +0x40] satisfies Bounds_string +const defaultArrayBounds = [0, +0x10] satisfies Bounds_array + +export const defaults = { + int: defaultIntBounds, + number: defaultNumberBounds, + bigint: defaultBigIntBounds, + string: defaultStringBounds, + array: defaultArrayBounds, +} + +const clampMin + : (min: T, max: T, predicate: (x: T | null) => x is T) => (x: T | null, y: T | null) => T | null + = (min, max, predicate) => (x, y) => { + if (!predicate(x)) { + return null + } else if (!predicate(y)) { + return x < min ? min : max < x ? max : x + } else { + const z = x < y ? x : y + return z < min ? min : max < z ? max : z + } + } + +const clampMax + : (min: T, max: T, predicate: (x: T | null) => x is T) => (x: T | null, y: T | null) => T | null + = (min, max, predicate) => (x, y) => { + if (!predicate(x)) { + return null + } else if (!predicate(y)) { + return x < min ? min : max < x ? max : x + } else { + const z = x > y ? x : y + return z < min ? min : max < z ? max : z + } + } + +const clampIntMin = clampMin(defaults.int[0], defaults.int[1], Number_isSafeInteger) +const clampIntMax = clampMax(defaults.int[0], defaults.int[1], Number_isSafeInteger) +const clampBigIntMin = clampMin(defaults.bigint[0], defaults.bigint[1], isBigInt) +const clampBigIntMax = clampMax(defaults.bigint[0], defaults.bigint[1], isBigInt) +const clampNumberMin = clampMin(defaults.number[0], defaults.number[1], Number_isFinite) +const clampNumberMax = clampMin(defaults.number[0], defaults.number[1], Number_isFinite) +const clampStringMin = clampMin(defaults.string[0], defaults.string[1], Number_isNatural) +const clampStringMax = clampMax(defaults.string[0], defaults.string[1], Number_isNatural) +const clampArrayMin = clampMin(defaults.array[0], defaults.array[1], Number_isNatural) +const clampArrayMax = clampMax(defaults.array[0], defaults.array[1], Number_isNatural) + +export const makeInclusiveBounds = (model: fc.Arbitrary) => ({ minimum: model, maximum: model }) + +export { Bounds_int as int } +interface Bounds_int extends newtype<[ + minimum: number | null, + maximum: number | null, + multipleOf: number | null, +]> {} + + + +const Bounds_int + : (model: fc.Arbitrary) => fc.Arbitrary + = (model) => fc.tuple(nullable(model), nullable(model), nullable(model)).map(([x, y, multipleOf]) => [ + clampIntMin(x, y), + clampIntMax(y, x), + multipleOf, + // clampInt(multipleOf), + ]) + +export { Bounds_bigint as bigint } +interface Bounds_bigint extends newtype<[ + minimum: bigint | null, + maximum: bigint | null, + multipleOf: bigint | null, +]> {} + +const Bounds_bigint + : (model: fc.Arbitrary) => fc.Arbitrary + = (model) => fc.tuple(nullable(model), nullable(model), nullable(model)).map(([x, y, multipleOf]) => [ + clampBigIntMin(x, y), + clampBigIntMax(y, x), + multipleOf, // clampBigInt(multipleOf), + ]) + +export { Bounds_string as string } +interface Bounds_string extends newtype<[ + minLength: number | null, + maxLength: number | null, +]> {} + +const Bounds_string + : (model: fc.Arbitrary) => fc.Arbitrary + = (model) => fc.tuple(nullable(model), nullable(model), nullable(model)).map( + ([x, y, length]) => Number_isNatural(length) + ? [null, null] satisfies [any, any] + // [clampString(length), clampString(length)] + : [clampStringMin(x, y), clampStringMax(y, x)] + ) + +export { Bounds_number as number } +interface Bounds_number extends newtype<[ + minimum: number | null, + maximum: number | null, + multipleOf: number | null, + exclusiveMinimum: boolean, + exclusiveMaximum: boolean, +]> {} + +const deltaIsSubEpsilon = (x: number, y: number) => Math.abs(x - y) < Number.EPSILON + +const Bounds_number + : (model: fc.Arbitrary) => fc.Arbitrary + = (model) => fc.tuple( + nullable(model), + nullable(model), + nullable(model), + fc.boolean(), + fc.boolean() + ).map( + fn.flow( + ([x, y, multipleOf, minExcluded, maxExcluded]): Bounds_number => [ + clampNumberMin(x, y), + clampNumberMax(y, x), + // clampNumber(multipleOf), + multipleOf, + minExcluded, + maxExcluded, + ], + ([min, max, multipleOf, minExcluded, maxExcluded]): Bounds_number => [ + min, + ((minExcluded || maxExcluded) && min != null && max != null && deltaIsSubEpsilon(min, max)) ? max + 1 : max, + multipleOf, + minExcluded, + maxExcluded + ], + ) + ) + +export { Bounds_array as array } +interface Bounds_array extends newtype<[ + minLength: number | null, + maxLength: number | null, +]> {} + +const Bounds_array + : (model: fc.Arbitrary) => fc.Arbitrary + = (model) => fc.tuple( + nullable(model), + nullable(model), + fc.constant(null) + ).map(([x, y, exactLength]) => + Number_isNatural(exactLength) + ? [null, null] + : [clampArrayMin(x, y), clampArrayMax(y, x)] + ) + + +export const intBoundsToIntegerConstraints + : (bounds?: Bounds_int) => fc.IntegerConstraints + = (bounds = defaultIntBounds) => { + const [min, max, multipleOf] = bounds + return { + max: max ?? void 0, + min: min ?? void 0, + } satisfies fc.IntegerConstraints + } + +export const bigintBoundsToBigIntConstraints + : (bounds?: Bounds_bigint) => fc.BigIntConstraints + = (bounds = defaultBigIntBounds) => { + const [min, max, multipleOf] = bounds + return { + max: max ?? void 0, + min: min ?? void 0, + } satisfies fc.BigIntConstraints + } + +export const numberBoundsToDoubleConstraints + : (bounds?: Bounds_number) => fc.DoubleConstraints + = (bounds = defaultNumberBounds) => { + let [min, max, multipleOf, minExcluded, maxExcluded] = bounds + if (minExcluded && Object_is(min, -0)) min = +0 + if (maxExcluded && Object_is(max, +0)) max = -0 + return { + ...defaultDoubleConstraints, max: max ?? void 0, + min: min ?? void 0, + minExcluded, + maxExcluded, + } satisfies fc.DoubleConstraints + } + +export const stringBoundsToStringConstraints + : (bounds?: Bounds_string) => fc.StringConstraints + = ([minLength, maxLength] = defaultStringBounds) => ({ + minLength: minLength ?? void 0, + maxLength: maxLength ?? void 0 + }) satisfies fc.StringConstraints + +export const arrayBoundsToArrayConstraints + : (bounds?: Bounds_array) => fc.ArrayConstraints + = ([minLength, maxLength] = defaultArrayBounds) => ({ + minLength: minLength ?? void 0, + maxLength: maxLength ?? void 0 + }) satisfies fc.ArrayConstraints diff --git a/packages/graphql-test/src/generator-options.ts b/packages/graphql-test/src/generator-options.ts new file mode 100644 index 00000000..742158cb --- /dev/null +++ b/packages/graphql-test/src/generator-options.ts @@ -0,0 +1,502 @@ +import * as fc from 'fast-check' +import { Tags } from '@traversable/graphql-types' + +// import type { SeedMap } from './generator.js' +import { byTag } from './generator-seed.js' + +export type ArrayParams = { + /* length?: number */ + minLength?: number + maxLength?: number +} + +export type IntegerParams = { + minimum?: number + maximum?: number + multipleOf?: number +} + +export type NumberParams = { + minimum?: number + maximum?: number + minExcluded?: boolean + maxExcluded?: boolean + multipleOf?: number +} + +export type BigIntParams = { + minimum?: bigint + maximum?: bigint + multipleOf?: bigint +} + +export type StringParams = { + /* prefix?: string, postfix?: string, pattern?: string, substring?: string, length?: number */ + minLength?: number + maxLength?: number +} + +// export interface Options extends Partial>, Constraints {} + +// export type InferConfigType = S extends Options ? T : never + +// export interface OptionsBase< +// T = never, +// K extends +// | string & keyof T +// = string & keyof T +// > { +// include: readonly K[] +// exclude: readonly K[] +// root: '*' | K +// sortBias: { [K in keyof SeedMap]+?: number } +// forceInvalid: boolean +// } +// export interface Config extends OptionsBase, byTypeName {} + +// export type Constraints = { +// any?: {} +// array?: { minLength?: number, maxLength?: number, unbounded?: boolean } +// bigint?: { min?: undefined | bigint, max?: undefined | bigint, multipleOf?: bigint | null, unbounded?: boolean } +// boolean?: {} +// custom?: {} +// date?: {} +// enum?: {} +// file?: {} +// blob?: {} +// intersect?: {} +// lazy?: {} +// literal?: {} +// map?: {} +// nan?: {} +// never?: {} +// null?: {} +// number?: { min?: undefined | number, max?: undefined | number, multipleOf?: number, unbounded?: boolean } & fc.DoubleConstraints +// object?: ObjectConstraints +// object_with_rest?: ObjectConstraints +// loose_object?: ObjectConstraints +// strict_object?: ObjectConstraints +// optional?: {} +// non_optional?: {} +// undefinedable?: {} +// nullish?: {} +// non_nullish?: {} +// nullable?: {} +// non_nullable?: {} +// record?: fc.DictionaryConstraints +// set?: {} +// string?: { unbounded?: boolean } & fc.StringConstraints +// symbol?: {} +// tuple?: fc.ArrayConstraints +// tuple_with_rest?: fc.ArrayConstraints +// loose_tuple?: fc.ArrayConstraints +// strict_tuple?: fc.ArrayConstraints +// undefined?: {} +// union?: fc.ArrayConstraints +// variant?: fc.ArrayConstraints +// unknown?: {} +// void?: {} +// promise?: {} +// ['*']?: fc.OneOfConstraints +// } + +// export interface byTypeName extends Required> { +// object: fc.UniqueArrayConstraintsRecommended<[k: string, v: unknown], string> +// array: fc.IntegerConstraints & { unbounded?: boolean } +// } + +// export type ObjectConstraints = +// & Omit, 'minLength' | 'maxLength'> +// & { +// minKeys?: number +// maxKeys?: number +// size?: fc.SizeForArbitrary +// } + +// const objectDefaults = { +// minKeys: 1, +// maxKeys: 3, +// size: 'xsmall', +// selector: ([k]) => k, +// comparator: 'SameValueZero', +// depthIdentifier: fc.createDepthIdentifier(), +// } satisfies ObjectConstraints + +// export const defaultConstraints = { +// object: objectDefaults, +// loose_object: objectDefaults, +// strict_object: objectDefaults, +// object_with_rest: objectDefaults, +// any: {}, +// array: { +// minLength: 0, +// maxLength: 0x10 +// }, +// bigint: { +// unbounded: false, +// min: undefined, +// max: undefined, +// multipleOf: null, +// }, +// boolean: {}, +// custom: {}, +// date: {}, +// enum: {}, +// file: {}, +// blob: {}, +// intersect: {}, +// lazy: {}, +// literal: {}, +// map: {}, +// nan: {}, +// never: {}, +// null: {}, +// number: { +// unbounded: false, +// min: -0x10000, +// max: 0x10000, +// multipleOf: Number.NaN, +// noNaN: true, +// noDefaultInfinity: true, +// minExcluded: false, +// maxExcluded: false, +// noInteger: false, +// }, +// optional: {}, +// non_optional: {}, +// undefinedable: {}, +// nullish: {}, +// non_nullish: {}, +// nullable: {}, +// non_nullable: {}, +// record: { +// depthIdentifier: fc.createDepthIdentifier(), +// maxKeys: 3, +// minKeys: 1, +// noNullPrototype: false, +// size: 'xsmall', +// } satisfies fc.DictionaryConstraints, +// set: {}, +// string: { +// unbounded: false, +// minLength: 0, +// maxLength: 0x100, +// size: 'xsmall', +// unit: 'grapheme-ascii', +// }, +// symbol: {}, +// tuple: { +// minLength: 1, +// maxLength: 3, +// size: 'xsmall', +// depthIdentifier: fc.createDepthIdentifier(), +// } satisfies fc.ArrayConstraints, +// tuple_with_rest: { +// minLength: 1, +// maxLength: 3, +// size: 'xsmall', +// depthIdentifier: fc.createDepthIdentifier(), +// } satisfies fc.ArrayConstraints, +// strict_tuple: { +// minLength: 1, +// maxLength: 3, +// size: 'xsmall', +// depthIdentifier: fc.createDepthIdentifier(), +// } satisfies fc.ArrayConstraints, +// loose_tuple: { +// minLength: 1, +// maxLength: 3, +// size: 'xsmall', +// depthIdentifier: fc.createDepthIdentifier(), +// } satisfies fc.ArrayConstraints, +// undefined: {}, +// union: { +// depthIdentifier: fc.createDepthIdentifier(), +// minLength: 1, +// maxLength: 3, +// size: 'xsmall', +// } satisfies fc.ArrayConstraints, +// variant: { +// depthIdentifier: fc.createDepthIdentifier(), +// minLength: 1, +// maxLength: 3, +// size: 'xsmall', +// } satisfies fc.ArrayConstraints, +// unknown: {}, +// void: {}, +// promise: {}, +// ['*']: { +// maxDepth: 3, +// depthIdentifier: fc.createDepthIdentifier(), +// depthSize: 'xsmall', +// withCrossShrink: true, +// } satisfies fc.OneOfConstraints, +// } as const satisfies { [K in keyof Constraints]-?: Constraints[K] } + +// export const unsupportedSchemas = ['promise'] satisfies (keyof SeedMap)[] + +// export const defaults = { +// exclude: unsupportedSchemas, +// forceInvalid: false, +// include: Tags, +// root: '*', +// sortBias: byTag, +// } as const satisfies OptionsBase + +// export function parseOptions(options?: Opts): Config> +// export function parseOptions(options?: Options): Config +// export function parseOptions(options: Options = defaults as never): Config { +// const { +// exclude = defaults.exclude, +// forceInvalid = defaults.forceInvalid, +// include = defaults.include, +// root = defaults.root, +// sortBias = defaults.sortBias, +// ['*']: { +// maxDepth: starMaxDepth = defaultConstraints['*'].maxDepth, +// depthSize: starDepthSize = defaultConstraints['*'].depthSize, +// ...STAR +// } = defaultConstraints['*'], +// any = defaultConstraints.any, +// array: { +// maxLength: arrayMax = defaultConstraints.array.maxLength, +// minLength: arrayMin = defaultConstraints.array.minLength, +// ...ARRAY +// } = defaultConstraints.array, +// bigint: { +// unbounded: bigIntUnbounded, +// max: bigIntMax, +// min: bigIntMin, +// ...BIGINT +// } = defaultConstraints.bigint, +// boolean = defaultConstraints.boolean, +// custom = defaultConstraints.custom, +// date = defaultConstraints.date, +// enum: enum_ = defaultConstraints.enum, +// file = defaultConstraints.file, +// intersect = defaultConstraints.intersect, +// lazy = defaultConstraints.lazy, +// literal = defaultConstraints.literal, +// map = defaultConstraints.map, +// nan = defaultConstraints.nan, +// never = defaultConstraints.never, +// non_optional = defaultConstraints.non_optional, +// non_nullable = defaultConstraints.non_nullable, +// non_nullish = defaultConstraints.non_nullable, +// undefinedable = defaultConstraints.undefinedable, +// nullish = defaultConstraints.nullish, +// null: null_ = defaultConstraints.null, +// nullable = defaultConstraints.nullable, +// number: { +// unbounded: numberUnbounded, +// max: numberMax, +// maxExcluded: numberMaxExcluded, +// min: numberMin, +// minExcluded: numberMinExcluded, +// // ...NUMBER +// } = defaultConstraints.number, +// optional = defaultConstraints.optional, +// record: { +// maxKeys: recordMaxKeys = defaultConstraints.record.maxKeys, +// minKeys: recordMinKeys = defaultConstraints.record.minKeys, +// size: recordSize = defaultConstraints.record.size, +// ...RECORD +// } = defaultConstraints.record, +// set = defaultConstraints.set, +// string: { +// unbounded: stringUnbounded, +// minLength: stringMinLength, +// maxLength: stringMaxLength, +// size: stringSize = defaultConstraints.string.size, +// // ...STRING +// } = defaultConstraints.string, +// symbol = defaultConstraints.symbol, +// undefined: undefined_ = defaultConstraints.undefined, +// union: { +// minLength: unionMinLength = defaultConstraints.union.minLength, +// maxLength: unionMaxLength = defaultConstraints.union.maxLength, +// size: unionSize = defaultConstraints.union.size, +// ...UNION +// } = defaultConstraints.union, +// variant: { +// minLength: variantMinLength = defaultConstraints.variant.minLength, +// maxLength: variantMaxLength = defaultConstraints.variant.maxLength, +// size: variantSize = defaultConstraints.variant.size, +// ...VARIANT +// } = defaultConstraints.variant, +// unknown = defaultConstraints.unknown, +// void: void_ = defaultConstraints.void, +// promise = defaultConstraints.promise, +// object: { +// maxKeys: objectMaxKeys = defaultConstraints.object.maxKeys, +// minKeys: objectMinKeys = defaultConstraints.object.minKeys, +// size: objectSize = defaultConstraints.object.size, +// ...OBJECT +// } = defaultConstraints.object, +// blob = defaultConstraints.blob, +// strict_object: { +// maxKeys: strictObjectMaxKeys = defaultConstraints.strict_object.maxKeys, +// minKeys: strictObjectMinKeys = defaultConstraints.strict_object.minKeys, +// size: strictObjectSize = defaultConstraints.strict_object.size, +// ...STRICT_OBJECT +// } = defaultConstraints.strict_object, +// loose_object: { +// maxKeys: looseObjectMaxKeys = defaultConstraints.loose_object.maxKeys, +// minKeys: looseObjectMinKeys = defaultConstraints.loose_object.minKeys, +// size: looseObjectSize = defaultConstraints.loose_object.size, +// ...LOOSE_OBJECT +// } = defaultConstraints.loose_object, +// object_with_rest: { +// maxKeys: objectWithRestMaxKeys = defaultConstraints.object_with_rest.maxKeys, +// minKeys: objectWithRestMinKeys = defaultConstraints.object_with_rest.minKeys, +// size: objectWithRestSize = defaultConstraints.object_with_rest.size, +// ...OBJECT_WITH_REST +// } = defaultConstraints.object_with_rest, +// tuple: { +// maxLength: tupleMaxLength = defaultConstraints.tuple.maxLength, +// minLength: tupleMinLength = defaultConstraints.tuple.minLength, +// ...TUPLE +// } = defaultConstraints.tuple, +// strict_tuple: { +// maxLength: strictTupleMaxLength = defaultConstraints.strict_tuple.maxLength, +// minLength: strictTupleMinLength = defaultConstraints.strict_tuple.minLength, +// ...STRICT_TUPLE +// } = defaultConstraints.strict_tuple, +// loose_tuple: { +// maxLength: looseTupleMaxLength = defaultConstraints.loose_tuple.maxLength, +// minLength: looseTupleMinLength = defaultConstraints.loose_tuple.minLength, +// ...LOOSE_TUPLE +// } = defaultConstraints.loose_tuple, +// tuple_with_rest: { +// maxLength: tupleWithRestMaxLength = defaultConstraints.tuple_with_rest.maxLength, +// minLength: tupleWithRestMinLength = defaultConstraints.tuple_with_rest.minLength, +// ...TUPLE_WITH_REST +// } = defaultConstraints.tuple_with_rest, +// } = options +// return { +// exclude, +// forceInvalid, +// include: include.length === 0 || include[0] === '*' ? defaults.include : include, +// root, +// sortBias: { ...defaults.sortBias, ...sortBias }, +// ['*']: { +// ...STAR, +// depthSize: starDepthSize, +// maxDepth: starMaxDepth, +// }, +// object: { +// ...OBJECT, +// minLength: objectMinKeys, +// maxLength: objectMaxKeys, +// size: objectSize, +// }, +// strict_object: { +// ...STRICT_OBJECT, +// maxKeys: strictObjectMaxKeys, +// minKeys: strictObjectMinKeys, +// size: strictObjectSize, +// }, +// loose_object: { +// ...LOOSE_OBJECT, +// maxKeys: looseObjectMaxKeys, +// minKeys: looseObjectMinKeys, +// size: looseObjectSize, +// }, +// object_with_rest: { +// ...OBJECT_WITH_REST, +// maxKeys: objectWithRestMaxKeys, +// minKeys: objectWithRestMinKeys, +// size: objectWithRestSize, +// }, +// any, +// array: { +// ...ARRAY, +// min: arrayMin, +// max: arrayMax, +// }, +// bigint: { +// ...BIGINT, +// unbounded: bigIntUnbounded, +// max: bigIntMax, +// min: bigIntMin, +// }, +// boolean, +// date, +// enum: enum_, +// file, +// intersect, +// lazy, +// literal, +// map, +// nan, +// never, +// non_optional, +// null: null_, +// nullable, +// number: { +// unbounded: numberUnbounded, +// max: numberMax, +// min: numberMin, +// maxExcluded: numberMaxExcluded, +// minExcluded: numberMinExcluded, +// }, +// optional, +// record: { +// ...RECORD, +// maxKeys: recordMaxKeys, +// minKeys: recordMinKeys, +// size: recordSize, +// }, +// set, +// string: { +// // ...STRING, +// unbounded: stringUnbounded, +// minLength: stringMinLength, +// maxLength: stringMaxLength, +// size: stringSize, +// }, +// symbol, +// tuple: { +// ...TUPLE, +// minLength: tupleMinLength, +// maxLength: tupleMaxLength, +// }, +// loose_tuple: { +// ...LOOSE_TUPLE, +// minLength: looseTupleMinLength, +// maxLength: looseTupleMaxLength, +// }, +// strict_tuple: { +// ...STRICT_TUPLE, +// minLength: strictTupleMinLength, +// maxLength: strictTupleMaxLength, +// }, +// tuple_with_rest: { +// ...TUPLE_WITH_REST, +// minLength: tupleWithRestMinLength, +// maxLength: tupleWithRestMaxLength, +// }, +// blob, +// custom, +// non_nullable, +// nullish, +// non_nullish, +// undefinedable, +// variant: { +// ...VARIANT, +// minLength: variantMinLength, +// maxLength: variantMaxLength, +// size: variantSize, +// }, +// undefined: undefined_, +// union: { +// ...UNION, +// minLength: unionMinLength, +// maxLength: unionMaxLength, +// size: unionSize, +// }, +// unknown, +// void: void_, +// promise, +// } +// } diff --git a/packages/graphql-test/src/generator-seed.ts b/packages/graphql-test/src/generator-seed.ts new file mode 100644 index 00000000..aa3db170 --- /dev/null +++ b/packages/graphql-test/src/generator-seed.ts @@ -0,0 +1,412 @@ +import * as gql from 'graphql' +import type * as T from '@traversable/registry' +import { fn, Object_keys } from '@traversable/registry' +import type { AnyTag } from '@traversable/graphql-types' + +import * as Bounds from './generator-bounds.js' + +export function invert>(x: T): { [K in keyof T as T[K]]: K } +export function invert(x: Record) { + return Object_keys(x).reduce((acc, k) => (acc[x[k]] = k, acc), {} as typeof x) +} + +export type Tag = byTag[keyof byTag] +export type byTag = typeof byTag +export const byTag = { + // any: 10, + Boolean: 15, + // date: 20, + // file: 25, + // blob: 27, + // nan: 30, + // never: 35, + NullValue: 40, + // symbol: 45, + // undefined: 50, + // unknown: 55, + // void: 60, + // function: 70, + // instance: 80, + // bigint: 150, + Number: 200, + String: 250, + EnumValue: 500, + EnumValueDefinition: 550, + ListType: 1000, + // non_optional: 1500, + // nullable: 2000, + NonNullType: 2100, + // nullish: 2200, + // non_nullish: 2300, + // optional: 2500, + // exact_optional: 2600, + // undefinedable: 2700, + // set: 3500, + // intersect: 6000, + // map: 6500, + // record: 7000, + // object: 7500, + // loose_object: 7600, + // strict_object: 7700, + // object_with_rest: 7800, + // tuple: 8000, + // loose_tuple: 8100, + // strict_tuple: 8200, + // tuple_with_rest: 8300, + // union: 8500, + // variant: 8600, + // custom: 9500, + // lazy: 10_500, + // picklist: 11_000, + // /** @deprecated */ + // promise: -1000, +} as const // satisfies Record + +/** + * @example + * { + * Document: 'Document', + * EnumTypeDefinition: 'EnumTypeDefinition', + * EnumValueDefinition: 'EnumValueDefinition', + * FieldDefinition: 'FieldDefinition', + * InputObjectTypeDefinition: 'InputObjectTypeDefinition', + * InputValueDefinition: 'InputValueDefinition', + * InterfaceTypeDefinition: 'InterfaceTypeDefinition', + * ListType: 'ListType', + * Name: 'Name', + * NamedType: 'NamedType', + * NonNullType: 'NonNullType', + * ObjectTypeDefinition: 'ObjectTypeDefinition', + * OperationDefinition: 'OperationDefinition', + * ScalarTypeDefinition: 'ScalarTypeDefinition', + * SelectionSet: 'SelectionSet', + * UnionTypeDefinition: 'UnionTypeDefinition', + * Variable: 'Variable', + * VariableDefinition: 'VariableDefinition', + * // + * FragmentSpread: 'FragmentSpread', + * InlineFragment: 'InlineFragment', + * FragmentDefinition: 'FragmentDefinition', + * // + * Argument: 'Argument', + * Directive: 'Directive', + * DirectiveDefinition: 'DirectiveDefinition', + * EnumValue: 'EnumValue', + * Field: 'Field', + * FloatValue: 'FloatValue', + * StringValue: 'StringValue', + * BooleanValue: 'BooleanValue', + * IntValue: 'IntValue', + * ListValue: 'ListValue', + * NullValue: 'NullValue', + * ObjectValue: 'ObjectValue', + * ObjectField: 'ObjectField', + * SchemaDefinition: 'SchemaDefinition', + * SchemaExtension: 'SchemaExtension', + * OperationTypeDefinition: 'OperationTypeDefinition', + * } + */ + +export type bySeed = typeof bySeed +export const bySeed = invert(byTag) + +// export type Seed = +// & Seed.TerminalMap +// & Seed.BoundableMap +// & Seed.ValueMap +// & Seed.UnaryMap + +// export declare namespace Seed { +// type Fixpoint = [ +// tag: Tag, children?: +// unknown, +// bounds?: unknown +// ] +// type F = +// | Seed.Nullary +// | Seed.Unary +// type Nullary = +// | Seed.Terminal +// | Seed.Boundable +// | Seed.Value +// type Unary = +// | Seed.Array +// | Seed.Record +// | Seed.Object +// | Seed.LooseObject +// | Seed.StrictObject +// | Seed.ObjectWithRest +// | Seed.Tuple +// | Seed.TupleWithRest +// | Seed.LooseTuple +// | Seed.StrictTuple +// | Seed.Union +// | Seed.Variant +// | Seed.Optional +// | Seed.NonOptional +// | Seed.Undefinedable +// | Seed.Nullable +// | Seed.NonNullable +// | Seed.Nullish +// | Seed.NonNullish +// | Seed.Set +// | Seed.Map +// | Seed.Custom +// | Seed.Lazy +// | Seed.Intersect +// | Seed.Promise + +// interface Free extends T.HKT { [-1]: Seed.F } +// //////////////// +// /// nullary +// type Any = [any: byTag['any']] +// type Boolean = [boolean: byTag['boolean']] +// type Date = [date: byTag['date']] +// type File = [file: byTag['file']] +// type Blob = [blob: byTag['blob']] +// type NaN = [NaN: byTag['nan']] +// type Never = [never: byTag['never']] +// type Null = [null: byTag['null']] +// type Symbol = [symbol: byTag['symbol']] +// type Undefined = [undefined: byTag['undefined']] +// type Unknown = [unknown: byTag['unknown']] +// type Void = [void: byTag['void']] +// type Terminal = TerminalMap[keyof TerminalMap] +// type TerminalMap = { +// any: Any +// boolean: Boolean +// date: Date +// file: File +// blob: Blob +// nan: NaN +// never: Never +// null: Null +// symbol: Symbol +// undefined: Undefined +// unknown: Unknown +// void: Void +// } +// //////////////// +// /// boundable +// type BigInt = [bigint: byTag['bigint'], bounds?: Bounds.bigint] +// type Number = [number: byTag['number'], bounds?: Bounds.number] +// type String = [string: byTag['string'], bounds?: Bounds.string] +// type Boundable = BoundableMap[keyof BoundableMap] +// type BoundableMap = { +// bigint: BigInt +// number: Number +// string: String +// } +// //////////////// +// /// value +// type Enum = [enum_: byTag['enum'], value: { [x: string]: number | string }] +// type Literal = [literal: byTag['literal'], value: boolean | number | string] +// namespace TemplateLiteral { +// type Node = T.Showable | Seed.Boolean | Seed.Null | Seed.Undefined | Seed.Number | Seed.BigInt | Seed.String | Seed.Literal +// } +// type Value = ValueMap[keyof ValueMap] +// type ValueMap = { +// enum: Enum +// literal: Literal +// } +// //////////////// +// /// unary +// type Array = [array: byTag['array'], item: T, bounds?: Bounds.array] +// type Optional = [optional: byTag['optional'], wrapped: T] +// type NonOptional = [nonOptional: byTag['non_optional'], wrapped: T] +// type Undefinedable = [undefinedable: byTag['undefinedable'], wrapped: T] +// type Nullish = [nullish: byTag['nullish'], wrapped: T] +// type NonNullish = [nonNullish: byTag['non_nullish'], wrapped: T] +// type Nullable = [nullable: byTag['nullable'], wrapped: T] +// type NonNullable = [nonNullable: byTag['non_nullable'], wrapped: T] +// type Set = [set: byTag['set'], value: T] + +// type UnaryMap = { +// array: Seed.Array +// record: Seed.Record +// object: Seed.Object +// strict_object: Seed.StrictObject +// loose_object: Seed.LooseObject +// object_with_rest: Seed.ObjectWithRest +// tuple: Seed.Tuple +// loose_tuple: Seed.LooseTuple +// strict_tuple: Seed.StrictTuple +// tuple_with_rest: Seed.TupleWithRest +// union: Seed.Union +// variant: Seed.Variant +// optional: Seed.Optional +// non_optional: Seed.NonOptional +// undefinedable: Seed.Undefinedable +// nullable: Seed.Nullable +// non_nullable: Seed.NonNullable +// nullish: Seed.Nullish +// non_nullish: Seed.NonNullish +// set: Seed.Set +// map: Seed.Map +// custom: Seed.Custom +// lazy: Seed.Lazy +// intersect: Seed.Intersect +// promise: Seed.Promise +// } +// type Composite = +// | Seed.Array +// | Seed.Record +// | Seed.Tuple +// | Seed.TupleWithRest +// | Seed.LooseTuple +// | Seed.StrictTuple +// | Seed.Object +// | Seed.ObjectWithRest +// | Seed.LooseObject +// | Seed.StrictObject +// type fromComposite = { +// [byTag.array]: unknown[] +// [byTag.tuple]: unknown[] +// [byTag.loose_tuple]: unknown[] +// [byTag.strict_tuple]: unknown[] +// [byTag.tuple_with_rest]: unknown[] +// [byTag.record]: globalThis.Record +// [byTag.object]: { [x: string]: unknown } +// [byTag.loose_object]: { [x: string]: unknown } +// [byTag.strict_object]: { [x: string]: unknown } +// [byTag.object_with_rest]: { [x: string]: unknown } +// } +// type schemaFromComposite = { +// [byTag.array]: v.ArraySchema +// [byTag.tuple]: v.TupleSchema +// [byTag.loose_tuple]: v.LooseTupleSchema +// [byTag.strict_tuple]: v.StrictTupleSchema +// [byTag.tuple_with_rest]: v.TupleWithRestSchema +// [byTag.record]: v.RecordSchema +// [byTag.object]: v.ObjectSchema +// [byTag.loose_object]: v.LooseObjectSchema +// [byTag.strict_object]: v.StrictObjectSchema +// [byTag.object_with_rest]: v.ObjectWithRestSchema +// } +// //////////////// +// /// applicative +// type Object = [object: byTag['object'], entries: [k: string, v: T][]] +// type LooseObject = [looseObject: byTag['loose_object'], entries: [k: string, v: T][]] +// type StrictObject = [strictObject: byTag['strict_object'], entries: [k: string, v: T][]] +// type ObjectWithRest = [objectWithRest: byTag['object_with_rest'], entries: [k: string, v: T][], rest: T] +// type Union = [union: byTag['union'], options: T[]] +// type Variant = [variant: byTag['variant'], [tag: string, options: [k: string, v: T][]][], discriminator: string] +// type Tuple = [tuple: byTag['tuple'], items: T[]] +// type LooseTuple = [looseTuple: byTag['loose_tuple'], items: T[]] +// type StrictTuple = [strictTuple: byTag['strict_tuple'], items: T[]] +// type TupleWithRest = [tupleWithRest: byTag['tuple_with_rest'], items: T[], rest: T] +// //////////////// +// /// binary +// type Map = [seed: byTag['map'], def: [key: T, value: T]] +// type Record = [seed: byTag['record'], value: T] +// type Intersect = [seed: byTag['intersect'], def: [left: T, right: T]] +// //////////////// +// /// special +// type Custom = [seed: byTag['custom'], def: T] +// type Lazy = [seed: byTag['lazy'], getter: () => T] +// //////////////// +// /// deprecated +// /** @deprecated */ +// type Promise = [seed: byTag['promise'], wrapped: T] +// } + +// export const Functor: T.Functor.Ix> = { +// map(f) { +// return (x) => { +// switch (true) { +// default: return x +// case x[0] === byTag.any: return x +// case x[0] === byTag.boolean: return x +// case x[0] === byTag.date: return x +// case x[0] === byTag.file: return x +// case x[0] === byTag.nan: return x +// case x[0] === byTag.never: return x +// case x[0] === byTag.null: return x +// case x[0] === byTag.symbol: return x +// case x[0] === byTag.undefined: return x +// case x[0] === byTag.unknown: return x +// case x[0] === byTag.void: return x +// case x[0] === byTag.bigint: return x +// case x[0] === byTag.number: return x +// case x[0] === byTag.string: return x +// case x[0] === byTag.enum: return x +// case x[0] === byTag.literal: return x +// case x[0] === byTag.array: return [x[0], f(x[1]), x[2]] +// case x[0] === byTag.optional: return [x[0], f(x[1])] +// case x[0] === byTag.non_optional: return [x[0], f(x[1])] +// case x[0] === byTag.undefinedable: return [x[0], f(x[1])] +// case x[0] === byTag.nullable: return [x[0], f(x[1])] +// case x[0] === byTag.non_nullable: return [x[0], f(x[1])] +// case x[0] === byTag.nullish: return [x[0], f(x[1])] +// case x[0] === byTag.non_nullish: return [x[0], f(x[1])] +// case x[0] === byTag.set: return [x[0], f(x[1])] +// case x[0] === byTag.intersect: return [x[0], [f(x[1][0]), f(x[1][1])]] +// case x[0] === byTag.map: return [x[0], [f(x[1][0]), f(x[1][1])]] +// case x[0] === byTag.record: return [x[0], f(x[1])] +// case x[0] === byTag.object: return [x[0], x[1].map(([k, v]) => [k, f(v)] satisfies [any, any])] +// case x[0] === byTag.loose_object: return [x[0], x[1].map(([k, v]) => [k, f(v)] satisfies [any, any])] +// case x[0] === byTag.strict_object: return [x[0], x[1].map(([k, v]) => [k, f(v)] satisfies [any, any])] +// case x[0] === byTag.object_with_rest: return [x[0], x[1].map(([k, v]) => [k, f(v)] satisfies [any, any]), f(x[2])] +// case x[0] === byTag.tuple: return [x[0], x[1].map(f)] +// case x[0] === byTag.loose_tuple: return [x[0], x[1].map(f)] +// case x[0] === byTag.strict_tuple: return [x[0], x[1].map(f)] +// case x[0] === byTag.tuple_with_rest: return [x[0], x[1].map(f), f(x[2])] +// case x[0] === byTag.union: return [x[0], x[1].map(f)] +// case x[0] === byTag.custom: return [x[0], f(x[1])] +// case x[0] === byTag.lazy: return [x[0], () => f(x[1]())] +// case x[0] === byTag.variant: return [x[0], x[1].map(([tag, ys]) => [tag, ys.map(([k, v]) => [k, f(v)] satisfies [any, any])] satisfies [any, any]), x[2]] +// case x[0] === byTag.promise: return PromiseSchemaIsUnsupported('Functor') +// } +// } +// }, +// mapWithIndex(f) { +// return (x, isProperty) => { +// switch (true) { +// default: return x +// case x[0] === byTag.any: return x +// case x[0] === byTag.boolean: return x +// case x[0] === byTag.date: return x +// case x[0] === byTag.file: return x +// case x[0] === byTag.nan: return x +// case x[0] === byTag.never: return x +// case x[0] === byTag.null: return x +// case x[0] === byTag.symbol: return x +// case x[0] === byTag.undefined: return x +// case x[0] === byTag.unknown: return x +// case x[0] === byTag.void: return x +// case x[0] === byTag.bigint: return x +// case x[0] === byTag.number: return x +// case x[0] === byTag.string: return x +// case x[0] === byTag.enum: return x +// case x[0] === byTag.literal: return x +// case x[0] === byTag.array: return [x[0], f(x[1], false, x), x[2]] +// case x[0] === byTag.optional: return [x[0], f(x[1], isProperty, x)] +// case x[0] === byTag.non_optional: return [x[0], f(x[1], false, x)] +// case x[0] === byTag.undefinedable: return [x[0], f(x[1], false, x)] +// case x[0] === byTag.nullable: return [x[0], f(x[1], false, x)] +// case x[0] === byTag.non_nullable: return [x[0], f(x[1], false, x)] +// case x[0] === byTag.nullish: return [x[0], f(x[1], false, x)] +// case x[0] === byTag.non_nullish: return [x[0], f(x[1], false, x)] +// case x[0] === byTag.set: return [x[0], f(x[1], false, x)] +// case x[0] === byTag.intersect: return [x[0], [f(x[1][0], false, x), f(x[1][1], false, x)]] +// case x[0] === byTag.map: return [x[0], [f(x[1][0], false, x), f(x[1][1], false, x)]] +// case x[0] === byTag.record: return [x[0], f(x[1], false, x)] +// case x[0] === byTag.tuple: return [x[0], x[1].map((_) => f(_, false, x))] +// case x[0] === byTag.loose_tuple: return [x[0], x[1].map((_) => f(_, false, x))] +// case x[0] === byTag.strict_tuple: return [x[0], x[1].map((_) => f(_, false, x))] +// case x[0] === byTag.tuple_with_rest: return [x[0], x[1].map((_) => f(_, false, x)), f(x[2], false, x)] +// case x[0] === byTag.union: return [x[0], x[1].map((_) => f(_, false, x))] +// case x[0] === byTag.custom: return [x[0], f(x[1], false, x)] +// case x[0] === byTag.lazy: return [x[0], () => f(x[1](), false, x)] +// case x[0] === byTag.object: return [x[0], x[1].map(([k, v]) => [k, f(v, true, x)] satisfies [any, any])] +// case x[0] === byTag.loose_object: return [x[0], x[1].map(([k, v]) => [k, f(v, true, x)] satisfies [any, any])] +// case x[0] === byTag.strict_object: return [x[0], x[1].map(([k, v]) => [k, f(v, true, x)] satisfies [any, any])] +// case x[0] === byTag.object_with_rest: return [x[0], x[1].map(([k, v]) => [k, f(v, true, x)] satisfies [any, any]), f(x[2], true, x)] +// case x[0] === byTag.variant: return [x[0], x[1].map(([tag, ys]) => [tag, ys.map(([k, v]) => [k, f(v, false, x)] satisfies [any, any])] satisfies [any, any]), x[2]] +// case x[0] === byTag.promise: return PromiseSchemaIsUnsupported('Functor') +// } +// } +// } +// } + +// export const fold = fn.catamorphism(Functor, false) diff --git a/packages/graphql-test/src/generator.ts b/packages/graphql-test/src/generator.ts new file mode 100644 index 00000000..1d7bfec0 --- /dev/null +++ b/packages/graphql-test/src/generator.ts @@ -0,0 +1,758 @@ +import * as gql from 'graphql' +import * as fc from 'fast-check' +// import type { LowerBound } from '@traversable/valibot-types' + +import type { newtype, inline } from '@traversable/registry' +import { + Array_isArray, + fn, + isKeyOf, + isObject, + mutateRandomElementOf, + mutateRandomValueOf, + Number_isFinite, + Number_isNatural, + Object_assign, + Object_create, + Object_entries, + Object_fromEntries, + Object_keys, + Object_values, + omit, + pair, + PATTERN, + pick, + symbol, +} from '@traversable/registry' + +// type Config = import('./generator-options.js').Config +// import * as Config from './generator-options.js' +// import * as Bounds from './generator-bounds.js' +// import type { Tag } from './generator-seed.js' +// import { byTag, bySeed, Seed, fold } from './generator-seed.js' + +// const identifier = fc.stringMatching(new RegExp(PATTERN.identifier, 'u')) + +// const enumValues = fc.uniqueArray( +// fc.tuple( +// identifier, +// identifier, +// ), +// { +// selector: ([k]) => k, +// minLength: 1, +// } +// ).map(Object_fromEntries) + +// const literalValue = fc.oneof( +// fc.string({ minLength: Bounds.defaults.string[0], maxLength: Bounds.defaults.string[1] }), +// fc.double({ min: Bounds.defaults.number[0], max: Bounds.defaults.number[1], noNaN: true }), +// fc.boolean(), +// ) + +// const TerminalMap = { +// any: fn.const(fc.tuple(fc.constant(byTag.any))), +// boolean: fn.const(fc.tuple(fc.constant(byTag.boolean))), +// date: fn.const(fc.tuple(fc.constant(byTag.date))), +// file: fn.const(fc.tuple(fc.constant(byTag.file))), +// blob: fn.const(fc.tuple(fc.constant(byTag.blob))), +// nan: fn.const(fc.tuple(fc.constant(byTag.nan))), +// never: fn.const(fc.tuple(fc.constant(byTag.never))), +// null: fn.const(fc.tuple(fc.constant(byTag.null))), +// undefined: fn.const(fc.tuple(fc.constant(byTag.undefined))), +// unknown: fn.const(fc.tuple(fc.constant(byTag.unknown))), +// void: fn.const(fc.tuple(fc.constant(byTag.void))), +// symbol: fn.const(fc.tuple(fc.constant(byTag.symbol))), +// } satisfies { [K in keyof Seed.TerminalMap]: SeedBuilder } + +// const bigIntBounds = Bounds.bigint(fc.bigInt()) +// const numberBounds = Bounds.number(fc.double()) +// const stringBounds = Bounds.string(fc.integer({ min: 0 })) + +// const BoundableMap = { +// bigint: fn.const(fc.tuple(fc.constant(byTag.bigint), bigIntBounds)), +// number: fn.const(fc.tuple(fc.constant(byTag.number), numberBounds)), +// string: fn.const(fc.tuple(fc.constant(byTag.string), stringBounds)), +// } satisfies { [K in keyof Seed.BoundableMap]: SeedBuilder } + +// const ValueMap = { +// enum: fn.const(fc.tuple(fc.constant(byTag.enum), enumValues)), +// literal: fn.const(fc.tuple(fc.constant(byTag.literal), literalValue)), +// } satisfies { [K in keyof Seed.ValueMap]: SeedBuilder } + +// const UnaryMap = { +// array: (tie) => fc.tuple(fc.constant(byTag.array), tie('*'), Bounds.array(fc.integer({ min: 0 }))), +// custom: (tie) => fc.tuple(fc.constant(byTag.custom), tie('*')), +// lazy: (tie) => fc.tuple(fc.constant(byTag.lazy), fc.func<[], unknown>(tie('*'))), +// optional: (tie) => fc.tuple(fc.constant(byTag.optional), tie('*')), +// non_optional: (tie) => fc.tuple(fc.constant(byTag.non_optional), tie('*')), +// undefinedable: (tie) => fc.tuple(fc.constant(byTag.undefinedable), tie('*')), +// nullable: (tie) => fc.tuple(fc.constant(byTag.nullable), tie('*')), +// non_nullable: (tie) => fc.tuple(fc.constant(byTag.non_nullable), tie('*')), +// nullish: (tie) => fc.tuple(fc.constant(byTag.nullish), tie('*')), +// non_nullish: (tie) => fc.tuple(fc.constant(byTag.non_nullish), tie('*')), +// record: (tie) => fc.tuple(fc.constant(byTag.record), tie('*')), +// set: (tie) => fc.tuple(fc.constant(byTag.set), tie('*')), +// object: (tie, $) => fc.tuple( +// fc.constant(byTag.object), +// fc.uniqueArray(fc.tuple(identifier, tie('*')), $) +// ), +// loose_object: (tie, $) => fc.tuple( +// fc.constant(byTag.loose_object), +// fc.uniqueArray(fc.tuple(identifier, tie('*')), $) +// ), +// strict_object: (tie, $) => fc.tuple( +// fc.constant(byTag.strict_object), +// fc.uniqueArray(fc.tuple(identifier, tie('*')), $) +// ), +// object_with_rest: (tie, $) => fc.tuple( +// fc.constant(byTag.object_with_rest), +// fc.uniqueArray(fc.tuple(identifier, tie('*')), $), +// tie('*'), +// ), +// tuple: (tie, $) => fc.tuple(fc.constant(byTag.tuple), fc.array(tie('*'), $)), +// loose_tuple: (tie, $) => fc.tuple(fc.constant(byTag.loose_tuple), fc.array(tie('*'), $)), +// strict_tuple: (tie, $) => fc.tuple(fc.constant(byTag.strict_tuple), fc.array(tie('*'), $)), +// tuple_with_rest: (tie, $) => fc.tuple(fc.constant(byTag.tuple_with_rest), fc.array(tie('*'), $), tie('*')), +// union: (tie, $) => fc.tuple(fc.constant(byTag.union), fc.array(tie('*'), $)), +// variant: (tie, $) => fc.tuple( +// fc.constant(byTag.variant), +// fc.uniqueArray( +// fc.tuple( +// identifier, +// fc.uniqueArray( +// fc.tuple( +// identifier, +// tie('*'), +// ), +// { selector: ([key]) => key } +// ) +// ), +// { ...$, selector: ([uniqueTag]) => uniqueTag } +// ), +// identifier, +// ), +// intersect: (tie) => entries(tie('*'), { minLength: 2 }).map(fn.flow( +// (xs) => pair( +// xs.slice(0, Math.ceil(xs.length / 2)), +// xs.slice(Math.ceil(xs.length / 2)), +// ), +// ([l, r]) => pair( +// pair(byTag.object, l), +// pair(byTag.object, r), +// ), +// (both) => pair(byTag.intersect, both), +// )), +// map: (tie) => fc.tuple(fc.constant(byTag.map), fc.tuple(tie('*'), tie('*'))), +// promise: () => PromiseSchemaIsUnsupported('SeedMap'), +// } satisfies { [K in keyof Seed.UnaryMap]: SeedBuilder } + +// const TerminalSeeds = fn.map(Object_keys(TerminalMap), (tag) => byTag[tag]) +// const BoundableSeeds = fn.map(Object_keys(BoundableMap), (tag) => byTag[tag]) + +// export interface SeedBuilder { +// (tie: fc.LetrecTypedTie, $: Config.byTypeName[K]): fc.Arbitrary +// } + +// export interface SeedMap extends newtype<{ [K in keyof Seed]: SeedBuilder }> {} +// export const SeedMap = { +// ...TerminalMap, +// ...BoundableMap, +// ...ValueMap, +// ...UnaryMap, +// } satisfies SeedMap + +// export function isTerminal(x: unknown): x is Seed.Terminal | Seed.Boundable +// export function isTerminal(x: unknown) { +// if (!Array_isArray(x)) return false +// else { +// const tag = x[0] as never +// return TerminalSeeds.includes(tag) || BoundableSeeds.includes(tag) +// } +// } + +// export const pickAndSortNodes +// : (nodes: readonly ([keyof SeedMap, unknown])[]) => ($: Config) => (keyof SeedMap)[] +// = (nodes) => ({ include, exclude, sortBias } = Config.defaults as never) => +// nodes +// .map(([k]) => k) +// .filter((x) => +// (include ? include.includes(x as never) : true) && +// (exclude ? !exclude.includes(x as never) : true) +// ) +// // TODO: remove nullish coalesce operators +// .sort((l, r) => sortBias[l]! < sortBias[r]! ? -1 : sortBias[l]! > sortBias[r]! ? 1 : 0) + +// export function v_bigint(bounds?: Bounds.bigint): v.BigintSchema +// export function v_bigint(bounds: Bounds.bigint = Bounds.defaults.bigint) { +// const [min, max, multipleOf] = bounds +// const schema = v.bigint() +// let pipeline = Array.of>() +// if (typeof min === 'bigint') pipeline.push(v.minValue(min)) +// if (typeof max === 'bigint') pipeline.push(v.maxValue(max)) +// // if (typeof multipleOf === 'bigint') pipeline.push(v.multipleOf(multipleOf)) +// return pipeline.length === 0 ? schema : v.pipe(schema, ...pipeline) +// } + +// export function v_number(bounds?: Bounds.number): v.NumberSchema +// export function v_number(bounds: Bounds.number = Bounds.defaults.number) { +// const [min, max, multipleOf, minExcluded, maxExcluded] = bounds +// const schema = v.number() +// let pipeline = Array.of>() +// if (Number_isFinite(min)) pipeline.push(minExcluded ? v.gtValue(min) : v.minValue(min)) +// if (Number_isFinite(max)) pipeline.push(maxExcluded ? v.ltValue(max) : v.maxValue(max)) +// // if (Number_isFinite(multipleOf) pipeline.push(v.multipleOf(multipleOf)) +// return pipeline.length === 0 ? schema : v.pipe(schema, ...pipeline) +// } + +// export function v_string(bounds?: Bounds.string): v.StringSchema +// export function v_string(bounds: Bounds.string = Bounds.defaults.string) { +// const [min, max, exactLength] = bounds +// const schema = v.string() +// let pipeline = Array.of>() +// if (Number_isNatural(exactLength)) { +// pipeline.push(v.minLength(exactLength), v.maxLength(exactLength)) +// } else { +// if (Number_isNatural(min)) pipeline.push(v.minLength(min)) +// if (Number_isNatural(max)) pipeline.push(v.maxLength(max)) +// } +// return pipeline.length === 0 ? schema : v.pipe(schema, ...pipeline) +// } + +// export function v_array(elementSchema: T, bounds?: Bounds.array): v.ArraySchema +// export function v_array(elementSchema: T, bounds: Bounds.array = Bounds.defaults.array) { +// const [min, max, exactLength] = bounds +// const schema: v.ArraySchema = v.array(elementSchema) +// let pipeline = Array.of>() +// if (Number_isNatural(exactLength)) { +// pipeline.push(v.minLength(exactLength), v.maxLength(exactLength)) +// } else { +// if (Number_isNatural(min)) pipeline.push(v.minLength(min)) +// if (Number_isNatural(max)) pipeline.push(v.maxLength(max)) +// } +// return pipeline.length === 0 ? schema : v.pipe(schema, ...pipeline) +// } + +// const unboundedSeed = { +// bigint: () => fc.constant([byTag.bigint, [null, null, null]]), +// number: () => fc.constant([byTag.number, [null, null, null, false, false]]), +// string: () => fc.constant([byTag.string, [null, null]]), +// array: (tie) => fc.tuple(fc.constant(byTag.array), tie('*'), fc.constant([null, null])), +// } satisfies Record fc.Arbitrary> + +// export interface Builder extends inline<{ [K in Tag]+?: fc.Arbitrary }> { +// root?: fc.Arbitrary +// invalid?: fc.Arbitrary +// ['*']: fc.Arbitrary +// } + +// export function Builder(base: Gen.Base): +// >( +// options?: Options, +// overrides?: Partial> +// ) => (tie: fc.LetrecLooselyTypedTie) => Builder + +// export function Builder(base: Gen.Base) { +// return >(options?: Options, overrides?: Partial>) => { +// const $ = Config.parseOptions(options) +// return (tie: fc.LetrecLooselyTypedTie) => { +// const builder: { [x: string]: fc.Arbitrary } = fn.pipe( +// { +// ...base, +// ...$.bigint.unbounded && 'bigint' in base && { bigint: unboundedSeed.bigint }, +// ...$.number.unbounded && 'number' in base && { number: unboundedSeed.number }, +// ...$.string.unbounded && 'string' in base && { string: unboundedSeed.string }, +// ...$.array.unbounded && 'array' in base && { array: unboundedSeed.array }, +// ...overrides, +// }, +// (x) => pick(x, $.include), +// (x) => omit(x, $.exclude), +// (x) => fn.map(x, (f, k) => f(tie, $[k as never])), +// ) +// const nodes = pickAndSortNodes(Object_entries(builder) as [k: keyof SeedMap, unknown][])($) +// builder['*'] = fc.oneof($['*'], ...nodes.map((k) => builder[k])) +// const root = isKeyOf(builder, $.root) && builder[$.root] +// let leaf = builder['*'] + +// return Object_assign( +// builder, { +// ...root && { root }, +// ['*']: leaf +// }) +// } +// } +// } + +// export declare namespace Gen { +// type Base = { [K in keyof T]: (tie: fc.LetrecLooselyTypedTie, constraints: $[K & keyof $]) => fc.Arbitrary } +// type Values = never | T[Exclude] +// type InferArb = S extends fc.Arbitrary ? T : never +// interface Builder extends newtype { ['*']: fc.Arbitrary>> } +// type BuildBuilder, Out extends {} = BuilderBase> = never | Builder +// type BuilderBase, $ extends ParseOptions = ParseOptions> = never | +// & ([$['root']] extends [never] ? unknown : { root: fc.Arbitrary<$['root']> }) +// & { [K in Exclude<$['include'], $['exclude']>]: fc.Arbitrary } +// type ParseOptions> = never | { +// include: Options['include'] extends readonly unknown[] ? Options['include'][number] : keyof T +// exclude: Options['exclude'] extends readonly unknown[] ? Options['exclude'][number] : never +// root: Options['root'] extends keyof T ? T[Options['root']] : never +// } +// } + +// /** +// * ## {@link Gen `Gen`} +// */ +// export function Gen(base: Gen.Base): +// >( +// options?: Options, +// overrides?: Partial> +// ) => Gen.BuildBuilder + +// export function Gen(base: Gen.Base) { +// return >( +// options?: Options, +// overrides?: Partial> +// ): Builder => { +// return fc.letrec(Builder(base)(options, overrides)) +// } +// } + +// const typedArray = fc.oneof( +// fc.int8Array(), +// fc.uint8Array(), +// fc.uint8ClampedArray(), +// fc.int16Array(), +// fc.uint16Array(), +// fc.int32Array(), +// fc.uint32Array(), +// fc.float32Array(), +// fc.float64Array(), +// fc.bigInt64Array(), +// fc.bigUint64Array(), +// ) + +// const pathName = fc.webUrl().map((webUrl) => new URL(webUrl).pathname) +// const ext = fc.string({ minLength: 2, maxLength: 3 }) +// const fileName = fc.tuple(pathName, ext).map(([pathName, ext]) => `${pathName}.${ext}` as const) +// const fileBits = fc.array(typedArray) +// const file = fc.tuple(fileBits, fileName).map(([fileBits, filename]) => new File(fileBits, filename)) +// const blob = fc.tuple(fileBits, fileName).map(([fileBits, filename]) => new Blob(fileBits)) +// const arbitrarySymbol = fc.oneof(fc.constant(Symbol()), fc.string().map((s) => Symbol.for(s))) + +// function entries(model: fc.Arbitrary, options?: fc.UniqueArrayConstraintsRecommended<[k: string, T], unknown>) { +// return fc.uniqueArray( +// fc.tuple(identifier, model), +// { selector: options?.selector || (([k]) => k), ...options }, +// ) +// } + +// // const is = { +// // null: (x: unknown): x is [byTag['null']] => Array_isArray(x) && x[0] === byTag.null, +// // undefined: (x: unknown): x is [byTag['undefined']] => Array_isArray(x) && x[0] === byTag.undefined, +// // boolean: (x: unknown): x is [byTag['boolean']] => Array_isArray(x) && x[0] === byTag.boolean, +// // int: (x: unknown): x is [byTag['int'], Bounds.int] => Array_isArray(x) && x[0] === byTag.int, +// // number: (x: unknown): x is [byTag['number'], Bounds.number] => Array_isArray(x) && x[0] === byTag.number, +// // string: (x: unknown): x is [byTag['number'], Bounds.string] => Array_isArray(x) && x[0] === byTag.string, +// // literal: (x: unknown): x is [byTag['literal'], z.core.util.Literal] => Array_isArray(x) && x[0] === byTag.literal, +// // bigint: (x: unknown): x is [byTag['number'], Bounds.bigint] => Array_isArray(x) && x[0] === byTag.bigint, +// // } + +// function intersect(x: unknown, y: unknown) { +// return !isObject(x) ? y : !isObject(y) ? x : Object_assign(x, y) +// } + +// const GeneratorByTag = { +// any: () => fc.anything(), +// custom: () => fc.anything(), +// boolean: () => fc.boolean(), +// date: () => fc.date({ noInvalidDate: true }), +// file: () => file, +// blob: () => blob, +// nan: () => fc.constant(Number.NaN), +// never: () => fc.constant(void 0 as never), +// null: () => fc.constant(null), +// symbol: () => arbitrarySymbol, +// undefined: () => fc.constant(undefined), +// unknown: () => fc.anything(), +// void: () => fc.constant(void 0 as void), +// bigint: (x) => fc.bigInt(Bounds.bigintBoundsToBigIntConstraints(x[1])), +// number: (x) => fc.double(Bounds.numberBoundsToDoubleConstraints(x[1])), +// string: (x) => fc.string(Bounds.stringBoundsToStringConstraints(x[1])), +// enum: (x) => fc.constantFrom(...Object_values(x[1])), +// literal: (x) => fc.constant(x[1]), +// array: (x) => fc.array(x[1], Bounds.arrayBoundsToArrayConstraints(x[2])), +// optional: (x, _$, isProperty) => isProperty ? x[1] : fc.oneof(x[1], fc.constant(undefined)), +// non_optional: (x) => x[1].map((_) => _ === undefined ? {} : _), +// undefinedable: (x) => fc.oneof(x[1], fc.constant(undefined)), +// nullable: (x) => fc.oneof(x[1], fc.constant(null)), +// non_nullable: (x) => x[1].map((_) => _ === null ? {} : _), +// nullish: (x) => fc.oneof(x[1], fc.constant(undefined), fc.constant(null)), +// non_nullish: (x) => x[1].map((_) => _ == null ? {} : _), +// set: (x) => x[1].map((v) => new globalThis.Set([v])), +// map: (x) => fc.tuple(x[1][0], x[1][1]).map(([k, v]) => new Map([[k, v]])), +// record: (x) => fc.dictionary(identifier, x[1]), +// tuple: (x) => fc.tuple(...x[1]), +// loose_tuple: (x) => fc.tuple(...x[1]), +// strict_tuple: (x) => fc.tuple(...x[1]), +// tuple_with_rest: (x) => fc.tuple(fc.tuple(...x[1]), fc.array(x[2])).map(([xs, rest]) => [...xs, ...rest]), +// union: (x) => fc.oneof(...(x[1] || [fc.constant(void 0 as never)])), +// lazy: (x) => x[1](), +// object: (x) => fc.record(Object.fromEntries(x[1])), +// strict_object: (x) => fc.record(Object.fromEntries(x[1])), +// loose_object: (x) => fc.record(Object.fromEntries(x[1])), +// object_with_rest: (x) => fc.tuple(fc.record(Object.fromEntries(x[1])), fc.dictionary(identifier, x[2])).map(([xs, rest]) => ({ ...rest, ...xs })), +// intersect: (x) => fc.tuple(...x[1]).map(([x, y]) => intersect(x, y)), +// variant: (x) => fc.oneof( +// ...x[1].map( +// ([tag, entries]) => fc.record( +// Object_assign( +// Object_create(null), +// Object_fromEntries(entries), +// { [x[2]]: fc.constant(tag) } +// ) +// ) +// ) +// ), +// /** +// * variant: (tie, $) => fc.tuple( +// * fc.constant(byTag.variant), +// * identifier, +// * fc.uniqueArray( +// * fc.tuple( +// * identifier, +// * fc.dictionary(identifier, tie('*'), $), +// * ), +// * { selector: ([uniqueTag]) => uniqueTag } +// * ) +// * ).map( +// * ([seedTag, discriminator, xs]) => [ +// * seedTag, +// * xs.map(([uniqueTag, x]) => ({ ...x, [discriminator]: uniqueTag })), +// * ] satisfies [any, any] +// * ), +// */ +// // fc.oneof(...(x[1] || [fc.constant(void 0 as never)])) +// promise: () => PromiseSchemaIsUnsupported('GeneratorByTag'), +// } satisfies { +// [K in keyof Seed]: (x: Seed>[K], $: Config, isProperty: boolean) => fc.Arbitrary +// } + +// /** +// * ## {@link seedToValidDataGenerator `seedToValidDataGenerator`} +// * +// * Convert a seed into an valid data generator. +// * +// * Valid in this context means that it will always satisfy the valibot schema that the seed produces. +// * +// * To convert a seed to a valibot schema, use {@link seedToSchema `seedToSchema`}. +// * +// * To convert a seed to an _invalid_ data generator, use {@link seedToInvalidDataGenerator `seedToInvalidDataGenerator`}. +// */ +// export function seedToValidDataGenerator(seed: Seed.F, options?: Config.Options): fc.Arbitrary +// export function seedToValidDataGenerator(seed: Seed.F, options?: Config.Options): fc.Arbitrary { +// const $ = Config.parseOptions(options) +// return fold>((x, isProperty) => GeneratorByTag[bySeed[x[0]]](x as never, $, isProperty || x[0] === 7500))(seed as never) +// } + +// /** +// * ## {@link seedToInvalidDataGenerator `seedToInvalidDataGenerator`} +// * +// * Convert a seed into an invalid data generator. +// * +// * Invalid in this context means that it will never satisfy the valibot schema that the seed produces. +// * +// * To convert a seed to a valibot schema, use {@link seedToSchema `seedToSchema`}. +// * +// * To convert a seed to an _valid_ data generator, use +// * {@link seedToValidDataGenerator `seedToValidDataGenerator`}. +// */ +// export function seedToInvalidDataGenerator(seed: T, options?: Config.Options): fc.Arbitrary +// export function seedToInvalidDataGenerator(seed: Seed.F, options?: Config.Options): fc.Arbitrary +// export function seedToInvalidDataGenerator(seed: Seed.F, options?: Config.Options): fc.Arbitrary { +// const $ = Config.parseOptions(options) +// return fold>((x) => { +// switch (x[0]) { +// case byTag.record: return GeneratorByTag.record(x).map(mutateRandomValueOf) +// case byTag.array: return GeneratorByTag.array(x).map(mutateRandomElementOf) +// case byTag.tuple: return GeneratorByTag.tuple(x).map(mutateRandomElementOf) +// case byTag.object: return GeneratorByTag.object(x).map(mutateRandomValueOf) +// default: return GeneratorByTag[bySeed[x[0]]](x as never, $, false) +// } +// })(seed as never) +// } + +// /** +// * ## {@link SeedGenerator `SeedGenerator`} +// * +// * Pseudo-random seed generator. +// * +// * The generator supports a wide range of discoverable configuration options via the +// * optional `options` argument. +// * +// * Many of those options are forwarded to the corresponding `fast-check` arbitrary. +// * +// * See also: +// * - {@link SeedGenerator `SeedGenerator`} +// * +// * @example +// * import * as fc from 'fast-check' +// * import * as v from 'valibot' +// * import { vxTest } from '@traversable/valibot-test' +// * +// * const Json = vxTest.SeedGenerator({ include: ['null', 'boolean', 'number', 'string', 'array', 'object'] }) +// * const [jsonNumber, jsonObject, anyJson] = [ +// * fc.sample(Json.number, 1)[0], +// * fc.sample(Json.object, 1)[0], +// * fc.sample(Json['*'], 1)[0], +// * ] as const +// * +// * console.log(JSON.stringify(jsonNumber)) +// * // => [200,[2.96e-322,1,null,false,true]] +// * +// * console.log(vxTest.toString(vxTest.seedToSchema(jsonNumber))) +// * // => v.pipe(v.number(), v.maxValue(2.96e-322), v.ltValue(1) +// * +// * console.log(JSON.stringify(jsonObject)) +// * // => [7500,[["n;}289K~",[250,[null,null]]]]] +// * +// * console.log(vxTest.toString(vxTest.seedToSchema(jsonObject))) +// * // => v.object({ "n;}289K~": v.string() }) +// * +// * console.log(anyJson) +// * // => [250,[23,64]] +// * +// * console.log(vxTest.toString(vxTest.seedToSchema(anyJson))) +// * // => v.pipe(v.string(), v.minValue(23), v.maxValue(64)) +// */ +// export const SeedGenerator = Gen(SeedMap) + +// const seedsThatPreventGeneratingValidData = [ +// 'custom', +// 'never', +// 'non_optional', +// 'non_nullable', +// 'non_nullish', +// 'promise', +// ] satisfies SchemaGenerator.Options['exclude'] + +// const seedsThatPreventGeneratingInvalidData = [ +// ...seedsThatPreventGeneratingValidData, +// 'any', +// 'promise', +// 'symbol', +// 'unknown', +// ] satisfies SchemaGenerator.Options['exclude'] + +// /** +// * ## {@link SeedValidDataGenerator `SeedValidDataGenerator`} +// * +// * A seed generator that can be interpreted to produce reliably valid data. +// * +// * This was originally developed to test for parity between various schema libraries. +// * +// * Note that certain schemas make generating valid data impossible +// * (like {@link v.never `v.never`}). For this reason, those schemas are not seeded. +// * +// * To see the list of excluded schemas, see +// * {@link seedsThatPreventGeneratingValidData `seedsThatPreventGeneratingValidData`}. +// * +// * See also: +// * - {@link SeedInvalidDataGenerator `SeedInvalidDataGenerator`} +// * +// * @example +// * import * as fc from 'fast-check' +// * import * as v from 'valibot' +// * import { vxTest } from '@traversable/valibot-test' +// * +// * const [seed] = fc.sample(vxTest.SeedValidDataGenerator, 1) +// * const ValibotSchema = vxTest.seedToSchema(seed) +// * const dataset = fc.sample(vxTest.seedToValidData(seed), 5) +// * +// * const results = dataset.map((pt) => ValibotSchema.safeParse(pt).success) +// * +// * console.log(results) // => [true, true, true, true, true] +// */ + +// export const SeedValidDataGenerator = SeedGenerator({ exclude: seedsThatPreventGeneratingValidData })['*'] + +// /** +// * ## {@link SeedInvalidDataGenerator `vxTest.SeedInvalidDataGenerator`} +// * +// * A seed generator that can be interpreted to produce reliably invalid data. +// * +// * This was originally developed to test for parity between various schema libraries. +// * +// * Note that certain schemas make generating invalid data impossible +// * (like {@link v.any `v.any`}). For this reason, those schemas are not seeded. +// * +// * To see the list of excluded schemas, see +// * {@link seedsThatPreventGeneratingInvalidData `vxTest.seedsThatPreventGeneratingInvalidData`}. +// * +// * See also: +// * - {@link SeedValidDataGenerator `vxTest.SeedValidDataGenerator`} +// * +// * @example +// * import * as fc from 'fast-check' +// * import * as v from 'valibot' +// * import { vxTest } from '@traversable/valibot-test' +// * +// * const [seed] = fc.sample(vxTest.SeedInvalidDataGenerator, 1) +// * const ValibotSchema = vxTest.seedToSchema(seed) +// * const dataset = fc.sample(vxTest.seedToInvalidData(seed), 5) +// * +// * const results = dataset.map((pt) => ValibotSchema.safeParse(pt).success) +// * +// * console.log(results) // => [false, false, false, false, false] +// */ +// export const SeedInvalidDataGenerator = fn.pipe( +// SeedGenerator({ exclude: seedsThatPreventGeneratingInvalidData }), +// ($) => fc.oneof( +// $.object, +// $.tuple, +// $.array, +// $.record, +// ) +// ) + +// /** +// * ## {@link SchemaGenerator `vxTest.SchemaGenerator`} +// * +// * A valibot schema generator that can be interpreted to produce an arbitrary valibot schema. +// * +// * The generator supports a wide range of configuration options that are discoverable via the +// * optional `options` argument. +// * +// * Many of those options are forwarded to the corresponding `fast-check` arbitrary. +// * +// * See also: +// * - {@link SeedGenerator `vxTest.SeedGenerator`} +// * +// * @example +// * import * as fc from 'fast-check' +// * import * as v from 'valibot' +// * import { vxTest } from '@traversable/valibot-test' +// * +// * const tenSchemas = fc.sample(vxTest.SchemaGenerator({ +// * include: ['null', 'boolean', 'number', 'string', 'array', 'object'] +// * }), 10) +// * +// * tenSchemas.forEach((s) => console.log(vxTest.toString(s))) +// * // => v.number() +// * // => v.pipe(v.string(), v.minValue(9.1e-53)) +// * // => v.null() +// * // => v.array(v.boolean()) +// * // => v.boolean() +// * // => v.object({ "": v.object({ "/d2P} {/": v.boolean() }), "svH2]L'x": v.pipe(v.number(), v.ltValue(-65536)) }) +// * // => v.null() +// * // => v.string() +// * // => v.array(v.array(v.null())) +// * // => v.object({ "y(Qza": v.boolean(), "G1S\\U 4Y6i": v.object({ "YtO3]ia0cM": v.boolean() }) }) +// */ + +// export const SchemaGenerator = fn.flow( +// SeedGenerator, +// builder => builder['*'], +// (arb) => arb.map(seedToSchema), +// ) + +// export declare namespace SchemaGenerator { +// type Options = Config.Options +// } + +// /** +// * ## {@link seedToSchema `vxTest.seedToSchema`} +// * +// * Interpreter that converts a seed value into its corresponding valibot schema. +// * +// * To get a seed, use {@link SeedGenerator `vxTest.SeedGenerator`}. +// */ +// export function seedToSchema(seed: T): Seed.schemaFromComposite[T[0]] +// export function seedToSchema(seed: Seed.F): v.BaseSchema> +// export function seedToSchema(seed: Seed.F) { +// return fold((x) => { +// switch (true) { +// default: return fn.exhaustive(x) +// case x[0] === byTag.any: return v.any() +// case x[0] === byTag.custom: return v.custom(() => true) +// case x[0] === byTag.boolean: return v.boolean() +// case x[0] === byTag.date: return v.date() +// case x[0] === byTag.file: return v.file() +// case x[0] === byTag.blob: return v.blob() +// case x[0] === byTag.nan: return v.nan() +// case x[0] === byTag.never: return v.never() +// case x[0] === byTag.null: return v.null() +// case x[0] === byTag.symbol: return v.symbol() +// case x[0] === byTag.undefined: return v.undefined() +// case x[0] === byTag.unknown: return v.unknown() +// case x[0] === byTag.void: return v.void() +// case x[0] === byTag.bigint: return v_bigint(x[1]) +// case x[0] === byTag.number: return v_number(x[1]) +// case x[0] === byTag.string: return v_string(x[1]) +// case x[0] === byTag.enum: return v.enum(x[1]) +// case x[0] === byTag.literal: return v.literal(x[1]) +// case x[0] === byTag.array: return v.array(x[1]) +// case x[0] === byTag.optional: return v.optional(x[1]) +// case x[0] === byTag.non_optional: return v.nonOptional(x[1]) +// case x[0] === byTag.undefinedable: return v.undefinedable(x[1]) +// case x[0] === byTag.nullable: return v.nullable(x[1]) +// case x[0] === byTag.non_nullable: return v.nonNullable(x[1]) +// case x[0] === byTag.nullish: return v.nullish(x[1]) +// case x[0] === byTag.non_nullish: return v.nonNullish(x[1]) +// case x[0] === byTag.set: return v.set(x[1]) +// case x[0] === byTag.intersect: return v.intersect(x[1]) +// case x[0] === byTag.lazy: return v.lazy(x[1]) +// case x[0] === byTag.map: return v.map(x[1][0], x[1][1]) +// case x[0] === byTag.record: return v.record(v.string(), x[1]) +// case x[0] === byTag.object: return v.object(Object.fromEntries(x[1])) +// case x[0] === byTag.loose_object: return v.looseObject(Object.fromEntries(x[1])) +// case x[0] === byTag.strict_object: return v.strictObject(Object.fromEntries(x[1])) +// case x[0] === byTag.object_with_rest: return v.objectWithRest(Object.fromEntries(x[1]), x[2]) +// case x[0] === byTag.tuple: return v.tuple(x[1]) +// case x[0] === byTag.loose_tuple: return v.looseTuple(x[1]) +// case x[0] === byTag.strict_tuple: return v.strictTuple(x[1]) +// case x[0] === byTag.tuple_with_rest: return v.tupleWithRest(x[1], x[2]) +// case x[0] === byTag.strict_object: return v.strictObject(Object.fromEntries(x[1])) +// case x[0] === byTag.union: return v.union(x[1]) +// case x[0] === byTag.variant: return v.variant(x[2], x[1].map( +// ([tag, entries]) => v.object({ ...Object_fromEntries(entries), [x[2]]: v.literal(tag) })) +// ) +// case x[0] === byTag.promise: return PromiseSchemaIsUnsupported('seedToSchema') +// } +// })(seed as never) +// } + +// /** +// * ## {@link seedToValidData `seedToValidData`} +// * +// * Given a seed, generates an single example of valid data. +// * +// * Valid in this context means that it will always satisfy the valibot schema that the seed produces. +// * +// * To use it, you'll need to have [fast-check](https://github.com/dubzzz/fast-check) installed. +// * +// * To convert a seed to a valibot schema, use {@link seedToSchema `seedToSchema`}. +// * +// * To convert a seed to a single example of _invalid_ data, use {@link seedToInvalidData `seedToInvalidData`}. +// */ +// export const seedToValidData = fn.flow( +// seedToValidDataGenerator, +// (model) => fc.sample(model, 1)[0], +// ) + +// /** +// * ## {@link seedToInvalidData `seedToInvalidData`} +// * +// * Given a seed, generates an single example of invalid data. +// * +// * Invalid in this context means that it will never satisfy the valibot schema that the seed produces. +// * +// * To use it, you'll need to have [fast-check](https://github.com/dubzzz/fast-check) installed. +// * +// * To convert a seed to a valibot schema, use {@link seedToSchema `seedToSchema`}. +// * +// * To convert a seed to a single example of _valid_ data, use {@link seedToValidData `seedToValidData`}. +// */ +// export const seedToInvalidData = fn.flow( +// seedToInvalidDataGenerator, +// (model) => fc.sample(model, 1)[0], +// ) diff --git a/packages/graphql-test/tsconfig.build.json b/packages/graphql-test/tsconfig.build.json index 16c1c0e4..ee34f8a2 100644 --- a/packages/graphql-test/tsconfig.build.json +++ b/packages/graphql-test/tsconfig.build.json @@ -7,5 +7,8 @@ "outDir": "build/esm", "stripInternal": true }, - "references": [{ "path": "../graphql-types" }, { "path": "../registry" }] + "references": [ + { "path": "../graphql-types" }, + { "path": "../registry" } + ] } diff --git a/packages/graphql-test/tsconfig.src.json b/packages/graphql-test/tsconfig.src.json index c110a318..fed5dd75 100644 --- a/packages/graphql-test/tsconfig.src.json +++ b/packages/graphql-test/tsconfig.src.json @@ -6,6 +6,9 @@ "types": ["node"], "outDir": "build/src" }, - "references": [{ "path": "../graphql-types" }, { "path": "../registry" }], + "references": [ + { "path": "../graphql-types" }, + { "path": "../registry" } + ], "include": ["src"] } diff --git a/packages/graphql-types/package.json b/packages/graphql-types/package.json index cf97581a..5be57de8 100644 --- a/packages/graphql-types/package.json +++ b/packages/graphql-types/package.json @@ -45,15 +45,11 @@ }, "peerDependencies": { "@traversable/registry": "workspace:^", - "graphql": "^16.11.0" + "graphql": ">= 16" }, "peerDependenciesMeta": { - "@traversable/registry": { - "optional": false - }, - "graphql": { - "optional": false - } + "@traversable/registry": { "optional": false }, + "graphql": { "optional": true } }, "devDependencies": { "@prettier/sync": "catalog:", diff --git a/packages/graphql-types/src/exports.ts b/packages/graphql-types/src/exports.ts index e20f0bff..b834ec99 100644 --- a/packages/graphql-types/src/exports.ts +++ b/packages/graphql-types/src/exports.ts @@ -1,16 +1,21 @@ export { VERSION } from './version.js' export type { + AnyKind, + AnyNamedType, + AnyTag, AST, - Algebra, - Fold, Index, } from './functor.js' export { Functor, Kind, + Kinds, NamedType, + NamedTypes, + Tag, + Tags, defaultIndex, fold, isBooleanNode, @@ -52,3 +57,4 @@ export { } from './functor.js' export { toType } from './to-type.js' +export { toString } from './to-string.js' diff --git a/packages/graphql-types/src/functor.ts b/packages/graphql-types/src/functor.ts index 4e828bf7..8f11d4de 100644 --- a/packages/graphql-types/src/functor.ts +++ b/packages/graphql-types/src/functor.ts @@ -91,6 +91,10 @@ export declare namespace Kind { type OperationTypeDefinition = typeof Kind.OperationTypeDefinition } +export const Kinds = Object.values(Kind) + +export type AnyKind = typeof Kinds[number] + /** * ## {@link NamedType `NamedType`} */ @@ -112,6 +116,19 @@ export declare namespace NamedType { type String = typeof NamedType.String } +export const NamedTypes = Object.values(NamedType) + +export type AnyNamedType = typeof NamedTypes[number] + +export const Tag = { + ...Kind, + ...NamedType, +} + +export const Tags = Object.values(Tag) + +export type AnyTag = typeof Tags[number] + export const OperationType = { Query: 'query', Mutation: 'mutation', @@ -653,14 +670,6 @@ export const defaultIndex = { isNonNull: false, } satisfies Index -export type Algebra = { - (src: AST.F, ix?: Index): T - (src: gql.ASTNode, ix?: Index): T - (src: AST.F, ix?: Index): T -} - -export type Fold = (g: (src: AST.F, ix: Index, x: gql.ASTNode) => T) => Algebra - export interface Functor extends T.HKT { [-1]: AST.F } export declare namespace Functor { @@ -879,4 +888,4 @@ export const Functor: T.Functor.Ix = { } } -export const fold: Fold = fn.catamorphism(Functor, defaultIndex) as never +export const fold = fn.catamorphism(Functor, defaultIndex) diff --git a/packages/graphql-types/src/to-string.ts b/packages/graphql-types/src/to-string.ts new file mode 100644 index 00000000..e805657c --- /dev/null +++ b/packages/graphql-types/src/to-string.ts @@ -0,0 +1,66 @@ +import { fn, escape, has, parseKey } from '@traversable/registry' +import * as F from './functor.js' +import type * as gql from 'graphql' +import type { AST } from './functor.js' + +function valueNodeToString(x: AST.ValueNode): string { + switch (x.kind) { + default: return fn.exhaustive(x) + case 'NullValue': return 'null' + case 'BooleanValue': return `${x.value}` + case 'IntValue': return `${x.value}` + case 'FloatValue': return `${x.value}` + case 'StringValue': return `"${escape(x.value)}"` + case 'EnumValueDefinition': return `"${x.name.value}"` + case 'ListValue': return `[${x.values.map(valueNodeToString).join(', ')}]` + case 'ObjectValue': return '' + + '{ ' + + x.fields.map((node) => `${parseKey(node.name.value)}: ${valueNodeToString(node.value)}`).join(', ') + + ' }' + case 'Variable': return `${x.name.value}` + } +} + +const fold = F.fold((x, _, _original) => { + switch (true) { + default: return fn.exhaustive(x) + case F.isRefNode(x): return x.name.value + case F.isValueNode(x): return valueNodeToString(x) + case F.isSelectionSetNode(x): return `{ ${x.selections.join('\n')} }` + case F.isScalarTypeDefinition(x): return `scalar ${x.name.value}` + case F.isBooleanNode(x): return 'Boolean' + case F.isIntNode(x): return 'Int' + case F.isNumberNode(x): return 'Number' + case F.isFloatNode(x): return 'Float' + case F.isStringNode(x): return 'String' + case F.isIDNode(x): return 'ID' + case F.isEnumNode(x): return `enum ${x.name.value} { ${x.values.map((v) => v.name.value).join('\n')} }` + case F.isNonNullTypeNode(x): return `${x.type}!` + case F.isUnionNode(x): return `union ${x.name.value} = ${x.types}` + case F.isListNode(x): return `[${x.type}]` + case F.isDirectiveNode(x): return `@${x.name.value}(${(x.arguments ?? []).join(', ')})` + case F.isFieldNode(x): return `${parseKey(x.name.value)}: ${x.type}` + case F.isInputValueNode(x): return `${parseKey(x.name.value)}: ${x.type}` + case F.isObjectNode(x): return `type ${x.name.value} { ${x.fields.join('\n')} }` + case F.isInterfaceNode(x): return `interface ${x.name.value} { ${x.fields.join('\n')} }` + case F.isInputObjectNode(x): return `input ${x.name.value} { ${x.fields.join('\n')} }` + case F.isInlineFragmentNode(x): return `...${!x.typeCondition ? '' : ` on ${x.typeCondition.name.value}`} ${x.selectionSet}` + case F.isFragmentDefinitionNode(x): return `fragment ${x.name.value} on ${x.typeCondition.name.value} ${x.selectionSet}` + case F.isFragmentSpreadNode(x): return `...${x.name.value}` + case F.isQueryOperation(x): return `query ${x.name?.value ?? ''}${!x.variableDefinitions ? '' : `(${x.variableDefinitions.join(', ')})`} { ${x.selectionSet} }` + case F.isMutationOperation(x): return `mutation ${x.name?.value ?? ''}${!x.variableDefinitions ? '' : `(${x.variableDefinitions.join(', ')})`} { ${x.selectionSet} }` + case F.isSubscriptionOperation(x): return `mutation ${x.name?.value ?? ''}${!x.variableDefinitions ? '' : `(${x.variableDefinitions.join(', ')})`} { ${x.selectionSet} }` + case F.isDocumentNode(x): return x.definitions.join('\n\r') + } +}) + +export declare namespace toString {} + +/** + * ## {@link toType `toType`} + * + * Convert a GraphQL AST into its corresponding TypeScript type. + */ +export function toString(doc: gql.DocumentNode): string { + return fold(doc) +} diff --git a/packages/graphql-types/src/to-type.ts b/packages/graphql-types/src/to-type.ts index c9aa9c2e..6d9cceef 100644 --- a/packages/graphql-types/src/to-type.ts +++ b/packages/graphql-types/src/to-type.ts @@ -1,5 +1,5 @@ import { fn, has, parseKey } from '@traversable/registry' -import * as GQL from './functor.js' +import * as F from './functor.js' import type * as gql from 'graphql' import type { AST } from './functor.js' @@ -12,7 +12,7 @@ const unsupported = [ 'InputValueDefinition', 'SelectionSet', 'OperationDefinition' -] as const satisfies Array +] as const satisfies Array type UnsupportedNodeMap = Pick type UnsupportedNode = UnsupportedNodeMap[keyof UnsupportedNodeMap] @@ -41,43 +41,38 @@ function valueNodeToString(x: AST.ValueNode): string { } } -const fold = GQL.fold((x, _, original) => { +const fold = F.fold((x, _, original) => { switch (true) { default: return fn.exhaustive(x) - case GQL.isRefNode(x): return x.name.value - case GQL.isValueNode(x): return valueNodeToString(x) - case GQL.isScalarTypeDefinition(x): return x.name.value - case GQL.isBooleanNode(x): return 'boolean' - case GQL.isIntNode(x): return 'number' - case GQL.isNumberNode(x): return 'number' - case GQL.isFloatNode(x): return 'number' - case GQL.isStringNode(x): return 'string' - case GQL.isIDNode(x): return 'string' - case GQL.isEnumNode(x): return ( + case F.isRefNode(x): return x.name.value + case F.isValueNode(x): return valueNodeToString(x) + case F.isScalarTypeDefinition(x): return x.name.value + case F.isBooleanNode(x): return 'boolean' + case F.isIntNode(x): return 'number' + case F.isNumberNode(x): return 'number' + case F.isFloatNode(x): return 'number' + case F.isStringNode(x): return 'string' + case F.isIDNode(x): return 'string' + case F.isEnumNode(x): return ( x.values.length === 0 ? 'never' : x.values.length === 1 ? JSON.stringify(x.values[0]) : `(${x.values.map((v) => JSON.stringify(v)).join(' | ')})` ) - case GQL.isNonNullTypeNode(x): return `${x.type}!` - case GQL.isUnionNode(x): return ( + case F.isNonNullTypeNode(x): return `${x.type}!` + case F.isUnionNode(x): return ( x.types.length === 0 ? 'never' : x.types.length === 1 ? JSON.stringify(x.types[0]) : `(${x.types.map((v) => JSON.stringify(v)).join(' | ')})` ) - case GQL.isListNode(x): { - if (!GQL.isListNode(original)) throw Error('Illegal state') - const isNonNull = x.type.endsWith('!') - const TYPE = isNonNull ? x.type.slice(0, -1) : `${x.type} | null` - return `Array<${TYPE}>` - } - case GQL.isFieldNode(x): { + case F.isListNode(x): return `Array<${x.type.endsWith('!') ? x.type.slice(0, -1) : `${x.type} | null`}>` + case F.isFieldNode(x): { const isNonNull = x.type.endsWith('!') const VALUE = isNonNull ? x.type.slice(0, -1) : x.type return `${parseKey(x.name.value)}${isNonNull ? '' : '?'}: ${VALUE}` } - case GQL.isObjectNode(x): { return x.fields.length === 0 ? `{}` : `{ ${x.fields.join(', ')} }` } - case GQL.isInterfaceNode(x): { return x.fields.length === 0 ? `{}` : `{ ${x.fields.join(', ')} }` } - case GQL.isDocumentNode(x): throw Error('[@traversable/graphql-types/to-type.js]: Nesting documents is not allowed') + case F.isObjectNode(x): { return `{ ${x.fields.join(', ')} }` } + case F.isInterfaceNode(x): { return `{ ${x.fields.join(', ')} }` } + case F.isDocumentNode(x): throw Error('[@traversable/graphql-types/to-type.js]: Nesting documents is not allowed') case isUnsupportedNode(x): throw Error(`[@traversable/graphql-types/to-type.js]: Unsupported node: ${x.kind}`) } }) @@ -97,7 +92,7 @@ toType.unsupported = unsupported */ export function toType(doc: gql.DocumentNode) { const types = doc.definitions.map( - (x, i) => `type ${GQL.isNamedTypeNode(x) ? x.name.value : `Type${i}`} = ${fold(x)}` + (x, i) => `type ${F.isNamedTypeNode(x) ? x.name.value : `Type${i}`} = ${fold(x)}` ) return types.join('\n') } diff --git a/packages/graphql-types/test/to-string.test.ts b/packages/graphql-types/test/to-string.test.ts new file mode 100644 index 00000000..26ed9c5c --- /dev/null +++ b/packages/graphql-types/test/to-string.test.ts @@ -0,0 +1,38 @@ +import * as vi from 'vitest' +import { toString } from '@traversable/graphql-types' +import * as graphql from 'graphql' +import prettier from '@prettier/sync' + +const format = (src: string) => prettier.format( + src, + { parser: 'graphql', semi: false, printWidth: 35 } +) + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/graphql-types❳', () => { + vi.test('〖⛳️〗› ❲toString❳', () => { + vi.expect.soft(format( + toString(graphql.parse(` + type Pet { + petName: [String!] + } + type Human { + humanName: String! + pet: Pet + } + ` + )) + )).toMatchInlineSnapshot + (` + "type Pet { + petName: [String!] + } + + type Human { + humanName: String! + pet: Pet + } + " + `) + }) +}) + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 36b98bc1..23275149 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -173,61 +173,61 @@ importers: version: 0.34.41 '@traversable/arktype': specifier: latest - version: 0.0.25(@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0))(arktype@2.1.22)(prettier@3.6.2) + version: 0.0.26(@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0))(arktype@2.1.22)(prettier@3.6.2) '@traversable/arktype-test': specifier: latest - version: 0.0.19(@traversable/registry@0.0.48)(arktype@2.1.22)(fast-check@4.3.0) + version: 0.0.20(@traversable/registry@0.0.49)(arktype@2.1.22)(fast-check@4.3.0) '@traversable/json': specifier: latest - version: 0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0) + version: 0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0) '@traversable/json-schema': specifier: latest - version: 0.0.25(@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0))(prettier@3.6.2) + version: 0.0.26(@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0))(prettier@3.6.2) '@traversable/json-schema-test': specifier: latest - version: 0.0.24(fast-check@4.3.0) + version: 0.0.25(fast-check@4.3.0) '@traversable/registry': specifier: latest - version: 0.0.48 + version: 0.0.49 '@traversable/schema': specifier: latest - version: 0.0.61(@traversable/registry@0.0.48) + version: 0.0.62(@traversable/registry@0.0.49) '@traversable/schema-codec': specifier: latest - version: 0.0.32(@traversable/registry@0.0.48)(@traversable/schema@0.0.61(@traversable/registry@0.0.48)) + version: 0.0.33(@traversable/registry@0.0.49)(@traversable/schema@0.0.62(@traversable/registry@0.0.49)) '@traversable/schema-deep-equal': specifier: latest - version: 0.0.18(@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0))(@traversable/registry@0.0.48)(@traversable/schema@0.0.61(@traversable/registry@0.0.48)) + version: 0.0.19(@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0))(@traversable/registry@0.0.49)(@traversable/schema@0.0.62(@traversable/registry@0.0.49)) '@traversable/schema-seed': specifier: latest - version: 0.0.51(@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0))(@traversable/registry@0.0.48)(@traversable/schema@0.0.61(@traversable/registry@0.0.48)) + version: 0.0.52(@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0))(@traversable/registry@0.0.49)(@traversable/schema@0.0.62(@traversable/registry@0.0.49)) '@traversable/schema-to-json-schema': specifier: latest - version: 0.0.48(@traversable/registry@0.0.48)(@traversable/schema@0.0.61(@traversable/registry@0.0.48)) + version: 0.0.49(@traversable/registry@0.0.49)(@traversable/schema@0.0.62(@traversable/registry@0.0.49)) '@traversable/schema-to-string': specifier: latest - version: 0.0.49(@traversable/registry@0.0.48)(@traversable/schema@0.0.61(@traversable/registry@0.0.48)) + version: 0.0.50(@traversable/registry@0.0.49)(@traversable/schema@0.0.62(@traversable/registry@0.0.49)) '@traversable/schema-to-validator': specifier: latest - version: 0.0.18(@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0))(@traversable/registry@0.0.48)(@traversable/schema@0.0.61(@traversable/registry@0.0.48)) + version: 0.0.19(@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0))(@traversable/registry@0.0.49)(@traversable/schema@0.0.62(@traversable/registry@0.0.49)) '@traversable/typebox': specifier: latest - version: 0.0.30(@sinclair/typebox@0.34.41)(fast-check@4.3.0) + version: 0.0.31(@sinclair/typebox@0.34.41)(fast-check@4.3.0) '@traversable/typebox-test': specifier: latest - version: 0.0.19(@sinclair/typebox@0.34.41)(@traversable/registry@0.0.48)(fast-check@4.3.0) + version: 0.0.20(@sinclair/typebox@0.34.41)(@traversable/registry@0.0.49)(fast-check@4.3.0) '@traversable/valibot': specifier: latest - version: 0.0.25(fast-check@4.3.0)(valibot@1.1.0(typescript@5.9.2)) + version: 0.0.26(fast-check@4.3.0)(valibot@1.1.0(typescript@5.9.2)) '@traversable/valibot-test': specifier: latest - version: 0.0.19(@traversable/registry@0.0.48)(@traversable/valibot-types@0.0.17(@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0))(@traversable/registry@0.0.48)(valibot@1.1.0(typescript@5.9.2)))(fast-check@4.3.0)(valibot@1.1.0(typescript@5.9.2)) + version: 0.0.20(@traversable/registry@0.0.49)(@traversable/valibot-types@0.0.18(@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0))(@traversable/registry@0.0.49)(valibot@1.1.0(typescript@5.9.2)))(fast-check@4.3.0)(valibot@1.1.0(typescript@5.9.2)) '@traversable/zod': specifier: latest - version: 0.0.52(fast-check@4.3.0)(zod@4.1.11) + version: 0.0.53(fast-check@4.3.0)(zod@4.1.11) '@traversable/zod-test': specifier: latest - version: 0.0.24(@traversable/registry@0.0.48)(fast-check@4.3.0)(zod@4.1.11) + version: 0.0.25(@traversable/registry@0.0.49)(fast-check@4.3.0)(zod@4.1.11) arktype: specifier: latest version: 2.1.22 @@ -339,6 +339,9 @@ importers: '@traversable/registry': specifier: workspace:^ version: link:../registry/dist + graphql: + specifier: ^16.11.0 + version: 16.11.0 publishDirectory: dist packages/graphql-types: @@ -1592,141 +1595,141 @@ packages: '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} - '@traversable/arktype-test@0.0.19': - resolution: {integrity: sha512-cO5Isay/bK18HPqmwWxmBBYGh6BWZYi5cJd2ScvhXjlg7YPdVEtC7qDT4UK8vvjAn54/eG2jjXa3mPj5jhwhiw==} + '@traversable/arktype-test@0.0.20': + resolution: {integrity: sha512-9CCK07+dbntEvYcwMI+j3Od6I05Q1kyP1Upafzyffmk63WQqA1BnNZV0rPs8FzIsIsPo1LxfFOdK6bJzmzrtOQ==} peerDependencies: - '@traversable/registry': ^0.0.48 + '@traversable/registry': ^0.0.49 arktype: '2' fast-check: 3 - 4 - '@traversable/arktype@0.0.25': - resolution: {integrity: sha512-RJ0N9WJo8Ga2SIxNKgAXizWvCAM4GOmSWWD6MdxEEHAMnIJFibWWRsdQIGyEijn8mawuEDvL4Xa2H3swbpiRNA==} + '@traversable/arktype@0.0.26': + resolution: {integrity: sha512-KJau5blnVRkHGt3Zj8fdGslsYe8bpQptGpQXXY9L1Snkau1fRYSCJrv95eK22tTfqOwLi8nw41gQGuuzzPe3SA==} peerDependencies: arktype: '2' - '@traversable/json-schema-test@0.0.24': - resolution: {integrity: sha512-llo8aVnsuvPxvx4cwosS9sE93njFJ/dQ2b9ErXXKba/RQ+syt+ZBgDZgGxOqi9eb3BHQFi8c6LPudEqf5SvWNA==} + '@traversable/json-schema-test@0.0.25': + resolution: {integrity: sha512-R0VJZS5StUfTmm0oXiNPOSpguTHKKi/jr9N/FnfwTTr4taZ2KPms+/Glupdvkwfw00P3prroJ10CLYRr4qyoeQ==} - '@traversable/json-schema-types@0.0.23': - resolution: {integrity: sha512-9kasF0AfhRciwwRVRCYVr6Ci2KNGP5YoOEJGM3VyObyNhos1ez9lTPbxKu4UtCEA5oDwPDETQEk3TJMY+/LMHw==} + '@traversable/json-schema-types@0.0.24': + resolution: {integrity: sha512-frnXjhDC6GaFjc1qs3yY35XODRWgIxXxohN2ETagYLK4lzy769473m+bVXZbKPCZ/iZTZL1hY3+YVjgdoke+Xw==} peerDependencies: - '@traversable/json': ^0.0.51 - '@traversable/registry': ^0.0.48 + '@traversable/json': ^0.0.52 + '@traversable/registry': ^0.0.49 - '@traversable/json-schema@0.0.25': - resolution: {integrity: sha512-wvyoDXJcMQa6sPawDki1u6byHJFuE+XK5yytBpoxgnccsU71XtayE+UMX3cmbq3Gz/iGxIBE0if66hO9GBayPQ==} + '@traversable/json-schema@0.0.26': + resolution: {integrity: sha512-WimdJ92KJ+E2BDVL1BfmeQ79guNnRTAY0izCqugcIwgs7FeNbgbgFIP2/aPEshneuN2Oaw8IJJEIV5xoaT2piQ==} - '@traversable/json@0.0.51': - resolution: {integrity: sha512-+VjT2NAGSK1VFZpGR/AH9oB20G6cVxNjLDT4vejZcgkALtxXKmPStK7H/2tcy8Evp5PFCubWJEyNNI/0L/Jb4w==} + '@traversable/json@0.0.52': + resolution: {integrity: sha512-QddhvjiJULZw6z/OhHE0YchZfKr1LRvRvs5dZqGpYb0UB3GpWSn/2fIJjYTOCT0pG95QhuxexPpKky6eihFBLA==} peerDependencies: - '@traversable/registry': ^0.0.48 + '@traversable/registry': ^0.0.49 fast-check: 3 - 4 peerDependenciesMeta: fast-check: optional: true - '@traversable/registry@0.0.48': - resolution: {integrity: sha512-7JCMNR5c9Lund0mIowkN30wf+07Q5WZtRU9vXxnVtou8+bZ38dXpVIvGsW8MQOrLjNIPsnRKczRt48i6QHiZfQ==} + '@traversable/registry@0.0.49': + resolution: {integrity: sha512-uof62gHhyf5MebDkjQMeJj1yIAQkCbCcJYl848b4A+jFnUXxo2NUuHi0NIZOeG3r+XgNSF4MapDED89tFM6iLA==} - '@traversable/schema-codec@0.0.32': - resolution: {integrity: sha512-8XYlN2zq+VmqATH7GrKEYb3NxfSqcFI8AfYC29uaRI7cMps+vuWAi0Nw29fP6+tEeQNCVFq83Aqos4q9AHx2tw==} + '@traversable/schema-codec@0.0.33': + resolution: {integrity: sha512-iDpQVOuMfALm4oDS3ljMQfZLagD6aqPWN59CVV/Jbys4+uDz9vws9gcxITVb/8sIgn0uwnYJizUO2AwgFFCYtg==} peerDependencies: - '@traversable/registry': ^0.0.48 - '@traversable/schema': ^0.0.61 + '@traversable/registry': ^0.0.49 + '@traversable/schema': ^0.0.62 - '@traversable/schema-deep-equal@0.0.18': - resolution: {integrity: sha512-vKOXSnVruNPwai0BVm5QgoarkKYEx7lGToC9VU/6Jq/sd/oupK/sX9iJPjIK0FvaVayTHbUMdQQPiwEMCzhcQw==} + '@traversable/schema-deep-equal@0.0.19': + resolution: {integrity: sha512-bkQNSPq4UOealtfkQRiAd1XY+/ey33TvKZagZywXCMRZb8ZAsxSpGU+j3T7+fQUZetFjGWyx5umqX3xm6ZoHeg==} peerDependencies: - '@traversable/json': ^0.0.51 - '@traversable/registry': ^0.0.48 - '@traversable/schema': ^0.0.61 + '@traversable/json': ^0.0.52 + '@traversable/registry': ^0.0.49 + '@traversable/schema': ^0.0.62 - '@traversable/schema-seed@0.0.51': - resolution: {integrity: sha512-UPDQsYh3PQIn1fX5bhtEqxSFwS5UUl5xJAw9vpNT6Zhpo1Wv9legHhbjvtqMUahPXdUUM14Hyo0TRifnagTjeQ==} + '@traversable/schema-seed@0.0.52': + resolution: {integrity: sha512-UF8xvfi8jZRRj3WQE4zymvuEd+WlqkzXYEaDh+nMV5RptO/NToa2vBmA+tKvtS8IkOGirznEYhgKxOuT/8FtNw==} peerDependencies: - '@traversable/json': ^0.0.51 - '@traversable/registry': ^0.0.48 - '@traversable/schema': ^0.0.61 + '@traversable/json': ^0.0.52 + '@traversable/registry': ^0.0.49 + '@traversable/schema': ^0.0.62 - '@traversable/schema-to-json-schema@0.0.48': - resolution: {integrity: sha512-qtjo/YjtcvXTGm+ow9tvgnVXOqaPjfjCMlgKM7ECrdLOW8PKybxEUikbhVjgjx1ZyGeInZo9CUg0xRj6HkXbXQ==} + '@traversable/schema-to-json-schema@0.0.49': + resolution: {integrity: sha512-BQR7nsvz5XRZ+3suNFXN8sZUsBXMXzwsYP7jTa6XgLAGa143SfLDQv1BMmKJf334LB4USgmbLpBwQG3J/riAlQ==} peerDependencies: - '@traversable/registry': ^0.0.48 - '@traversable/schema': ^0.0.61 + '@traversable/registry': ^0.0.49 + '@traversable/schema': ^0.0.62 - '@traversable/schema-to-string@0.0.49': - resolution: {integrity: sha512-p68n4ITb8gdvqYuLhcMETO/zS1XkB2m3ZqZoUuA4kc7i0B7N3yjFnv90jsr8Hq/888fODbWgqqBdZnLgFPGeIQ==} + '@traversable/schema-to-string@0.0.50': + resolution: {integrity: sha512-96wDzmSBnCGW0F3nE7qKCt+C35HtUt5dafNjBQL/qGP60DeWiLzEDwwWw6BxaGIhn/ryAx2DMIgWHvvdkUEUMw==} peerDependencies: - '@traversable/registry': ^0.0.48 - '@traversable/schema': ^0.0.61 + '@traversable/registry': ^0.0.49 + '@traversable/schema': ^0.0.62 - '@traversable/schema-to-validator@0.0.18': - resolution: {integrity: sha512-eFkdjS/sYhyJ1rU1eRftaXxGkZPVXSXh6cqCmAjuSeqq/ceASuaFeEYEqhYKtWLeOLyeL5UxnzZt/5eBahIVow==} + '@traversable/schema-to-validator@0.0.19': + resolution: {integrity: sha512-tQUWbdyVHDc5esg72op0//lhQ/QO3vXu1O6HXYkLq7mEQV4qLjlfMn4dYxlhMvZ+7nRGHVy0CmEnKrlNWbM8XQ==} peerDependencies: - '@traversable/json': ^0.0.51 - '@traversable/registry': ^0.0.48 - '@traversable/schema': ^0.0.61 + '@traversable/json': ^0.0.52 + '@traversable/registry': ^0.0.49 + '@traversable/schema': ^0.0.62 - '@traversable/schema@0.0.61': - resolution: {integrity: sha512-YreUC706WvzIanEj9il/XuUf1U077s0VmX9vLmytAeIs+TFr+s4uX5WCyi/HpqAUxmL11/BR6BpN+kLD03te4Q==} + '@traversable/schema@0.0.62': + resolution: {integrity: sha512-dQbeVj6CWDuAJj5EZdUOawK26qdUxwnKeQZ4CVBpahE/ecGkbFXXSPsonQphFhAosi0y16jn/7mWw1HpDN6jzw==} peerDependencies: - '@traversable/registry': ^0.0.48 + '@traversable/registry': ^0.0.49 - '@traversable/typebox-test@0.0.19': - resolution: {integrity: sha512-4wnyL7s2EGFxo44XhYecMQBJJ/3Xwl6qlkcw+y6FrNMNoPM0+7/0tc6wPLG3LiGstimoOvZ1WRV1+S/9wO6zlw==} + '@traversable/typebox-test@0.0.20': + resolution: {integrity: sha512-NP+p+2R8RKGY1kO0gRI52KLnR61uquSce1lC4MQWZyQDZ5aLNVBy95DNAXBAd10X1H5VW+/zsZ0WpnhZBr+gvw==} peerDependencies: '@sinclair/typebox': '0.34' - '@traversable/registry': ^0.0.48 + '@traversable/registry': ^0.0.49 fast-check: 3 - 4 - '@traversable/typebox-types@0.0.21': - resolution: {integrity: sha512-9G5wUpjw2PgEV8MXLcQs44gvgjm9PZ//Q1k2TMGaUeoB9e8i1p/v1Hs8TSzlPU2kuYLMJgXocCIxeHR3a6+SDw==} + '@traversable/typebox-types@0.0.22': + resolution: {integrity: sha512-NgQLmirXesoPcinNtJFmoP5ODsiVeBYU6h5XS0PlaIsZvHbFsgTUv9dQmaqd6TzTXDL5Slt5QnPLRtB5b8kMtg==} peerDependencies: '@sinclair/typebox': 0.34.40 - '@traversable/json': ^0.0.51 - '@traversable/registry': ^0.0.48 + '@traversable/json': ^0.0.52 + '@traversable/registry': ^0.0.49 - '@traversable/typebox@0.0.30': - resolution: {integrity: sha512-W7VCsd79+eVqEwhD4KN+EZrq2MQkKGhx2DqOFHuuFQQNjRkRBkwh8/J7ejd1fZ3v4cz2ToKXDrvx4qheNiUgaA==} + '@traversable/typebox@0.0.31': + resolution: {integrity: sha512-EyJPK/1mUdGnhg8SDPEarsquwFAQzlRYYyg5cxaJ6b7YFgX9jj5tSidxoAe9mEs1LEHFj26lPaM9G7neHp/9/w==} peerDependencies: '@sinclair/typebox': '0.34' - '@traversable/valibot-test@0.0.19': - resolution: {integrity: sha512-AGvykheUSs8nUN518wG/l2N2w684sUZz5dY0r1rn+yUUT/26y3f2vlA+6rszRWe7M7SICDAIKrxUR1Cy71q7ow==} + '@traversable/valibot-test@0.0.20': + resolution: {integrity: sha512-fdI2MLLPZ33NCifvjheGKPcixjrjYXYEm6CMebHU7W8JP2Jkj8WD/cp0bjCys3mzH/jlauQ0uYv9jre0ASjRYQ==} peerDependencies: - '@traversable/registry': ^0.0.48 - '@traversable/valibot-types': ^0.0.17 + '@traversable/registry': ^0.0.49 + '@traversable/valibot-types': ^0.0.18 fast-check: 3 - 4 valibot: '1' - '@traversable/valibot-types@0.0.17': - resolution: {integrity: sha512-DFr1O+qLrk+DcBT4fO2iICP7RT9UdwfQHhTVg7hq9OC4d22sPfgp6iPRIRS2yjTEMlEdsgo3A53gVMfrcx14qg==} + '@traversable/valibot-types@0.0.18': + resolution: {integrity: sha512-zMA18Op3J8hrzVtNlKTl2PLJ04g/PgxVnFX1mcoM4fbNxT8oAGYLjyyORdCb2icaunwvo84E6C0+LuQWMbeSKA==} peerDependencies: - '@traversable/json': ^0.0.51 - '@traversable/registry': ^0.0.48 + '@traversable/json': ^0.0.52 + '@traversable/registry': ^0.0.49 valibot: '1' - '@traversable/valibot@0.0.25': - resolution: {integrity: sha512-1ckh/bIVDF5bj5B5ZL1buTSLn/dTa5avBUYwb9TDCWLT5vjl/qPMPtXk+MAVV+eEballLdsbbXd0s9xxTFl46A==} + '@traversable/valibot@0.0.26': + resolution: {integrity: sha512-LtrJy+bpbjg1r/aFYcV38NAV6SnYOWVa4Z7n8oqCnlnEJEiCW8vIlEwpTFTRDLLa3O5GRZnvdBUQaoPsm+59vw==} peerDependencies: valibot: '1' - '@traversable/zod-test@0.0.24': - resolution: {integrity: sha512-1uwQqn1Eaq0PIoPG/fpXHnTMQ78BV3Zdm/tJnn8dkq1lbkuV/9tu3JWCcmXjcX/uT5iec/SyVSzHRCwXE8j5/Q==} + '@traversable/zod-test@0.0.25': + resolution: {integrity: sha512-19318RSW8yn07qQ5dZJmZnMtqh2iLW4n9Iv+N6HgwOJ4EturbkX5ZYlJDODHDAP8gI9MOF9+JoC50eIALg0Syg==} peerDependencies: - '@traversable/registry': ^0.0.48 + '@traversable/registry': ^0.0.49 fast-check: 3 - 4 zod: '4' - '@traversable/zod-types@0.0.27': - resolution: {integrity: sha512-AjQ3NF/GlHqCUoJKhyMaBLsZtvs7WJBmgaYARm+Q986E1nrG5bfJtqc6t6znTGfTn97DRGGo+s/jqgk7fpH2CA==} + '@traversable/zod-types@0.0.28': + resolution: {integrity: sha512-dV4Cm00k4xdDO4l60ttDGgY+7xFTJdfLoMvVoWvkJ8ijAeirnVe4+xMIiT6H27rdjqjnUUCx6TtVqt8E6M5Vqg==} peerDependencies: - '@traversable/json': ^0.0.51 - '@traversable/registry': ^0.0.48 + '@traversable/json': ^0.0.52 + '@traversable/registry': ^0.0.49 zod: '4' - '@traversable/zod@0.0.52': - resolution: {integrity: sha512-v+rqeJLtWP1YnOLdBkn4vN6zvp67wxtHzSyYHmnjHpsm0wHyqQXunhH8j4Sql7RHXHckJ1+z+/gYgyGLIjpwNg==} + '@traversable/zod@0.0.53': + resolution: {integrity: sha512-K6eu7YMCIwvU8DLy9pt+BBG02BEq8MavfBPX6CS08/xDX0fZ9w2zxtdT3ZvMuneCgcn28yqJGM2kznKWPPXmOA==} peerDependencies: zod: '4' @@ -4593,148 +4596,148 @@ snapshots: '@standard-schema/spec@1.0.0': {} - '@traversable/arktype-test@0.0.19(@traversable/registry@0.0.48)(arktype@2.1.22)(fast-check@4.3.0)': + '@traversable/arktype-test@0.0.20(@traversable/registry@0.0.49)(arktype@2.1.22)(fast-check@4.3.0)': dependencies: - '@traversable/registry': 0.0.48 + '@traversable/registry': 0.0.49 arktype: 2.1.22 fast-check: 4.3.0 - '@traversable/arktype@0.0.25(@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0))(arktype@2.1.22)(prettier@3.6.2)': + '@traversable/arktype@0.0.26(@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0))(arktype@2.1.22)(prettier@3.6.2)': dependencies: - '@traversable/json-schema': 0.0.25(@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0))(prettier@3.6.2) - '@traversable/registry': 0.0.48 + '@traversable/json-schema': 0.0.26(@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0))(prettier@3.6.2) + '@traversable/registry': 0.0.49 arktype: 2.1.22 transitivePeerDependencies: - '@traversable/json' - prettier - '@traversable/json-schema-test@0.0.24(fast-check@4.3.0)': + '@traversable/json-schema-test@0.0.25(fast-check@4.3.0)': dependencies: - '@traversable/json': 0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0) - '@traversable/json-schema-types': 0.0.23(@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0))(@traversable/registry@0.0.48) - '@traversable/registry': 0.0.48 + '@traversable/json': 0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0) + '@traversable/json-schema-types': 0.0.24(@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0))(@traversable/registry@0.0.49) + '@traversable/registry': 0.0.49 transitivePeerDependencies: - fast-check - '@traversable/json-schema-types@0.0.23(@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0))(@traversable/registry@0.0.48)': + '@traversable/json-schema-types@0.0.24(@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0))(@traversable/registry@0.0.49)': dependencies: - '@traversable/json': 0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0) - '@traversable/registry': 0.0.48 + '@traversable/json': 0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0) + '@traversable/registry': 0.0.49 - '@traversable/json-schema@0.0.25(@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0))(prettier@3.6.2)': + '@traversable/json-schema@0.0.26(@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0))(prettier@3.6.2)': dependencies: '@prettier/sync': 0.5.5(prettier@3.6.2) - '@traversable/json-schema-types': 0.0.23(@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0))(@traversable/registry@0.0.48) - '@traversable/registry': 0.0.48 + '@traversable/json-schema-types': 0.0.24(@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0))(@traversable/registry@0.0.49) + '@traversable/registry': 0.0.49 transitivePeerDependencies: - '@traversable/json' - prettier - '@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0)': + '@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0)': dependencies: - '@traversable/registry': 0.0.48 + '@traversable/registry': 0.0.49 optionalDependencies: fast-check: 4.3.0 - '@traversable/registry@0.0.48': {} + '@traversable/registry@0.0.49': {} - '@traversable/schema-codec@0.0.32(@traversable/registry@0.0.48)(@traversable/schema@0.0.61(@traversable/registry@0.0.48))': + '@traversable/schema-codec@0.0.33(@traversable/registry@0.0.49)(@traversable/schema@0.0.62(@traversable/registry@0.0.49))': dependencies: - '@traversable/registry': 0.0.48 - '@traversable/schema': 0.0.61(@traversable/registry@0.0.48) + '@traversable/registry': 0.0.49 + '@traversable/schema': 0.0.62(@traversable/registry@0.0.49) - '@traversable/schema-deep-equal@0.0.18(@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0))(@traversable/registry@0.0.48)(@traversable/schema@0.0.61(@traversable/registry@0.0.48))': + '@traversable/schema-deep-equal@0.0.19(@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0))(@traversable/registry@0.0.49)(@traversable/schema@0.0.62(@traversable/registry@0.0.49))': dependencies: - '@traversable/json': 0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0) - '@traversable/registry': 0.0.48 - '@traversable/schema': 0.0.61(@traversable/registry@0.0.48) + '@traversable/json': 0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0) + '@traversable/registry': 0.0.49 + '@traversable/schema': 0.0.62(@traversable/registry@0.0.49) - '@traversable/schema-seed@0.0.51(@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0))(@traversable/registry@0.0.48)(@traversable/schema@0.0.61(@traversable/registry@0.0.48))': + '@traversable/schema-seed@0.0.52(@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0))(@traversable/registry@0.0.49)(@traversable/schema@0.0.62(@traversable/registry@0.0.49))': dependencies: - '@traversable/json': 0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0) - '@traversable/registry': 0.0.48 - '@traversable/schema': 0.0.61(@traversable/registry@0.0.48) + '@traversable/json': 0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0) + '@traversable/registry': 0.0.49 + '@traversable/schema': 0.0.62(@traversable/registry@0.0.49) - '@traversable/schema-to-json-schema@0.0.48(@traversable/registry@0.0.48)(@traversable/schema@0.0.61(@traversable/registry@0.0.48))': + '@traversable/schema-to-json-schema@0.0.49(@traversable/registry@0.0.49)(@traversable/schema@0.0.62(@traversable/registry@0.0.49))': dependencies: - '@traversable/registry': 0.0.48 - '@traversable/schema': 0.0.61(@traversable/registry@0.0.48) + '@traversable/registry': 0.0.49 + '@traversable/schema': 0.0.62(@traversable/registry@0.0.49) - '@traversable/schema-to-string@0.0.49(@traversable/registry@0.0.48)(@traversable/schema@0.0.61(@traversable/registry@0.0.48))': + '@traversable/schema-to-string@0.0.50(@traversable/registry@0.0.49)(@traversable/schema@0.0.62(@traversable/registry@0.0.49))': dependencies: - '@traversable/registry': 0.0.48 - '@traversable/schema': 0.0.61(@traversable/registry@0.0.48) + '@traversable/registry': 0.0.49 + '@traversable/schema': 0.0.62(@traversable/registry@0.0.49) - '@traversable/schema-to-validator@0.0.18(@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0))(@traversable/registry@0.0.48)(@traversable/schema@0.0.61(@traversable/registry@0.0.48))': + '@traversable/schema-to-validator@0.0.19(@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0))(@traversable/registry@0.0.49)(@traversable/schema@0.0.62(@traversable/registry@0.0.49))': dependencies: - '@traversable/json': 0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0) - '@traversable/registry': 0.0.48 - '@traversable/schema': 0.0.61(@traversable/registry@0.0.48) + '@traversable/json': 0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0) + '@traversable/registry': 0.0.49 + '@traversable/schema': 0.0.62(@traversable/registry@0.0.49) - '@traversable/schema@0.0.61(@traversable/registry@0.0.48)': + '@traversable/schema@0.0.62(@traversable/registry@0.0.49)': dependencies: - '@traversable/registry': 0.0.48 + '@traversable/registry': 0.0.49 - '@traversable/typebox-test@0.0.19(@sinclair/typebox@0.34.41)(@traversable/registry@0.0.48)(fast-check@4.3.0)': + '@traversable/typebox-test@0.0.20(@sinclair/typebox@0.34.41)(@traversable/registry@0.0.49)(fast-check@4.3.0)': dependencies: '@sinclair/typebox': 0.34.41 - '@traversable/registry': 0.0.48 + '@traversable/registry': 0.0.49 fast-check: 4.3.0 - '@traversable/typebox-types@0.0.21(@sinclair/typebox@0.34.41)(@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0))(@traversable/registry@0.0.48)': + '@traversable/typebox-types@0.0.22(@sinclair/typebox@0.34.41)(@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0))(@traversable/registry@0.0.49)': dependencies: '@sinclair/typebox': 0.34.41 - '@traversable/json': 0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0) - '@traversable/registry': 0.0.48 + '@traversable/json': 0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0) + '@traversable/registry': 0.0.49 - '@traversable/typebox@0.0.30(@sinclair/typebox@0.34.41)(fast-check@4.3.0)': + '@traversable/typebox@0.0.31(@sinclair/typebox@0.34.41)(fast-check@4.3.0)': dependencies: '@sinclair/typebox': 0.34.41 - '@traversable/json': 0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0) - '@traversable/registry': 0.0.48 - '@traversable/typebox-types': 0.0.21(@sinclair/typebox@0.34.41)(@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0))(@traversable/registry@0.0.48) + '@traversable/json': 0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0) + '@traversable/registry': 0.0.49 + '@traversable/typebox-types': 0.0.22(@sinclair/typebox@0.34.41)(@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0))(@traversable/registry@0.0.49) transitivePeerDependencies: - fast-check - '@traversable/valibot-test@0.0.19(@traversable/registry@0.0.48)(@traversable/valibot-types@0.0.17(@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0))(@traversable/registry@0.0.48)(valibot@1.1.0(typescript@5.9.2)))(fast-check@4.3.0)(valibot@1.1.0(typescript@5.9.2))': + '@traversable/valibot-test@0.0.20(@traversable/registry@0.0.49)(@traversable/valibot-types@0.0.18(@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0))(@traversable/registry@0.0.49)(valibot@1.1.0(typescript@5.9.2)))(fast-check@4.3.0)(valibot@1.1.0(typescript@5.9.2))': dependencies: - '@traversable/registry': 0.0.48 - '@traversable/valibot-types': 0.0.17(@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0))(@traversable/registry@0.0.48)(valibot@1.1.0(typescript@5.9.2)) + '@traversable/registry': 0.0.49 + '@traversable/valibot-types': 0.0.18(@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0))(@traversable/registry@0.0.49)(valibot@1.1.0(typescript@5.9.2)) fast-check: 4.3.0 valibot: 1.1.0(typescript@5.9.2) - '@traversable/valibot-types@0.0.17(@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0))(@traversable/registry@0.0.48)(valibot@1.1.0(typescript@5.9.2))': + '@traversable/valibot-types@0.0.18(@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0))(@traversable/registry@0.0.49)(valibot@1.1.0(typescript@5.9.2))': dependencies: - '@traversable/json': 0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0) - '@traversable/registry': 0.0.48 + '@traversable/json': 0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0) + '@traversable/registry': 0.0.49 valibot: 1.1.0(typescript@5.9.2) - '@traversable/valibot@0.0.25(fast-check@4.3.0)(valibot@1.1.0(typescript@5.9.2))': + '@traversable/valibot@0.0.26(fast-check@4.3.0)(valibot@1.1.0(typescript@5.9.2))': dependencies: - '@traversable/json': 0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0) - '@traversable/registry': 0.0.48 - '@traversable/valibot-types': 0.0.17(@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0))(@traversable/registry@0.0.48)(valibot@1.1.0(typescript@5.9.2)) + '@traversable/json': 0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0) + '@traversable/registry': 0.0.49 + '@traversable/valibot-types': 0.0.18(@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0))(@traversable/registry@0.0.49)(valibot@1.1.0(typescript@5.9.2)) valibot: 1.1.0(typescript@5.9.2) transitivePeerDependencies: - fast-check - '@traversable/zod-test@0.0.24(@traversable/registry@0.0.48)(fast-check@4.3.0)(zod@4.1.11)': + '@traversable/zod-test@0.0.25(@traversable/registry@0.0.49)(fast-check@4.3.0)(zod@4.1.11)': dependencies: - '@traversable/registry': 0.0.48 + '@traversable/registry': 0.0.49 fast-check: 4.3.0 zod: 4.1.11 - '@traversable/zod-types@0.0.27(@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0))(@traversable/registry@0.0.48)(zod@4.1.11)': + '@traversable/zod-types@0.0.28(@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0))(@traversable/registry@0.0.49)(zod@4.1.11)': dependencies: - '@traversable/json': 0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0) - '@traversable/registry': 0.0.48 + '@traversable/json': 0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0) + '@traversable/registry': 0.0.49 zod: 4.1.11 - '@traversable/zod@0.0.52(fast-check@4.3.0)(zod@4.1.11)': + '@traversable/zod@0.0.53(fast-check@4.3.0)(zod@4.1.11)': dependencies: - '@traversable/json': 0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0) - '@traversable/registry': 0.0.48 - '@traversable/zod-types': 0.0.27(@traversable/json@0.0.51(@traversable/registry@0.0.48)(fast-check@4.3.0))(@traversable/registry@0.0.48)(zod@4.1.11) + '@traversable/json': 0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0) + '@traversable/registry': 0.0.49 + '@traversable/zod-types': 0.0.28(@traversable/json@0.0.52(@traversable/registry@0.0.49)(fast-check@4.3.0))(@traversable/registry@0.0.49)(zod@4.1.11) zod: 4.1.11 transitivePeerDependencies: - fast-check From f29d3293af46b4d8cb961164d11514b74f0bfde2 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Fri, 26 Sep 2025 08:54:58 -0500 Subject: [PATCH 12/32] feat(graphql): adds support for Field nodes, Selection set nodes --- packages/graphql-types/src/exports.ts | 1 + packages/graphql-types/src/functor.ts | 41 +++++++++- packages/graphql-types/src/to-string.ts | 11 +-- packages/graphql-types/src/to-type.ts | 2 +- packages/graphql-types/test/to-string.test.ts | 78 +++++++++++++------ 5 files changed, 99 insertions(+), 34 deletions(-) diff --git a/packages/graphql-types/src/exports.ts b/packages/graphql-types/src/exports.ts index b834ec99..4fd6fa12 100644 --- a/packages/graphql-types/src/exports.ts +++ b/packages/graphql-types/src/exports.ts @@ -25,6 +25,7 @@ export { isEnumNode, isEnumValueNode, isFieldNode, + isFieldDefinitionNode, isFloatNode, isFloatValueNode, isFragmentDefinitionNode, diff --git a/packages/graphql-types/src/functor.ts b/packages/graphql-types/src/functor.ts index 8f11d4de..a108beb1 100644 --- a/packages/graphql-types/src/functor.ts +++ b/packages/graphql-types/src/functor.ts @@ -283,7 +283,7 @@ export declare namespace AST { loc?: Location } - interface FieldNode { + interface FieldDefinitionNode { kind: Kind.FieldDefinition name: NameNode type: T @@ -293,6 +293,16 @@ export declare namespace AST { loc?: Location } + interface FieldNode { + kind: Kind.FieldDefinition + alias: NameNode + name: NameNode + selectionSet?: T + arguments?: readonly T[] + directives?: readonly T[] + loc?: Location + } + interface ObjectNode { kind: Kind.ObjectTypeDefinition name: NameNode @@ -459,6 +469,7 @@ export declare namespace AST { | NonNullTypeNode | ListNode | FieldNode + | FieldDefinitionNode | ObjectNode | InterfaceNode | UnionNode @@ -555,6 +566,10 @@ export function isListNode(x: unknown): x is AST.ListNode { } export function isFieldNode(x: unknown): x is AST.FieldNode { + return has('kind', (kind) => kind === Kind.Field)(x) +} + +export function isFieldDefinitionNode(x: unknown): x is AST.FieldDefinitionNode { return has('kind', (kind) => kind === Kind.FieldDefinition)(x) } @@ -603,15 +618,15 @@ export function isDirectiveNode(x: unknown): x is AST.DirectiveNode { } export function isQueryOperation(x: unknown): x is AST.QueryOperation { - return has('kind', (kind) => kind === OperationType.Query)(x) + return has('operation', (op) => op === OperationType.Query)(x) } export function isMutationOperation(x: unknown): x is AST.MutationOperation { - return has('kind', (kind) => kind === OperationType.Mutation)(x) + return has('operation', (op) => op === OperationType.Mutation)(x) } export function isSubscriptionOperation(x: unknown): x is AST.SubscriptionOperation { - return has('kind', (kind) => kind === OperationType.Subscription)(x) + return has('operation', (op) => op === OperationType.Subscription)(x) } export function isDocumentNode(x: unknown): x is AST.DocumentNode { @@ -698,6 +713,15 @@ export const Functor: T.Functor.Ix = { } } case isFieldNode(x): { + const { arguments: args, directives, selectionSet, ...xs } = x + return { + ...xs, + ...args && { arguments: args.map(g) }, + ...directives && { directives: directives.map(g) }, + ...selectionSet && { selectionSet: g(selectionSet) }, + } + } + case isFieldDefinitionNode(x): { const { arguments: args, directives, ...xs } = x return { ...xs, @@ -802,6 +826,15 @@ export const Functor: T.Functor.Ix = { } } case isFieldNode(x): { + const { arguments: args, directives, selectionSet, ...xs } = x + return { + ...xs, + ...args && { arguments: args.map((_) => g(_, ix, x)) }, + ...directives && { directives: directives.map((_) => g(_, ix, x)) }, + ...selectionSet && { selectionSet: g(selectionSet, ix, x) }, + } + } + case isFieldDefinitionNode(x): { const { arguments: args, directives, ...xs } = x return { ...xs, diff --git a/packages/graphql-types/src/to-string.ts b/packages/graphql-types/src/to-string.ts index e805657c..429f1ff6 100644 --- a/packages/graphql-types/src/to-string.ts +++ b/packages/graphql-types/src/to-string.ts @@ -23,7 +23,7 @@ function valueNodeToString(x: AST.ValueNode): string { const fold = F.fold((x, _, _original) => { switch (true) { - default: return fn.exhaustive(x) + default: return x // fn.exhaustive(x) case F.isRefNode(x): return x.name.value case F.isValueNode(x): return valueNodeToString(x) case F.isSelectionSetNode(x): return `{ ${x.selections.join('\n')} }` @@ -39,7 +39,8 @@ const fold = F.fold((x, _, _original) => { case F.isUnionNode(x): return `union ${x.name.value} = ${x.types}` case F.isListNode(x): return `[${x.type}]` case F.isDirectiveNode(x): return `@${x.name.value}(${(x.arguments ?? []).join(', ')})` - case F.isFieldNode(x): return `${parseKey(x.name.value)}: ${x.type}` + case F.isFieldNode(x): return x.selectionSet ? `${parseKey(x.name.value)}: ${x.selectionSet}` : `${parseKey(x.alias.value)}: ${x.name.value}` + case F.isFieldDefinitionNode(x): return `${parseKey(x.name.value)}: ${x.type}` case F.isInputValueNode(x): return `${parseKey(x.name.value)}: ${x.type}` case F.isObjectNode(x): return `type ${x.name.value} { ${x.fields.join('\n')} }` case F.isInterfaceNode(x): return `interface ${x.name.value} { ${x.fields.join('\n')} }` @@ -47,9 +48,9 @@ const fold = F.fold((x, _, _original) => { case F.isInlineFragmentNode(x): return `...${!x.typeCondition ? '' : ` on ${x.typeCondition.name.value}`} ${x.selectionSet}` case F.isFragmentDefinitionNode(x): return `fragment ${x.name.value} on ${x.typeCondition.name.value} ${x.selectionSet}` case F.isFragmentSpreadNode(x): return `...${x.name.value}` - case F.isQueryOperation(x): return `query ${x.name?.value ?? ''}${!x.variableDefinitions ? '' : `(${x.variableDefinitions.join(', ')})`} { ${x.selectionSet} }` - case F.isMutationOperation(x): return `mutation ${x.name?.value ?? ''}${!x.variableDefinitions ? '' : `(${x.variableDefinitions.join(', ')})`} { ${x.selectionSet} }` - case F.isSubscriptionOperation(x): return `mutation ${x.name?.value ?? ''}${!x.variableDefinitions ? '' : `(${x.variableDefinitions.join(', ')})`} { ${x.selectionSet} }` + case F.isQueryOperation(x): return `query ${x.name?.value ?? ''}${!x.variableDefinitions?.length ? '' : `(${x.variableDefinitions.join(', ')})`} ${x.selectionSet}` + case F.isMutationOperation(x): return `mutation ${x.name?.value ?? ''}${!x.variableDefinitions?.length ? '' : `(${x.variableDefinitions.join(', ')})`} ${x.selectionSet}` + case F.isSubscriptionOperation(x): return `mutation ${x.name?.value ?? ''}${!x.variableDefinitions?.length ? '' : `(${x.variableDefinitions.join(', ')})`} ${x.selectionSet}` case F.isDocumentNode(x): return x.definitions.join('\n\r') } }) diff --git a/packages/graphql-types/src/to-type.ts b/packages/graphql-types/src/to-type.ts index 6d9cceef..781fed0d 100644 --- a/packages/graphql-types/src/to-type.ts +++ b/packages/graphql-types/src/to-type.ts @@ -65,7 +65,7 @@ const fold = F.fold((x, _, original) => { : `(${x.types.map((v) => JSON.stringify(v)).join(' | ')})` ) case F.isListNode(x): return `Array<${x.type.endsWith('!') ? x.type.slice(0, -1) : `${x.type} | null`}>` - case F.isFieldNode(x): { + case F.isFieldDefinitionNode(x): { const isNonNull = x.type.endsWith('!') const VALUE = isNonNull ? x.type.slice(0, -1) : x.type return `${parseKey(x.name.value)}${isNonNull ? '' : '?'}: ${VALUE}` diff --git a/packages/graphql-types/test/to-string.test.ts b/packages/graphql-types/test/to-string.test.ts index 26ed9c5c..f48e2bb2 100644 --- a/packages/graphql-types/test/to-string.test.ts +++ b/packages/graphql-types/test/to-string.test.ts @@ -3,35 +3,65 @@ import { toString } from '@traversable/graphql-types' import * as graphql from 'graphql' import prettier from '@prettier/sync' -const format = (src: string) => prettier.format( - src, - { parser: 'graphql', semi: false, printWidth: 35 } -) +// const format = (src: string) => prettier.format( +// src, +// { parser: 'graphql', semi: false, printWidth: 35 } +// ) + +const format = (src: string) => src vi.describe('〖⛳️〗‹‹‹ ❲@traversable/graphql-types❳', () => { vi.test('〖⛳️〗› ❲toString❳', () => { - vi.expect.soft(format( - toString(graphql.parse(` - type Pet { - petName: [String!] - } - type Human { - humanName: String! - pet: Pet - } - ` - )) - )).toMatchInlineSnapshot + // vi.expect.soft( + // format( + // toString( + // graphql.parse( + // format( + // `type Pet { + // petName: [String!] + // } + // type Human { + // humanName: String! + // pet: Pet + // }` + // ) + // ) + // ) + // ) + // ).toMatchInlineSnapshot + // (` + // "type Pet { + // petName: [String!] + // } + + // type Human { + // humanName: String! + // pet: Pet + // } + // " + // `) + + vi.expect.soft( + format( + toString( + graphql.parse( + format( + `type Human { + def: String + } + + query { + abc: Boolean + }` + ) + ) + ) + ) + ).toMatchInlineSnapshot (` - "type Pet { - petName: [String!] - } + "type Human { def: String } - type Human { - humanName: String! - pet: Pet - } - " + query { Boolean }" `) }) }) From 2b3368263af01fec464cfec7f42564ed48662408 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Fri, 26 Sep 2025 13:33:07 -0500 Subject: [PATCH 13/32] fix(graphql): fixes `F.isNullary` typeguard --- packages/graphql-types/src/functor.ts | 40 ++++-- packages/graphql-types/src/to-string.ts | 34 +++-- packages/graphql-types/test/to-string.test.ts | 117 ++++++++++-------- 3 files changed, 117 insertions(+), 74 deletions(-) diff --git a/packages/graphql-types/src/functor.ts b/packages/graphql-types/src/functor.ts index a108beb1..01d1e7fd 100644 --- a/packages/graphql-types/src/functor.ts +++ b/packages/graphql-types/src/functor.ts @@ -295,7 +295,7 @@ export declare namespace AST { interface FieldNode { kind: Kind.FieldDefinition - alias: NameNode + alias: NameNode | undefined name: NameNode selectionSet?: T arguments?: readonly T[] @@ -494,27 +494,33 @@ export function isScalarTypeDefinition(x: unknown): x is AST.ScalarTypeDefinitio } export function isBooleanNode(x: unknown): x is AST.Boolean { - return has('name', 'value', (value) => value === NamedType.Boolean)(x) + return has('kind', (kind) => kind === Kind.NamedType)(x) + && has('name', 'value', (value) => value === NamedType.Boolean)(x) } export function isIntNode(x: unknown): x is AST.Int { - return has('name', 'value', (value) => value === NamedType.Int)(x) + return has('kind', (kind) => kind === Kind.NamedType)(x) + && has('name', 'value', (value) => value === NamedType.Int)(x) } export function isNumberNode(x: unknown): x is AST.Number { - return has('name', 'value', (value) => value === NamedType.Number)(x) + return has('kind', (kind) => kind === Kind.NamedType)(x) + && has('name', 'value', (value) => value === NamedType.Number)(x) } export function isFloatNode(x: unknown): x is AST.Float { - return has('name', 'value', (value) => value === NamedType.Float)(x) + return has('kind', (kind) => kind === Kind.NamedType)(x) + && has('name', 'value', (value) => value === NamedType.Float)(x) } export function isStringNode(x: unknown): x is AST.String { - return has('name', 'value', (value) => value === NamedType.String)(x) + return has('kind', (kind) => kind === Kind.NamedType)(x) + && has('name', 'value', (value) => value === NamedType.String)(x) } export function isIDNode(x: unknown): x is AST.ID { - return has('name', 'value', (value) => value === NamedType.ID)(x) + return has('kind', (kind) => kind === Kind.NamedType)(x) + && has('name', 'value', (value) => value === NamedType.ID)(x) } export function isEnumNode(x: unknown): x is AST.EnumNode { @@ -812,11 +818,21 @@ export const Functor: T.Functor.Ix = { switch (true) { default: return fn.exhaustive(x) case isRefNode(x): return x - case isNullaryNode(x): return x - case isListNode(x): return { ...x, type: g(x.type, ix, x) } - case isNonNullTypeNode(x): return { ...x, type: g(x.type, ix, x) } - case isSelectionSetNode(x): return { ...x, selections: x.selections.map((_) => g(_, ix, x)) } - case isDocumentNode(x): return { ...x, definitions: x.definitions.map((_) => g(_, ix, x)) } + case isNullaryNode(x): { + return x + } + case isListNode(x): { + return { ...x, type: g(x.type, ix, x) } + } + case isNonNullTypeNode(x): { + return { ...x, type: g(x.type, ix, x) } + } + case isSelectionSetNode(x): { + return { ...x, selections: x.selections.map((_) => g(_, ix, x)) } + } + case isDocumentNode(x): { + return { ...x, definitions: x.definitions.map((_) => g(_, ix, x)) } + } case isUnionNode(x): { const { directives, ...xs } = x return { diff --git a/packages/graphql-types/src/to-string.ts b/packages/graphql-types/src/to-string.ts index 429f1ff6..e4eb2c9d 100644 --- a/packages/graphql-types/src/to-string.ts +++ b/packages/graphql-types/src/to-string.ts @@ -23,7 +23,7 @@ function valueNodeToString(x: AST.ValueNode): string { const fold = F.fold((x, _, _original) => { switch (true) { - default: return x // fn.exhaustive(x) + default: return fn.exhaustive(x) case F.isRefNode(x): return x.name.value case F.isValueNode(x): return valueNodeToString(x) case F.isSelectionSetNode(x): return `{ ${x.selections.join('\n')} }` @@ -39,19 +39,29 @@ const fold = F.fold((x, _, _original) => { case F.isUnionNode(x): return `union ${x.name.value} = ${x.types}` case F.isListNode(x): return `[${x.type}]` case F.isDirectiveNode(x): return `@${x.name.value}(${(x.arguments ?? []).join(', ')})` - case F.isFieldNode(x): return x.selectionSet ? `${parseKey(x.name.value)}: ${x.selectionSet}` : `${parseKey(x.alias.value)}: ${x.name.value}` - case F.isFieldDefinitionNode(x): return `${parseKey(x.name.value)}: ${x.type}` - case F.isInputValueNode(x): return `${parseKey(x.name.value)}: ${x.type}` - case F.isObjectNode(x): return `type ${x.name.value} { ${x.fields.join('\n')} }` + case F.isFieldNode(x): { + const KEY = x.alias?.value ?? x.name.value + const VALUE = !x.alias?.value ? '' : `: ${x.name.value}` + return x.selectionSet + ? `${KEY} ${x.selectionSet}` + : `${KEY}${VALUE}` + } + case F.isFieldDefinitionNode(x): return `${parseKey(x.name.value)}: ${x.type} ` + case F.isInputValueNode(x): return `${parseKey(x.name.value)}: ${x.type} ` + case F.isObjectNode(x): { + const IMPLEMENTS = x.interfaces.length ? ` implements ${x.interfaces.join(' & ')}` : '' + return `type ${x.name.value}${IMPLEMENTS} { ${x.fields.join('\n')} } ` + } case F.isInterfaceNode(x): return `interface ${x.name.value} { ${x.fields.join('\n')} }` - case F.isInputObjectNode(x): return `input ${x.name.value} { ${x.fields.join('\n')} }` - case F.isInlineFragmentNode(x): return `...${!x.typeCondition ? '' : ` on ${x.typeCondition.name.value}`} ${x.selectionSet}` - case F.isFragmentDefinitionNode(x): return `fragment ${x.name.value} on ${x.typeCondition.name.value} ${x.selectionSet}` - case F.isFragmentSpreadNode(x): return `...${x.name.value}` - case F.isQueryOperation(x): return `query ${x.name?.value ?? ''}${!x.variableDefinitions?.length ? '' : `(${x.variableDefinitions.join(', ')})`} ${x.selectionSet}` - case F.isMutationOperation(x): return `mutation ${x.name?.value ?? ''}${!x.variableDefinitions?.length ? '' : `(${x.variableDefinitions.join(', ')})`} ${x.selectionSet}` - case F.isSubscriptionOperation(x): return `mutation ${x.name?.value ?? ''}${!x.variableDefinitions?.length ? '' : `(${x.variableDefinitions.join(', ')})`} ${x.selectionSet}` + case F.isInputObjectNode(x): return `input ${x.name.value} { ${x.fields.join('\n')} } ` + case F.isInlineFragmentNode(x): return `...${!x.typeCondition ? '' : ` on ${x.typeCondition.name.value}`} ${x.selectionSet} ` + case F.isFragmentDefinitionNode(x): return `fragment ${x.name.value} on ${x.typeCondition.name.value} ${x.selectionSet} ` + case F.isFragmentSpreadNode(x): return `...${x.name.value} ` case F.isDocumentNode(x): return x.definitions.join('\n\r') + case F.isOperationDefinitionNode(x): { + const NAME = x.name?.value ? ` ${x.name.value} ` : '' + return `${x.operation}${NAME}${!x.variableDefinitions?.length ? '' : `(${x.variableDefinitions.join(', ')})`} ${x.selectionSet} ` + } } }) diff --git a/packages/graphql-types/test/to-string.test.ts b/packages/graphql-types/test/to-string.test.ts index f48e2bb2..c84bb60e 100644 --- a/packages/graphql-types/test/to-string.test.ts +++ b/packages/graphql-types/test/to-string.test.ts @@ -3,66 +3,83 @@ import { toString } from '@traversable/graphql-types' import * as graphql from 'graphql' import prettier from '@prettier/sync' -// const format = (src: string) => prettier.format( -// src, -// { parser: 'graphql', semi: false, printWidth: 35 } -// ) - -const format = (src: string) => src +const format = (src: string) => prettier.format( + src, + { parser: 'graphql', semi: false, printWidth: 35 } +) vi.describe('〖⛳️〗‹‹‹ ❲@traversable/graphql-types❳', () => { vi.test('〖⛳️〗› ❲toString❳', () => { - // vi.expect.soft( - // format( - // toString( - // graphql.parse( - // format( - // `type Pet { - // petName: [String!] - // } - // type Human { - // humanName: String! - // pet: Pet - // }` - // ) - // ) - // ) - // ) - // ).toMatchInlineSnapshot - // (` - // "type Pet { - // petName: [String!] - // } + vi.expect.soft(format( + toString( + graphql.parse(format(` + type Pet { + petName: [String!] + } + + type Human { + humanName: String! + pet: Pet + } + `)) + ) + )).toMatchInlineSnapshot + (` + "type Pet { + petName: [String!] + } - // type Human { - // humanName: String! - // pet: Pet - // } - // " - // `) + type Human { + humanName: String! + pet: Pet + } + " + `) - vi.expect.soft( - format( - toString( - graphql.parse( - format( - `type Human { - def: String - } - - query { - abc: Boolean - }` - ) - ) + vi.expect.soft(format( + toString( + graphql.parse( + format(` + query { + abc: Boolean + } + `) ) ) - ).toMatchInlineSnapshot + )).toMatchInlineSnapshot (` - "type Human { def: String } + "query { + abc: Boolean + } + " + `) - query { Boolean }" + vi.expect.soft(format( + toString( + graphql.parse( + format(` + fragment comparisonFields on Character { + name + appearsIn + friends { + name + } + } + `) + ) + ) + )).toMatchInlineSnapshot + (` + "fragment comparisonFields on Character { + name + appearsIn + friends { + name + } + } + " `) + }) }) From e15b876fc58e2c29f000e95141ec1a4061f72517 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 27 Sep 2025 23:44:21 -0500 Subject: [PATCH 14/32] feat(graphql,graphql-types,graphql-test): sorts graph topologically --- .../src/__generated__/__manifest__.ts | 19 +- packages/graphql-test/src/generator-bounds.ts | 32 +- packages/graphql-test/src/generator-seed.ts | 23 +- packages/graphql-test/src/generator.ts | 8 +- .../src/__generated__/__manifest__.ts | 10 +- packages/graphql-types/src/exports.ts | 33 +- packages/graphql-types/src/functor.ts | 1166 ++++++++++------- packages/graphql-types/src/to-string.ts | 116 +- packages/graphql-types/src/to-type.ts | 75 +- packages/graphql-types/test/to-string.test.ts | 773 ++++++++++- packages/graphql-types/test/to-type.test.ts | 3 + packages/graphql/package.json | 9 + .../graphql/src/__generated__/__manifest__.ts | 9 + packages/registry/src/topological-sort.ts | 131 ++ 14 files changed, 1791 insertions(+), 616 deletions(-) create mode 100644 packages/registry/src/topological-sort.ts diff --git a/packages/graphql-test/src/__generated__/__manifest__.ts b/packages/graphql-test/src/__generated__/__manifest__.ts index c578a8d7..273f265f 100644 --- a/packages/graphql-test/src/__generated__/__manifest__.ts +++ b/packages/graphql-test/src/__generated__/__manifest__.ts @@ -15,8 +15,12 @@ export default { "email": "ahrjarrett@gmail.com" }, "@traversable": { - "generateExports": { "include": ["**/*.ts"] }, - "generateIndex": { "include": ["**/*.ts"] } + "generateExports": { + "include": ["**/*.ts"] + }, + "generateIndex": { + "include": ["**/*.ts"] + } }, "publishConfig": { "access": "public", @@ -37,10 +41,17 @@ export default { }, "peerDependencies": { "@traversable/graphql-types": "workspace:^", - "@traversable/registry": "workspace:^" + "@traversable/registry": "workspace:^", + "graphql": ">= 16" + }, + "peerDependenciesMeta": { + "@traversable/graphql-types": { "optional": false }, + "@traversable/registry": { "optional": false }, + "graphql": { "optional": true } }, "devDependencies": { "@traversable/graphql-types": "workspace:^", - "@traversable/registry": "workspace:^" + "@traversable/registry": "workspace:^", + "graphql": "^16.11.0" } } as const \ No newline at end of file diff --git a/packages/graphql-test/src/generator-bounds.ts b/packages/graphql-test/src/generator-bounds.ts index 1fa992f9..15bda141 100644 --- a/packages/graphql-test/src/generator-bounds.ts +++ b/packages/graphql-test/src/generator-bounds.ts @@ -1,7 +1,11 @@ import * as fc from 'fast-check' - -import type { newtype } from '@traversable/registry' -import { fn, Number_isFinite, Number_isNatural, Number_isSafeInteger, Object_is } from '@traversable/registry' +import { + fn, + Number_isFinite, + Number_isNatural, + Number_isSafeInteger, + Object_is, +} from '@traversable/registry' /** @internal */ const nullable = (model: fc.Arbitrary) => fc.oneof(fc.constant(null), fc.constant(null), model) @@ -68,13 +72,11 @@ const clampArrayMax = clampMax(defaults.array[0], defaults.array[1], Number_isNa export const makeInclusiveBounds = (model: fc.Arbitrary) => ({ minimum: model, maximum: model }) export { Bounds_int as int } -interface Bounds_int extends newtype<[ +type Bounds_int = [ minimum: number | null, maximum: number | null, multipleOf: number | null, -]> {} - - +] const Bounds_int : (model: fc.Arbitrary) => fc.Arbitrary @@ -86,11 +88,11 @@ const Bounds_int ]) export { Bounds_bigint as bigint } -interface Bounds_bigint extends newtype<[ +type Bounds_bigint = [ minimum: bigint | null, maximum: bigint | null, multipleOf: bigint | null, -]> {} +] const Bounds_bigint : (model: fc.Arbitrary) => fc.Arbitrary @@ -101,10 +103,10 @@ const Bounds_bigint ]) export { Bounds_string as string } -interface Bounds_string extends newtype<[ +type Bounds_string = [ minLength: number | null, maxLength: number | null, -]> {} +] const Bounds_string : (model: fc.Arbitrary) => fc.Arbitrary @@ -116,13 +118,13 @@ const Bounds_string ) export { Bounds_number as number } -interface Bounds_number extends newtype<[ +type Bounds_number = [ minimum: number | null, maximum: number | null, multipleOf: number | null, exclusiveMinimum: boolean, exclusiveMaximum: boolean, -]> {} +] const deltaIsSubEpsilon = (x: number, y: number) => Math.abs(x - y) < Number.EPSILON @@ -155,10 +157,10 @@ const Bounds_number ) export { Bounds_array as array } -interface Bounds_array extends newtype<[ +type Bounds_array = [ minLength: number | null, maxLength: number | null, -]> {} +] const Bounds_array : (model: fc.Arbitrary) => fc.Arbitrary diff --git a/packages/graphql-test/src/generator-seed.ts b/packages/graphql-test/src/generator-seed.ts index aa3db170..cc0119af 100644 --- a/packages/graphql-test/src/generator-seed.ts +++ b/packages/graphql-test/src/generator-seed.ts @@ -1,7 +1,7 @@ import * as gql from 'graphql' import type * as T from '@traversable/registry' import { fn, Object_keys } from '@traversable/registry' -import type { AnyTag } from '@traversable/graphql-types' +import type { Kind, NamedType } from '@traversable/graphql-types' import * as Bounds from './generator-bounds.js' @@ -13,14 +13,17 @@ export function invert(x: Record) { export type Tag = byTag[keyof byTag] export type byTag = typeof byTag export const byTag = { - // any: 10, + Null: 10, Boolean: 15, + Number: 20, + String: 25, + // date: 20, // file: 25, // blob: 27, // nan: 30, // never: 35, - NullValue: 40, + // symbol: 45, // undefined: 50, // unknown: 55, @@ -28,14 +31,14 @@ export const byTag = { // function: 70, // instance: 80, // bigint: 150, - Number: 200, - String: 250, - EnumValue: 500, - EnumValueDefinition: 550, - ListType: 1000, + // EnumValue: 500, + // EnumValueDefinition: 550, + ListType: 100, + + Document: 1000, // non_optional: 1500, // nullable: 2000, - NonNullType: 2100, + // NonNullType: 2100, // nullish: 2200, // non_nullish: 2300, // optional: 2500, @@ -60,7 +63,7 @@ export const byTag = { // picklist: 11_000, // /** @deprecated */ // promise: -1000, -} as const // satisfies Record +} as const // satisfies Record /** * @example diff --git a/packages/graphql-test/src/generator.ts b/packages/graphql-test/src/generator.ts index 1d7bfec0..d84e892a 100644 --- a/packages/graphql-test/src/generator.ts +++ b/packages/graphql-test/src/generator.ts @@ -1,8 +1,7 @@ import * as gql from 'graphql' import * as fc from 'fast-check' -// import type { LowerBound } from '@traversable/valibot-types' -import type { newtype, inline } from '@traversable/registry' +import type { inline } from '@traversable/registry' import { Array_isArray, fn, @@ -154,7 +153,7 @@ import { // (tie: fc.LetrecTypedTie, $: Config.byTypeName[K]): fc.Arbitrary // } -// export interface SeedMap extends newtype<{ [K in keyof Seed]: SeedBuilder }> {} +// export type SeedMap = { [K in keyof Seed]: SeedBuilder } // export const SeedMap = { // ...TerminalMap, // ...BoundableMap, @@ -287,7 +286,8 @@ import { // type Base = { [K in keyof T]: (tie: fc.LetrecLooselyTypedTie, constraints: $[K & keyof $]) => fc.Arbitrary } // type Values = never | T[Exclude] // type InferArb = S extends fc.Arbitrary ? T : never -// interface Builder extends newtype { ['*']: fc.Arbitrary>> } +// type Builder = T & BuilderStar +// interface BuilderStar { ['*']: fc.Arbitrary>> } // type BuildBuilder, Out extends {} = BuilderBase> = never | Builder // type BuilderBase, $ extends ParseOptions = ParseOptions> = never | // & ([$['root']] extends [never] ? unknown : { root: fc.Arbitrary<$['root']> }) diff --git a/packages/graphql-types/src/__generated__/__manifest__.ts b/packages/graphql-types/src/__generated__/__manifest__.ts index 90ca0075..214b1ff0 100644 --- a/packages/graphql-types/src/__generated__/__manifest__.ts +++ b/packages/graphql-types/src/__generated__/__manifest__.ts @@ -41,15 +41,11 @@ export default { }, "peerDependencies": { "@traversable/registry": "workspace:^", - "graphql": "^16.11.0" + "graphql": ">= 16" }, "peerDependenciesMeta": { - "@traversable/registry": { - "optional": false - }, - "graphql": { - "optional": false - } + "@traversable/registry": { "optional": false }, + "graphql": { "optional": true } }, "devDependencies": { "@prettier/sync": "catalog:", diff --git a/packages/graphql-types/src/exports.ts b/packages/graphql-types/src/exports.ts index 4fd6fa12..d484d5d4 100644 --- a/packages/graphql-types/src/exports.ts +++ b/packages/graphql-types/src/exports.ts @@ -1,9 +1,9 @@ export { VERSION } from './version.js' +export { toType } from './to-type.js' +export { toString } from './to-string.js' + export type { - AnyKind, - AnyNamedType, - AnyTag, AST, Index, } from './functor.js' @@ -16,46 +16,51 @@ export { NamedTypes, Tag, Tags, - defaultIndex, + createIndex, fold, + isArgumentNode, isBooleanNode, isBooleanValueNode, isDirectiveNode, isDocumentNode, - isEnumNode, + isEnumTypeDefinitionNode, + isEnumValueDefinitionNode, isEnumValueNode, - isFieldNode, isFieldDefinitionNode, + isFieldNode, isFloatNode, isFloatValueNode, isFragmentDefinitionNode, isFragmentSpreadNode, isIDNode, isInlineFragmentNode, - isInputObjectNode, - isInputValueNode, - isInterfaceNode, + isInputObjectTypeDefinitionNode, + isInputValueDefinitionNode, + isInterfaceTypeDefinitionNode, isIntNode, isIntValueNode, isListNode, isListValueNode, + isMutationOperation, isNamedTypeNode, isNonNullTypeNode, isNullaryNode, isNullValueNode, isNumberNode, - isObjectNode, + isObjectTypeDefinitionNode, isObjectValueNode, + isOperationDefinitionNode, + isOperationTypeDefinitionNode, + isQueryOperation, isRefNode, isScalarTypeDefinition, + isSchemaDefinitionNode, isSelectionSetNode, isStringNode, isStringValueNode, + isSubscriptionOperation, isUnaryNode, - isUnionNode, + isUnionTypeDefinitionNode, isValueNode, isVariableNode, } from './functor.js' - -export { toType } from './to-type.js' -export { toString } from './to-string.js' diff --git a/packages/graphql-types/src/functor.ts b/packages/graphql-types/src/functor.ts index 01d1e7fd..cb7fdb3e 100644 --- a/packages/graphql-types/src/functor.ts +++ b/packages/graphql-types/src/functor.ts @@ -1,103 +1,103 @@ +import type * as GQL from 'graphql' + import type * as T from '@traversable/registry' -import { fn, has } from '@traversable/registry' -import type * as gql from 'graphql' +import { fn, has, topologicalSort } from '@traversable/registry' + +export type Algebra = { + (src: AST.DocumentNode, ix?: Index): { order: string[], byName: Record T> } + (src: GQL.DocumentNode, ix?: Index): { order: string[], byName: Record T> } + (src: AST.DocumentNode, ix?: Index): { order: string[], byName: Record T> } +} /** * ## {@link Kind `Kind`} * * [Reference](https://github.com/graphql/graphql-js/blob/16.x.x/src/language/kinds.ts#L4) */ + export const Kind = { + Argument: 'Argument', + BooleanValue: 'BooleanValue', + Directive: 'Directive', + DirectiveDefinition: 'DirectiveDefinition', Document: 'Document', EnumTypeDefinition: 'EnumTypeDefinition', + EnumValue: 'EnumValue', EnumValueDefinition: 'EnumValueDefinition', + Field: 'Field', FieldDefinition: 'FieldDefinition', + FloatValue: 'FloatValue', + FragmentDefinition: 'FragmentDefinition', + FragmentSpread: 'FragmentSpread', + InlineFragment: 'InlineFragment', InputObjectTypeDefinition: 'InputObjectTypeDefinition', InputValueDefinition: 'InputValueDefinition', InterfaceTypeDefinition: 'InterfaceTypeDefinition', + IntValue: 'IntValue', ListType: 'ListType', + ListValue: 'ListValue', Name: 'Name', NamedType: 'NamedType', NonNullType: 'NonNullType', + NullValue: 'NullValue', + ObjectField: 'ObjectField', ObjectTypeDefinition: 'ObjectTypeDefinition', + ObjectValue: 'ObjectValue', OperationDefinition: 'OperationDefinition', + OperationTypeDefinition: 'OperationTypeDefinition', ScalarTypeDefinition: 'ScalarTypeDefinition', + SchemaDefinition: 'SchemaDefinition', + SchemaExtension: 'SchemaExtension', SelectionSet: 'SelectionSet', + StringValue: 'StringValue', UnionTypeDefinition: 'UnionTypeDefinition', Variable: 'Variable', VariableDefinition: 'VariableDefinition', - // - FragmentSpread: 'FragmentSpread', - InlineFragment: 'InlineFragment', - FragmentDefinition: 'FragmentDefinition', - // - Argument: 'Argument', - Directive: 'Directive', - DirectiveDefinition: 'DirectiveDefinition', - EnumValue: 'EnumValue', - Field: 'Field', - FloatValue: 'FloatValue', - StringValue: 'StringValue', - BooleanValue: 'BooleanValue', - IntValue: 'IntValue', - ListValue: 'ListValue', - NullValue: 'NullValue', - ObjectValue: 'ObjectValue', - ObjectField: 'ObjectField', - SchemaDefinition: 'SchemaDefinition', - SchemaExtension: 'SchemaExtension', - OperationTypeDefinition: 'OperationTypeDefinition', } as const +export type Kind = typeof Kinds[number] +export const Kinds = Object.values(Kind) + export declare namespace Kind { + type Argument = typeof Kind.Argument + type BooleanValue = typeof Kind.BooleanValue + type Directive = typeof Kind.Directive + type DirectiveDefinition = typeof Kind.DirectiveDefinition type Document = typeof Kind.Document type EnumTypeDefinition = typeof Kind.EnumTypeDefinition + type EnumValue = typeof Kind.EnumValue type EnumValueDefinition = typeof Kind.EnumValueDefinition + type Field = typeof Kind.Field type FieldDefinition = typeof Kind.FieldDefinition + type FloatValue = typeof Kind.FloatValue + type FragmentDefinition = typeof Kind.FragmentDefinition + type FragmentSpread = typeof Kind.FragmentSpread + type InlineFragment = typeof Kind.InlineFragment type InputObjectTypeDefinition = typeof Kind.InputObjectTypeDefinition type InputValueDefinition = typeof Kind.InputValueDefinition type InterfaceTypeDefinition = typeof Kind.InterfaceTypeDefinition + type IntValue = typeof Kind.IntValue type ListType = typeof Kind.ListType + type ListValue = typeof Kind.ListValue type Name = typeof Kind.Name type NamedType = typeof Kind.NamedType type NonNullType = typeof Kind.NonNullType + type NullValue = typeof Kind.NullValue + type ObjectField = typeof Kind.ObjectField type ObjectTypeDefinition = typeof Kind.ObjectTypeDefinition + type ObjectValue = typeof Kind.ObjectValue type OperationDefinition = typeof Kind.OperationDefinition + type OperationTypeDefinition = typeof Kind.OperationTypeDefinition type ScalarTypeDefinition = typeof Kind.ScalarTypeDefinition + type SchemaDefinition = typeof Kind.SchemaDefinition + type SchemaExtension = typeof Kind.SchemaExtension type SelectionSet = typeof Kind.SelectionSet + type StringValue = typeof Kind.StringValue type UnionTypeDefinition = typeof Kind.UnionTypeDefinition type Variable = typeof Kind.Variable type VariableDefinition = typeof Kind.VariableDefinition - // - type FragmentSpread = typeof Kind.FragmentSpread - type InlineFragment = typeof Kind.InlineFragment - type FragmentDefinition = typeof Kind.FragmentDefinition - // - type Argument = typeof Kind.Argument - type Directive = typeof Kind.Directive - type DirectiveDefinition = typeof Kind.DirectiveDefinition - type EnumValue = typeof Kind.EnumValue - type Field = typeof Kind.Field - type FloatValue = typeof Kind.FloatValue - type StringValue = typeof Kind.StringValue - type BooleanValue = typeof Kind.BooleanValue - type IntValue = typeof Kind.IntValue - type ListValue = typeof Kind.ListValue - type NullValue = typeof Kind.NullValue - type ObjectValue = typeof Kind.ObjectValue - type ObjectField = typeof Kind.ObjectField - type SchemaDefinition = typeof Kind.SchemaDefinition - type SchemaExtension = typeof Kind.SchemaExtension - type OperationTypeDefinition = typeof Kind.OperationTypeDefinition } -export const Kinds = Object.values(Kind) - -export type AnyKind = typeof Kinds[number] - -/** - * ## {@link NamedType `NamedType`} - */ export const NamedType = { Boolean: 'Boolean', Float: 'Float', @@ -105,8 +105,12 @@ export const NamedType = { Int: 'Int', Number: 'Number', String: 'String', + Null: 'Null', } as const +export type NamedType = typeof NamedTypes[number] +export const NamedTypes = Object.values(NamedType) + export declare namespace NamedType { type Boolean = typeof NamedType.Boolean type Float = typeof NamedType.Float @@ -116,421 +120,536 @@ export declare namespace NamedType { type String = typeof NamedType.String } -export const NamedTypes = Object.values(NamedType) - -export type AnyNamedType = typeof NamedTypes[number] - -export const Tag = { - ...Kind, - ...NamedType, -} - -export const Tags = Object.values(Tag) - -export type AnyTag = typeof Tags[number] - export const OperationType = { Query: 'query', Mutation: 'mutation', Subscription: 'subscription', } as const +export type OperationType = typeof OperationTypes[number] +export const OperationTypes = Object.values(OperationType) + export declare namespace OperationType { type Query = typeof OperationType.Query type Mutation = typeof OperationType.Mutation type Subscription = typeof OperationType.Subscription } +export const Tag = { ...Kind, ...NamedType } +export type Tag = typeof Tags[number] +export const Tags = Object.values(Tag) + +export type Catalog = { [Node in F as Node['kind']]: Node } + +export interface Location { + start: number + end: number +} + +export interface SelectionSetNode { + kind: Kind.SelectionSet + selections: readonly T[] + loc?: Location +} + +export interface NameNode { + kind: Kind.Name + value: Value + loc?: Location +} + +export interface NamedTypeNode { + name: NameNode + loc?: Location +} + /** - * ## {@link AST `AST`} + * ## {@link RefNode `RefNode`} + * + * A {@link RefNode `RefNode`} is a named type that is not one of the built-in types. */ -export declare namespace AST { - type Catalog = { [Node in F as Node['kind']]: Node } +export interface RefNode { + kind: Kind.NamedType + name: NameNode + loc?: Location +} - interface Location { - start: number - end: number - } +export interface ArgumentNode { + kind: Kind.Argument + loc?: Location + name: NameNode + value: T +} - interface SelectionSetNode { - kind: Kind.SelectionSet - selections: readonly T[] - loc?: Location - } +export interface DocumentNode { + kind: Kind.Document + definitions: readonly T[] + loc?: Location +} - interface NameNode { - kind: Kind.Name - value: Value - loc?: Location - } +export interface InputValueDefinitionNode { + kind: Kind.InputValueDefinition + name: NameNode + type: T + defaultValue?: T + directives?: readonly T[] + description?: StringValueNode + loc?: Location +} - interface NamedTypeNode { - name: NameNode - loc?: Location - } +export interface InputObjectTypeDefinitionNode { + kind: Kind.InputObjectTypeDefinition + name: NameNode + fields: readonly T[] + directives?: readonly T[] + description?: StringValueNode + loc?: Location +} - /** - * ## {@link RefNode `RefNode`} - * - * A {@link RefNode `RefNode`} is a named type that is not one of the built-in types. - */ - interface RefNode { - kind: Kind.NamedType - name: NameNode - loc?: Location - } +export interface VariableNode { + kind: Kind.Variable + name: NameNode + loc?: Location +} - interface DocumentNode { - kind: Kind.Document - definitions: readonly T[] - loc?: Location - } +export interface VariableDefinitionNode { + kind: Kind.VariableDefinition + variable: VariableNode + type: T + defaultValue?: T + directives?: readonly T[] + loc?: Location +} - interface InputValueNode { - kind: Kind.InputValueDefinition - name: NameNode - type: T - directives?: readonly T[] - loc?: Location - } +export interface ScalarTypeDefinitionNode { + kind: Kind.ScalarTypeDefinition + name: NameNode + directives?: T[] + description?: StringValueNode + loc?: Location +} - interface InputObjectNode { - kind: Kind.InputObjectTypeDefinition - name: NameNode - fields: readonly T[] - directives?: readonly T[] - loc?: Location - } +export interface BooleanNode { + kind: Kind.NamedType + name: NameNode + loc?: Location +} - interface VariableNode { - kind: Kind.Variable - name: NameNode - loc?: Location - } +export interface IntNode { + kind: Kind.NamedType + name: NameNode + loc?: Location +} - interface VariableDefinitionNode { - kind: Kind.VariableDefinition - variable: VariableNode - type: T - directives?: readonly T[] - loc?: Location - } +export interface NumberNode { + kind: Kind.NamedType + name: NameNode + loc?: Location +} - interface ScalarTypeDefinition { - kind: Kind.ScalarTypeDefinition - name: NameNode - loc?: Location - } +export interface FloatNode { + kind: Kind.NamedType + name: NameNode + loc?: Location +} - interface Boolean { - kind: Kind.NamedType - name: NameNode - loc?: Location - } +export interface StringNode { + kind: Kind.NamedType + name: NameNode + loc?: Location +} - interface Int { - kind: Kind.NamedType - name: NameNode - loc?: Location - } +export interface IDNode { + kind: Kind.NamedType + name: NameNode + loc?: Location +} - interface Number { - kind: Kind.NamedType - name: NameNode - loc?: Location - } +export interface EnumValueDefinitionNode { + kind: Kind.EnumValueDefinition + name: NameNode + loc?: Location +} - interface Float { - kind: Kind.NamedType - name: NameNode - loc?: Location - } +export interface EnumValueNode { + kind: Kind.EnumValue + value: string + description?: StringValueNode + loc?: Location +} - interface String { - kind: Kind.NamedType - name: NameNode - loc?: Location - } +export interface EnumTypeDefinitionNode { + kind: Kind.EnumTypeDefinition + name: NameNode + values: readonly EnumValueDefinitionNode[] + directives?: readonly T[] + description?: StringValueNode + loc?: Location +} - interface ID { - kind: Kind.NamedType - name: NameNode - loc?: Location - } +export interface NonNullTypeNode { + kind: Kind.NonNullType + type: T + loc?: Location +} - interface EnumValueNode { - kind: Kind.EnumValueDefinition - name: NameNode - loc?: Location - } +export interface ListNode { + kind: Kind.ListType + type: T + loc?: Location +} - interface EnumNode { - kind: Kind.EnumTypeDefinition - name: NameNode - values: readonly EnumValueNode[] - loc?: Location - } +export interface FieldDefinitionNode { + kind: Kind.FieldDefinition + name: NameNode + type: T + arguments?: readonly T[] + directives?: readonly T[] + description?: StringValueNode + loc?: Location +} - interface NonNullTypeNode { - kind: Kind.NonNullType - type: T - loc?: Location - } +export interface FieldNode { + kind: Kind.FieldDefinition + alias: NameNode | undefined + name: NameNode + selectionSet?: T + arguments?: readonly T[] + directives?: readonly T[] + description?: StringValueNode + loc?: Location +} - interface ListNode { - kind: Kind.ListType - type: T - loc?: Location - } +export interface ObjectTypeDefinitionNode { + kind: Kind.ObjectTypeDefinition + name: NameNode + fields: readonly T[] + interfaces: readonly T[] + directives?: readonly T[] + description?: StringValueNode + loc?: Location +} - interface FieldDefinitionNode { - kind: Kind.FieldDefinition - name: NameNode - type: T - defaultValue?: unknown - arguments?: readonly T[] - directives?: readonly T[] - loc?: Location - } +export interface InterfaceTypeDefinitionNode { + kind: Kind.InterfaceTypeDefinition + name: NameNode + fields: readonly T[] + interfaces: readonly T[] + directives?: readonly T[] + description?: StringValueNode + loc?: Location +} - interface FieldNode { - kind: Kind.FieldDefinition - alias: NameNode | undefined - name: NameNode - selectionSet?: T - arguments?: readonly T[] - directives?: readonly T[] - loc?: Location - } +export interface UnionTypeDefinitionNode { + kind: Kind.UnionTypeDefinition + name: NameNode + types: readonly T[] + directives?: readonly T[] + description?: StringValueNode + loc?: Location +} - interface ObjectNode { - kind: Kind.ObjectTypeDefinition - name: NameNode - fields: readonly T[] - interfaces: readonly T[] - directives?: readonly T[] - loc?: Location - } +export interface FragmentDefinitionNode { + kind: Kind.FragmentDefinition + name: NameNode + typeCondition: NamedTypeNode + directives?: readonly T[] + selectionSet: T + loc?: Location +} - interface InterfaceNode { - kind: Kind.InterfaceTypeDefinition - name: NameNode - fields: readonly T[] - interfaces: readonly T[] - directives?: readonly T[] - loc?: Location - } +export interface FragmentSpreadNode { + kind: Kind.FragmentSpread + name: NameNode + directives?: readonly T[] + loc?: Location +} - interface UnionNode { - kind: Kind.UnionTypeDefinition - name: NameNode - types: readonly T[] - directives?: readonly T[] - loc?: Location - } +export interface InlineFragmentNode { + kind: Kind.InlineFragment + typeCondition?: NamedTypeNode + directives?: readonly T[] + selectionSet: T + loc?: Location +} - interface FragmentDefinitionNode { - kind: Kind.FragmentDefinition - name: NameNode - typeCondition: NamedTypeNode - directives?: readonly T[] - selectionSet: T - loc?: Location - } +export interface IntValueNode { + kind: Kind.IntValue + value: string + loc?: Location +} - interface FragmentSpreadNode { - kind: Kind.FragmentSpread - name: NameNode - directives?: readonly T[] - loc?: Location - } +export interface FloatValueNode { + kind: Kind.FloatValue + value: string + loc?: Location +} - interface InlineFragmentNode { - kind: Kind.InlineFragment - typeCondition?: NamedTypeNode - directives?: readonly T[] - selectionSet: T - loc?: Location - } +export interface StringValueNode { + kind: Kind.StringValue + value: string + block: boolean + loc?: Location +} - interface IntValueNode { - kind: Kind.IntValue - value: string - loc?: Location - } +export interface BooleanValueNode { + kind: Kind.BooleanValue + value: boolean + loc?: Location +} - interface FloatValueNode { - kind: Kind.FloatValue - value: string - loc?: Location - } +export interface NullValueNode { + kind: Kind.NullValue + loc?: Location +} - interface StringValueNode { - kind: Kind.StringValue - value: string - loc?: Location - } +export interface ListValueNode { + kind: Kind.ListValue + values: readonly ValueNode[] + loc?: Location +} - interface BooleanValueNode { - kind: Kind.BooleanValue - value: boolean - loc?: Location - } +export interface ObjectValueNode { + kind: Kind.ObjectValue + fields: readonly ObjectFieldNode[] + loc?: Location +} - interface NullValueNode { - kind: Kind.NullValue - loc?: Location - } +export interface ObjectFieldNode { + kind: Kind.ObjectField + name: NameNode + value: ValueNode + loc?: Location +} - interface ListValueNode { - kind: Kind.ListValue - values: readonly ValueNode[] - loc?: Location - } +export interface DirectiveNode { + kind: Kind.Directive + name: NameNode + arguments?: readonly T[] + loc?: Location +} - interface ObjectValueNode { - kind: Kind.ObjectValue - fields: readonly ObjectFieldNode[] - loc?: Location - } +export interface DirectiveDefinitionNode { + kind: Kind.DirectiveDefinition + name: NameNode + arguments?: readonly T[] + repeatable: boolean + locations: readonly NameNode[] + description?: StringValueNode + loc?: Location +} - interface ObjectFieldNode { - kind: Kind.ObjectField - name: NameNode - value: ValueNode - loc?: Location - } +export interface QueryOperation { + kind: Kind.OperationDefinition + operation: OperationType.Query + name?: NameNode + variableDefinitions?: readonly T[] + directives?: readonly T[] + selectionSet: T + loc?: Location +} - interface DirectiveNode { - kind: Kind.Directive - name: NameNode - arguments?: readonly T[] - loc?: Location - } +export interface MutationOperation { + kind: Kind.OperationDefinition + operation: OperationType.Mutation + name?: NameNode + variableDefinitions?: readonly T[] + directives?: readonly T[] + selectionSet: T + loc?: Location +} - interface QueryOperation { - kind: Kind.OperationDefinition - operation: OperationType.Query - name?: NameNode - variableDefinitions?: readonly T[] - directives?: readonly T[] - selectionSet: T - loc?: Location - } +export interface SubscriptionOperation { + kind: Kind.OperationDefinition + operation: OperationType.Subscription + name?: NameNode + variableDefinitions?: readonly T[] + directives?: readonly T[] + selectionSet: T + loc?: Location +} - interface MutationOperation { - kind: Kind.OperationDefinition - operation: OperationType.Mutation - name?: NameNode - variableDefinitions?: readonly T[] - directives?: readonly T[] - selectionSet: T - loc?: Location - } +export interface OperationTypeDefinitionNode { + kind: Kind.OperationTypeDefinition + operation: OperationType + type: NamedTypeNode + loc?: Location +} - interface SubscriptionOperation { - kind: Kind.OperationDefinition - operation: OperationType.Subscription - name?: NameNode - variableDefinitions?: readonly T[] - directives?: readonly T[] - selectionSet: T - loc?: Location +export interface SchemaDefinitionNode { + kind: Kind.SchemaDefinition + directives?: readonly T[] + operationTypes: readonly OperationTypeDefinitionNode[] + description?: StringValueNode + loc?: Location +} + +export type ValueNode = + | VariableNode + | IntValueNode + | FloatValueNode + | StringValueNode + | BooleanValueNode + | NullValueNode + | ListValueNode + | ObjectValueNode + | EnumValueNode + +export type Nullary = + | BooleanNode + | IntNode + | NumberNode + | FloatNode + | StringNode + | IDNode + | EnumValueDefinitionNode + | ValueNode + +export type OperationDefinitionNode = + | QueryOperation + | MutationOperation + | SubscriptionOperation + +export type Unary = + | NonNullTypeNode + | ListNode + | FieldNode + | FieldDefinitionNode + | ScalarTypeDefinitionNode + | ObjectTypeDefinitionNode + | InterfaceTypeDefinitionNode + | UnionTypeDefinitionNode + | ArgumentNode + | InputValueDefinitionNode + | InputObjectTypeDefinitionNode + | SelectionSetNode + | FragmentDefinitionNode + | FragmentSpreadNode + | InlineFragmentNode + | DirectiveNode + | EnumTypeDefinitionNode + | DirectiveDefinitionNode + | SchemaDefinitionNode + | VariableDefinitionNode + +export type F = + | RefNode + | Nullary + | Unary + | OperationDefinitionNode + | DocumentNode + +/** + * ## {@link AST `AST`} + */ +export declare namespace AST { + export { + ArgumentNode, + BooleanNode, + BooleanValueNode, + DirectiveDefinitionNode, + DirectiveNode, + DocumentNode, + EnumTypeDefinitionNode, + EnumValueDefinitionNode, + EnumValueNode, + FieldDefinitionNode, + FieldNode, + FloatNode, + FloatValueNode, + FragmentDefinitionNode, + FragmentSpreadNode, + IDNode, + InlineFragmentNode, + InputObjectTypeDefinitionNode, + InputValueDefinitionNode, + InterfaceTypeDefinitionNode, + IntNode, + IntValueNode, + ListNode, + ListValueNode, + MutationOperation, + NamedTypeNode, + NonNullTypeNode, + NullValueNode, + NumberNode, + ObjectTypeDefinitionNode, + ObjectValueNode, + OperationDefinitionNode, + OperationTypeDefinitionNode, + QueryOperation, + RefNode, + ScalarTypeDefinitionNode, + SchemaDefinitionNode, + SelectionSetNode, + StringNode, + StringValueNode, + SubscriptionOperation, + UnionTypeDefinitionNode, + ValueNode, + VariableNode, + VariableDefinitionNode, + Nullary, + Unary, + F, + Catalog, } +} - type ValueNode = - | VariableNode - | IntValueNode - | FloatValueNode - | StringValueNode - | BooleanValueNode - | NullValueNode - | EnumValueNode - | ListValueNode - | ObjectValueNode - - type Nullary = - | Boolean - | Int - | Number - | Float - | String - | ID - | ScalarTypeDefinition - | EnumNode - | ValueNode - - type OperationDefinitionNode = - | QueryOperation - | MutationOperation - | SubscriptionOperation - - type Unary = - | NonNullTypeNode - | ListNode - | FieldNode - | FieldDefinitionNode - | ObjectNode - | InterfaceNode - | UnionNode - | InputValueNode - | InputObjectNode - | SelectionSetNode - | FragmentDefinitionNode - | FragmentSpreadNode - | InlineFragmentNode - | DirectiveNode - - type F = - | RefNode - | Nullary - | Unary - | OperationDefinitionNode - | DocumentNode -} - -export function isScalarTypeDefinition(x: unknown): x is AST.ScalarTypeDefinition { +export function isScalarTypeDefinition(x: unknown): x is AST.ScalarTypeDefinitionNode { return has('kind', (kind) => kind === Kind.ScalarTypeDefinition)(x) } -export function isBooleanNode(x: unknown): x is AST.Boolean { +export function isBooleanNode(x: unknown): x is AST.BooleanNode { return has('kind', (kind) => kind === Kind.NamedType)(x) && has('name', 'value', (value) => value === NamedType.Boolean)(x) } -export function isIntNode(x: unknown): x is AST.Int { +export function isIntNode(x: unknown): x is AST.IntNode { return has('kind', (kind) => kind === Kind.NamedType)(x) && has('name', 'value', (value) => value === NamedType.Int)(x) } -export function isNumberNode(x: unknown): x is AST.Number { +export function isNumberNode(x: unknown): x is AST.NumberNode { return has('kind', (kind) => kind === Kind.NamedType)(x) && has('name', 'value', (value) => value === NamedType.Number)(x) } -export function isFloatNode(x: unknown): x is AST.Float { +export function isFloatNode(x: unknown): x is AST.FloatNode { return has('kind', (kind) => kind === Kind.NamedType)(x) && has('name', 'value', (value) => value === NamedType.Float)(x) } -export function isStringNode(x: unknown): x is AST.String { +export function isStringNode(x: unknown): x is AST.StringNode { return has('kind', (kind) => kind === Kind.NamedType)(x) && has('name', 'value', (value) => value === NamedType.String)(x) } -export function isIDNode(x: unknown): x is AST.ID { +export function isIDNode(x: unknown): x is AST.IDNode { return has('kind', (kind) => kind === Kind.NamedType)(x) && has('name', 'value', (value) => value === NamedType.ID)(x) } -export function isEnumNode(x: unknown): x is AST.EnumNode { +export function isEnumTypeDefinitionNode(x: unknown): x is AST.EnumTypeDefinitionNode { return has('kind', (kind) => kind === Kind.EnumTypeDefinition)(x) } +export function isEnumValueDefinitionNode(x: unknown): x is AST.EnumValueDefinitionNode { + return has('kind', (kind) => kind === Kind.EnumValueDefinition)(x) +} + +export function isEnumValueNode(x: unknown): x is AST.EnumValueNode { + return has('kind', (kind) => kind === Kind.EnumValue)(x) +} + export function isVariableNode(x: unknown): x is AST.VariableNode { return has('kind', (kind) => kind === Kind.Variable)(x) } +export function isVariableDefinitionNode(x: unknown): x is AST.VariableDefinitionNode { + return has('kind', (kind) => kind === Kind.VariableDefinition)(x) +} + export function isBooleanValueNode(x: unknown): x is AST.BooleanValueNode { return has('kind', (kind) => kind === Kind.BooleanValue)(x) } @@ -551,10 +670,6 @@ export function isNullValueNode(x: unknown): x is AST.NullValueNode { return has('kind', (kind) => kind === Kind.NullValue)(x) } -export function isEnumValueNode(x: unknown): x is AST.EnumValueNode { - return has('kind', (kind) => kind === Kind.EnumValue)(x) -} - export function isListValueNode(x: unknown): x is AST.ListValueNode { return has('kind', (kind) => kind === Kind.ListValue)(x) } @@ -579,23 +694,27 @@ export function isFieldDefinitionNode(x: unknown): x is AST.FieldDefinitionNo return has('kind', (kind) => kind === Kind.FieldDefinition)(x) } -export function isObjectNode(x: unknown): x is AST.ObjectNode { +export function isObjectTypeDefinitionNode(x: unknown): x is AST.ObjectTypeDefinitionNode { return has('kind', (kind) => kind === Kind.ObjectTypeDefinition)(x) } -export function isInterfaceNode(x: unknown): x is AST.InterfaceNode { +export function isInterfaceTypeDefinitionNode(x: unknown): x is AST.InterfaceTypeDefinitionNode { return has('kind', (kind) => kind === Kind.InterfaceTypeDefinition)(x) } -export function isUnionNode(x: unknown): x is AST.UnionNode { +export function isUnionTypeDefinitionNode(x: unknown): x is AST.UnionTypeDefinitionNode { return has('kind', (kind) => kind === Kind.UnionTypeDefinition)(x) } -export function isInputValueNode(x: unknown): x is AST.InputValueNode { +export function isArgumentNode(x: unknown): x is AST.ArgumentNode { + return has('kind', (kind) => kind === Kind.Argument)(x) +} + +export function isInputValueDefinitionNode(x: unknown): x is AST.InputValueDefinitionNode { return has('kind', (kind) => kind === Kind.InputValueDefinition)(x) } -export function isInputObjectNode(x: unknown): x is AST.InputObjectNode { +export function isInputObjectTypeDefinitionNode(x: unknown): x is AST.InputObjectTypeDefinitionNode { return has('kind', (kind) => kind === Kind.InputObjectTypeDefinition)(x) } @@ -623,6 +742,10 @@ export function isDirectiveNode(x: unknown): x is AST.DirectiveNode { return has('kind', (kind) => kind === Kind.Directive)(x) } +export function isDirectiveDefinitionNode(x: unknown): x is AST.DirectiveDefinitionNode { + return has('kind', (kind) => kind === Kind.DirectiveDefinition)(x) +} + export function isQueryOperation(x: unknown): x is AST.QueryOperation { return has('operation', (op) => op === OperationType.Query)(x) } @@ -635,6 +758,10 @@ export function isSubscriptionOperation(x: unknown): x is AST.SubscriptionOpe return has('operation', (op) => op === OperationType.Subscription)(x) } +export function isSchemaDefinitionNode(x: unknown): x is AST.SchemaDefinitionNode { + return has('kind', (kind) => kind === Kind.SchemaDefinition)(x) +} + export function isDocumentNode(x: unknown): x is AST.DocumentNode { return has('kind', (kind) => kind === Kind.Document)(x) } @@ -655,51 +782,65 @@ export function isValueNode(x: unknown): x is AST.ValueNode { || isEnumValueNode(x) || isListValueNode(x) || isObjectValueNode(x) + || isEnumValueNode(x) + || isEnumValueDefinitionNode(x) } export function isNullaryNode(x: unknown): x is AST.Nullary { - return isScalarTypeDefinition(x) - || isBooleanNode(x) + return isBooleanNode(x) || isIntNode(x) || isNumberNode(x) || isFloatNode(x) || isStringNode(x) || isIDNode(x) - || isEnumNode(x) + || isScalarTypeDefinition(x) + || isEnumValueDefinitionNode(x) + || isOperationTypeDefinitionNode(x) } export function isUnaryNode(x: unknown): x is AST.Unary { return isNonNullTypeNode(x) || isListNode(x) - || isObjectNode(x) - || isInterfaceNode(x) - || isUnionNode(x) - || isInputObjectNode(x) + || isFieldNode(x) + || isScalarTypeDefinition(x) + || isFieldDefinitionNode(x) + || isObjectTypeDefinitionNode(x) + || isInterfaceTypeDefinitionNode(x) + || isUnionTypeDefinitionNode(x) + || isArgumentNode(x) + || isInputValueDefinitionNode(x) + || isInputObjectTypeDefinitionNode(x) + || isSelectionSetNode(x) + || isEnumTypeDefinitionNode(x) + || isFragmentDefinitionNode(x) + || isFragmentSpreadNode(x) + || isInlineFragmentNode(x) + || isDirectiveNode(x) + || isDirectiveDefinitionNode(x) + || isSchemaDefinitionNode(x) + || isVariableDefinitionNode(x) } export function isOperationDefinitionNode(x: unknown): x is AST.OperationDefinitionNode { - return isQueryOperation(x) - || isMutationOperation(x) - || isSubscriptionOperation(x) + return has('kind', (kind) => kind === Kind.OperationDefinition)(x) } -export interface Index { - isNonNull: boolean +export function isOperationTypeDefinitionNode(x: unknown): x is AST.OperationTypeDefinitionNode { + return has('kind', (kind) => kind === Kind.OperationTypeDefinition)(x) } -export const defaultIndex = { - isNonNull: false, -} satisfies Index +export interface Index { + namedTypes: Record T> +} -export interface Functor extends T.HKT { [-1]: AST.F } +export const createIndex: () => Index = () => ({ + namedTypes: Object.create(null), +}) -export declare namespace Functor { - export { - Index, - } -} +export interface Functor extends T.HKT { [-1]: AST.F } +export declare namespace Functor { export { Index } } -export const Functor: T.Functor.Ix = { +export const Functor: T.Functor.Ix = { map(g) { return (x) => { switch (true) { @@ -710,12 +851,36 @@ export const Functor: T.Functor.Ix = { case isNonNullTypeNode(x): return { ...x, type: g(x.type) } case isSelectionSetNode(x): return { ...x, selections: x.selections.map(g) } case isDocumentNode(x): return { ...x, definitions: x.definitions.map(g) } - case isUnionNode(x): { + case isArgumentNode(x): return { ...x, value: g(x.value) } + case isScalarTypeDefinition(x): { + const { directives, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map(g) }, + } + } + case isEnumTypeDefinitionNode(x): { const { directives, ...xs } = x return { ...xs, ...directives && { directives: directives.map(g) }, - types: x.types.map(g), + } + } + case isVariableDefinitionNode(x): { + const { directives, defaultValue, type, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map(g) }, + ...defaultValue && { defaultValue: g(defaultValue) }, + type: g(type), + } + } + case isUnionTypeDefinitionNode(x): { + const { directives, types, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map(g) }, + types: types.map(g), } } case isFieldNode(x): { @@ -728,54 +893,55 @@ export const Functor: T.Functor.Ix = { } } case isFieldDefinitionNode(x): { - const { arguments: args, directives, ...xs } = x + const { arguments: args, directives, type, ...xs } = x return { ...xs, ...args && { arguments: args.map(g) }, ...directives && { directives: directives.map(g) }, - type: g(x.type), + type: g(type), } } - case isObjectNode(x): { - const { directives, ...xs } = x + case isObjectTypeDefinitionNode(x): { + const { directives, interfaces, fields, ...xs } = x return { ...xs, ...directives && { directives: directives.map(g) }, - interfaces: x.interfaces.map(g), - fields: x.fields.map(g), + interfaces: interfaces.map(g), + fields: fields.map(g), } } - case isInterfaceNode(x): { - const { directives, ...xs } = x + case isInterfaceTypeDefinitionNode(x): { + const { directives, interfaces, fields, ...xs } = x return { ...xs, ...directives && { directives: directives.map(g) }, - interfaces: x.interfaces.map(g), - fields: x.fields.map(g), + interfaces: interfaces.map(g), + fields: fields.map(g), } } - case isInputValueNode(x): { - const { directives, ...xs } = x + case isInputValueDefinitionNode(x): { + const { directives, defaultValue, type, ...xs } = x return { ...xs, ...directives && { directives: directives.map(g) }, - type: g(x.type), + ...defaultValue && { defaultValue: g(defaultValue) }, + type: g(type), } } - case isInputObjectNode(x): { - const { directives, ...xs } = x + case isInputObjectTypeDefinitionNode(x): { + const { directives, fields, ...xs } = x return { ...xs, ...directives && { directives: directives.map(g) }, - fields: x.fields.map(g), + fields: fields.map(g), } } case isFragmentDefinitionNode(x): { - const { directives, ...xs } = x + const { directives, selectionSet, ...xs } = x return { ...xs, ...directives && { directives: directives.map(g) }, - selectionSet: g(x.selectionSet), + selectionSet: g(selectionSet), } } case isFragmentSpreadNode(x): { @@ -786,11 +952,11 @@ export const Functor: T.Functor.Ix = { } } case isInlineFragmentNode(x): { - const { directives, ...xs } = x + const { directives, selectionSet, ...xs } = x return { ...xs, ...directives && { directives: directives.map(g) }, - selectionSet: g(x.selectionSet), + selectionSet: g(selectionSet), } } case isDirectiveNode(x): { @@ -800,40 +966,71 @@ export const Functor: T.Functor.Ix = { ...args && { arguments: args.map(g) }, } } + case isDirectiveDefinitionNode(x): { + const { arguments: args, ...xs } = x + return { + ...xs, + ...args && { arguments: args.map(g) }, + } + } case isOperationDefinitionNode(x): { - const { directives, variableDefinitions: vars, ...xs } = x + const { directives, variableDefinitions: vars, selectionSet, ...xs } = x return { ...xs, ...directives && { directives: directives.map(g) }, ...vars && { variableDefinitions: vars.map(g) }, - selectionSet: g(x.selectionSet) + selectionSet: g(selectionSet) + } + } + case isSchemaDefinitionNode(x): { + const { directives, operationTypes, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map(g) }, + ...operationTypes && { operationTypes }, } } } } }, mapWithIndex(g) { - return (x, _ix) => { - const ix = isNonNullTypeNode(x) ? { isNonNull: true } satisfies Index : defaultIndex + return (x, ix) => { switch (true) { - default: return fn.exhaustive(x) + default: return x satisfies never + // default: return fn.exhaustive(x) case isRefNode(x): return x - case isNullaryNode(x): { - return x + case isNullaryNode(x): return x + case isArgumentNode(x): return { ...x, value: g(x.value, ix, x) } + case isListNode(x): return { ...x, type: g(x.type, ix, x) } + case isNonNullTypeNode(x): return { ...x, type: g(x.type, ix, x) } + case isDocumentNode(x): return { ...x, definitions: x.definitions.map((_) => g(_, ix, x)) } + case isScalarTypeDefinition(x): { + const { directives, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map((_) => g(_, ix, x)) }, + } } - case isListNode(x): { - return { ...x, type: g(x.type, ix, x) } + case isEnumTypeDefinitionNode(x): { + const { directives, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map((_) => g(_, ix, x)) }, + } } - case isNonNullTypeNode(x): { - return { ...x, type: g(x.type, ix, x) } + case isVariableDefinitionNode(x): { + const { directives, defaultValue, type, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map((_) => g(_, ix, x)) }, + ...defaultValue && { defaultValue: g(defaultValue, ix, x) }, + type: g(type, ix, x), + } } case isSelectionSetNode(x): { return { ...x, selections: x.selections.map((_) => g(_, ix, x)) } } - case isDocumentNode(x): { - return { ...x, definitions: x.definitions.map((_) => g(_, ix, x)) } - } - case isUnionNode(x): { + case isUnionTypeDefinitionNode(x): { const { directives, ...xs } = x return { ...xs, @@ -856,10 +1053,10 @@ export const Functor: T.Functor.Ix = { ...xs, ...args && { arguments: args.map((_) => g(_, ix, x)) }, ...directives && { directives: directives.map((_) => g(_, ix, x)) }, - type: g(x.type, isNonNullTypeNode(x.type) ? { isNonNull: true } : ix, x), + type: g(x.type, ix, x), } } - case isObjectNode(x): { + case isObjectTypeDefinitionNode(x): { const { directives, ...xs } = x return { ...xs, @@ -868,7 +1065,7 @@ export const Functor: T.Functor.Ix = { interfaces: x.interfaces.map((_) => g(_, ix, x)), } } - case isInterfaceNode(x): { + case isInterfaceTypeDefinitionNode(x): { const { directives, ...xs } = x return { ...xs, @@ -877,15 +1074,16 @@ export const Functor: T.Functor.Ix = { interfaces: x.interfaces.map((_) => g(_, ix, x)), } } - case isInputValueNode(x): { - const { directives, ...xs } = x + case isInputValueDefinitionNode(x): { + const { directives, defaultValue, ...xs } = x return { ...xs, ...directives && { directives: directives.map((_) => g(_, ix, x)) }, + ...defaultValue && { defaultValue: g(defaultValue, ix, x) }, type: g(x.type, ix, x), } } - case isInputObjectNode(x): { + case isInputObjectTypeDefinitionNode(x): { const { directives, ...xs } = x return { ...xs, @@ -923,6 +1121,13 @@ export const Functor: T.Functor.Ix = { ...args && { arguments: args.map((_) => g(_, ix, x)) }, } } + case isDirectiveDefinitionNode(x): { + const { arguments: args, ...xs } = x + return { + ...xs, + ...args && { arguments: args.map((_) => g(_, ix, x)) }, + } + } case isOperationDefinitionNode(x): { const { directives, variableDefinitions: vars, ...xs } = x return { @@ -932,9 +1137,70 @@ export const Functor: T.Functor.Ix = { selectionSet: g(x.selectionSet, ix, x) } } + case isSchemaDefinitionNode(x): { + const { directives, operationTypes, ...xs } = x + return { + ...xs, + ...directives && { directives: directives.map((_) => g(_, ix, x)) }, + ...operationTypes && { operationTypes }, + } + } } } } } -export const fold = fn.catamorphism(Functor, defaultIndex) +const fold_ = fn.catamorphism(Functor, createIndex()) + +function getDependencies(node: GQL.ASTNode, ix: Index): Set { + const deps = new Set() + + void fold_( + (x) => isRefNode(x) ? (void deps.add(x.name.value), x) : x + )(node, ix) + + return deps +} + +function reducer(g: (src: AST.F, ix: Index, x: GQL.ASTNode) => T, ix: Index): + (acc: Record T>, node: T | GQL.ASTNode, i: number) => Record T> + +function reducer(g: (src: AST.F, ix: Index, x: GQL.ASTNode) => T, ix: Index) { + return (acc: Record T>, node: GQL.ASTNode, i: number) => { + const key = has('name', 'value', (v) => typeof v === 'string')(node) ? node.name.value : `__${i}` + acc[key] = () => fold_(g)(node, ix) + return acc + } +} + +function setGroups(groups: Map, ix: Index): (node: T | GQL.ASTNode, i: number) => void +function setGroups(groups: Map, ix: Index): (node: GQL.ASTNode, i: number) => void { + return (node: GQL.ASTNode, i: number) => { + if (has('name', 'value', (v) => typeof v === 'string')(node)) { + groups.set(node.name.value, Array.from(getDependencies(node, ix))) + } else { + groups.set(`__${i}`, Array.from(getDependencies(node, ix))) + } + } +} + +export function fold(g: (src: AST.F, ix: Index, x: GQL.ASTNode) => T): Algebra { + return (x, ix) => { + const index = ix ?? createIndex() as Index + const groups = new Map() + const byName = x.definitions.reduce(reducer(g, index), index.namedTypes) + void x.definitions.forEach(setGroups(groups, index)) + + try { + const graph = topologicalSort(groups) + const order = graph.chunks.flat() + return { order, byName } + } catch (e) { + throw Error( + 'Dependency graph contains unknown nodes: { ' + + Array.from(groups).map(([k, v]) => `${k}: [${v.join(', ')}]`).join(', ') + + ' }' + ) + } + } +} diff --git a/packages/graphql-types/src/to-string.ts b/packages/graphql-types/src/to-string.ts index e4eb2c9d..b4d80953 100644 --- a/packages/graphql-types/src/to-string.ts +++ b/packages/graphql-types/src/to-string.ts @@ -1,67 +1,105 @@ -import { fn, escape, has, parseKey } from '@traversable/registry' +import { escape, fn } from '@traversable/registry' import * as F from './functor.js' import type * as gql from 'graphql' import type { AST } from './functor.js' +import { Kind } from './functor.js' -function valueNodeToString(x: AST.ValueNode): string { - switch (x.kind) { - default: return fn.exhaustive(x) - case 'NullValue': return 'null' - case 'BooleanValue': return `${x.value}` - case 'IntValue': return `${x.value}` - case 'FloatValue': return `${x.value}` - case 'StringValue': return `"${escape(x.value)}"` - case 'EnumValueDefinition': return `"${x.name.value}"` - case 'ListValue': return `[${x.values.map(valueNodeToString).join(', ')}]` - case 'ObjectValue': return '' - + '{ ' - + x.fields.map((node) => `${parseKey(node.name.value)}: ${valueNodeToString(node.value)}`).join(', ') - + ' }' - case 'Variable': return `${x.name.value}` +function directives(x: { directives?: readonly string[] }): string { + return !x.directives ? '' : ` ${x.directives.join(' ')} ` +} + +function defaultValue(x: { defaultValue?: AST.ValueNode | string }): string { + return x.defaultValue ? ` = ${serializeValueNode(x.defaultValue)}` : '' +} + +function description(x: { description?: AST.StringValueNode }): string { + return !x.description ? '' + : x.description.block ? + `"""\n${x.description.value}\n"""` + : ` "${x.description.value}" ` +} + +function serializeValueNode(x?: AST.ValueNode | string): string { + if (!x) return '' + else if (typeof x === 'string') return x + else { + switch (x.kind) { + default: return x satisfies never + case Kind.NullValue: return 'null' + case Kind.BooleanValue: return `${x.value}` + case Kind.IntValue: return `${x.value}` + case Kind.FloatValue: return `${x.value}` + case Kind.StringValue: return `"${escape(x.value)}"` + case Kind.EnumValue: return `${x.value}` + case Kind.ListValue: return `[${x.values.map(serializeValueNode).join(', ')}]` + case Kind.Variable: return `$${x.name.value}` + case Kind.ObjectValue: return `{ ${x.fields.map((n) => `${n.name.value}: ${serializeValueNode(n.value)}`).join(', ')} }` + } } } -const fold = F.fold((x, _, _original) => { +const fold = F.fold((x) => { switch (true) { default: return fn.exhaustive(x) - case F.isRefNode(x): return x.name.value - case F.isValueNode(x): return valueNodeToString(x) + case F.isEnumValueDefinitionNode(x): throw Error('Not sure what an enum value definition node is...') + case F.isValueNode(x): return serializeValueNode(x) case F.isSelectionSetNode(x): return `{ ${x.selections.join('\n')} }` - case F.isScalarTypeDefinition(x): return `scalar ${x.name.value}` + case F.isScalarTypeDefinition(x): return `scalar ${directives(x)}${x.name.value}` case F.isBooleanNode(x): return 'Boolean' case F.isIntNode(x): return 'Int' case F.isNumberNode(x): return 'Number' case F.isFloatNode(x): return 'Float' case F.isStringNode(x): return 'String' case F.isIDNode(x): return 'ID' - case F.isEnumNode(x): return `enum ${x.name.value} { ${x.values.map((v) => v.name.value).join('\n')} }` - case F.isNonNullTypeNode(x): return `${x.type}!` - case F.isUnionNode(x): return `union ${x.name.value} = ${x.types}` case F.isListNode(x): return `[${x.type}]` + case F.isNonNullTypeNode(x): return `${x.type}!` + case F.isArgumentNode(x): return `${x.name.value}: ${x.value}` case F.isDirectiveNode(x): return `@${x.name.value}(${(x.arguments ?? []).join(', ')})` + case F.isInputObjectTypeDefinitionNode(x): return `${description(x)}input ${x.name.value} { ${x.fields.join('\n')} } ` + case F.isUnionTypeDefinitionNode(x): return `${description(x)}union ${x.name.value}${directives(x)} = ${x.types.join(' | ')}` + case F.isEnumTypeDefinitionNode(x): return `enum ${x.name.value}${directives(x)} { ${x.values.map((v) => v.name.value).join('\n')} }` + case F.isDirectiveDefinitionNode(x): { + const ARGS = x.arguments?.length ? `(${x.arguments.join(', ')})` : '' + return `${description(x)}directive @${x.name.value}${ARGS} on ${x.locations.map((loc) => loc.value).join(' | ')}` + } + case F.isInlineFragmentNode(x): return `...${!x.typeCondition ? '' : ` on ${x.typeCondition.name.value}`} ${x.selectionSet} ` + case F.isFragmentDefinitionNode(x): { + return `fragment ${x.name.value} on ${x.typeCondition.name.value}${directives(x)}${x.selectionSet}` + } + case F.isFragmentSpreadNode(x): return `...${x.name.value}${directives(x)}` + case F.isVariableDefinitionNode(x): return `$${x.variable.name.value}: ${x.type}${defaultValue(x)}${directives(x)}` + case F.isDocumentNode(x): return x.definitions.join('\n\r') case F.isFieldNode(x): { - const KEY = x.alias?.value ?? x.name.value - const VALUE = !x.alias?.value ? '' : `: ${x.name.value}` + const KEY = !x.alias?.value ? `${x.name.value} ` : `${x.alias.value}: ${x.name.value}` + const ARGS = !x.arguments?.length ? '' : `(${x.arguments.join(', ')})` return x.selectionSet - ? `${KEY} ${x.selectionSet}` - : `${KEY}${VALUE}` + ? `${description(x)}${KEY}${directives(x)}${ARGS}${x.selectionSet}` + : `${description(x)}${KEY}${directives(x)}${ARGS}` } - case F.isFieldDefinitionNode(x): return `${parseKey(x.name.value)}: ${x.type} ` - case F.isInputValueNode(x): return `${parseKey(x.name.value)}: ${x.type} ` - case F.isObjectNode(x): { + case F.isFieldDefinitionNode(x): { + const ARGS = !x.arguments?.length ? '' : `(${x.arguments.join(', ')})` + return `${description(x)}${x.name.value}${directives(x)}${ARGS}: ${x.type}` + } + case F.isInputValueDefinitionNode(x): { + return `${description(x)}${x.name.value}${directives(x)}: ${x.type}${defaultValue(x)}` + } + case F.isObjectTypeDefinitionNode(x): { const IMPLEMENTS = x.interfaces.length ? ` implements ${x.interfaces.join(' & ')}` : '' - return `type ${x.name.value}${IMPLEMENTS} { ${x.fields.join('\n')} } ` + return `${description(x)}type ${x.name.value}${directives(x)}${IMPLEMENTS} { ${x.fields.join('\n')} } ` + } + case F.isInterfaceTypeDefinitionNode(x): { + const IMPLEMENTS = x.interfaces.length ? ` implements ${x.interfaces.join(' & ')}` : '' + return `${description(x)}interface ${x.name.value}${directives(x)}${IMPLEMENTS} { ${x.fields.join('\n')} } ` } - case F.isInterfaceNode(x): return `interface ${x.name.value} { ${x.fields.join('\n')} }` - case F.isInputObjectNode(x): return `input ${x.name.value} { ${x.fields.join('\n')} } ` - case F.isInlineFragmentNode(x): return `...${!x.typeCondition ? '' : ` on ${x.typeCondition.name.value}`} ${x.selectionSet} ` - case F.isFragmentDefinitionNode(x): return `fragment ${x.name.value} on ${x.typeCondition.name.value} ${x.selectionSet} ` - case F.isFragmentSpreadNode(x): return `...${x.name.value} ` - case F.isDocumentNode(x): return x.definitions.join('\n\r') case F.isOperationDefinitionNode(x): { const NAME = x.name?.value ? ` ${x.name.value} ` : '' - return `${x.operation}${NAME}${!x.variableDefinitions?.length ? '' : `(${x.variableDefinitions.join(', ')})`} ${x.selectionSet} ` + const VARS = !x.variableDefinitions?.length ? '' : `(${x.variableDefinitions.join(', ')})` + return `${x.operation}${NAME}${VARS}${directives(x)} ${x.selectionSet} ` } + case F.isSchemaDefinitionNode(x): { + return `${description(x)}schema ${directives(x)}{ ${x.operationTypes.map((op) => `${op.operation}: ${op.type.name.value}`).join('\n')} }` + } + case F.isRefNode(x): return x.name.value } }) @@ -73,5 +111,5 @@ export declare namespace toString {} * Convert a GraphQL AST into its corresponding TypeScript type. */ export function toString(doc: gql.DocumentNode): string { - return fold(doc) + return Object.values(fold(doc).byName).map((thunk) => thunk()).join('\n\r') } diff --git a/packages/graphql-types/src/to-type.ts b/packages/graphql-types/src/to-type.ts index 781fed0d..8b065f5f 100644 --- a/packages/graphql-types/src/to-type.ts +++ b/packages/graphql-types/src/to-type.ts @@ -2,6 +2,7 @@ import { fn, has, parseKey } from '@traversable/registry' import * as F from './functor.js' import type * as gql from 'graphql' import type { AST } from './functor.js' +import { Kind } from './functor.js' const unsupported = [ 'Directive', @@ -11,7 +12,11 @@ const unsupported = [ 'InputObjectTypeDefinition', 'InputValueDefinition', 'SelectionSet', - 'OperationDefinition' + 'OperationDefinition', + 'Argument', + 'SchemaDefinition', + 'VariableDefinition', + 'DirectiveDefinition', ] as const satisfies Array type UnsupportedNodeMap = Pick @@ -19,31 +24,33 @@ type UnsupportedNode = UnsupportedNodeMap[keyof UnsupportedNodeMap] function isUnsupportedNode(x: unknown): x is UnsupportedNode { return unsupported.some( - (nope) => has('kind', (kind): kind is never => kind === nope)(x) + (tag) => has('kind', (kind) => kind === tag)(x) ) } function valueNodeToString(x: AST.ValueNode): string { switch (x.kind) { default: return fn.exhaustive(x) - case 'NullValue': return 'null' - case 'BooleanValue': return `${x.value}` - case 'IntValue': return `${x.value}` - case 'FloatValue': return `${x.value}` - case 'StringValue': return `"${x.value}"` - case 'EnumValueDefinition': return `"${x.name.value}"` - case 'ListValue': return `[${x.values.map(valueNodeToString).join(', ')}]` - case 'ObjectValue': return '' + case Kind.NullValue: return 'null' + case Kind.BooleanValue: return `${x.value}` + case Kind.IntValue: return `${x.value}` + case Kind.FloatValue: return `${x.value}` + case Kind.StringValue: return `"${x.value}"` + case Kind.EnumValue: return `${x.value}` + case Kind.ListValue: return `[${x.values.map(valueNodeToString).join(', ')}]` + case Kind.ObjectValue: return '' + '{ ' + x.fields.map((node) => `${parseKey(node.name.value)}: ${valueNodeToString(node.value)}`).join(', ') + ' }' - case 'Variable': return `${x.name.value}` + case Kind.Variable: return `${x.name.value}` } } -const fold = F.fold((x, _, original) => { +const fold = F.fold((x) => { switch (true) { default: return fn.exhaustive(x) + case isUnsupportedNode(x): return '' + case F.isEnumValueDefinitionNode(x): throw Error('Not sure what an enum value definition node is...') case F.isRefNode(x): return x.name.value case F.isValueNode(x): return valueNodeToString(x) case F.isScalarTypeDefinition(x): return x.name.value @@ -53,16 +60,14 @@ const fold = F.fold((x, _, original) => { case F.isFloatNode(x): return 'number' case F.isStringNode(x): return 'string' case F.isIDNode(x): return 'string' - case F.isEnumNode(x): return ( - x.values.length === 0 ? 'never' - : x.values.length === 1 ? JSON.stringify(x.values[0]) - : `(${x.values.map((v) => JSON.stringify(v)).join(' | ')})` + case F.isEnumTypeDefinitionNode(x): return ( + x.values.length === 1 ? JSON.stringify(x.values[0]) + : `(${x.values.map((v) => JSON.stringify(v)).join(' | ')})` ) case F.isNonNullTypeNode(x): return `${x.type}!` - case F.isUnionNode(x): return ( - x.types.length === 0 ? 'never' - : x.types.length === 1 ? JSON.stringify(x.types[0]) - : `(${x.types.map((v) => JSON.stringify(v)).join(' | ')})` + case F.isUnionTypeDefinitionNode(x): return ( + x.types.length === 1 ? `type ${x.name.value} = ${JSON.stringify(x.types[0])}` + : `type ${x.name.value} = ${x.types.map((v) => JSON.stringify(v)).join(' | ')}` ) case F.isListNode(x): return `Array<${x.type.endsWith('!') ? x.type.slice(0, -1) : `${x.type} | null`}>` case F.isFieldDefinitionNode(x): { @@ -70,10 +75,27 @@ const fold = F.fold((x, _, original) => { const VALUE = isNonNull ? x.type.slice(0, -1) : x.type return `${parseKey(x.name.value)}${isNonNull ? '' : '?'}: ${VALUE}` } - case F.isObjectNode(x): { return `{ ${x.fields.join(', ')} }` } - case F.isInterfaceNode(x): { return `{ ${x.fields.join(', ')} }` } - case F.isDocumentNode(x): throw Error('[@traversable/graphql-types/to-type.js]: Nesting documents is not allowed') - case isUnsupportedNode(x): throw Error(`[@traversable/graphql-types/to-type.js]: Unsupported node: ${x.kind}`) + case F.isFieldNode(x): { + const KEY = x.alias?.value ?? x.name.value + return x.selectionSet + ? `${KEY}: ${x.selectionSet}` + : `${KEY}: ${x.name.value}` + } + case F.isObjectTypeDefinitionNode(x): { + const IMPLEMENTS = !x.interfaces.length ? '' : `${x.interfaces.join(' & ')} & ` + return `type ${x.name.value} = ${IMPLEMENTS}{ + __typename?: ${x.name.value} + ${x.fields.join('\n')} + }` + } + case F.isInterfaceTypeDefinitionNode(x): { + const IMPLEMENTS = !x.interfaces.length ? '' : `extends ${x.interfaces.join(', ')} ` + return `interface ${x.name.value} ${IMPLEMENTS}{ + __typename?: ${x.name.value} + ${x.fields.join('\n')} + }` + } + case F.isDocumentNode(x): return x.definitions.join('\n\r') } }) @@ -91,8 +113,5 @@ toType.unsupported = unsupported * Convert a GraphQL AST into its corresponding TypeScript type. */ export function toType(doc: gql.DocumentNode) { - const types = doc.definitions.map( - (x, i) => `type ${F.isNamedTypeNode(x) ? x.name.value : `Type${i}`} = ${fold(x)}` - ) - return types.join('\n') + return Object.values(fold(doc).byName).map((thunk) => thunk()).join('\n\r') } diff --git a/packages/graphql-types/test/to-string.test.ts b/packages/graphql-types/test/to-string.test.ts index c84bb60e..7e023b3b 100644 --- a/packages/graphql-types/test/to-string.test.ts +++ b/packages/graphql-types/test/to-string.test.ts @@ -1,29 +1,462 @@ import * as vi from 'vitest' -import { toString } from '@traversable/graphql-types' +import * as AST from '@traversable/graphql-types' import * as graphql from 'graphql' import prettier from '@prettier/sync' const format = (src: string) => prettier.format( src, - { parser: 'graphql', semi: false, printWidth: 35 } + { parser: 'graphql', semi: false, printWidth: 60 } ) vi.describe('〖⛳️〗‹‹‹ ❲@traversable/graphql-types❳', () => { - vi.test('〖⛳️〗› ❲toString❳', () => { - vi.expect.soft(format( - toString( - graphql.parse(format(` - type Pet { - petName: [String!] - } + vi.test('〖⛳️〗› ❲AST.toString❳: AST.ScalarTypeDefinition', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + scalar Date + `))))).toMatchInlineSnapshot + (` + "scalar Date + " + `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: AST.NullNode', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + type Test { + abc: Null + } + `))))).toMatchInlineSnapshot + (` + "type Test { + abc: Null + } + " + `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: AST.BooleanNode', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + type Test { + abc: Boolean + } + `))))).toMatchInlineSnapshot + (` + "type Test { + abc: Boolean + } + " + `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: AST.IntNode', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + type Test { + abc: Int + } + `))))).toMatchInlineSnapshot + (` + "type Test { + abc: Int + } + " + `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: AST.NumberNode', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + type Test { + abc: Number + } + `))))).toMatchInlineSnapshot + (` + "type Test { + abc: Number + } + " + `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: AST.FloatNode', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + type Test { + abc: Float + } + `))))).toMatchInlineSnapshot + (` + "type Test { + abc: Float + } + " + `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: AST.StringNode', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + type Test { + abc: String + } + `))))).toMatchInlineSnapshot + (` + "type Test { + abc: String + } + " + `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: AST.IDNode', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + type Test { + abc: ID + } + `))))).toMatchInlineSnapshot + (` + "type Test { + abc: ID + } + " + `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: AST.NonNullTypeNode', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + type Test { + abc: String! + } + `))))).toMatchInlineSnapshot + (` + "type Test { + abc: String! + } + " + `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: AST.ListNode', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + type Test { + abc: [String] + def: [String!] + ghi: [String]! + jkl: [String!]! + } + `))))).toMatchInlineSnapshot + (` + "type Test { + abc: [String] + def: [String!] + ghi: [String]! + jkl: [String!]! + } + " + `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: AST.EnumTypeDefinition', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + enum Role { + USER + ADMIN + } + `))))).toMatchInlineSnapshot + (` + "enum Role { + USER + ADMIN + } + " + `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: AST.OperationDefinitionNode', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + query { + abc: Boolean + } + `))))).toMatchInlineSnapshot + (` + "query { + abc: Boolean + } + " + `) + + vi.expect.soft(format(AST.toString(graphql.parse(format(` + query { + empireHero: hero { + stuff + } + } + `))))).toMatchInlineSnapshot + (` + "query { + empireHero: hero { + stuff + } + } + " + `) + + vi.expect.soft(format(AST.toString(graphql.parse(format(` + query { + empireHero: hero(episode: EMPIRE) { + name + } + jediHero: hero(episode: JEDI) { + name + } + } + `))))).toMatchInlineSnapshot + (` + "query { + empireHero: hero(episode: EMPIRE) { + name + } + jediHero: hero(episode: JEDI) { + name + } + } + " + `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: AST.QueryOperation', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + type Query { + createReview(episode: Episode, review: ReviewInput!): Review + } + `))))).toMatchInlineSnapshot + (` + "type Query { + createReview( + episode: Episode + review: ReviewInput! + ): Review + } + " + `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: AST.MutationOperation', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + type Mutation { + createReview( + episode: Episode, + review: ReviewInput! + ): Review + } + `))))).toMatchInlineSnapshot + (` + "type Mutation { + createReview( + episode: Episode + review: ReviewInput! + ): Review + } + " + `) + + vi.expect.soft(format(AST.toString(graphql.parse(format(` + mutation CreateReviewForEpisode( + $ep: Episode!, + $review: ReviewInput! + ) { + createReview(episode: $ep, review: $review) { + stars + commentary + } + } + `))))).toMatchInlineSnapshot + (` + "mutation CreateReviewForEpisode( + $ep: Episode! + $review: ReviewInput! + ) { + createReview(episode: $ep, review: $review) { + stars + commentary + } + } + " + `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: AST.SubscriptionOperation', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + type Subscription { + createReview( + episode: Episode, + review: ReviewInput! + ): Review + } + `))))).toMatchInlineSnapshot + (` + "type Subscription { + createReview( + episode: Episode + review: ReviewInput! + ): Review + } + " + `) + + vi.expect.soft(format(AST.toString(graphql.parse(format(` + subscription { + episode: Episode, + review: ReviewInput + } + `))))).toMatchInlineSnapshot + (` + "subscription { + episode: Episode + review: ReviewInput + } + " + `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: AST.DirectiveNode', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + directive @directive1 on FIELD_DEFINITION | ENUM_VALUE + + directive @directive2( + reason: String = "No longer supported" + ) on FIELD_DEFINITION | ENUM_VALUE + `))))).toMatchInlineSnapshot + (` + "directive @directive1 on FIELD_DEFINITION | ENUM_VALUE + + directive @directive2( + reason: String = "No longer supported" + ) on FIELD_DEFINITION | ENUM_VALUE + " + `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: AST.FieldDefinitionNode', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + type Character { + "The name of the character." + name: String! + } + `))))).toMatchInlineSnapshot + (` + "type Character { + "The name of the character." + name: String! + } + " + `) + }) - type Human { - humanName: String! - pet: Pet + vi.test('〖⛳️〗› ❲AST.toString❳: AST.InputObjectTypeDefinitionNode', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + input ReviewInput { + stars: Int! + commentary: String + } + `))))).toMatchInlineSnapshot + (` + "input ReviewInput { + stars: Int! + commentary: String + } + " + `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: AST.VariableDefinitionNode', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + query HeroNameAndFriends($episode: Episode = JEDI) { + hero(episode: $episode) { + name + friends { + name + } + } + } + `))))).toMatchInlineSnapshot + (` + "query HeroNameAndFriends($episode: Episode = JEDI) { + hero(episode: $episode) { + name + friends { + name } - `)) - ) - )).toMatchInlineSnapshot + } + } + " + `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: AST.FragmentDefinitionNode', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + fragment comparisonFields on Character { + name + appearsIn + friends { + name + } + } + `))))).toMatchInlineSnapshot + (` + "fragment comparisonFields on Character { + name + appearsIn + friends { + name + } + } + " + `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: AST.FragmentSpreadNode', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + query findUser($userId: ID!) { + user(id: $userId) { + ...UserFields + } + } + `))))).toMatchInlineSnapshot + (` + "query findUser($userId: ID!) { + user(id: $userId) { + ...UserFields + } + } + " + `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: AST.InputValueDefinitionNode', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + type Starship { + id: ID! + name: String! + length(unit: LengthUnit = METER): Float + } + `))))).toMatchInlineSnapshot + (` + "type Starship { + id: ID! + name: String! + length(unit: LengthUnit = METER): Float + } + " + `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: AST.ObjectNode', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + type Pet { + petName: [String!] + } + + type Human { + humanName: String! + pet: Pet + } + `))))).toMatchInlineSnapshot (` "type Pet { petName: [String!] @@ -36,50 +469,300 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/graphql-types❳', () => { " `) - vi.expect.soft(format( - toString( - graphql.parse( - format(` - query { - abc: Boolean - } - `) - ) - ) - )).toMatchInlineSnapshot + vi.expect.soft(format(AST.toString(graphql.parse(format(` + """ + A character from the Star Wars universe + """ + type Character { + "The name of the character." + name: String! + } + `))))).toMatchInlineSnapshot + (` + """" + A character from the Star Wars universe + """ + type Character { + "The name of the character." + name: String! + } + " + `) + }) + + vi.test.only('〖⛳️〗› ❲AST.toString❳: AST.InterfaceNode', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + interface Node { + id: ID! + } + + interface Character implements Node { + id: ID! + name: String! + friends: [Character] + appearsIn: [Episode]! + } + + type Episode { + id: ID! + } + `))))).toMatchInlineSnapshot + (` + "interface Node { + id: ID! + } + + interface Character implements Node { + id: ID! + name: String! + friends: [Character] + appearsIn: [Episode]! + } + + type Episode { + id: ID! + } + " + `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: AST.UnionNode', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + union SearchResult = Human | Droid | Starship + `))))).toMatchInlineSnapshot + (` + "union SearchResult = Human | Droid | Starship + " + `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: AST.ArgumentNode', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + query { + search(text: "an") { + __typename + ... on Human { + name + height + } + ... on Droid { + name + primaryFunction + } + ... on Starship { + name + length + } + } + } + `))))).toMatchInlineSnapshot (` "query { - abc: Boolean + search(text: "an") { + __typename + ... on Human { + name + height + } + ... on Droid { + name + primaryFunction + } + ... on Starship { + name + length + } + } } " `) + }) + + vi.test('〖⛳️〗› ❲AST.toString❳: kitchen sink', () => { + vi.expect.soft(format(AST.toString(graphql.parse(format(` + scalar Date + + # This line is treated like whitespace and ignored by GraphQL + schema { + query: Query + } + + """ + The query type, represents all of the entry points into our object graph + """ + type Query { + me: User! + """ + Fetches the hero of a specified Star Wars film. + """ + user( + "The name of the film that the hero appears in." + id: ID! + ): User + allUsers: [User] + search(term: String!): [SearchResult!]! + myChats: [Chat!]! + } + + enum Role { + USER + ADMIN + } + + interface Node { + id: ID! + } - vi.expect.soft(format( - toString( - graphql.parse( - format(` - fragment comparisonFields on Character { + union SearchResult = User | Chat | ChatMessage + + type User implements Node { + id: ID! + username: String! + email: String! + role: Role! + } + + type Chat implements Node { + id: ID! + users: [User!]! + messages: [ChatMessage!]! + } + + type ChatMessage implements Node { + id: ID! + content: String! + time: Date! + user: User! + } + + query findUser($userId: ID!) { + user(id: $userId) { + ...UserFields + } + } + + query HeroComparison($first: Int = 3) { + leftComparison: hero(episode: EMPIRE) { + ...comparisonFields + } + rightComparison: hero(episode: JEDI) { + ...comparisonFields + } + } + + fragment comparisonFields on Character { + name + friendsConnection(first: $first) { + totalCount + edges { + node { name - appearsIn - friends { - name - } } - `) - ) - ) - )).toMatchInlineSnapshot + } + } + } + + query Hero($episode: Episode, $withFriends: Boolean!) { + hero(episode: $episode) { + name + friends @include(if: $withFriends) { + name + } + } + } + `))))).toMatchInlineSnapshot (` - "fragment comparisonFields on Character { + "scalar Date + + schema { + query: Query + } + + """ + The query type, represents all of the entry points into our object graph + """ + type Query { + me: User! + """ + Fetches the hero of a specified Star Wars film. + """ + user( + "The name of the film that the hero appears in." + id: ID! + ): User + allUsers: [User] + search(term: String!): [SearchResult!]! + myChats: [Chat!]! + } + + enum Role { + USER + ADMIN + } + + interface Node { + id: ID! + } + + union SearchResult = User | Chat | ChatMessage + + type User implements Node { + id: ID! + username: String! + email: String! + role: Role! + } + + type Chat implements Node { + id: ID! + users: [User!]! + messages: [ChatMessage!]! + } + + type ChatMessage implements Node { + id: ID! + content: String! + time: Date! + user: User! + } + + query findUser($userId: ID!) { + user(id: $userId) { + ...UserFields + } + } + + query HeroComparison($first: Int = 3) { + leftComparison: hero(episode: EMPIRE) { + ...comparisonFields + } + rightComparison: hero(episode: JEDI) { + ...comparisonFields + } + } + + fragment comparisonFields on Character { name - appearsIn - friends { + friendsConnection(first: $first) { + totalCount + edges { + node { + name + } + } + } + } + + query Hero($episode: Episode, $withFriends: Boolean!) { + hero(episode: $episode) { name + friends @include(if: $withFriends) { + name + } } } " `) - }) -}) +}) diff --git a/packages/graphql-types/test/to-type.test.ts b/packages/graphql-types/test/to-type.test.ts index 5d06f81b..b0858bf8 100644 --- a/packages/graphql-types/test/to-type.test.ts +++ b/packages/graphql-types/test/to-type.test.ts @@ -24,9 +24,12 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/graphql-types❳', () => { )).toMatchInlineSnapshot (` "type Pet = { + __typename?: Pet petName?: Array } + type Human = { + __typename?: Human humanName: string pet?: Pet } diff --git a/packages/graphql/package.json b/packages/graphql/package.json index 80af8949..da04538b 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -38,5 +38,14 @@ "dependencies": { "@traversable/graphql-types": "workspace:^", "@traversable/registry": "workspace:^" + }, + "devDependencies": { + "graphql": "catalog:" + }, + "peerDependencies": { + "graphql": ">= 16" + }, + "peerDependenciesMeta": { + "graphql": { "optional": true } } } diff --git a/packages/graphql/src/__generated__/__manifest__.ts b/packages/graphql/src/__generated__/__manifest__.ts index 92c562e7..a2a6f217 100644 --- a/packages/graphql/src/__generated__/__manifest__.ts +++ b/packages/graphql/src/__generated__/__manifest__.ts @@ -38,5 +38,14 @@ export default { "dependencies": { "@traversable/graphql-types": "workspace:^", "@traversable/registry": "workspace:^" + }, + "devDependencies": { + "graphql": "catalog:" + }, + "peerDependencies": { + "graphql": ">= 16" + }, + "peerDependenciesMeta": { + "graphql": { "optional": true } } } as const \ No newline at end of file diff --git a/packages/registry/src/topological-sort.ts b/packages/registry/src/topological-sort.ts new file mode 100644 index 00000000..ca22f1b8 --- /dev/null +++ b/packages/registry/src/topological-sort.ts @@ -0,0 +1,131 @@ +/** + * Stolen from: @pnpm/deps.graph-sequencer + * Source code: https://github.com/pnpm/pnpm/blob/main/deps/graph-sequencer/src/index.ts + * Date: 2025-09-27 + */ + +export type Graph = Map +export type Groups = T[][] + +export interface Options { + graph: Graph + groups: Groups +} + +export interface Result { + safe: boolean + chunks: Groups + cycles: Groups +} + +/** + * Performs topological sorting on a graph while supporting node restrictions. + * + * @param {Graph} graph - The graph represented as a Map where keys are nodes and values are their outgoing edges. + * @param {T[]} includedNodes - An array of nodes that should be included in the sorting process. Other nodes will be ignored. + * @returns {Result} An object containing the result of the sorting, including safe, chunks, and cycles. + */ +export function topologicalSort(graph: Graph, includedNodes: T[] = [...graph.keys()]): Result { + // Initialize reverseGraph with empty arrays for all nodes. + const reverseGraph = new Map() + for (const key of graph.keys()) { + reverseGraph.set(key, []) + } + + // Calculate outDegree and reverseGraph for the included nodes. + const nodes = new Set(includedNodes) + const visited = new Set() + const outDegree = new Map() + for (const [from, edges] of graph.entries()) { + outDegree.set(from, 0) + for (const to of edges) { + if (nodes.has(from) && nodes.has(to)) { + changeOutDegree(from, 1) + reverseGraph.get(to)!.push(from) + } + } + if (!nodes.has(from)) { + visited.add(from) + } + } + + const chunks: T[][] = [] + const cycles: T[][] = [] + let safe = true + while (nodes.size) { + const chunk: T[] = [] + let minDegree = Number.MAX_SAFE_INTEGER + for (const node of nodes) { + const degree = outDegree.get(node)! + if (degree === 0) { + chunk.push(node) + } + minDegree = Math.min(minDegree, degree) + } + + if (minDegree === 0) { + chunk.forEach(removeNode) + chunks.push(chunk) + } else { + const cycleNodes: T[] = [] + for (const node of nodes) { + const cycle = findCycle(node) + if (cycle.length) { + cycles.push(cycle) + cycle.forEach(removeNode) + cycleNodes.push(...cycle) + + if (cycle.length > 1) { + safe = false + } + } + } + chunks.push(cycleNodes) + } + } + + return { safe, chunks, cycles } + + // Function to update the outDegree of a node. + function changeOutDegree(node: T, value: number) { + const degree = outDegree.get(node) ?? 0 + outDegree.set(node, degree + value) + } + + // Function to remove a node from the graph. + function removeNode(node: T) { + for (const from of reverseGraph.get(node)!) { + changeOutDegree(from, -1) + } + visited.add(node) + nodes.delete(node) + } + + function findCycle(startNode: T): T[] { + const queue: Array<[T, T[]]> = [[startNode, [startNode]]] + const cycleVisited = new Set() + const cycles: T[][] = [] + + while (queue.length) { + const [id, cycle] = queue.shift()! + for (const to of graph.get(id)!) { + if (to === startNode) { + cycleVisited.add(to) + cycles.push([...cycle]) + continue + } + if (visited.has(to) || cycleVisited.has(to)) { + continue + } + cycleVisited.add(to) + queue.push([to, [...cycle, to]]) + } + } + + if (!cycles.length) { + return [] + } + cycles.sort((a, b) => b.length - a.length) + return cycles[0] + } +} From 8f23b9105adad726cfa87f30cdf90fedfc398ba8 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 27 Sep 2025 23:44:58 -0500 Subject: [PATCH 15/32] chore(*): freeze npm versions --- package.json | 2 +- pnpm-lock.yaml | 383 ++++++++++++++++++++++++++++++-------------- pnpm-workspace.yaml | 32 ++-- 3 files changed, 285 insertions(+), 132 deletions(-) diff --git a/package.json b/package.json index 4b96dd5d..ff840338 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "fast-check": "catalog:", "madge": "catalog:", "tinybench": "catalog:", - "typedoc": "^0.28.9", + "typedoc": "catalog:", "typescript": "catalog:", "vitest": "catalog:" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 23275149..c57cbd1b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,26 +7,26 @@ settings: catalogs: default: '@ark/attest': - specifier: ^0.44.8 + specifier: 0.44.8 version: 0.44.8 '@babel/cli': - specifier: ^7.25.9 - version: 7.28.3 + specifier: 7.25.9 + version: 7.25.9 '@babel/core': - specifier: ^7.26.0 - version: 7.28.3 + specifier: 7.26.0 + version: 7.26.0 '@babel/plugin-transform-export-namespace-from': - specifier: ^7.25.9 - version: 7.27.1 + specifier: 7.25.9 + version: 7.25.9 '@babel/plugin-transform-modules-commonjs': - specifier: ^7.25.9 - version: 7.27.1 + specifier: 7.25.9 + version: 7.25.9 '@changesets/changelog-github': - specifier: ^0.5.0 - version: 0.5.1 + specifier: 0.5.0 + version: 0.5.0 '@changesets/cli': - specifier: ^2.27.9 - version: 2.29.6 + specifier: 2.27.9 + version: 2.27.9 '@prettier/sync': specifier: 0.5.5 version: 0.5.5 @@ -34,14 +34,14 @@ catalogs: specifier: 0.34.40 version: 0.34.40 '@types/lodash.isequal': - specifier: ^4.5.8 + specifier: 4.5.8 version: 4.5.8 '@types/madge': - specifier: ^5.0.3 + specifier: 5.0.3 version: 5.0.3 '@types/node': - specifier: ^22.9.0 - version: 22.17.2 + specifier: 22.9.0 + version: 22.9.0 '@vitest/coverage-v8': specifier: 3.2.4 version: 3.2.4 @@ -52,20 +52,26 @@ catalogs: specifier: 2.1.20 version: 2.1.20 babel-plugin-annotate-pure-calls: - specifier: ^0.4.0 + specifier: 0.4.0 version: 0.4.0 fast-check: - specifier: ^4.1.1 - version: 4.2.0 + specifier: 4.1.1 + version: 4.1.1 + graphql: + specifier: 16.11.0 + version: 16.11.0 lodash.isequal: - specifier: ^4.5.0 + specifier: 4.5.0 version: 4.5.0 madge: - specifier: ^8.0.0 + specifier: 8.0.0 version: 8.0.0 tinybench: - specifier: ^3.0.4 - version: 3.1.1 + specifier: 3.0.4 + version: 3.0.4 + typedoc: + specifier: 0.28.9 + version: 0.28.9 typescript: specifier: 5.9.2 version: 5.9.2 @@ -88,28 +94,28 @@ importers: version: 0.44.8(typescript@5.9.2) '@babel/cli': specifier: 'catalog:' - version: 7.28.3(@babel/core@7.28.3) + version: 7.25.9(@babel/core@7.26.0) '@babel/core': specifier: 'catalog:' - version: 7.28.3 + version: 7.26.0 '@babel/plugin-transform-export-namespace-from': specifier: 'catalog:' - version: 7.27.1(@babel/core@7.28.3) + version: 7.25.9(@babel/core@7.26.0) '@babel/plugin-transform-modules-commonjs': specifier: 'catalog:' - version: 7.27.1(@babel/core@7.28.3) + version: 7.25.9(@babel/core@7.26.0) '@changesets/changelog-github': specifier: 'catalog:' - version: 0.5.1 + version: 0.5.0 '@changesets/cli': specifier: 'catalog:' - version: 2.29.6(@types/node@22.17.2) + version: 2.27.9 '@types/madge': specifier: 'catalog:' version: 5.0.3 '@types/node': specifier: 'catalog:' - version: 22.17.2 + version: 22.9.0 '@vitest/coverage-v8': specifier: 'catalog:' version: 3.2.4(vitest@3.2.4) @@ -118,25 +124,25 @@ importers: version: 3.2.4(vitest@3.2.4) babel-plugin-annotate-pure-calls: specifier: 'catalog:' - version: 0.4.0(@babel/core@7.28.3) + version: 0.4.0(@babel/core@7.26.0) fast-check: specifier: 'catalog:' - version: 4.2.0 + version: 4.1.1 madge: specifier: 'catalog:' version: 8.0.0(typescript@5.9.2) tinybench: specifier: 'catalog:' - version: 3.1.1 + version: 3.0.4 typedoc: - specifier: ^0.28.9 - version: 0.28.10(typescript@5.9.2) + specifier: 'catalog:' + version: 0.28.9(typescript@5.9.2) typescript: specifier: 'catalog:' version: 5.9.2 vitest: specifier: 'catalog:' - version: 3.2.4(@types/node@22.17.2)(@vitest/ui@3.2.4)(yaml@2.8.1) + version: 3.2.4(@types/node@22.9.0)(@vitest/ui@3.2.4)(yaml@2.8.1) bin: dependencies: @@ -305,7 +311,7 @@ importers: version: 2.1.20 fast-check: specifier: 'catalog:' - version: 4.2.0 + version: 4.1.1 publishDirectory: dist packages/arktype-types: @@ -329,6 +335,10 @@ importers: '@traversable/registry': specifier: workspace:^ version: link:../registry/dist + devDependencies: + graphql: + specifier: 'catalog:' + version: 16.11.0 publishDirectory: dist packages/graphql-test: @@ -550,7 +560,7 @@ importers: version: link:../schema/dist fast-check: specifier: 'catalog:' - version: 4.2.0 + version: 4.1.1 publishDirectory: dist packages/schema-to-json-schema: @@ -634,7 +644,7 @@ importers: version: link:../registry/dist fast-check: specifier: 'catalog:' - version: 4.2.0 + version: 4.1.1 publishDirectory: dist packages/typebox-types: @@ -683,7 +693,7 @@ importers: version: link:../valibot-types/dist fast-check: specifier: 'catalog:' - version: 4.2.0 + version: 4.1.1 valibot: specifier: 'catalog:' version: 1.1.0(typescript@5.9.2) @@ -783,7 +793,7 @@ importers: version: link:../registry/dist fast-check: specifier: 'catalog:' - version: 4.2.0 + version: 4.1.1 zod: specifier: 'catalog:' version: 4.1.3 @@ -839,8 +849,8 @@ packages: '@ark/util@0.49.0': resolution: {integrity: sha512-/BtnX7oCjNkxi2vi6y1399b+9xd1jnCrDYhZ61f0a+3X8x8DxlK52VgEEzyuC2UQMPACIfYrmHkhD3lGt2GaMA==} - '@babel/cli@7.28.3': - resolution: {integrity: sha512-n1RU5vuCX0CsaqaXm9I0KUCNKNQMy5epmzl/xdSSm70bSqhg9GWhgeosypyQLc0bK24+Xpk1WGzZlI9pJtkZdg==} + '@babel/cli@7.25.9': + resolution: {integrity: sha512-I+02IfrTiSanpxJBlZQYb18qCxB6c2Ih371cVpfgIrPQrjAYkf45XxomTJOG8JBWX5GY35/+TmhCMdJ4ZPkL8Q==} engines: {node: '>=6.9.0'} hasBin: true peerDependencies: @@ -854,6 +864,10 @@ packages: resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} engines: {node: '>=6.9.0'} + '@babel/core@7.26.0': + resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} + engines: {node: '>=6.9.0'} + '@babel/core@7.28.3': resolution: {integrity: sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==} engines: {node: '>=6.9.0'} @@ -884,6 +898,10 @@ packages: resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} engines: {node: '>=6.9.0'} + '@babel/helper-simple-access@7.27.1': + resolution: {integrity: sha512-OU4zVQrJgFBNXMjrHs1yFSdlTgufO4tefcUZoqNhukVfw0p8x1Asht/gcGZ3bpHbi8gu/76m4JhrlKPqkrs/WQ==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -905,14 +923,14 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-transform-export-namespace-from@7.27.1': - resolution: {integrity: sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==} + '@babel/plugin-transform-export-namespace-from@7.25.9': + resolution: {integrity: sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-commonjs@7.27.1': - resolution: {integrity: sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==} + '@babel/plugin-transform-modules-commonjs@7.25.9': + resolution: {integrity: sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -958,11 +976,11 @@ packages: '@changesets/changelog-git@0.2.1': resolution: {integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==} - '@changesets/changelog-github@0.5.1': - resolution: {integrity: sha512-BVuHtF+hrhUScSoHnJwTELB4/INQxVFc+P/Qdt20BLiBFIHFJDDUaGsZw+8fQeJTRP5hJZrzpt3oZWh0G19rAQ==} + '@changesets/changelog-github@0.5.0': + resolution: {integrity: sha512-zoeq2LJJVcPJcIotHRJEEA2qCqX0AQIeFE+L21L8sRLPVqDhSXY8ZWAt2sohtBpFZkBwu+LUwMSKRr2lMy3LJA==} - '@changesets/cli@2.29.6': - resolution: {integrity: sha512-6qCcVsIG1KQLhpQ5zE8N0PckIx4+9QlHK3z6/lwKnw7Tir71Bjw8BeOZaxA/4Jt00pcgCnCSWZnyuZf5Il05QQ==} + '@changesets/cli@2.27.9': + resolution: {integrity: sha512-q42a/ZbDnxPpCb5Wkm6tMVIxgeI9C/bexntzTeCFBrQEdpisQqk8kCHllYZMDjYtEc1ZzumbMJAG8H0Z4rdvjg==} hasBin: true '@changesets/config@3.1.1': @@ -1007,8 +1025,8 @@ packages: '@changesets/types@6.1.0': resolution: {integrity: sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==} - '@changesets/write@0.4.0': - resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} + '@changesets/write@0.3.2': + resolution: {integrity: sha512-kDxDrPNpUgsjDbWBvUo27PzKX4gqeKOlhibaOXDJA6kuBisGqNHv/HwGJrAu8U/dSf8ZEFIeHIPtvSlZI1kULw==} '@dependents/detective-less@5.0.1': resolution: {integrity: sha512-Y6+WUMsTFWE5jb20IFP4YGa5IrGY/+a/FbOSjDF/wz9gepU2hwCYSXRHP/vPwBvwcY3SVMASt4yXxbXNXigmZQ==} @@ -1281,15 +1299,6 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@inquirer/external-editor@1.0.1': - resolution: {integrity: sha512-Oau4yL24d2B5IL4ma4UpbQigkVhzPDXLoqy1ggK4gnHg/stmkffJE4oOXHXF3uz0UEpywG68KcyXsyYpA1Re/Q==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - '@isaacs/balanced-match@4.0.1': resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} engines: {node: 20 || >=22} @@ -1800,6 +1809,9 @@ packages: '@types/node@22.17.2': resolution: {integrity: sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w==} + '@types/node@22.9.0': + resolution: {integrity: sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==} + '@types/react-dom@19.1.9': resolution: {integrity: sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==} peerDependencies: @@ -2098,8 +2110,8 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chardet@2.1.0: - resolution: {integrity: sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==} + chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} check-error@2.1.1: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} @@ -2160,6 +2172,9 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cross-spawn@5.1.0: + resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -2401,10 +2416,18 @@ packages: extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + fast-check@3.23.2: resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} engines: {node: '>=8.0.0'} + fast-check@4.1.1: + resolution: {integrity: sha512-8+yQYeNYqBfWem0Nmm7BUnh27wm+qwGvI0xln60c8RPM5rVekxZf/Ildng2GNBfjaG6utIebFmVBPlNtZlBLxg==} + engines: {node: '>=12.17.0'} + fast-check@4.2.0: resolution: {integrity: sha512-buxrKEaSseOwFjt6K1REcGMeFOrb0wk3cXifeMAG8yahcE9kV20PjQn1OdzPGL6OBFTbYXfjleNBARf/aCfV1A==} engines: {node: '>=12.17.0'} @@ -2613,12 +2636,11 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - human-id@4.1.1: - resolution: {integrity: sha512-3gKm/gCSUipeLsRYZbbdA1BD83lBoWUkZ7G9VFrhWPAU76KwYo5KR8V28bpoPm/ygy0x5/GCbpRQdY7VLYCoIg==} - hasBin: true + human-id@1.0.2: + resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} ieee754@1.2.1: @@ -2892,6 +2914,9 @@ packages: resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==} engines: {node: 20 || >=22} + lru-cache@4.1.5: + resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -3063,6 +3088,10 @@ packages: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} + os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} @@ -3203,6 +3232,9 @@ packages: resolution: {integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==} engines: {node: '>=10'} + pseudomap@1.0.2: + resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} + punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} @@ -3358,10 +3390,18 @@ packages: resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} + shebang-command@1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} + shebang-regex@1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} @@ -3415,6 +3455,9 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + spawndamnit@2.0.0: + resolution: {integrity: sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==} + spawndamnit@3.0.1: resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} @@ -3506,8 +3549,8 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinybench@3.1.1: - resolution: {integrity: sha512-74pmf47HY/bHqamcCMGris+1AtGGsqTZ3Hc/UK4QvSmRuf/9PIF9753+c8XBh7JfX2r9KeZtVjOYjd6vFpc0qQ==} + tinybench@3.0.4: + resolution: {integrity: sha512-JMCuHaSJh6i1/8RMgZiRhA2KY/SiwnCxxGmoRz7onx69vDlh9YkbBFoi37WOssH+EccktzXYacTUtmIfdSqFTw==} engines: {node: '>=18.0.0'} tinyexec@0.3.2: @@ -3529,6 +3572,10 @@ packages: resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} engines: {node: '>=14.0.0'} + tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -3568,8 +3615,8 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - typedoc@0.28.10: - resolution: {integrity: sha512-zYvpjS2bNJ30SoNYfHSRaFpBMZAsL7uwKbWwqoCNFWjcPnI3e/mPLh2SneH9mX7SJxtDpvDgvd9/iZxGbo7daw==} + typedoc@0.28.9: + resolution: {integrity: sha512-aw45vwtwOl3QkUAmWCnLV9QW1xY+FSX2zzlit4MAfE99wX+Jij4ycnpbAWgBXsRrxmfs9LaYktg/eX5Bpthd3g==} engines: {node: '>= 18', pnpm: '>= 10'} hasBin: true peerDependencies: @@ -3593,6 +3640,9 @@ packages: underscore@1.13.7: resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==} + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -3722,6 +3772,10 @@ packages: resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} engines: {node: '>= 0.4'} + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -3763,6 +3817,9 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + yallist@2.1.2: + resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -3837,9 +3894,9 @@ snapshots: '@ark/util@0.49.0': {} - '@babel/cli@7.28.3(@babel/core@7.28.3)': + '@babel/cli@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.26.0 '@jridgewell/trace-mapping': 0.3.30 commander: 6.2.1 convert-source-map: 2.0.0 @@ -3859,6 +3916,26 @@ snapshots: '@babel/compat-data@7.28.0': {} + '@babel/core@7.26.0': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.26.0) + '@babel/helpers': 7.28.3 + '@babel/parser': 7.28.3 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 + convert-source-map: 2.0.0 + debug: 4.4.1 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/core@7.28.3': dependencies: '@ampproject/remapping': 2.3.0 @@ -3904,6 +3981,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-transforms@7.28.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.3)': dependencies: '@babel/core': 7.28.3 @@ -3915,6 +4001,13 @@ snapshots: '@babel/helper-plugin-utils@7.27.1': {} + '@babel/helper-simple-access@7.27.1': + dependencies: + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 + transitivePeerDependencies: + - supports-color + '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.27.1': {} @@ -3930,16 +4023,17 @@ snapshots: dependencies: '@babel/types': 7.28.2 - '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.28.3)': + '@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.28.3)': + '@babel/plugin-transform-modules-commonjs@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.28.3 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) + '@babel/core': 7.26.0 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.26.0) '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-simple-access': 7.27.1 transitivePeerDependencies: - supports-color @@ -4009,7 +4103,7 @@ snapshots: dependencies: '@changesets/types': 6.1.0 - '@changesets/changelog-github@0.5.1': + '@changesets/changelog-github@0.5.0': dependencies: '@changesets/get-github-info': 0.6.0 '@changesets/types': 6.1.0 @@ -4017,7 +4111,7 @@ snapshots: transitivePeerDependencies: - encoding - '@changesets/cli@2.29.6(@types/node@22.17.2)': + '@changesets/cli@2.27.9': dependencies: '@changesets/apply-release-plan': 7.0.12 '@changesets/assemble-release-plan': 6.0.9 @@ -4032,12 +4126,12 @@ snapshots: '@changesets/read': 0.6.5 '@changesets/should-skip-package': 0.1.2 '@changesets/types': 6.1.0 - '@changesets/write': 0.4.0 - '@inquirer/external-editor': 1.0.1(@types/node@22.17.2) + '@changesets/write': 0.3.2 '@manypkg/get-packages': 1.1.3 ansi-colors: 4.1.3 ci-info: 3.9.0 enquirer: 2.4.1 + external-editor: 3.1.0 fs-extra: 7.0.1 mri: 1.2.0 p-limit: 2.3.0 @@ -4045,10 +4139,8 @@ snapshots: picocolors: 1.1.1 resolve-from: 5.0.0 semver: 7.7.2 - spawndamnit: 3.0.1 + spawndamnit: 2.0.0 term-size: 2.2.1 - transitivePeerDependencies: - - '@types/node' '@changesets/config@3.1.1': dependencies: @@ -4132,11 +4224,11 @@ snapshots: '@changesets/types@6.1.0': {} - '@changesets/write@0.4.0': + '@changesets/write@0.3.2': dependencies: '@changesets/types': 6.1.0 fs-extra: 7.0.1 - human-id: 4.1.1 + human-id: 1.0.2 prettier: 2.8.8 '@dependents/detective-less@5.0.1': @@ -4344,13 +4436,6 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@inquirer/external-editor@1.0.1(@types/node@22.17.2)': - dependencies: - chardet: 2.1.0 - iconv-lite: 0.6.3 - optionalDependencies: - '@types/node': 22.17.2 - '@isaacs/balanced-match@4.0.1': {} '@isaacs/brace-expansion@5.0.0': @@ -4806,7 +4891,7 @@ snapshots: '@types/madge@5.0.3': dependencies: - '@types/node': 22.17.2 + '@types/node': 20.19.11 '@types/node@12.20.55': {} @@ -4817,6 +4902,11 @@ snapshots: '@types/node@22.17.2': dependencies: undici-types: 6.21.0 + optional: true + + '@types/node@22.9.0': + dependencies: + undici-types: 6.19.8 '@types/react-dom@19.1.9(@types/react@19.1.13)': dependencies: @@ -4968,7 +5058,7 @@ snapshots: std-env: 3.9.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/node@22.17.2)(@vitest/ui@3.2.4)(yaml@2.8.1) + vitest: 3.2.4(@types/node@22.9.0)(@vitest/ui@3.2.4)(yaml@2.8.1) transitivePeerDependencies: - supports-color @@ -4980,13 +5070,13 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@6.3.5(@types/node@22.17.2)(yaml@2.8.1))': + '@vitest/mocker@3.2.4(vite@6.3.5(@types/node@22.9.0)(yaml@2.8.1))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.18 optionalDependencies: - vite: 6.3.5(@types/node@22.17.2)(yaml@2.8.1) + vite: 6.3.5(@types/node@22.9.0)(yaml@2.8.1) '@vitest/pretty-format@3.2.4': dependencies: @@ -5017,7 +5107,7 @@ snapshots: sirv: 3.0.1 tinyglobby: 0.2.14 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/node@22.17.2)(@vitest/ui@3.2.4)(yaml@2.8.1) + vitest: 3.2.4(@types/node@22.9.0)(@vitest/ui@3.2.4)(yaml@2.8.1) '@vitest/utils@3.2.4': dependencies: @@ -5134,9 +5224,9 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 - babel-plugin-annotate-pure-calls@0.4.0(@babel/core@7.28.3): + babel-plugin-annotate-pure-calls@0.4.0(@babel/core@7.26.0): dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.26.0 balanced-match@1.0.2: {} @@ -5216,7 +5306,7 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chardet@2.1.0: {} + chardet@0.7.0: {} check-error@2.1.1: {} @@ -5271,6 +5361,12 @@ snapshots: convert-source-map@2.0.0: {} + cross-spawn@5.1.0: + dependencies: + lru-cache: 4.1.5 + shebang-command: 1.2.0 + which: 1.3.1 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -5578,10 +5674,20 @@ snapshots: extendable-error@0.1.7: {} + external-editor@3.1.0: + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + fast-check@3.23.2: dependencies: pure-rand: 6.1.0 + fast-check@4.1.1: + dependencies: + pure-rand: 7.0.1 + fast-check@4.2.0: dependencies: pure-rand: 7.0.1 @@ -5800,9 +5906,9 @@ snapshots: html-escaper@2.0.2: {} - human-id@4.1.1: {} + human-id@1.0.2: {} - iconv-lite@0.6.3: + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -6047,6 +6153,11 @@ snapshots: lru-cache@11.1.0: {} + lru-cache@4.1.5: + dependencies: + pseudomap: 1.0.2 + yallist: 2.1.2 + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -6223,6 +6334,8 @@ snapshots: strip-ansi: 6.0.1 wcwidth: 1.0.1 + os-tmpdir@1.0.2: {} + outdent@0.5.0: {} p-filter@2.1.0: @@ -6342,6 +6455,8 @@ snapshots: dependencies: parse-ms: 2.1.0 + pseudomap@1.0.2: {} + punycode.js@2.3.1: {} punycode@2.3.1: {} @@ -6504,10 +6619,16 @@ snapshots: functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 + shebang-command@1.2.0: + dependencies: + shebang-regex: 1.0.0 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 + shebang-regex@1.0.0: {} + shebang-regex@3.0.0: {} side-channel-list@1.0.0: @@ -6565,6 +6686,11 @@ snapshots: source-map@0.6.1: optional: true + spawndamnit@2.0.0: + dependencies: + cross-spawn: 5.1.0 + signal-exit: 3.0.7 + spawndamnit@3.0.1: dependencies: cross-spawn: 7.0.6 @@ -6655,7 +6781,7 @@ snapshots: tinybench@2.9.0: {} - tinybench@3.1.1: {} + tinybench@3.0.4: {} tinyexec@0.3.2: {} @@ -6670,6 +6796,10 @@ snapshots: tinyspy@4.0.3: {} + tmp@0.0.33: + dependencies: + os-tmpdir: 1.0.2 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -6705,7 +6835,7 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typedoc@0.28.10(typescript@5.9.2): + typedoc@0.28.9(typescript@5.9.2): dependencies: '@gerrit0/mini-shiki': 3.11.0 lunr: 2.3.9 @@ -6731,6 +6861,8 @@ snapshots: underscore@1.13.7: {} + undici-types@6.19.8: {} + undici-types@6.21.0: {} undici@6.21.3: {} @@ -6753,13 +6885,13 @@ snapshots: optionalDependencies: typescript: 5.9.2 - vite-node@3.2.4(@types/node@22.17.2)(yaml@2.8.1): + vite-node@3.2.4(@types/node@22.9.0)(yaml@2.8.1): dependencies: cac: 6.7.14 debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 6.3.5(@types/node@22.17.2)(yaml@2.8.1) + vite: 6.3.5(@types/node@22.9.0)(yaml@2.8.1) transitivePeerDependencies: - '@types/node' - jiti @@ -6787,11 +6919,24 @@ snapshots: fsevents: 2.3.3 yaml: 2.8.1 - vitest@3.2.4(@types/node@22.17.2)(@vitest/ui@3.2.4)(yaml@2.8.1): + vite@6.3.5(@types/node@22.9.0)(yaml@2.8.1): + dependencies: + esbuild: 0.25.9 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.48.0 + tinyglobby: 0.2.14 + optionalDependencies: + '@types/node': 22.9.0 + fsevents: 2.3.3 + yaml: 2.8.1 + + vitest@3.2.4(@types/node@22.9.0)(@vitest/ui@3.2.4)(yaml@2.8.1): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@22.17.2)(yaml@2.8.1)) + '@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@22.9.0)(yaml@2.8.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -6809,11 +6954,11 @@ snapshots: tinyglobby: 0.2.14 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 6.3.5(@types/node@22.17.2)(yaml@2.8.1) - vite-node: 3.2.4(@types/node@22.17.2)(yaml@2.8.1) + vite: 6.3.5(@types/node@22.9.0)(yaml@2.8.1) + vite-node: 3.2.4(@types/node@22.9.0)(yaml@2.8.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.17.2 + '@types/node': 22.9.0 '@vitest/ui': 3.2.4(vitest@3.2.4) transitivePeerDependencies: - jiti @@ -6867,6 +7012,10 @@ snapshots: gopd: 1.2.0 has-tostringtag: 1.0.2 + which@1.3.1: + dependencies: + isexe: 2.0.0 + which@2.0.2: dependencies: isexe: 2.0.0 @@ -6896,6 +7045,8 @@ snapshots: y18n@5.0.8: {} + yallist@2.1.2: {} + yallist@3.1.1: {} yaml@2.8.1: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6859b191..8d6ec9ac 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -4,26 +4,28 @@ packages: - packages/*/ catalog: - '@ark/attest': ^0.44.8 - '@babel/cli': ^7.25.9 - '@babel/core': ^7.26.0 - '@babel/plugin-transform-export-namespace-from': ^7.25.9 - '@babel/plugin-transform-modules-commonjs': ^7.25.9 - '@changesets/changelog-github': ^0.5.0 - '@changesets/cli': ^2.27.9 + '@ark/attest': 0.44.8 + '@babel/cli': 7.25.9 + '@babel/core': 7.26.0 + '@babel/plugin-transform-export-namespace-from': 7.25.9 + '@babel/plugin-transform-modules-commonjs': 7.25.9 + '@changesets/changelog-github': 0.5.0 + '@changesets/cli': 2.27.9 '@prettier/sync': 0.5.5 '@sinclair/typebox': 0.34.40 - '@types/lodash.isequal': ^4.5.8 - '@types/madge': ^5.0.3 - '@types/node': ^22.9.0 + '@types/lodash.isequal': 4.5.8 + '@types/madge': 5.0.3 + '@types/node': 22.9.0 '@vitest/coverage-v8': 3.2.4 '@vitest/ui': 3.2.4 arktype: 2.1.20 - babel-plugin-annotate-pure-calls: ^0.4.0 - fast-check: ^4.1.1 - lodash.isequal: ^4.5.0 - madge: ^8.0.0 - tinybench: ^3.0.4 + babel-plugin-annotate-pure-calls: 0.4.0 + fast-check: 4.1.1 + graphql: 16.11.0 + lodash.isequal: 4.5.0 + madge: 8.0.0 + tinybench: 3.0.4 + typedoc: 0.28.9 typescript: 5.9.2 valibot: 1.1.0 vitest: 3.2.4 From 6c2a6a0953efc6d4921e4d407ca140cbc0c98f6e Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 27 Sep 2025 23:45:36 -0500 Subject: [PATCH 16/32] chore(registry): simplifies HKT encoding --- packages/registry/src/exports.ts | 1 + packages/registry/src/has.ts | 1 + packages/registry/src/hkt.ts | 17 ++++++++--------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/registry/src/exports.ts b/packages/registry/src/exports.ts index 5d50957b..24f525f1 100644 --- a/packages/registry/src/exports.ts +++ b/packages/registry/src/exports.ts @@ -56,6 +56,7 @@ export { stringifyKey, stringifyLiteral, } from './parse.js' +export { topologicalSort } from './topological-sort.js' export type { GlobalOptions, OptionalTreatment, SchemaOptions } from './options.js' export type { GlobalConfig, SchemaConfig } from './config.js' diff --git a/packages/registry/src/has.ts b/packages/registry/src/has.ts index d49b478e..cef8d427 100644 --- a/packages/registry/src/has.ts +++ b/packages/registry/src/has.ts @@ -71,6 +71,7 @@ export declare namespace has { */ export function has(...params: [...KS]): (u: unknown) => u is has export function has(...params: [...KS, (u: unknown) => u is T]): (u: unknown) => u is has +export function has(...params: [...KS, (u: unknown) => boolean]): (u: unknown) => u is has // impl. export function has( ...args: diff --git a/packages/registry/src/hkt.ts b/packages/registry/src/hkt.ts index 0daec364..d6414b62 100644 --- a/packages/registry/src/hkt.ts +++ b/packages/registry/src/hkt.ts @@ -1,15 +1,14 @@ -import type { newtype } from './newtype.js' import type { Either } from './either.js' import type { Option } from './option.js' import type { Tuple } from './tuple.js' -import { typeclass as kind } from './symbol.js' +import { typeclass } from './symbol.js' -export interface HKT extends newtype<{ [0]: I, [-1]: O }> {} -export interface HKT2 extends newtype<{ [0]: { [0]: A, [1]: B }, [-1]: Z }> {} +export interface HKT { [0]: I, [-1]: O } +export interface HKT2 { [0]: { [0]: A, [1]: B }, [-1]: Z } export type Kind = (F & { [0]: T })[-1] export type Kind2 = (F & { [0]: { [0]: S, [1]: T } })[-1] -export type Bind = { [kind]?: T } +export type Bind = { [typeclass]?: T } export type Box = Partial, T = unknown> = (F & { [0]: T })[-1] export type Boxed = Box.infer @@ -37,7 +36,7 @@ export declare namespace Kind { export { Any as any, Box as lax } } export declare namespace Kind { type Product = Box, T>> type Coproduct = Box, T>> - type infer = Exclude + type infer = Exclude type of = F extends Kind.apply ? T : never type Any = Bind /** @ts-expect-error */ @@ -49,9 +48,9 @@ export type Identity = T export interface Comparator extends Bind { (left: T, right: T): number } export interface Equal extends Bind { (left: T, right: T): boolean } export interface Record extends Bind { [x: string]: T } -export interface Array extends newtype, Bind {} -export interface ReadonlyArray extends newtype, Bind {} -export interface Duplicate extends newtype<{ [0]: T, [1]: T }>, Bind { [Symbol.iterator](): Iterator } +export interface Array extends globalThis.Array, Bind {} +export interface ReadonlyArray extends globalThis.ReadonlyArray, Bind {} +export interface Duplicate extends Bind { [Symbol.iterator](): Iterator, [0]: T, [1]: T } export declare namespace Type { export { From 1b4f204fe8f6e0fe19dd48a3b2e71256b350027e Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 27 Sep 2025 23:46:11 -0500 Subject: [PATCH 17/32] optimize(arktype-test): improves type-level performance of Seed/Gen --- packages/arktype-test/src/generator-bounds.ts | 40 +++++++++---------- packages/arktype-test/src/generator.ts | 7 ++-- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/packages/arktype-test/src/generator-bounds.ts b/packages/arktype-test/src/generator-bounds.ts index ea2049bf..b5ef548e 100644 --- a/packages/arktype-test/src/generator-bounds.ts +++ b/packages/arktype-test/src/generator-bounds.ts @@ -1,6 +1,5 @@ import * as fc from 'fast-check' -import type { newtype } from '@traversable/registry' import { fn, Number_isFinite, Number_isNatural, Object_is } from '@traversable/registry' /** @internal */ @@ -17,8 +16,8 @@ export const defaultDoubleConstraints = { // const defaultIntBounds = [-0x1000, +0x1000, null] satisfies Bounds_int const defaultBigIntBounds = [-0x1000000n, 0x1000000n, null] satisfies Bounds_bigint const defaultNumberBounds = [-0x10000, +0x10000, null, false, false] satisfies Bounds_number -const defaultStringBounds = [0, +0x40] satisfies Bounds_string -const defaultArrayBounds = [0, +0x10] satisfies Bounds_array +const defaultStringBounds = [0, +0x40, null] satisfies Bounds_string +const defaultArrayBounds = [0, +0x10, null] satisfies Bounds_array export const defaults = { // int: defaultIntBounds, @@ -68,11 +67,11 @@ const clampArrayMax = clampMax(defaults.array[0], defaults.array[1], Number_isNa export const makeInclusiveBounds = (model: fc.Arbitrary) => ({ minimum: model, maximum: model }) // export { Bounds_int as int } -// interface Bounds_int extends newtype<[ +// type Bounds_int = [ // minimum: number | null, // maximum: number | null, // multipleOf: number | null, -// ]> {} +// ] // const Bounds_int // : (model: fc.Arbitrary) => fc.Arbitrary @@ -84,11 +83,11 @@ export const makeInclusiveBounds = (model: fc.Arbitrary) => ({ minimum: mo // ]) export { Bounds_bigint as bigint } -interface Bounds_bigint extends newtype<[ +type Bounds_bigint = [ minimum: bigint | null, maximum: bigint | null, multipleOf: bigint | null, -]> {} +] const Bounds_bigint : (model: fc.Arbitrary) => fc.Arbitrary @@ -99,28 +98,28 @@ const Bounds_bigint ]) export { Bounds_string as string } -interface Bounds_string extends newtype<[ +type Bounds_string = [ minLength: number | null, maxLength: number | null, -]> {} + exactLength: number | null, +] const Bounds_string : (model: fc.Arbitrary) => fc.Arbitrary = (model) => fc.tuple(nullable(model), nullable(model), nullable(model)).map( ([x, y, length]) => Number_isNatural(length) - ? [null, null] satisfies [any, any] - // [clampString(length), clampString(length)] - : [clampStringMin(x, y), clampStringMax(y, x)] + ? [null, null, null] satisfies Bounds_string + : [clampStringMin(x, y), clampStringMax(y, x), null] satisfies Bounds_string ) export { Bounds_number as number } -interface Bounds_number extends newtype<[ +type Bounds_number = [ minimum: number | null, maximum: number | null, multipleOf: number | null, exclusiveMinimum: boolean, exclusiveMaximum: boolean, -]> {} +] const deltaIsSubEpsilon = (x: number, y: number) => Math.abs(x - y) < Number.EPSILON @@ -153,10 +152,11 @@ const Bounds_number ) export { Bounds_array as array } -interface Bounds_array extends newtype<[ +type Bounds_array = [ minLength: number | null, maxLength: number | null, -]> {} + exactLength: number | null, +] const Bounds_array : (model: fc.Arbitrary) => fc.Arbitrary @@ -166,8 +166,8 @@ const Bounds_array fc.constant(null) ).map(([x, y, exactLength]) => Number_isNatural(exactLength) - ? [null, null] - : [clampArrayMin(x, y), clampArrayMax(y, x)] + ? [null, null, exactLength] satisfies Bounds_array + : [clampArrayMin(x, y), clampArrayMax(y, x), null] satisfies Bounds_array ) @@ -184,7 +184,7 @@ const Bounds_array export const bigintBoundsToBigIntConstraints : (bounds?: Bounds_bigint) => fc.BigIntConstraints = (bounds = defaultBigIntBounds) => { - const [min, max, multipleOf] = bounds + const [min, max, _multipleOf] = bounds return { max: max ?? void 0, min: min ?? void 0, @@ -207,7 +207,7 @@ export const numberBoundsToDoubleConstraints export const stringBoundsToStringConstraints : (bounds?: Bounds_string) => fc.StringConstraints - = ([minLength, maxLength] = defaultStringBounds) => ({ + = ([minLength, maxLength, exactLength] = defaultStringBounds) => ({ minLength: minLength ?? void 0, maxLength: maxLength ?? void 0 }) satisfies fc.StringConstraints diff --git a/packages/arktype-test/src/generator.ts b/packages/arktype-test/src/generator.ts index 58c260e3..caabe698 100644 --- a/packages/arktype-test/src/generator.ts +++ b/packages/arktype-test/src/generator.ts @@ -1,7 +1,7 @@ import { type } from 'arktype' import * as fc from 'fast-check' -import type { newtype, inline } from '@traversable/registry' +import type { inline } from '@traversable/registry' import { Array_isArray, fn, @@ -120,7 +120,7 @@ export interface SeedBuilder { (tie: fc.LetrecTypedTie, $: Config.byTypeName[K]): fc.Arbitrary } -export interface SeedMap extends newtype<{ [K in keyof Seed]: SeedBuilder }> {} +export type SeedMap = { [K in keyof Seed]: SeedBuilder } export const SeedMap = { ...TerminalMap, ...BoundableMap, @@ -262,7 +262,8 @@ export declare namespace Gen { type Base = { [K in keyof T]: (tie: fc.LetrecLooselyTypedTie, constraints: $[K & keyof $]) => fc.Arbitrary } type Values = never | T[Exclude] type InferArb = S extends fc.Arbitrary ? T : never - interface Builder extends newtype { ['*']: fc.Arbitrary>> } + type Builder = T & BuilderStar + interface BuilderStar { ['*']: fc.Arbitrary>> } type BuildBuilder, Out extends {} = BuilderBase> = never | Builder type BuilderBase, $ extends ParseOptions = ParseOptions> = never | & ([$['root']] extends [never] ? unknown : { root: fc.Arbitrary<$['root']> }) From 914ed6b97af471b3f6ba97bafc9094e3c17530cc Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 27 Sep 2025 23:46:39 -0500 Subject: [PATCH 18/32] optimize(json-schema-types,json-schema-test): improves type-level performance of Seed/Gen/Functor --- .../json-schema-test/src/generator-bounds.ts | 17 +++++------ packages/json-schema-test/src/generator.ts | 29 ++++++++++--------- packages/json-schema-types/src/to-type.ts | 2 +- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/packages/json-schema-test/src/generator-bounds.ts b/packages/json-schema-test/src/generator-bounds.ts index 6fe8278c..420208de 100644 --- a/packages/json-schema-test/src/generator-bounds.ts +++ b/packages/json-schema-test/src/generator-bounds.ts @@ -1,6 +1,5 @@ import * as fc from 'fast-check' -import type { newtype } from '@traversable/registry' import { fn, Number_isFinite, Number_isNatural, Number_isSafeInteger, Object_is } from '@traversable/registry' /** @internal */ @@ -61,11 +60,11 @@ const clampArrayMax = clampMax(defaults.array[0], defaults.array[1], Number_isNa export const makeInclusiveBounds = (model: fc.Arbitrary) => ({ minimum: model, maximum: model }) export { Bounds_int as int } -interface Bounds_int extends newtype<[ +type Bounds_int = [ minimum: number | null, maximum: number | null, multipleOf: number | null, -]> {} +] const Bounds_int : (model: fc.Arbitrary) => fc.Arbitrary @@ -77,10 +76,10 @@ const Bounds_int ]) export { Bounds_string as string } -interface Bounds_string extends newtype<[ +type Bounds_string = [ minLength: number | null, maxLength: number | null, -]> {} +] const Bounds_string : (model: fc.Arbitrary) => fc.Arbitrary @@ -92,13 +91,13 @@ const Bounds_string ) export { Bounds_number as number } -interface Bounds_number extends newtype<[ +type Bounds_number = [ minimum: number | null, maximum: number | null, multipleOf: number | null, exclusiveMinimum: boolean, exclusiveMaximum: boolean, -]> {} +] const deltaIsSubEpsilon = (x: number, y: number) => Math.abs(x - y) < Number.EPSILON @@ -131,10 +130,10 @@ const Bounds_number ) export { Bounds_array as array } -interface Bounds_array extends newtype<[ +type Bounds_array = [ minLength: number | null, maxLength: number | null, -]> {} +] const Bounds_array : (model: fc.Arbitrary) => fc.Arbitrary diff --git a/packages/json-schema-test/src/generator.ts b/packages/json-schema-test/src/generator.ts index 026ae051..00e45c82 100644 --- a/packages/json-schema-test/src/generator.ts +++ b/packages/json-schema-test/src/generator.ts @@ -1,6 +1,6 @@ import * as fc from 'fast-check' -import type { newtype, inline } from '@traversable/registry' +import type { inline } from '@traversable/registry' import { Array_isArray, fn, @@ -122,7 +122,7 @@ export interface SeedBuilder { (tie: fc.LetrecTypedTie, $: Config.byTypeName[K]): fc.Arbitrary } -export interface SeedMap extends newtype<{ [K in keyof Seed]: SeedBuilder }> {} +export type SeedMap = { [K in keyof Seed]: SeedBuilder } export const SeedMap = { ...TerminalMap, ...BoundableMap, @@ -174,17 +174,17 @@ export function JsonSchema_Number(bounds: Bounds.number = Bounds.defaults.number } export function JsonSchema_String(bounds: Bounds.string = Bounds.defaults.string): JsonSchema.String { - const [min, max, exactLength] = bounds + const [min, max] = bounds let schema: JsonSchema.String = { type: 'string' } - if (Number_isNatural(exactLength)) { - schema.minLength = exactLength - schema.maxLength = exactLength - return schema - } else { - if (Number_isNatural(min)) schema.minLength = min - if (Number_isNatural(max)) schema.maxLength = max - return schema - } + // if (Number_isNatural(exactLength)) { + // schema.minLength = exactLength + // schema.maxLength = exactLength + // return schema + // } else { + // } + if (Number_isNatural(min)) schema.minLength = min + if (Number_isNatural(max)) schema.maxLength = max + return schema } export function JsonSchema_Array( @@ -251,7 +251,8 @@ export declare namespace Gen { type Base = { [K in keyof T]: (tie: fc.LetrecLooselyTypedTie, constraints: $[K & keyof $]) => fc.Arbitrary } type Values = never | T[Exclude] type InferArb = S extends fc.Arbitrary ? T : never - interface Builder extends newtype { ['*']: fc.Arbitrary>> } + type Builder = T & BuilderStar + interface BuilderStar { ['*']: fc.Arbitrary>> } type BuildBuilder, Out extends {} = BuilderBase> = never | Builder type BuilderBase, $ extends ParseOptions = ParseOptions> = never | & ([$['root']] extends [never] ? unknown : { root: fc.Arbitrary<$['root']> }) @@ -473,7 +474,7 @@ export const SeedInvalidDataGenerator = fn.pipe( export const SchemaGenerator = fn.flow( SeedGenerator, builder => builder['*'], - (arb) => arb.map(seedToSchema), + (model) => model.map((x) => seedToSchema(x as never)), ) export declare namespace SchemaGenerator { diff --git a/packages/json-schema-types/src/to-type.ts b/packages/json-schema-types/src/to-type.ts index 651c9bb0..909f69bd 100644 --- a/packages/json-schema-types/src/to-type.ts +++ b/packages/json-schema-types/src/to-type.ts @@ -21,7 +21,7 @@ const defaultIndex = { } satisfies FoldIndex function fold(schema: JsonSchema, index: FoldIndex) { - return F.fold((x, ix) => { + return F.fold((x) => { switch (true) { default: return x satisfies never case JsonSchema.isRef(x): return index.canonizeRefName(x.$ref) From f6c1eafc332f76397fbf131869bcee906f7eda9f Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 27 Sep 2025 23:47:07 -0500 Subject: [PATCH 19/32] optimize(schema): improves type-level performance of Seed/Gen --- packages/schema/test/seed.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/schema/test/seed.ts b/packages/schema/test/seed.ts index 89614f03..63915222 100644 --- a/packages/schema/test/seed.ts +++ b/packages/schema/test/seed.ts @@ -105,10 +105,10 @@ const isComposite = (x: unknown) => Array_isArray(x) || (x !== null && typeof x /** @internal */ const isNumeric = (x: unknown) => typeof x === 'number' || typeof x === 'bigint' -interface SeedWithRoot< +type SeedWithRoot< Root extends keyof Builder, T extends Partial -> extends T.newtype { +> = T & { tree: T[keyof T] root: fc.Arbitrary } From 538b83017e975defafc4d398d61534040dd2f7ec Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 27 Sep 2025 23:48:03 -0500 Subject: [PATCH 20/32] optimize(typebox-test,typebox-types): improves type-level performance of Seed/Gen --- packages/typebox-test/src/generator-bounds.ts | 52 ++++++++++--------- packages/typebox-test/src/generator.ts | 47 +++++++++-------- packages/typebox-types/src/to-type.ts | 32 +++--------- 3 files changed, 58 insertions(+), 73 deletions(-) diff --git a/packages/typebox-test/src/generator-bounds.ts b/packages/typebox-test/src/generator-bounds.ts index 329bdb14..1f9e110a 100644 --- a/packages/typebox-test/src/generator-bounds.ts +++ b/packages/typebox-test/src/generator-bounds.ts @@ -1,12 +1,14 @@ import * as fc from 'fast-check' +import { + fn, + Number_isFinite, + Number_isNatural, + Number_isSafeInteger, + Object_is, +} from '@traversable/registry' -import type { newtype } from '@traversable/registry' -import { fn, Number_isFinite, Number_isNatural, Number_isSafeInteger, Object_is } from '@traversable/registry' - -/** @internal */ const nullable = (model: fc.Arbitrary) => fc.oneof(fc.constant(null), fc.constant(null), model) -/** @internal */ const isBigInt = (x: unknown) => typeof x === 'bigint' export const defaultDoubleConstraints = { @@ -17,8 +19,8 @@ export const defaultDoubleConstraints = { const defaultIntBounds = [-0x1000, +0x1000, null] satisfies Bounds_int const defaultBigIntBounds = [-0x1000000n, 0x1000000n, null] satisfies Bounds_bigint const defaultNumberBounds = [-0x10000, +0x10000, null, false, false] satisfies Bounds_number -const defaultStringBounds = [0, +0x40] satisfies Bounds_string -const defaultArrayBounds = [0, +0x10] satisfies Bounds_array +const defaultStringBounds = [0, +0x40, null] satisfies Bounds_string +const defaultArrayBounds = [0, +0x10, null] satisfies Bounds_array export const defaults = { int: defaultIntBounds, @@ -68,11 +70,11 @@ const clampArrayMax = clampMax(defaults.array[0], defaults.array[1], Number_isNa export const makeInclusiveBounds = (model: fc.Arbitrary) => ({ minimum: model, maximum: model }) export { Bounds_int as int } -interface Bounds_int extends newtype<[ +type Bounds_int = [ minimum: number | null, maximum: number | null, multipleOf: number | null, -]> {} +] const Bounds_int : (model: fc.Arbitrary) => fc.Arbitrary @@ -84,11 +86,11 @@ const Bounds_int ]) export { Bounds_bigint as bigint } -interface Bounds_bigint extends newtype<[ +type Bounds_bigint = [ minimum: bigint | null, maximum: bigint | null, multipleOf: bigint | null, -]> {} +] const Bounds_bigint : (model: fc.Arbitrary) => fc.Arbitrary @@ -99,28 +101,29 @@ const Bounds_bigint ]) export { Bounds_string as string } -interface Bounds_string extends newtype<[ +type Bounds_string = [ minLength: number | null, maxLength: number | null, -]> {} + exactLength: number | null, +] const Bounds_string : (model: fc.Arbitrary) => fc.Arbitrary = (model) => fc.tuple(nullable(model), nullable(model), nullable(model)).map( ([x, y, length]) => Number_isNatural(length) - ? [null, null] satisfies [any, any] + ? [null, null, length] satisfies Bounds_string // [clampString(length), clampString(length)] - : [clampStringMin(x, y), clampStringMax(y, x)] - ) + : [clampStringMin(x, y), clampStringMax(y, x), null] satisfies Bounds_string + ) export { Bounds_number as number } -interface Bounds_number extends newtype<[ +type Bounds_number = [ minimum: number | null, maximum: number | null, multipleOf: number | null, exclusiveMinimum: boolean, exclusiveMaximum: boolean, -]> {} +] const deltaIsSubEpsilon = (x: number, y: number) => Math.abs(x - y) < Number.EPSILON @@ -153,10 +156,11 @@ const Bounds_number ) export { Bounds_array as array } -interface Bounds_array extends newtype<[ +type Bounds_array = [ minLength: number | null, maxLength: number | null, -]> {} + exactLength: number | null, +] const Bounds_array : (model: fc.Arbitrary) => fc.Arbitrary @@ -166,8 +170,8 @@ const Bounds_array fc.constant(null) ).map(([x, y, exactLength]) => Number_isNatural(exactLength) - ? [null, null] - : [clampArrayMin(x, y), clampArrayMax(y, x)] + ? [null, null, exactLength] satisfies Bounds_array + : [clampArrayMin(x, y), clampArrayMax(y, x), null] satisfies Bounds_array ) @@ -207,9 +211,9 @@ export const numberBoundsToDoubleConstraints export const stringBoundsToStringConstraints : (bounds?: Bounds_string) => fc.StringConstraints - = ([minLength, maxLength] = defaultStringBounds) => ({ + = ([minLength, maxLength, _exactLength] = defaultStringBounds) => ({ minLength: minLength ?? void 0, - maxLength: maxLength ?? void 0 + maxLength: maxLength ?? void 0, }) satisfies fc.StringConstraints export const arrayBoundsToArrayConstraints diff --git a/packages/typebox-test/src/generator.ts b/packages/typebox-test/src/generator.ts index 55505c6a..61e5b4f0 100644 --- a/packages/typebox-test/src/generator.ts +++ b/packages/typebox-test/src/generator.ts @@ -1,7 +1,7 @@ import * as T from '@sinclair/typebox' import * as fc from 'fast-check' -import type { newtype, inline } from '@traversable/registry' +import type { inline } from '@traversable/registry' import { Array_isArray, fn, @@ -95,7 +95,7 @@ export interface SeedBuilder { (tie: fc.LetrecTypedTie, $: Config.byTypeName[K]): fc.Arbitrary } -export interface SeedMap extends newtype<{ [K in keyof Seed]: SeedBuilder }> {} +export type SeedMap = { [K in keyof Seed]: SeedBuilder } export const SeedMap = { ...TerminalMap, ...BoundableMap, @@ -164,33 +164,33 @@ export const T_number export const T_string : (bounds?: Bounds.string) => T.TString = (bounds = Bounds.defaults.string) => { - const [min, max, exactLength] = bounds + const [min, max, _exactLength] = bounds let options: Parameters[0] = {} - if (Number_isNatural(exactLength)) { - options.minLength = exactLength - options.maxLength = exactLength - return T.String(options) - } else { - if (Number_isNatural(min)) options.minLength = min - if (Number_isNatural(max)) options.maxLength = max - return T.String(options) - } + // if (Number_isNatural(exactLength)) { + // options.minLength = exactLength + // options.maxLength = exactLength + // return T.String(options) + // } else { + if (Number_isNatural(min)) options.minLength = min + if (Number_isNatural(max)) options.maxLength = max + return T.String(options) + // } } export const T_array : (items: T, bounds?: Bounds.array) => T.TArray = (items, bounds = Bounds.defaults.array) => { - const [min, max, exactLength] = bounds + const [min, max, _exactLength] = bounds let schema = T.Array(items) - if (Number_isNatural(exactLength)) { - schema.maxItems = exactLength - schema.minItems = exactLength - return schema - } else { - if (Number_isNatural(min)) schema.minItems = min - if (Number_isNatural(max)) schema.maxItems = max - return schema - } + // if (Number_isNatural(exactLength)) { + // schema.maxItems = exactLength + // schema.minItems = exactLength + // return schema + // } else { + if (Number_isNatural(min)) schema.minItems = min + if (Number_isNatural(max)) schema.maxItems = max + return schema + // } } const unboundedSeed = { @@ -249,7 +249,8 @@ export declare namespace Gen { type Base = { [K in keyof T]: (tie: fc.LetrecLooselyTypedTie, constraints: $[K & keyof $]) => fc.Arbitrary } type Values = never | T[Exclude] type InferArb = S extends fc.Arbitrary ? T : never - interface Builder extends newtype { ['*']: fc.Arbitrary>> } + type Builder = T & BuilderStar + interface BuilderStar { ['*']: fc.Arbitrary>> } type BuildBuilder, Out extends {} = BuilderBase> = never | Builder type BuilderBase, $ extends ParseOptions = ParseOptions> = never | & ([$['root']] extends [never] ? unknown : { root: fc.Arbitrary<$['root']> }) diff --git a/packages/typebox-types/src/to-type.ts b/packages/typebox-types/src/to-type.ts index 8424efe3..bdcd21d0 100644 --- a/packages/typebox-types/src/to-type.ts +++ b/packages/typebox-types/src/to-type.ts @@ -10,17 +10,10 @@ import * as F from './functor.js' function canBeInterface(x: unknown): boolean { return F.tagged('object')(x) - || F.tagged('array')(x) - || F.tagged('record')(x) - || F.tagged('tuple')(x) - || F.tagged('allOf')(x) -} - -function needsNewtype(x: unknown): boolean { - return F.tagged('object')(x) - || F.tagged('record')(x) - || F.tagged('tuple')(x) - || F.tagged('allOf')(x) + // || F.tagged('array')(x) + // || F.tagged('record')(x) + // || F.tagged('tuple')(x) + // || F.tagged('allOf')(x) } const algebra = F.fold((x, ix, input) => { @@ -110,15 +103,9 @@ export function toType(schematic: Partial, options?: toType.Options): const $ = parseOptions(options) let TYPE = algebra(schematic as never) if (TYPE.startsWith('(') && TYPE.endsWith(')')) TYPE = TYPE.slice(1, -1) - const NEWTYPE = !$.includeNewtypeDeclaration ? null : [ - `// @ts-expect-error: newtype hack`, - `interface newtype extends T {}`, - ].join('\n') return $.typeName === undefined ? TYPE - : $.preferInterface && canBeInterface(schematic) ? [ - needsNewtype(schematic) ? NEWTYPE : null, - `interface ${$.typeName} extends ${needsNewtype(schematic) ? `newtype<${TYPE}>` : TYPE} {}` - ].filter((_) => _ !== null).join('\n') + : $.preferInterface && canBeInterface(schematic) + ? `interface ${$.typeName} ${TYPE}` : `type ${$.typeName} = ${TYPE}` } @@ -127,7 +114,6 @@ function parseOptions(options: toType.Options = {}): Partial interface MyType { a: number } */ preferInterface: boolean - /** - * ## {@link optionsWithInterface.includeNewtypeDeclaration `toType.Options.includeNewtypeDeclaration`} - * - * Default: true - */ - includeNewtypeDeclaration?: boolean } declare const optionsWithOptionalTypeName: { From f97c7cc01b92698d8809b41423157830e5ad9300 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 27 Sep 2025 23:48:23 -0500 Subject: [PATCH 21/32] optimize(valibot,valibot-test,valibot-types): improves type-level performance of Seed/Gen/Functor --- packages/valibot-test/src/generator.ts | 7 ++-- packages/valibot-types/src/functor.ts | 2 - packages/valibot-types/src/to-string.ts | 1 - packages/valibot/src/to-type.ts | 46 +++++----------------- packages/valibot/test/to-type.fuzz.test.ts | 6 +-- 5 files changed, 16 insertions(+), 46 deletions(-) diff --git a/packages/valibot-test/src/generator.ts b/packages/valibot-test/src/generator.ts index 58f7bc31..0e517ca8 100644 --- a/packages/valibot-test/src/generator.ts +++ b/packages/valibot-test/src/generator.ts @@ -2,7 +2,7 @@ import * as v from 'valibot' import * as fc from 'fast-check' import type { LowerBound } from '@traversable/valibot-types' -import type { newtype, inline } from '@traversable/registry' +import type { inline } from '@traversable/registry' import { Array_isArray, fn, @@ -157,7 +157,7 @@ export interface SeedBuilder { (tie: fc.LetrecTypedTie, $: Config.byTypeName[K]): fc.Arbitrary } -export interface SeedMap extends newtype<{ [K in keyof Seed]: SeedBuilder }> {} +export type SeedMap = { [K in keyof Seed]: SeedBuilder } export const SeedMap = { ...TerminalMap, ...BoundableMap, @@ -290,7 +290,8 @@ export declare namespace Gen { type Base = { [K in keyof T]: (tie: fc.LetrecLooselyTypedTie, constraints: $[K & keyof $]) => fc.Arbitrary } type Values = never | T[Exclude] type InferArb = S extends fc.Arbitrary ? T : never - interface Builder extends newtype { ['*']: fc.Arbitrary>> } + type Builder = T & BuilderStar + interface BuilderStar { ['*']: fc.Arbitrary>> } type BuildBuilder, Out extends {} = BuilderBase> = never | Builder type BuilderBase, $ extends ParseOptions = ParseOptions> = never | & ([$['root']] extends [never] ? unknown : { root: fc.Arbitrary<$['root']> }) diff --git a/packages/valibot-types/src/functor.ts b/packages/valibot-types/src/functor.ts index 98df8a75..d13b12e9 100644 --- a/packages/valibot-types/src/functor.ts +++ b/packages/valibot-types/src/functor.ts @@ -79,7 +79,6 @@ export declare namespace V { | V.Undefined | V.Unknown | V.Void - /** @deprecated */ | V.Promise type Unary = | V.Array @@ -177,7 +176,6 @@ export declare namespace V { interface LooseObject { type: Tag['looseObject'], entries: { [x: string]: S } } interface ObjectWithRest { type: Tag['objectWithRest'], entries: { [x: string]: S }, rest: S } interface StrictObject { type: Tag['strictObject'], entries: { [x: string]: S } } - /** @deprecated */ interface Promise { type: Tag['promise'] } } diff --git a/packages/valibot-types/src/to-string.ts b/packages/valibot-types/src/to-string.ts index aa8e1266..a9a56129 100644 --- a/packages/valibot-types/src/to-string.ts +++ b/packages/valibot-types/src/to-string.ts @@ -1,4 +1,3 @@ -import type * as v from 'valibot' import * as F from './functor.js' import { fn, Object_entries, parseKey } from '@traversable/registry' import { tagged } from './utils.js' diff --git a/packages/valibot/src/to-type.ts b/packages/valibot/src/to-type.ts index ae20487a..653a3886 100644 --- a/packages/valibot/src/to-type.ts +++ b/packages/valibot/src/to-type.ts @@ -61,12 +61,6 @@ export type WithInterface = { * // => interface MyType { a: number } */ preferInterface: boolean - /** - * ## {@link WithInterface `toType.Options.includeNewtypeDeclaration`} - * - * @default true - */ - includeNewtypeDeclaration?: boolean /** * ## {@link WithInterface `toType.Options.preserveJsDocs`} * @@ -103,28 +97,12 @@ function canBeInterface(x: unknown): boolean { || tagged('objectWithRest', x) || tagged('looseObject', x) || tagged('strictObject', x) - || tagged('array', x) - || tagged('record', x) - || tagged('tuple', x) - || tagged('looseTuple', x) - || tagged('strictTuple', x) - || tagged('tupleWithRest', x) - || tagged('intersect', x) - || tagged('set', x) - || tagged('map', x) } -function needsNewtype(x: unknown): boolean { - return tagged('object', x) - || tagged('objectWithRest', x) - || tagged('looseObject', x) - || tagged('strictObject', x) - || tagged('record', x) - || tagged('tuple', x) - || tagged('looseTuple', x) - || tagged('strictTuple', x) - || tagged('tupleWithRest', x) - || tagged('intersect', x) + +function canBeInterfaceViaExtends(x: unknown): boolean { + return tagged('set', x) + || tagged('map', x) } function preserveJsDocsEnabled(ix: F.Functor.Index) { @@ -173,6 +151,7 @@ const fold = F.fold((x, ix, input) => { case tagged('literal')(x): return stringifyLiteral(x.literal) case tagged('array')(x): return isReadonly(input) ? `ReadonlyArray<${x.item}>` : `Array<${x.item}>` case tagged('record')(x): return `Record<${x.key}, ${x.value}>` + case tagged('promise')(x): return `Promise` case tagged('intersect')(x): return x.options.length === 0 ? 'unknown' : x.options.length === 1 ? x.options[0] @@ -396,16 +375,12 @@ export function toType(type: v.BaseSchema | F.V.Hole, option const $ = parseOptions(options) let TYPE = fold(type as never, { ...F.defaultIndex, ...$ } as never) if (TYPE.startsWith('(') && TYPE.endsWith(')')) TYPE = TYPE.slice(1, -1) - const NEWTYPE = !$.includeNewtypeDeclaration ? null : [ - `// @ts-expect-error: newtype hack`, - `interface newtype extends T {}`, - ].join('\n') return $.typeName === undefined ? TYPE - : $.preferInterface && canBeInterface(type) ? [ - needsNewtype(type) ? NEWTYPE : null, - `interface ${$.typeName} extends ${needsNewtype(type) ? `newtype<${TYPE}>` : TYPE} {}` - ].filter((_) => _ !== null).join('\n') - : `type ${$.typeName} = ${TYPE}` + : $.preferInterface && canBeInterface(type) + ? `interface ${$.typeName} ${TYPE}` + : $.preferInterface && canBeInterfaceViaExtends(type) + ? `interface ${$.typeName} extends ${TYPE} {}` + : `type ${$.typeName} = ${TYPE}` } toType.unsupported = unsupported @@ -414,7 +389,6 @@ function parseOptions(options?: toType.Options): Partial function parseOptions($: toType.Options = {}): Partial { return { typeName: $?.typeName, - ...'includeNewtypeDeclaration' in $ && { includeNewtypeDeclaration: $.includeNewtypeDeclaration }, ...'preferInterface' in $ && { preferInterface: $.preferInterface }, ...'preserveJsDocs' in $ && { preserveJsDocs: $.preserveJsDocs }, } diff --git a/packages/valibot/test/to-type.fuzz.test.ts b/packages/valibot/test/to-type.fuzz.test.ts index ab6f662b..4c098c44 100644 --- a/packages/valibot/test/to-type.fuzz.test.ts +++ b/packages/valibot/test/to-type.fuzz.test.ts @@ -5,7 +5,7 @@ import * as fs from 'node:fs' import { vx } from '@traversable/valibot' import { vxTest } from '@traversable/valibot-test' -const NUM_RUNS = 100 +const NUM_RUNS = 1000 // const NUM_RUNS = 10_000 const EXCLUDE = [ @@ -60,7 +60,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traverable/valibot❳: vx.toType fuzz tes const interfaces = gen.map((schema, ix) => { const string = vx.toString(schema as never) - const INTERFACE = vx.toType(schema as never, { typeName: `_${ix + 1}`, preferInterface: true, includeNewtypeDeclaration: false }) + const INTERFACE = vx.toType(schema as never, { typeName: `_${ix + 1}`, preferInterface: true }) return [ `const _${ix + 1} = ${string}`, `// ^?`, @@ -79,8 +79,6 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traverable/valibot❳: vx.toType fuzz tes const interfacesOut = [ ...imports, - `import type { newtype } from '@traversable/registry'`, - '\n', ...interfaceDeps, '\n', ...interfaces, From 67c13ea50f47fa1101fd81c39a00a886d8c3a338 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 27 Sep 2025 23:48:43 -0500 Subject: [PATCH 22/32] optimize(zod,zod-test): improves type-level performance of Seed/Gen/Functor --- packages/zod-test/src/generator-bounds.ts | 47 +++++++++++--------- packages/zod-test/src/generator.ts | 52 +++++++++++------------ packages/zod/src/makeLens.ts | 38 ++++++----------- packages/zod/src/to-type.ts | 35 ++++----------- packages/zod/test/to-type.fuzz.test.ts | 4 +- 5 files changed, 75 insertions(+), 101 deletions(-) diff --git a/packages/zod-test/src/generator-bounds.ts b/packages/zod-test/src/generator-bounds.ts index 08b19249..9cd049a9 100644 --- a/packages/zod-test/src/generator-bounds.ts +++ b/packages/zod-test/src/generator-bounds.ts @@ -1,7 +1,11 @@ import * as fc from 'fast-check' - -import type { newtype } from '@traversable/registry' -import { fn, Number_isFinite, Number_isNatural, Number_isSafeInteger, Object_is } from '@traversable/registry' +import { + fn, + Number_isFinite, + Number_isNatural, + Number_isSafeInteger, + Object_is, +} from '@traversable/registry' /** @internal */ const nullable = (model: fc.Arbitrary) => fc.oneof(fc.constant(null), fc.constant(null), model) @@ -17,8 +21,8 @@ export const defaultDoubleConstraints = { const defaultIntBounds = [-0x1000, +0x1000, null] satisfies Bounds_int const defaultBigIntBounds = [-0x1000000n, 0x1000000n, null] satisfies Bounds_bigint const defaultNumberBounds = [-0x10000, +0x10000, null, false, false] satisfies Bounds_number -const defaultStringBounds = [0, +0x40] satisfies Bounds_string -const defaultArrayBounds = [0, +0x10] satisfies Bounds_array +const defaultStringBounds = [0, +0x40, null] satisfies Bounds_string +const defaultArrayBounds = [0, +0x10, null] satisfies Bounds_array export const defaults = { int: defaultIntBounds, @@ -68,11 +72,11 @@ const clampArrayMax = clampMax(defaults.array[0], defaults.array[1], Number_isNa export const makeInclusiveBounds = (model: fc.Arbitrary) => ({ minimum: model, maximum: model }) export { Bounds_int as int } -interface Bounds_int extends newtype<[ +type Bounds_int = [ minimum: number | null, maximum: number | null, multipleOf: number | null, -]> {} +] const Bounds_int : (model: fc.Arbitrary) => fc.Arbitrary @@ -84,11 +88,11 @@ const Bounds_int ]) export { Bounds_bigint as bigint } -interface Bounds_bigint extends newtype<[ +type Bounds_bigint = [ minimum: bigint | null, maximum: bigint | null, multipleOf: bigint | null, -]> {} +] const Bounds_bigint : (model: fc.Arbitrary) => fc.Arbitrary @@ -99,28 +103,28 @@ const Bounds_bigint ]) export { Bounds_string as string } -interface Bounds_string extends newtype<[ +type Bounds_string = [ minLength: number | null, maxLength: number | null, -]> {} + exactLength: number | null, +] const Bounds_string : (model: fc.Arbitrary) => fc.Arbitrary = (model) => fc.tuple(nullable(model), nullable(model), nullable(model)).map( - ([x, y, length]) => Number_isNatural(length) - ? [null, null] satisfies [any, any] - // [clampString(length), clampString(length)] - : [clampStringMin(x, y), clampStringMax(y, x)] + ([x, y, exactLength]) => Number_isNatural(exactLength) + ? [null, null, exactLength] satisfies Bounds_string + : [clampStringMin(x, y), clampStringMax(y, x), null] satisfies Bounds_string ) export { Bounds_number as number } -interface Bounds_number extends newtype<[ +type Bounds_number = [ minimum: number | null, maximum: number | null, multipleOf: number | null, exclusiveMinimum: boolean, exclusiveMaximum: boolean, -]> {} +] const deltaIsSubEpsilon = (x: number, y: number) => Math.abs(x - y) < Number.EPSILON @@ -153,10 +157,11 @@ const Bounds_number ) export { Bounds_array as array } -interface Bounds_array extends newtype<[ +type Bounds_array = [ minLength: number | null, maxLength: number | null, -]> {} + exactLength: number | null, +] const Bounds_array : (model: fc.Arbitrary) => fc.Arbitrary @@ -166,8 +171,8 @@ const Bounds_array fc.constant(null) ).map(([x, y, exactLength]) => Number_isNatural(exactLength) - ? [null, null] - : [clampArrayMin(x, y), clampArrayMax(y, x)] + ? [null, null, exactLength] satisfies Bounds_array + : [clampArrayMin(x, y), clampArrayMax(y, x), null] satisfies Bounds_array ) diff --git a/packages/zod-test/src/generator.ts b/packages/zod-test/src/generator.ts index 2b6f9251..1caa6190 100644 --- a/packages/zod-test/src/generator.ts +++ b/packages/zod-test/src/generator.ts @@ -206,16 +206,16 @@ export const z_string = (bounds = Bounds.defaults.string) => { const [min, max, exactLength] = bounds let schema = z.string() - if (Number_isNatural(exactLength)) return ( - schema = schema.min(exactLength), - schema = schema.max(exactLength), - schema - ) - else { - if (Number_isNatural(min)) schema = schema.min(min) - if (Number_isNatural(max)) schema = schema.max(max) - return schema - } + // if (Number_isNatural(exactLength)) return ( + // schema = schema.min(exactLength), + // schema = schema.max(exactLength), + // schema + // ) + // else { + if (Number_isNatural(min)) schema = schema.min(min) + if (Number_isNatural(max)) schema = schema.max(max) + return schema + // } } export const z_array @@ -223,16 +223,16 @@ export const z_array = (elementSchema, bounds = Bounds.defaults.array) => { const [min, max, exactLength] = bounds let schema = z.array(elementSchema) - if (Number_isNatural(exactLength)) return ( - schema = schema.min(exactLength), - schema = schema.max(exactLength), - schema - ) - else { - if (Number_isNatural(min)) schema = schema.min(min) - if (Number_isNatural(max)) schema = schema.max(max) - return schema - } + // if (Number_isNatural(exactLength)) return ( + // schema = schema.min(exactLength), + // schema = schema.max(exactLength), + // schema + // ) + // else { + if (Number_isNatural(min)) schema = schema.min(min) + if (Number_isNatural(max)) schema = schema.max(max) + return schema + // } } const unboundedSeed = { @@ -289,15 +289,15 @@ export function Builder(base: Gen.Base) { export declare namespace Gen { type Base = { [K in keyof T]: (tie: fc.LetrecLooselyTypedTie, constraints: $[K & keyof $]) => fc.Arbitrary } - type Values = never | T[Exclude] + type Values = T[Exclude] type InferArb = S extends fc.Arbitrary ? T : never - /* @ts-expect-error */ - interface Builder extends T { ['*']: fc.Arbitrary>> } - type BuildBuilder, Out extends {} = BuilderBase> = never | Builder - type BuilderBase, $ extends ParseOptions = ParseOptions> = never | + type Builder = T & BuilderStar + interface BuilderStar { ['*']: fc.Arbitrary>> } + type BuildBuilder, Out extends {} = BuilderBase> = Builder + type BuilderBase, $ extends ParseOptions = ParseOptions> = & ([$['root']] extends [never] ? unknown : { root: fc.Arbitrary<$['root']> }) & { [K in Exclude<$['include'], $['exclude']>]: fc.Arbitrary } - type ParseOptions> = never | { + type ParseOptions> = { include: Options['include'] extends readonly unknown[] ? Options['include'][number] : keyof T exclude: Options['exclude'] extends readonly unknown[] ? Options['exclude'][number] : never root: Options['root'] extends keyof T ? T[Options['root']] : never diff --git a/packages/zod/src/makeLens.ts b/packages/zod/src/makeLens.ts index e69c1dfe..4c41e9f3 100644 --- a/packages/zod/src/makeLens.ts +++ b/packages/zod/src/makeLens.ts @@ -1,5 +1,5 @@ import { z } from 'zod' -import type { Key, newtype, Primitive, Showable } from '@traversable/registry' +import type { Key, Primitive, Showable } from '@traversable/registry' import { Array_from, Array_isArray, @@ -518,19 +518,13 @@ function interpreter(type: T, ..._path: (keyof any)[]) { // console.log() -interface Proxy_object extends newtype< - { [K in keyof Args[1]]: Proxy } -> { - [symbol.type]: Args[0] - [symbol.path]: KS -} +type Proxy_object = + & { [K in keyof Args[1]]: Proxy } + & { [symbol.type]: Args[0], [symbol.path]: KS } -interface Proxy_tuple extends newtype< - { [I in Extract]: Proxy } -> { - [symbol.type]: T - [symbol.path]: KS -} +type Proxy_tuple = + & { [I in Extract]: Proxy } + & { [symbol.type]: T, [symbol.path]: KS } export type Atom = | globalThis.Date @@ -571,13 +565,9 @@ interface Proxy_nonfiniteRecord extends newtype< - { [K in S[0]['enum'][keyof S[0]['enum']]]: Proxy } -> { - [DSL.traverseRecord]: Proxy - [symbol.type]: T - [symbol.path]: KS -} +type Proxy_finiteRecord = + & { [K in S[0]['enum'][keyof S[0]['enum']]]: Proxy } + & { [DSL.traverseRecord]: Proxy, [symbol.type]: T, [symbol.path]: KS } interface Proxy_primitive { [symbol.type]: T @@ -611,9 +601,9 @@ type UnionType< [...KS, disjoint: symbol.disjoint] > -interface Proxy_union extends newtype< - { [I in Extract as `${GlobalDSL['unionPrefix']}${I}`]: Proxy } -> { [symbol.type]: T, [symbol.path]: KS } +type Proxy_union = + & { [I in Extract as `${GlobalDSL['unionPrefix']}${I}`]: Proxy } + & { [symbol.type]: T, [symbol.path]: KS } type Disjoint = never | Proxy_disjointUnion< U[2], @@ -621,7 +611,7 @@ type Disjoint -interface Proxy_disjointUnion extends newtype { +type Proxy_disjointUnion = S & { [symbol.type]: T [symbol.path]: KS } diff --git a/packages/zod/src/to-type.ts b/packages/zod/src/to-type.ts index 71a74289..a6009936 100644 --- a/packages/zod/src/to-type.ts +++ b/packages/zod/src/to-type.ts @@ -64,12 +64,6 @@ export type WithInterface = { * // => interface MyType { a: number } */ preferInterface: boolean - /** - * ## {@link WithInterface `toType.Options.includeNewtypeDeclaration`} - * - * @default true - */ - includeNewtypeDeclaration?: boolean /** * ## {@link WithInterface `toType.Options.preserveJsDocs`} * @@ -127,19 +121,11 @@ function canBeReadonly(x: unknown): boolean { function canBeInterface(x: unknown): boolean { return tagged('object', x) - || tagged('array', x) - || tagged('record', x) - || tagged('tuple', x) - || tagged('intersection', x) - || tagged('set', x) - || tagged('map', x) } -function needsNewtype(x: unknown): boolean { - return tagged('object', x) - || tagged('record', x) - || tagged('tuple', x) - || tagged('intersection', x) +function canBeInterfaceViaExtends(x: unknown): boolean { + return tagged('set', x) + || tagged('map', x) } function preserveJsDocsEnabled(ix: F.CompilerIndex) { @@ -393,16 +379,12 @@ export function toType(type: z.ZodType | z.core.$ZodType | F.Z.Hole, option const $ = parseOptions(options) let TYPE = compile(type as never, { ...F.defaultIndex, ...$ } as never) if (TYPE.startsWith('(') && TYPE.endsWith(')')) TYPE = TYPE.slice(1, -1) - const NEWTYPE = !$.includeNewtypeDeclaration ? null : [ - `// @ts-expect-error: newtype hack`, - `interface newtype extends T {}`, - ].join('\n') return $.typeName === undefined ? TYPE - : $.preferInterface && canBeInterface(type) ? [ - needsNewtype(type) ? NEWTYPE : null, - `interface ${$.typeName} extends ${needsNewtype(type) ? `newtype<${TYPE}>` : TYPE} {}` - ].filter((_) => _ !== null).join('\n') - : `type ${$.typeName} = ${TYPE}` + : $.preferInterface && canBeInterface(type) + ? `interface ${$.typeName} ${TYPE}` + : $.preferInterface && canBeInterfaceViaExtends(type) + ? `interface ${$.typeName} extends ${TYPE} {}` + : `type ${$.typeName} = ${TYPE}` } toType.unsupported = toType_unsupported @@ -412,7 +394,6 @@ function parseOptions(options?: toType.Options): Partial function parseOptions($: toType.Options = {}): Partial { return { typeName: $?.typeName, - ...'includeNewtypeDeclaration' in $ && { includeNewtypeDeclaration: $.includeNewtypeDeclaration }, ...'preferInterface' in $ && { preferInterface: $.preferInterface }, ...'preserveJsDocs' in $ && { preserveJsDocs: $.preserveJsDocs }, } diff --git a/packages/zod/test/to-type.fuzz.test.ts b/packages/zod/test/to-type.fuzz.test.ts index 6d88c9a1..f8036b74 100644 --- a/packages/zod/test/to-type.fuzz.test.ts +++ b/packages/zod/test/to-type.fuzz.test.ts @@ -67,7 +67,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traverable/zod❳: integration tests', () const interfaces = gen.map((schema, ix) => { const string = zx.toString(schema as never) - const INTERFACE = zx.toType(schema as never, { typeName: `_${ix + 1}`, preferInterface: true, includeNewtypeDeclaration: false }) + const INTERFACE = zx.toType(schema as never, { typeName: `_${ix + 1}`, preferInterface: true }) return [ `const _${ix + 1} = ${string}`, `// ^?`, @@ -87,8 +87,6 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traverable/zod❳: integration tests', () const interfacesOut = [ ...imports, - `import type { newtype } from '@traversable/registry'`, - '\n', ...interfaceDeps, '\n', ...interfaces, From 03afee2868543ff3f817b3de00f68a66dcd1535d Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sun, 28 Sep 2025 07:46:40 -0500 Subject: [PATCH 23/32] chore(*): upgrades to pnpm 10.17.1, adds minimumReleaseAge of 1 day --- package.json | 2 +- packages/graphql-types/test/to-string.test.ts | 2 +- pnpm-workspace.yaml | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ff840338..915f0336 100644 --- a/package.json +++ b/package.json @@ -57,5 +57,5 @@ "typescript": "catalog:", "vitest": "catalog:" }, - "packageManager": "pnpm@10.15.1" + "packageManager": "pnpm@10.17.1" } diff --git a/packages/graphql-types/test/to-string.test.ts b/packages/graphql-types/test/to-string.test.ts index 7e023b3b..a1ee2dba 100644 --- a/packages/graphql-types/test/to-string.test.ts +++ b/packages/graphql-types/test/to-string.test.ts @@ -490,7 +490,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/graphql-types❳', () => { `) }) - vi.test.only('〖⛳️〗› ❲AST.toString❳: AST.InterfaceNode', () => { + vi.test('〖⛳️〗› ❲AST.toString❳: AST.InterfaceNode', () => { vi.expect.soft(format(AST.toString(graphql.parse(format(` interface Node { id: ID! diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 8d6ec9ac..ea14553e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,6 +3,8 @@ packages: - bin - packages/*/ +minimumReleaseAge: 1440 + catalog: '@ark/attest': 0.44.8 '@babel/cli': 7.25.9 From 903303f5c9e0ba0eebee986119b0358e4ffb52d6 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Wed, 1 Oct 2025 07:03:51 -0500 Subject: [PATCH 24/32] feat(graph-test): implements schema arbitrary --- packages/graphql-test/package.json | 3 + .../src/__generated__/__manifest__.ts | 3 + packages/graphql-test/src/functor.ts | 116 ++ .../graphql-test/src/generator-options.ts | 124 +- packages/graphql-test/src/generator-seed.ts | 1070 ++++++++++------ packages/graphql-test/src/generator.ts | 1123 ++++++----------- packages/graphql-test/tsconfig.build.json | 1 + packages/graphql-test/tsconfig.src.json | 1 + packages/graphql-test/tsconfig.test.json | 1 + packages/graphql-types/src/exports.ts | 1 + packages/graphql-types/src/functor.ts | 228 ++-- packages/graphql-types/src/to-string.ts | 42 +- packages/graphql-types/src/to-type.ts | 33 +- packages/registry/src/pattern.ts | 1 + packages/zod-test/src/generator.ts | 6 +- pnpm-lock.yaml | 20 + 16 files changed, 1429 insertions(+), 1344 deletions(-) create mode 100644 packages/graphql-test/src/functor.ts diff --git a/packages/graphql-test/package.json b/packages/graphql-test/package.json index 5d3d2fed..944c915b 100644 --- a/packages/graphql-test/package.json +++ b/packages/graphql-test/package.json @@ -45,16 +45,19 @@ }, "peerDependencies": { "@traversable/graphql-types": "workspace:^", + "@traversable/json": "workspace:^", "@traversable/registry": "workspace:^", "graphql": ">= 16" }, "peerDependenciesMeta": { "@traversable/graphql-types": { "optional": false }, + "@traversable/json": { "optional": false }, "@traversable/registry": { "optional": false }, "graphql": { "optional": true } }, "devDependencies": { "@traversable/graphql-types": "workspace:^", + "@traversable/json": "workspace:^", "@traversable/registry": "workspace:^", "graphql": "^16.11.0" } diff --git a/packages/graphql-test/src/__generated__/__manifest__.ts b/packages/graphql-test/src/__generated__/__manifest__.ts index 273f265f..197ba5f2 100644 --- a/packages/graphql-test/src/__generated__/__manifest__.ts +++ b/packages/graphql-test/src/__generated__/__manifest__.ts @@ -41,16 +41,19 @@ export default { }, "peerDependencies": { "@traversable/graphql-types": "workspace:^", + "@traversable/json": "workspace:^", "@traversable/registry": "workspace:^", "graphql": ">= 16" }, "peerDependenciesMeta": { "@traversable/graphql-types": { "optional": false }, + "@traversable/json": { "optional": false }, "@traversable/registry": { "optional": false }, "graphql": { "optional": true } }, "devDependencies": { "@traversable/graphql-types": "workspace:^", + "@traversable/json": "workspace:^", "@traversable/registry": "workspace:^", "graphql": "^16.11.0" } diff --git a/packages/graphql-test/src/functor.ts b/packages/graphql-test/src/functor.ts new file mode 100644 index 00000000..83488029 --- /dev/null +++ b/packages/graphql-test/src/functor.ts @@ -0,0 +1,116 @@ +import type * as T from '@traversable/registry' +import { fn } from '@traversable/registry' + +import type { Seed } from './generator-seed.js' +import { byTag } from './generator-seed.js' + +export interface Index {} + +export const defaultIndex = Object.create(null) satisfies Index + +export const Functor: T.Functor.Ix = { + map(g) { + return (x) => { + switch (true) { + default: return x satisfies never + case x[0] === byTag.Name: return x + case x[0] === byTag.Boolean: return x + case x[0] === byTag.BooleanValue: return x + case x[0] === byTag.EnumValue: return x + case x[0] === byTag.EnumValueDefinition: return x + case x[0] === byTag.Float: return x + case x[0] === byTag.FloatValue: return x + case x[0] === byTag.ID: return x + case x[0] === byTag.Int: return x + case x[0] === byTag.IntValue: return x + case x[0] === byTag.NamedType: return x + case x[0] === byTag.Null: return x + case x[0] === byTag.NullValue: return x + case x[0] === byTag.Number: return x + case x[0] === byTag.ScalarTypeDefinition: return x + case x[0] === byTag.String: return x + case x[0] === byTag.StringValue: return x + case x[0] === byTag.ListValue: return x + case x[0] === byTag.ObjectValue: return x + case x[0] === byTag.ObjectField: return x + case x[0] === byTag.ListType: return [x[0], g(x[1])] + case x[0] === byTag.NonNullType: return [x[0], g(x[1])] + case x[0] === byTag.SelectionSet: return [x[0], fn.map(x[1], g)] + case x[0] === byTag.Argument: return [x[0], x[1], g(x[2])] + case x[0] === byTag.Directive: return [x[0], x[1], fn.map(x[2], g)] + case x[0] === byTag.DirectiveDefinition: return [x[0], x[1], x[2], x[3], x[4], fn.map(x[5], g)] + case x[0] === byTag.Document: return [x[0], fn.map(x[1], g)] + case x[0] === byTag.EnumTypeDefinition: return [x[0], x[1], x[2], x[3], fn.map(x[4], g)] + case x[0] === byTag.Field: return [x[0], x[1], x[2], g(x[3]), fn.map(x[4], g), fn.map(x[5], g)] + case x[0] === byTag.FieldDefinition: return [x[0], x[1], x[2], g(x[3]), fn.map(x[4], g), fn.map(x[5], g)] + case x[0] === byTag.FragmentDefinition: return [x[0], x[1], x[2], g(x[3]), fn.map(x[4], g)] + case x[0] === byTag.FragmentSpread: return [x[0], x[1], fn.map(x[2], g)] + case x[0] === byTag.InlineFragment: return [x[0], x[1], g(x[2]), fn.map(x[3], g)] + case x[0] === byTag.InputValueDefinition: return [x[0], x[1], x[2], g(x[3]), g(x[4]), fn.map(x[5], g)] + case x[0] === byTag.InputObjectTypeDefinition: return [x[0], x[1], x[2], fn.map(x[3], g), fn.map(x[4], g)] + case x[0] === byTag.InterfaceTypeDefinition: return [x[0], x[1], x[2], fn.map(x[3], g), fn.map(x[4], g), fn.map(x[5], g)] + case x[0] === byTag.ObjectTypeDefinition: return [x[0], x[1], x[2], fn.map(x[3], g), fn.map(x[4], g), fn.map(x[5], g)] + case x[0] === byTag.OperationDefinition: return [x[0], x[1], x[2], g(x[3]), fn.map(x[4], g), fn.map(x[5], g)] + case x[0] === byTag.OperationTypeDefinition: return [x[0], x[1], g(x[2])] + case x[0] === byTag.SchemaDefinition: return [x[0], x[1], fn.map(x[2], ([n, o]) => [n, g(o)] as const), fn.map(x[3], g)] + case x[0] === byTag.UnionTypeDefinition: return [x[0], x[1], fn.map(x[2], g), fn.map(x[3], g)] + case x[0] === byTag.Variable: return [x[0], x[1], x[2], fn.map(x[3], g)] + case x[0] === byTag.VariableDefinition: return [x[0], x[1], g(x[2]), g(x[3]), fn.map(x[4], g)] + // case x[0] === byTag.SchemaExtension: return [x[0], fn.map(x[1], g), fn.map(x[2], g)] + } + } + }, + mapWithIndex(g) { + return (x, ix) => { + switch (true) { + default: return x satisfies never + case x[0] === byTag.Name: return x + case x[0] === byTag.Boolean: return x + case x[0] === byTag.BooleanValue: return x + case x[0] === byTag.EnumValue: return x + case x[0] === byTag.EnumValueDefinition: return x + case x[0] === byTag.Float: return x + case x[0] === byTag.FloatValue: return x + case x[0] === byTag.ID: return x + case x[0] === byTag.Int: return x + case x[0] === byTag.IntValue: return x + case x[0] === byTag.NamedType: return x + case x[0] === byTag.Null: return x + case x[0] === byTag.NullValue: return x + case x[0] === byTag.Number: return x + case x[0] === byTag.ScalarTypeDefinition: return x + case x[0] === byTag.String: return x + case x[0] === byTag.StringValue: return x + case x[0] === byTag.ListValue: return x + case x[0] === byTag.ObjectValue: return x + case x[0] === byTag.ObjectField: return x + case x[0] === byTag.ListType: return [x[0], g(x[1], ix, x)] + case x[0] === byTag.NonNullType: return [x[0], g(x[1], ix, x)] + case x[0] === byTag.SelectionSet: return [x[0], fn.map(x[1], (_) => g(_, ix, x))] + case x[0] === byTag.Argument: return [x[0], x[1], g(x[2], ix, x)] + case x[0] === byTag.Directive: return [x[0], x[1], fn.map(x[2], (_) => g(_, ix, x))] + case x[0] === byTag.DirectiveDefinition: return [x[0], x[1], x[2], x[3], x[4], fn.map(x[5], (_) => g(_, ix, x))] + case x[0] === byTag.Document: return [x[0], fn.map(x[1], (_) => g(_, ix, x))] + case x[0] === byTag.EnumTypeDefinition: return [x[0], x[1], x[2], x[3], fn.map(x[4], (_) => g(_, ix, x))] + case x[0] === byTag.Field: return [x[0], x[1], x[2], g(x[3], ix, x), fn.map(x[4], (_) => g(_, ix, x)), fn.map(x[5], (_) => g(_, ix, x))] + case x[0] === byTag.FieldDefinition: return [x[0], x[1], x[2], g(x[3], ix, x), fn.map(x[4], (_) => g(_, ix, x)), fn.map(x[5], (_) => g(_, ix, x))] + case x[0] === byTag.FragmentDefinition: return [x[0], x[1], x[2], g(x[3], ix, x), fn.map(x[4], (_) => g(_, ix, x))] + case x[0] === byTag.FragmentSpread: return [x[0], x[1], fn.map(x[2], (_) => g(_, ix, x))] + case x[0] === byTag.InlineFragment: return [x[0], x[1], g(x[2], ix, x), fn.map(x[3], (_) => g(_, ix, x))] + case x[0] === byTag.InputValueDefinition: return [x[0], x[1], x[2], g(x[3], ix, x), g(x[4], ix, x), fn.map(x[5], (_) => g(_, ix, x))] + case x[0] === byTag.InputObjectTypeDefinition: return [x[0], x[1], x[2], fn.map(x[3], (_) => g(_, ix, x)), fn.map(x[4], (_) => g(_, ix, x))] + case x[0] === byTag.InterfaceTypeDefinition: return [x[0], x[1], x[2], fn.map(x[3], (_) => g(_, ix, x)), fn.map(x[4], (_) => g(_, ix, x)), fn.map(x[5], (_) => g(_, ix, x))] + case x[0] === byTag.ObjectTypeDefinition: return [x[0], x[1], x[2], fn.map(x[3], (_) => g(_, ix, x)), fn.map(x[4], (_) => g(_, ix, x)), fn.map(x[5], (_) => g(_, ix, x))] + case x[0] === byTag.OperationDefinition: return [x[0], x[1], x[2], g(x[3], ix, x), fn.map(x[4], (_) => g(_, ix, x)), fn.map(x[5], (_) => g(_, ix, x))] + case x[0] === byTag.OperationTypeDefinition: return [x[0], x[1], g(x[2], ix, x)] + case x[0] === byTag.SchemaDefinition: return [x[0], x[1], fn.map(x[2], ([n, o]) => [n, g(o, ix, x)] as const), fn.map(x[3], (_) => g(_, ix, x))] + case x[0] === byTag.UnionTypeDefinition: return [x[0], x[1], fn.map(x[2], (_) => g(_, ix, x)), fn.map(x[3], (_) => g(_, ix, x))] + case x[0] === byTag.Variable: return [x[0], x[1], x[2], fn.map(x[3], (_) => g(_, ix, x))] + case x[0] === byTag.VariableDefinition: return [x[0], x[1], g(x[2], ix, x), g(x[3], ix, x), fn.map(x[4], (_) => g(_, ix, x))] + // case x[0] === byTag.SchemaExtension: return [x[0], fn.map(x[1], (_) => g(_, ix, x)), fn.map(x[2], (_) => g(_, ix, x))] + } + } + } +} + +export const fold = fn.catamorphism(Functor, defaultIndex) diff --git a/packages/graphql-test/src/generator-options.ts b/packages/graphql-test/src/generator-options.ts index 742158cb..84d54259 100644 --- a/packages/graphql-test/src/generator-options.ts +++ b/packages/graphql-test/src/generator-options.ts @@ -1,109 +1,43 @@ import * as fc from 'fast-check' +import { fn } from '@traversable/registry' import { Tags } from '@traversable/graphql-types' -// import type { SeedMap } from './generator.js' +import type { Seed } from './generator-seed.js' import { byTag } from './generator-seed.js' -export type ArrayParams = { - /* length?: number */ - minLength?: number - maxLength?: number -} - -export type IntegerParams = { - minimum?: number - maximum?: number - multipleOf?: number -} +export interface Options extends Partial> {} -export type NumberParams = { - minimum?: number - maximum?: number - minExcluded?: boolean - maxExcluded?: boolean - multipleOf?: number -} - -export type BigIntParams = { - minimum?: bigint - maximum?: bigint - multipleOf?: bigint -} +export type InferConfigType = S extends Options ? T : never -export type StringParams = { - /* prefix?: string, postfix?: string, pattern?: string, substring?: string, length?: number */ - minLength?: number - maxLength?: number +export interface OptionsBase< + T = never, + K extends + | string & keyof T + = string & keyof T +> { + include: readonly K[] + exclude: readonly K[] + root: '*' | K + sortBias: { [K in keyof Seed]+?: number } + forceInvalid: boolean } -// export interface Options extends Partial>, Constraints {} +export interface Config extends OptionsBase {} -// export type InferConfigType = S extends Options ? T : never +export const defaultSortBias = (x?: T) => Object.fromEntries( + Object.entries(x ?? Object.create(null)).map(([k], i) => [k, i]) +) satisfies Config['sortBias'] -// export interface OptionsBase< -// T = never, -// K extends -// | string & keyof T -// = string & keyof T -// > { -// include: readonly K[] -// exclude: readonly K[] -// root: '*' | K -// sortBias: { [K in keyof SeedMap]+?: number } -// forceInvalid: boolean -// } -// export interface Config extends OptionsBase, byTypeName {} - -// export type Constraints = { -// any?: {} -// array?: { minLength?: number, maxLength?: number, unbounded?: boolean } -// bigint?: { min?: undefined | bigint, max?: undefined | bigint, multipleOf?: bigint | null, unbounded?: boolean } -// boolean?: {} -// custom?: {} -// date?: {} -// enum?: {} -// file?: {} -// blob?: {} -// intersect?: {} -// lazy?: {} -// literal?: {} -// map?: {} -// nan?: {} -// never?: {} -// null?: {} -// number?: { min?: undefined | number, max?: undefined | number, multipleOf?: number, unbounded?: boolean } & fc.DoubleConstraints -// object?: ObjectConstraints -// object_with_rest?: ObjectConstraints -// loose_object?: ObjectConstraints -// strict_object?: ObjectConstraints -// optional?: {} -// non_optional?: {} -// undefinedable?: {} -// nullish?: {} -// non_nullish?: {} -// nullable?: {} -// non_nullable?: {} -// record?: fc.DictionaryConstraints -// set?: {} -// string?: { unbounded?: boolean } & fc.StringConstraints -// symbol?: {} -// tuple?: fc.ArrayConstraints -// tuple_with_rest?: fc.ArrayConstraints -// loose_tuple?: fc.ArrayConstraints -// strict_tuple?: fc.ArrayConstraints -// undefined?: {} -// union?: fc.ArrayConstraints -// variant?: fc.ArrayConstraints -// unknown?: {} -// void?: {} -// promise?: {} -// ['*']?: fc.OneOfConstraints -// } - -// export interface byTypeName extends Required> { -// object: fc.UniqueArrayConstraintsRecommended<[k: string, v: unknown], string> -// array: fc.IntegerConstraints & { unbounded?: boolean } -// } +export function defaultOptions(x?: T): Config +export function defaultOptions(x?: T) { + return { + include: Object.keys(x ?? Object.create(null)), + exclude: [], + forceInvalid: false, + root: '*', + sortBias: defaultSortBias(x), + } satisfies Config +} // export type ObjectConstraints = // & Omit, 'minLength' | 'maxLength'> diff --git a/packages/graphql-test/src/generator-seed.ts b/packages/graphql-test/src/generator-seed.ts index cc0119af..6a231c78 100644 --- a/packages/graphql-test/src/generator-seed.ts +++ b/packages/graphql-test/src/generator-seed.ts @@ -1,9 +1,10 @@ -import * as gql from 'graphql' -import type * as T from '@traversable/registry' -import { fn, Object_keys } from '@traversable/registry' -import type { Kind, NamedType } from '@traversable/graphql-types' +import * as fc from 'fast-check' -import * as Bounds from './generator-bounds.js' +import type * as T from '@traversable/registry' +import { Object_keys, PATTERN } from '@traversable/registry' +import { Json } from '@traversable/json' +import type { OperationType } from '@traversable/graphql-types' +import { Kind, NamedType } from '@traversable/graphql-types' export function invert>(x: T): { [K in keyof T as T[K]]: K } export function invert(x: Record) { @@ -13,403 +14,670 @@ export function invert(x: Record) { export type Tag = byTag[keyof byTag] export type byTag = typeof byTag export const byTag = { - Null: 10, - Boolean: 15, - Number: 20, - String: 25, - - // date: 20, - // file: 25, - // blob: 27, - // nan: 30, - // never: 35, - - // symbol: 45, - // undefined: 50, - // unknown: 55, - // void: 60, - // function: 70, - // instance: 80, - // bigint: 150, - // EnumValue: 500, - // EnumValueDefinition: 550, - ListType: 100, - - Document: 1000, - // non_optional: 1500, - // nullable: 2000, - // NonNullType: 2100, - // nullish: 2200, - // non_nullish: 2300, - // optional: 2500, - // exact_optional: 2600, - // undefinedable: 2700, - // set: 3500, - // intersect: 6000, - // map: 6500, - // record: 7000, - // object: 7500, - // loose_object: 7600, - // strict_object: 7700, - // object_with_rest: 7800, - // tuple: 8000, - // loose_tuple: 8100, - // strict_tuple: 8200, - // tuple_with_rest: 8300, - // union: 8500, - // variant: 8600, - // custom: 9500, - // lazy: 10_500, - // picklist: 11_000, - // /** @deprecated */ - // promise: -1000, -} as const // satisfies Record - -/** - * @example - * { - * Document: 'Document', - * EnumTypeDefinition: 'EnumTypeDefinition', - * EnumValueDefinition: 'EnumValueDefinition', - * FieldDefinition: 'FieldDefinition', - * InputObjectTypeDefinition: 'InputObjectTypeDefinition', - * InputValueDefinition: 'InputValueDefinition', - * InterfaceTypeDefinition: 'InterfaceTypeDefinition', - * ListType: 'ListType', - * Name: 'Name', - * NamedType: 'NamedType', - * NonNullType: 'NonNullType', - * ObjectTypeDefinition: 'ObjectTypeDefinition', - * OperationDefinition: 'OperationDefinition', - * ScalarTypeDefinition: 'ScalarTypeDefinition', - * SelectionSet: 'SelectionSet', - * UnionTypeDefinition: 'UnionTypeDefinition', - * Variable: 'Variable', - * VariableDefinition: 'VariableDefinition', - * // - * FragmentSpread: 'FragmentSpread', - * InlineFragment: 'InlineFragment', - * FragmentDefinition: 'FragmentDefinition', - * // - * Argument: 'Argument', - * Directive: 'Directive', - * DirectiveDefinition: 'DirectiveDefinition', - * EnumValue: 'EnumValue', - * Field: 'Field', - * FloatValue: 'FloatValue', - * StringValue: 'StringValue', - * BooleanValue: 'BooleanValue', - * IntValue: 'IntValue', - * ListValue: 'ListValue', - * NullValue: 'NullValue', - * ObjectValue: 'ObjectValue', - * ObjectField: 'ObjectField', - * SchemaDefinition: 'SchemaDefinition', - * SchemaExtension: 'SchemaExtension', - * OperationTypeDefinition: 'OperationTypeDefinition', - * } - */ + Name: 10, + NamedType: 20, + /* scalars */ + Boolean: 30, + Float: 40, + ID: 50, + Int: 60, + Null: 70, + Number: 80, + ScalarTypeDefinition: 90, + String: 100, + /* values */ + BooleanValue: 110, + EnumValue: 120, + FloatValue: 130, + IntValue: 140, + ListValue: 150, + NullValue: 160, + ObjectValue: 170, + StringValue: 180, + /* type definitions */ + EnumTypeDefinition: 190, + EnumValueDefinition: 200, + Field: 210, + FieldDefinition: 220, + ObjectField: 230, + ObjectTypeDefinition: 240, + InterfaceTypeDefinition: 250, + /* modifiers */ + ListType: 260, + NonNullType: 270, + UnionTypeDefinition: 280, + /* args */ + Argument: 290, + InputObjectTypeDefinition: 300, + InputValueDefinition: 310, + Variable: 320, + VariableDefinition: 330, + /* special */ + Directive: 340, + DirectiveDefinition: 350, + Document: 360, + FragmentDefinition: 370, + FragmentSpread: 380, + InlineFragment: 390, + OperationDefinition: 400, + OperationTypeDefinition: 410, + SelectionSet: 420, + SchemaDefinition: 430, + // SchemaExtension: 440, +} as const satisfies Record<'Name' | Kind | NamedType, number> export type bySeed = typeof bySeed export const bySeed = invert(byTag) -// export type Seed = -// & Seed.TerminalMap -// & Seed.BoundableMap -// & Seed.ValueMap -// & Seed.UnaryMap - -// export declare namespace Seed { -// type Fixpoint = [ -// tag: Tag, children?: -// unknown, -// bounds?: unknown -// ] -// type F = -// | Seed.Nullary -// | Seed.Unary -// type Nullary = -// | Seed.Terminal -// | Seed.Boundable -// | Seed.Value -// type Unary = -// | Seed.Array -// | Seed.Record -// | Seed.Object -// | Seed.LooseObject -// | Seed.StrictObject -// | Seed.ObjectWithRest -// | Seed.Tuple -// | Seed.TupleWithRest -// | Seed.LooseTuple -// | Seed.StrictTuple -// | Seed.Union -// | Seed.Variant -// | Seed.Optional -// | Seed.NonOptional -// | Seed.Undefinedable -// | Seed.Nullable -// | Seed.NonNullable -// | Seed.Nullish -// | Seed.NonNullish -// | Seed.Set -// | Seed.Map -// | Seed.Custom -// | Seed.Lazy -// | Seed.Intersect -// | Seed.Promise - -// interface Free extends T.HKT { [-1]: Seed.F } -// //////////////// -// /// nullary -// type Any = [any: byTag['any']] -// type Boolean = [boolean: byTag['boolean']] -// type Date = [date: byTag['date']] -// type File = [file: byTag['file']] -// type Blob = [blob: byTag['blob']] -// type NaN = [NaN: byTag['nan']] -// type Never = [never: byTag['never']] -// type Null = [null: byTag['null']] -// type Symbol = [symbol: byTag['symbol']] -// type Undefined = [undefined: byTag['undefined']] -// type Unknown = [unknown: byTag['unknown']] -// type Void = [void: byTag['void']] -// type Terminal = TerminalMap[keyof TerminalMap] -// type TerminalMap = { -// any: Any -// boolean: Boolean -// date: Date -// file: File -// blob: Blob -// nan: NaN -// never: Never -// null: Null -// symbol: Symbol -// undefined: Undefined -// unknown: Unknown -// void: Void -// } -// //////////////// -// /// boundable -// type BigInt = [bigint: byTag['bigint'], bounds?: Bounds.bigint] -// type Number = [number: byTag['number'], bounds?: Bounds.number] -// type String = [string: byTag['string'], bounds?: Bounds.string] -// type Boundable = BoundableMap[keyof BoundableMap] -// type BoundableMap = { -// bigint: BigInt -// number: Number -// string: String -// } -// //////////////// -// /// value -// type Enum = [enum_: byTag['enum'], value: { [x: string]: number | string }] -// type Literal = [literal: byTag['literal'], value: boolean | number | string] -// namespace TemplateLiteral { -// type Node = T.Showable | Seed.Boolean | Seed.Null | Seed.Undefined | Seed.Number | Seed.BigInt | Seed.String | Seed.Literal -// } -// type Value = ValueMap[keyof ValueMap] -// type ValueMap = { -// enum: Enum -// literal: Literal -// } -// //////////////// -// /// unary -// type Array = [array: byTag['array'], item: T, bounds?: Bounds.array] -// type Optional = [optional: byTag['optional'], wrapped: T] -// type NonOptional = [nonOptional: byTag['non_optional'], wrapped: T] -// type Undefinedable = [undefinedable: byTag['undefinedable'], wrapped: T] -// type Nullish = [nullish: byTag['nullish'], wrapped: T] -// type NonNullish = [nonNullish: byTag['non_nullish'], wrapped: T] -// type Nullable = [nullable: byTag['nullable'], wrapped: T] -// type NonNullable = [nonNullable: byTag['non_nullable'], wrapped: T] -// type Set = [set: byTag['set'], value: T] - -// type UnaryMap = { -// array: Seed.Array -// record: Seed.Record -// object: Seed.Object -// strict_object: Seed.StrictObject -// loose_object: Seed.LooseObject -// object_with_rest: Seed.ObjectWithRest -// tuple: Seed.Tuple -// loose_tuple: Seed.LooseTuple -// strict_tuple: Seed.StrictTuple -// tuple_with_rest: Seed.TupleWithRest -// union: Seed.Union -// variant: Seed.Variant -// optional: Seed.Optional -// non_optional: Seed.NonOptional -// undefinedable: Seed.Undefinedable -// nullable: Seed.Nullable -// non_nullable: Seed.NonNullable -// nullish: Seed.Nullish -// non_nullish: Seed.NonNullish -// set: Seed.Set -// map: Seed.Map -// custom: Seed.Custom -// lazy: Seed.Lazy -// intersect: Seed.Intersect -// promise: Seed.Promise -// } -// type Composite = -// | Seed.Array -// | Seed.Record -// | Seed.Tuple -// | Seed.TupleWithRest -// | Seed.LooseTuple -// | Seed.StrictTuple -// | Seed.Object -// | Seed.ObjectWithRest -// | Seed.LooseObject -// | Seed.StrictObject -// type fromComposite = { -// [byTag.array]: unknown[] -// [byTag.tuple]: unknown[] -// [byTag.loose_tuple]: unknown[] -// [byTag.strict_tuple]: unknown[] -// [byTag.tuple_with_rest]: unknown[] -// [byTag.record]: globalThis.Record -// [byTag.object]: { [x: string]: unknown } -// [byTag.loose_object]: { [x: string]: unknown } -// [byTag.strict_object]: { [x: string]: unknown } -// [byTag.object_with_rest]: { [x: string]: unknown } -// } -// type schemaFromComposite = { -// [byTag.array]: v.ArraySchema -// [byTag.tuple]: v.TupleSchema -// [byTag.loose_tuple]: v.LooseTupleSchema -// [byTag.strict_tuple]: v.StrictTupleSchema -// [byTag.tuple_with_rest]: v.TupleWithRestSchema -// [byTag.record]: v.RecordSchema -// [byTag.object]: v.ObjectSchema -// [byTag.loose_object]: v.LooseObjectSchema -// [byTag.strict_object]: v.StrictObjectSchema -// [byTag.object_with_rest]: v.ObjectWithRestSchema -// } -// //////////////// -// /// applicative -// type Object = [object: byTag['object'], entries: [k: string, v: T][]] -// type LooseObject = [looseObject: byTag['loose_object'], entries: [k: string, v: T][]] -// type StrictObject = [strictObject: byTag['strict_object'], entries: [k: string, v: T][]] -// type ObjectWithRest = [objectWithRest: byTag['object_with_rest'], entries: [k: string, v: T][], rest: T] -// type Union = [union: byTag['union'], options: T[]] -// type Variant = [variant: byTag['variant'], [tag: string, options: [k: string, v: T][]][], discriminator: string] -// type Tuple = [tuple: byTag['tuple'], items: T[]] -// type LooseTuple = [looseTuple: byTag['loose_tuple'], items: T[]] -// type StrictTuple = [strictTuple: byTag['strict_tuple'], items: T[]] -// type TupleWithRest = [tupleWithRest: byTag['tuple_with_rest'], items: T[], rest: T] -// //////////////// -// /// binary -// type Map = [seed: byTag['map'], def: [key: T, value: T]] -// type Record = [seed: byTag['record'], value: T] -// type Intersect = [seed: byTag['intersect'], def: [left: T, right: T]] -// //////////////// -// /// special -// type Custom = [seed: byTag['custom'], def: T] -// type Lazy = [seed: byTag['lazy'], getter: () => T] -// //////////////// -// /// deprecated -// /** @deprecated */ -// type Promise = [seed: byTag['promise'], wrapped: T] -// } - -// export const Functor: T.Functor.Ix> = { -// map(f) { -// return (x) => { -// switch (true) { -// default: return x -// case x[0] === byTag.any: return x -// case x[0] === byTag.boolean: return x -// case x[0] === byTag.date: return x -// case x[0] === byTag.file: return x -// case x[0] === byTag.nan: return x -// case x[0] === byTag.never: return x -// case x[0] === byTag.null: return x -// case x[0] === byTag.symbol: return x -// case x[0] === byTag.undefined: return x -// case x[0] === byTag.unknown: return x -// case x[0] === byTag.void: return x -// case x[0] === byTag.bigint: return x -// case x[0] === byTag.number: return x -// case x[0] === byTag.string: return x -// case x[0] === byTag.enum: return x -// case x[0] === byTag.literal: return x -// case x[0] === byTag.array: return [x[0], f(x[1]), x[2]] -// case x[0] === byTag.optional: return [x[0], f(x[1])] -// case x[0] === byTag.non_optional: return [x[0], f(x[1])] -// case x[0] === byTag.undefinedable: return [x[0], f(x[1])] -// case x[0] === byTag.nullable: return [x[0], f(x[1])] -// case x[0] === byTag.non_nullable: return [x[0], f(x[1])] -// case x[0] === byTag.nullish: return [x[0], f(x[1])] -// case x[0] === byTag.non_nullish: return [x[0], f(x[1])] -// case x[0] === byTag.set: return [x[0], f(x[1])] -// case x[0] === byTag.intersect: return [x[0], [f(x[1][0]), f(x[1][1])]] -// case x[0] === byTag.map: return [x[0], [f(x[1][0]), f(x[1][1])]] -// case x[0] === byTag.record: return [x[0], f(x[1])] -// case x[0] === byTag.object: return [x[0], x[1].map(([k, v]) => [k, f(v)] satisfies [any, any])] -// case x[0] === byTag.loose_object: return [x[0], x[1].map(([k, v]) => [k, f(v)] satisfies [any, any])] -// case x[0] === byTag.strict_object: return [x[0], x[1].map(([k, v]) => [k, f(v)] satisfies [any, any])] -// case x[0] === byTag.object_with_rest: return [x[0], x[1].map(([k, v]) => [k, f(v)] satisfies [any, any]), f(x[2])] -// case x[0] === byTag.tuple: return [x[0], x[1].map(f)] -// case x[0] === byTag.loose_tuple: return [x[0], x[1].map(f)] -// case x[0] === byTag.strict_tuple: return [x[0], x[1].map(f)] -// case x[0] === byTag.tuple_with_rest: return [x[0], x[1].map(f), f(x[2])] -// case x[0] === byTag.union: return [x[0], x[1].map(f)] -// case x[0] === byTag.custom: return [x[0], f(x[1])] -// case x[0] === byTag.lazy: return [x[0], () => f(x[1]())] -// case x[0] === byTag.variant: return [x[0], x[1].map(([tag, ys]) => [tag, ys.map(([k, v]) => [k, f(v)] satisfies [any, any])] satisfies [any, any]), x[2]] -// case x[0] === byTag.promise: return PromiseSchemaIsUnsupported('Functor') -// } -// } -// }, -// mapWithIndex(f) { -// return (x, isProperty) => { -// switch (true) { -// default: return x -// case x[0] === byTag.any: return x -// case x[0] === byTag.boolean: return x -// case x[0] === byTag.date: return x -// case x[0] === byTag.file: return x -// case x[0] === byTag.nan: return x -// case x[0] === byTag.never: return x -// case x[0] === byTag.null: return x -// case x[0] === byTag.symbol: return x -// case x[0] === byTag.undefined: return x -// case x[0] === byTag.unknown: return x -// case x[0] === byTag.void: return x -// case x[0] === byTag.bigint: return x -// case x[0] === byTag.number: return x -// case x[0] === byTag.string: return x -// case x[0] === byTag.enum: return x -// case x[0] === byTag.literal: return x -// case x[0] === byTag.array: return [x[0], f(x[1], false, x), x[2]] -// case x[0] === byTag.optional: return [x[0], f(x[1], isProperty, x)] -// case x[0] === byTag.non_optional: return [x[0], f(x[1], false, x)] -// case x[0] === byTag.undefinedable: return [x[0], f(x[1], false, x)] -// case x[0] === byTag.nullable: return [x[0], f(x[1], false, x)] -// case x[0] === byTag.non_nullable: return [x[0], f(x[1], false, x)] -// case x[0] === byTag.nullish: return [x[0], f(x[1], false, x)] -// case x[0] === byTag.non_nullish: return [x[0], f(x[1], false, x)] -// case x[0] === byTag.set: return [x[0], f(x[1], false, x)] -// case x[0] === byTag.intersect: return [x[0], [f(x[1][0], false, x), f(x[1][1], false, x)]] -// case x[0] === byTag.map: return [x[0], [f(x[1][0], false, x), f(x[1][1], false, x)]] -// case x[0] === byTag.record: return [x[0], f(x[1], false, x)] -// case x[0] === byTag.tuple: return [x[0], x[1].map((_) => f(_, false, x))] -// case x[0] === byTag.loose_tuple: return [x[0], x[1].map((_) => f(_, false, x))] -// case x[0] === byTag.strict_tuple: return [x[0], x[1].map((_) => f(_, false, x))] -// case x[0] === byTag.tuple_with_rest: return [x[0], x[1].map((_) => f(_, false, x)), f(x[2], false, x)] -// case x[0] === byTag.union: return [x[0], x[1].map((_) => f(_, false, x))] -// case x[0] === byTag.custom: return [x[0], f(x[1], false, x)] -// case x[0] === byTag.lazy: return [x[0], () => f(x[1](), false, x)] -// case x[0] === byTag.object: return [x[0], x[1].map(([k, v]) => [k, f(v, true, x)] satisfies [any, any])] -// case x[0] === byTag.loose_object: return [x[0], x[1].map(([k, v]) => [k, f(v, true, x)] satisfies [any, any])] -// case x[0] === byTag.strict_object: return [x[0], x[1].map(([k, v]) => [k, f(v, true, x)] satisfies [any, any])] -// case x[0] === byTag.object_with_rest: return [x[0], x[1].map(([k, v]) => [k, f(v, true, x)] satisfies [any, any]), f(x[2], true, x)] -// case x[0] === byTag.variant: return [x[0], x[1].map(([tag, ys]) => [tag, ys.map(([k, v]) => [k, f(v, false, x)] satisfies [any, any])] satisfies [any, any]), x[2]] -// case x[0] === byTag.promise: return PromiseSchemaIsUnsupported('Functor') -// } -// } -// } -// } - -// export const fold = fn.catamorphism(Functor, false) +export type Seed = ( + & Seed.TerminalMap + & Seed.ValueMap + & Seed.UnaryMap +) + +export declare namespace Seed { + type Name = [ + Name: byTag['Name'], + value: string, + ] + + type NamedType = [ + NamedType: byTag['NamedType'], + name: string, + ] + + type Description = [description: string, block: boolean] + + //////////////////////// + /// Terminal nodes /// + type Boolean = [Boolean: byTag['Boolean']] + type Float = [Float: byTag['Float']] + type ID = [ID: byTag['ID']] + type Int = [Int: byTag['Int']] + type Null = [Null: byTag['Null']] + type Number = [Number: byTag['Number']] + type String = [String: byTag['String']] + + type ScalarTypeDefinition = [ + ScalarTypeDefinition: byTag['ScalarTypeDefinition'], + name: string, + description: Description, + ] + /// Terminal nodes /// + //////////////////////// + + ///////////////////// + /// Value nodes /// + type NullValue = [ + NullValue: byTag['NullValue'] + ] + + type BooleanValue = [ + BooleanValue: byTag['BooleanValue'], + value: boolean, + ] + + type EnumValue = [ + EnumValue: byTag['EnumValue'], + value: string, + ] + + type FloatValue = [ + FloatValue: byTag['FloatValue'], + value: number, + ] + + type IntValue = [ + IntValue: byTag['IntValue'], + value: number, + ] + + type StringValue = [ + StringValue: byTag['StringValue'], + value: string, + block: boolean, + ] + + type EnumValueDefinition = [ + EnumValueDefinition: byTag['EnumValueDefinition'], + value: string, + ] + + type ListValue = [ + ListValue: byTag['ListValue'], + value: readonly Json[], + ] + + type ObjectValue = [ + ObjectValue: byTag['ObjectValue'], + value: { [x: string]: Json }, + ] + /// Value nodes /// + ///////////////////// + + type ListType = [ + ListType: byTag['ListType'], + type: T, + ] + + type NonNullType = [ + NonNullType: byTag['NonNullType'], + type: T, + ] + + type UnionTypeDefinition = [ + UnionTypeDefinition: byTag['UnionTypeDefinition'], + name: string, + types: readonly T[], + directives: readonly T[] + ] + + type Variable = [ + Variable: byTag['Variable'], + name: string, + description: Description, + directives: readonly T[], + ] + + type EnumTypeDefinition = [ + EnumTypeDefinition: byTag['EnumTypeDefinition'], + name: string, + description: Description, + values: readonly string[], + directives: readonly T[], + ] + + type Field = [ + Field: byTag['Field'], + name: string, + alias: string, + selectionSet: T, + arguments: readonly T[], + directives: readonly T[], + ] + + type FieldDefinition = [ + FieldDefinition: byTag['FieldDefinition'], + name: string, + description: Description, + type: T, + arguments: readonly T[], + directives: readonly T[], + ] + + type ObjectField = [ + ObjectField: byTag['ObjectField'], + name: string, + value: Json + ] + + type ObjectTypeDefinition = [ + ObjectTypeDefinition: byTag['ObjectTypeDefinition'], + name: string, + description: Description, + fields: readonly T[], + interfaces: readonly T[], + directives: readonly T[], + ] + + type InterfaceTypeDefinition = [ + InterfaceTypeDefinition: byTag['InterfaceTypeDefinition'], + name: string, + description: Description, + fields: readonly T[], + interfaces: readonly T[], + directives: readonly T[], + ] + + type Argument = [ + Argument: byTag['Argument'], + name: string, + value: T, + ] + + type InputObjectTypeDefinition = [ + InputObjectTypeDefinition: byTag['InputObjectTypeDefinition'], + name: string, + description: Description, + fields: readonly T[], + directives: readonly T[], + ] + + type InputValueDefinition = [ + InputValueDefinition: byTag['InputValueDefinition'], + name: string, + description: Description, + type: T, + defaultValue: T, + directives: readonly T[], + ] + + type VariableDefinition = [ + VariableDefinition: byTag['VariableDefinition'], + variable: string, + type: T, + defaultValue: T, + directives: readonly T[], + ] + + type Directive = [ + Directive: byTag['Directive'], + name: string, + arguments: readonly T[], + ] + + type DirectiveDefinition = [ + DirectiveDefinition: byTag['DirectiveDefinition'], + name: string, + description: Description, + repeatable: boolean, + locations: readonly string[], + arguments: readonly T[], + ] + + type Document = [ + Document: byTag['Document'], + definition: readonly T[], + ] + + type FragmentDefinition = [ + FragmentDefinition: byTag['FragmentDefinition'], + name: string, + typeCondition: string, + selectionSet: T, + directives: readonly T[], + ] + + type FragmentSpread = [ + FragmentSpread: byTag['FragmentSpread'], + name: string, + directives: readonly T[], + ] + + type InlineFragment = [ + InlineFragment: byTag['InlineFragment'], + selectionSet: string, + typeCondition: T, + directives: readonly T[], + ] + + type OperationDefinition = [ + OperationDefinition: byTag['OperationDefinition'], + name: string, + operation: OperationType, + selectionSet: T, + variableDefinitions: readonly T[], + directives: readonly T[], + ] + + type OperationTypeDefinition = [ + OperationTypeDefinition: byTag['OperationTypeDefinition'], + type: string, + operation: T, + ] + + type SelectionSet = [ + SelectionSet: byTag['SelectionSet'], + selections: readonly T[], + ] + + type SchemaDefinition = [ + SchemaDefinition: byTag['SchemaDefinition'], + description: Description, + operationTypes: readonly (readonly [name: string, operation: T])[], + directives: readonly T[], + ] + + // type SchemaExtension = [ + // SchemaExtension: byTag['SchemaExtension'], + // operationTypes: readonly T[], + // directives: readonly T[], + // ] + + type Terminal = TerminalMap[keyof TerminalMap] + type TerminalMap = { + Boolean: Boolean + Float: Float + ID: ID + Int: Int + Name: Name + NamedType: NamedType + Null: Null + Number: Number + ScalarTypeDefinition: ScalarTypeDefinition + String: String + } + + type Value = ValueMap[keyof ValueMap] + type ValueMap = { + BooleanValue: BooleanValue + FloatValue: FloatValue + IntValue: IntValue + NullValue: NullValue + StringValue: StringValue + EnumValue: EnumValue + EnumValueDefinition: EnumValueDefinition + ListValue: ListValue + ObjectValue: ObjectValue + ObjectField: ObjectField + } + + type UnaryMap = { + ListType: ListType + NonNullType: NonNullType + UnionTypeDefinition: UnionTypeDefinition + Variable: Variable + EnumTypeDefinition: EnumTypeDefinition + Field: Field + FieldDefinition: FieldDefinition + ObjectTypeDefinition: ObjectTypeDefinition + InterfaceTypeDefinition: InterfaceTypeDefinition + Argument: Argument + InputObjectTypeDefinition: InputObjectTypeDefinition + InputValueDefinition: InputValueDefinition + VariableDefinition: VariableDefinition + Directive: Directive + DirectiveDefinition: DirectiveDefinition + Document: Document + FragmentDefinition: FragmentDefinition + FragmentSpread: FragmentSpread + InlineFragment: InlineFragment + OperationDefinition: OperationDefinition + OperationTypeDefinition: OperationTypeDefinition + SelectionSet: SelectionSet + SchemaDefinition: SchemaDefinition + // SchemaExtension: SchemaExtension + } + + type Nullary = + | Seed.Terminal + | Seed.Value + | Seed.EnumValueDefinition + + type Unary = + | Seed.ListType + | Seed.NonNullType + | Seed.UnionTypeDefinition + | Seed.Variable + | Seed.EnumTypeDefinition + | Seed.Field + | Seed.FieldDefinition + | Seed.ObjectTypeDefinition + | Seed.InterfaceTypeDefinition + | Seed.Argument + | Seed.InputObjectTypeDefinition + | Seed.InputValueDefinition + | Seed.VariableDefinition + | Seed.Directive + | Seed.DirectiveDefinition + | Seed.Document + | Seed.FragmentDefinition + | Seed.FragmentSpread + | Seed.InlineFragment + | Seed.OperationDefinition + | Seed.OperationTypeDefinition + | Seed.SelectionSet + | Seed.SchemaDefinition + // | Seed.SchemaExtension + + type F = + | Seed.Nullary + | Seed.Value + | Seed.Unary + + type Fixpoint = + | Seed.Nullary + | Seed.Value + | Seed.ListType + | Seed.NonNullType + | Seed.UnionTypeDefinition + | Seed.Variable + | Seed.EnumTypeDefinition + | Seed.Field + | Seed.FieldDefinition + | Seed.ObjectTypeDefinition + | Seed.InterfaceTypeDefinition + | Seed.Argument + | Seed.InputObjectTypeDefinition + | Seed.InputValueDefinition + | Seed.VariableDefinition + | Seed.Directive + | Seed.DirectiveDefinition + | Seed.Document + | Seed.FragmentDefinition + | Seed.FragmentSpread + | Seed.InlineFragment + | Seed.OperationDefinition + | Seed.OperationTypeDefinition + | Seed.SelectionSet + | Seed.SchemaDefinition + // | Seed.SchemaExtension + + interface Free extends T.HKT { [-1]: Seed.F } +} + +export const identifier = fc.stringMatching(new RegExp(PATTERN.identifier, 'u')) +export const name = fc.lorem({ maxCount: 1 }) + +export const description = fc.tuple( + fc.string(), + fc.boolean(), +) satisfies fc.Arbitrary + +export const operationType = fc.constantFrom( + 'query', + 'mutation', + 'subscription', +) satisfies fc.Arbitrary + +export const directives = (model: fc.Arbitrary) => fc.array(model, { maxLength: 2 }) + +export const Seed = { + Boolean: () => fc.constant([byTag['Boolean']]), + Float: () => fc.constant([byTag['Float']]), + Int: () => fc.constant([byTag['Int']]), + ID: () => fc.constant([byTag['ID']]), + Null: () => fc.constant([byTag['Null']]), + Number: () => fc.constant([byTag['Number']]), + String: () => fc.constant([byTag['String']]), + NamedType: () => fc.tuple( + fc.constant(byTag['NamedType']), + name, + ), + NullValue: () => fc.constant([byTag['NullValue']]), + BooleanValue: () => fc.tuple( + fc.constant(byTag['BooleanValue']), + fc.boolean(), + ), + FloatValue: () => fc.tuple( + fc.constant(byTag['FloatValue']), + fc.double(), + ), + IntValue: () => fc.tuple( + fc.constant(byTag['IntValue']), + fc.integer(), + ), + StringValue: () => fc.tuple( + fc.constant(byTag['StringValue']), + fc.string(), + fc.boolean(), + ), + ScalarTypeDefinition: () => fc.tuple( + fc.constant(byTag['ScalarTypeDefinition']), + name, + description, + ), + EnumValue: () => fc.tuple( + fc.constant(byTag['EnumValue']), + name, + ), + EnumValueDefinition: () => fc.tuple( + fc.constant(byTag['EnumValueDefinition']), + name, + ), + ListValue: () => fc.tuple( + fc.constant(byTag['ListValue']), + fc.array(fc.jsonValue()), + ), + ObjectValue: () => fc.tuple( + fc.constant(byTag['ObjectValue']), + fc.dictionary(identifier, fc.jsonValue()), + ), + ObjectField: () => fc.tuple( + fc.constant(byTag['ObjectField']), + name, + fc.jsonValue(), + ), + ListType: (model) => fc.tuple( + fc.constant(byTag['ListType']), + model, + ), + NonNullType: (model) => fc.tuple( + fc.constant(byTag['NonNullType']), + model, + ), + UnionTypeDefinition: (model) => fc.tuple( + fc.constant(byTag['UnionTypeDefinition']), + name, + fc.uniqueArray(model), + directives(model), + ), + Variable: (model) => fc.tuple( + fc.constant(byTag['Variable']), + name, + description, + directives(model) + ), + EnumTypeDefinition: (model) => fc.tuple( + fc.constant(byTag['EnumTypeDefinition']), + name, + description, + fc.uniqueArray(name), + directives(model), + ), + Field: (model) => fc.tuple( + fc.constant(byTag['Field']), + name, + name, + model, + fc.uniqueArray(model), + directives(model), + ), + FieldDefinition: (model) => fc.tuple( + fc.constant(byTag['FieldDefinition']), + name, + description, + model, + fc.uniqueArray(model), + directives(model), + ), + ObjectTypeDefinition: (model) => fc.tuple( + fc.constant(byTag['ObjectTypeDefinition']), + name, + description, + fc.uniqueArray(model), + fc.uniqueArray(model), + directives(model), + ), + InterfaceTypeDefinition: (model) => fc.tuple( + fc.constant(byTag['InterfaceTypeDefinition']), + name, + description, + fc.uniqueArray(model), + fc.uniqueArray(model), + directives(model), + ), + Argument: (model) => fc.tuple( + fc.constant(byTag['Argument']), + name, + model, + ), + InputObjectTypeDefinition: (model) => fc.tuple( + fc.constant(byTag['InputObjectTypeDefinition']), + name, + description, + fc.uniqueArray(model), + directives(model), + ), + InputValueDefinition: (model) => fc.tuple( + fc.constant(byTag['InputValueDefinition']), + name, + description, + model, + model, + directives(model), + ), + VariableDefinition: (model) => fc.tuple( + fc.constant(byTag['VariableDefinition']), + name, + model, + model, + directives(model), + ), + Directive: (model) => fc.tuple( + fc.constant(byTag['Directive']), + name, + fc.uniqueArray(model), + ), + DirectiveDefinition: (model) => fc.tuple( + fc.constant(byTag['DirectiveDefinition']), + name, + description, + fc.boolean(), + fc.uniqueArray(name), + fc.uniqueArray(model), + ), + Document: (model) => fc.tuple( + fc.constant(byTag['Document']), + fc.uniqueArray(model), + ), + FragmentDefinition: (model) => fc.tuple( + fc.constant(byTag['FragmentDefinition']), + name, + name, + model, + directives(model), + ), + FragmentSpread: (model) => fc.tuple( + fc.constant(byTag['FragmentSpread']), + name, + directives(model), + ), + InlineFragment: (model) => fc.tuple( + fc.constant(byTag['InlineFragment']), + name, + model, + directives(model), + ), + OperationDefinition: (model) => fc.tuple( + fc.constant(byTag['OperationDefinition']), + name, + operationType, + model, + fc.uniqueArray(model), + directives(model), + ), + OperationTypeDefinition: (model) => fc.tuple( + fc.constant(byTag['OperationTypeDefinition']), + operationType, + model, + ), + SelectionSet: (model) => fc.tuple( + fc.constant(byTag['SelectionSet']), + fc.uniqueArray(model), + ), + SchemaDefinition: (model) => fc.tuple( + fc.constant(byTag['SchemaDefinition']), + description, + fc.uniqueArray( + fc.tuple(name, model), + { selector: ([name]) => name }, + ), + directives(model), + ), + // SchemaExtension: (model: fc.Arbitrary) => fc.tuple( + // fc.constant(byTag['SchemaExtension']), + // fc.uniqueArray(model), + // directives(model), + // ) satisfies fc.Arbitrary, +} satisfies { [K in Kind | NamedType]: (model: fc.Arbitrary) => fc.Arbitrary } diff --git a/packages/graphql-test/src/generator.ts b/packages/graphql-test/src/generator.ts index d84e892a..4d4bba6e 100644 --- a/packages/graphql-test/src/generator.ts +++ b/packages/graphql-test/src/generator.ts @@ -1,4 +1,4 @@ -import * as gql from 'graphql' +import type * as gql from 'graphql' import * as fc from 'fast-check' import type { inline } from '@traversable/registry' @@ -6,13 +6,9 @@ import { Array_isArray, fn, isKeyOf, - isObject, - mutateRandomElementOf, - mutateRandomValueOf, Number_isFinite, - Number_isNatural, + Number_isSafeInteger, Object_assign, - Object_create, Object_entries, Object_fromEntries, Object_keys, @@ -23,736 +19,385 @@ import { pick, symbol, } from '@traversable/registry' - -// type Config = import('./generator-options.js').Config -// import * as Config from './generator-options.js' -// import * as Bounds from './generator-bounds.js' -// import type { Tag } from './generator-seed.js' -// import { byTag, bySeed, Seed, fold } from './generator-seed.js' - -// const identifier = fc.stringMatching(new RegExp(PATTERN.identifier, 'u')) - -// const enumValues = fc.uniqueArray( -// fc.tuple( -// identifier, -// identifier, -// ), -// { -// selector: ([k]) => k, -// minLength: 1, -// } -// ).map(Object_fromEntries) - -// const literalValue = fc.oneof( -// fc.string({ minLength: Bounds.defaults.string[0], maxLength: Bounds.defaults.string[1] }), -// fc.double({ min: Bounds.defaults.number[0], max: Bounds.defaults.number[1], noNaN: true }), -// fc.boolean(), -// ) - -// const TerminalMap = { -// any: fn.const(fc.tuple(fc.constant(byTag.any))), -// boolean: fn.const(fc.tuple(fc.constant(byTag.boolean))), -// date: fn.const(fc.tuple(fc.constant(byTag.date))), -// file: fn.const(fc.tuple(fc.constant(byTag.file))), -// blob: fn.const(fc.tuple(fc.constant(byTag.blob))), -// nan: fn.const(fc.tuple(fc.constant(byTag.nan))), -// never: fn.const(fc.tuple(fc.constant(byTag.never))), -// null: fn.const(fc.tuple(fc.constant(byTag.null))), -// undefined: fn.const(fc.tuple(fc.constant(byTag.undefined))), -// unknown: fn.const(fc.tuple(fc.constant(byTag.unknown))), -// void: fn.const(fc.tuple(fc.constant(byTag.void))), -// symbol: fn.const(fc.tuple(fc.constant(byTag.symbol))), -// } satisfies { [K in keyof Seed.TerminalMap]: SeedBuilder } - -// const bigIntBounds = Bounds.bigint(fc.bigInt()) -// const numberBounds = Bounds.number(fc.double()) -// const stringBounds = Bounds.string(fc.integer({ min: 0 })) - -// const BoundableMap = { -// bigint: fn.const(fc.tuple(fc.constant(byTag.bigint), bigIntBounds)), -// number: fn.const(fc.tuple(fc.constant(byTag.number), numberBounds)), -// string: fn.const(fc.tuple(fc.constant(byTag.string), stringBounds)), -// } satisfies { [K in keyof Seed.BoundableMap]: SeedBuilder } - -// const ValueMap = { -// enum: fn.const(fc.tuple(fc.constant(byTag.enum), enumValues)), -// literal: fn.const(fc.tuple(fc.constant(byTag.literal), literalValue)), -// } satisfies { [K in keyof Seed.ValueMap]: SeedBuilder } - -// const UnaryMap = { -// array: (tie) => fc.tuple(fc.constant(byTag.array), tie('*'), Bounds.array(fc.integer({ min: 0 }))), -// custom: (tie) => fc.tuple(fc.constant(byTag.custom), tie('*')), -// lazy: (tie) => fc.tuple(fc.constant(byTag.lazy), fc.func<[], unknown>(tie('*'))), -// optional: (tie) => fc.tuple(fc.constant(byTag.optional), tie('*')), -// non_optional: (tie) => fc.tuple(fc.constant(byTag.non_optional), tie('*')), -// undefinedable: (tie) => fc.tuple(fc.constant(byTag.undefinedable), tie('*')), -// nullable: (tie) => fc.tuple(fc.constant(byTag.nullable), tie('*')), -// non_nullable: (tie) => fc.tuple(fc.constant(byTag.non_nullable), tie('*')), -// nullish: (tie) => fc.tuple(fc.constant(byTag.nullish), tie('*')), -// non_nullish: (tie) => fc.tuple(fc.constant(byTag.non_nullish), tie('*')), -// record: (tie) => fc.tuple(fc.constant(byTag.record), tie('*')), -// set: (tie) => fc.tuple(fc.constant(byTag.set), tie('*')), -// object: (tie, $) => fc.tuple( -// fc.constant(byTag.object), -// fc.uniqueArray(fc.tuple(identifier, tie('*')), $) -// ), -// loose_object: (tie, $) => fc.tuple( -// fc.constant(byTag.loose_object), -// fc.uniqueArray(fc.tuple(identifier, tie('*')), $) -// ), -// strict_object: (tie, $) => fc.tuple( -// fc.constant(byTag.strict_object), -// fc.uniqueArray(fc.tuple(identifier, tie('*')), $) -// ), -// object_with_rest: (tie, $) => fc.tuple( -// fc.constant(byTag.object_with_rest), -// fc.uniqueArray(fc.tuple(identifier, tie('*')), $), -// tie('*'), -// ), -// tuple: (tie, $) => fc.tuple(fc.constant(byTag.tuple), fc.array(tie('*'), $)), -// loose_tuple: (tie, $) => fc.tuple(fc.constant(byTag.loose_tuple), fc.array(tie('*'), $)), -// strict_tuple: (tie, $) => fc.tuple(fc.constant(byTag.strict_tuple), fc.array(tie('*'), $)), -// tuple_with_rest: (tie, $) => fc.tuple(fc.constant(byTag.tuple_with_rest), fc.array(tie('*'), $), tie('*')), -// union: (tie, $) => fc.tuple(fc.constant(byTag.union), fc.array(tie('*'), $)), -// variant: (tie, $) => fc.tuple( -// fc.constant(byTag.variant), -// fc.uniqueArray( -// fc.tuple( -// identifier, -// fc.uniqueArray( -// fc.tuple( -// identifier, -// tie('*'), -// ), -// { selector: ([key]) => key } -// ) -// ), -// { ...$, selector: ([uniqueTag]) => uniqueTag } -// ), -// identifier, -// ), -// intersect: (tie) => entries(tie('*'), { minLength: 2 }).map(fn.flow( -// (xs) => pair( -// xs.slice(0, Math.ceil(xs.length / 2)), -// xs.slice(Math.ceil(xs.length / 2)), -// ), -// ([l, r]) => pair( -// pair(byTag.object, l), -// pair(byTag.object, r), -// ), -// (both) => pair(byTag.intersect, both), -// )), -// map: (tie) => fc.tuple(fc.constant(byTag.map), fc.tuple(tie('*'), tie('*'))), -// promise: () => PromiseSchemaIsUnsupported('SeedMap'), -// } satisfies { [K in keyof Seed.UnaryMap]: SeedBuilder } - -// const TerminalSeeds = fn.map(Object_keys(TerminalMap), (tag) => byTag[tag]) -// const BoundableSeeds = fn.map(Object_keys(BoundableMap), (tag) => byTag[tag]) - -// export interface SeedBuilder { -// (tie: fc.LetrecTypedTie, $: Config.byTypeName[K]): fc.Arbitrary -// } - -// export type SeedMap = { [K in keyof Seed]: SeedBuilder } -// export const SeedMap = { -// ...TerminalMap, -// ...BoundableMap, -// ...ValueMap, -// ...UnaryMap, -// } satisfies SeedMap - -// export function isTerminal(x: unknown): x is Seed.Terminal | Seed.Boundable -// export function isTerminal(x: unknown) { -// if (!Array_isArray(x)) return false -// else { -// const tag = x[0] as never -// return TerminalSeeds.includes(tag) || BoundableSeeds.includes(tag) -// } -// } - -// export const pickAndSortNodes -// : (nodes: readonly ([keyof SeedMap, unknown])[]) => ($: Config) => (keyof SeedMap)[] -// = (nodes) => ({ include, exclude, sortBias } = Config.defaults as never) => -// nodes -// .map(([k]) => k) -// .filter((x) => -// (include ? include.includes(x as never) : true) && -// (exclude ? !exclude.includes(x as never) : true) -// ) -// // TODO: remove nullish coalesce operators -// .sort((l, r) => sortBias[l]! < sortBias[r]! ? -1 : sortBias[l]! > sortBias[r]! ? 1 : 0) - -// export function v_bigint(bounds?: Bounds.bigint): v.BigintSchema -// export function v_bigint(bounds: Bounds.bigint = Bounds.defaults.bigint) { -// const [min, max, multipleOf] = bounds -// const schema = v.bigint() -// let pipeline = Array.of>() -// if (typeof min === 'bigint') pipeline.push(v.minValue(min)) -// if (typeof max === 'bigint') pipeline.push(v.maxValue(max)) -// // if (typeof multipleOf === 'bigint') pipeline.push(v.multipleOf(multipleOf)) -// return pipeline.length === 0 ? schema : v.pipe(schema, ...pipeline) -// } - -// export function v_number(bounds?: Bounds.number): v.NumberSchema -// export function v_number(bounds: Bounds.number = Bounds.defaults.number) { -// const [min, max, multipleOf, minExcluded, maxExcluded] = bounds -// const schema = v.number() -// let pipeline = Array.of>() -// if (Number_isFinite(min)) pipeline.push(minExcluded ? v.gtValue(min) : v.minValue(min)) -// if (Number_isFinite(max)) pipeline.push(maxExcluded ? v.ltValue(max) : v.maxValue(max)) -// // if (Number_isFinite(multipleOf) pipeline.push(v.multipleOf(multipleOf)) -// return pipeline.length === 0 ? schema : v.pipe(schema, ...pipeline) -// } - -// export function v_string(bounds?: Bounds.string): v.StringSchema -// export function v_string(bounds: Bounds.string = Bounds.defaults.string) { -// const [min, max, exactLength] = bounds -// const schema = v.string() -// let pipeline = Array.of>() -// if (Number_isNatural(exactLength)) { -// pipeline.push(v.minLength(exactLength), v.maxLength(exactLength)) -// } else { -// if (Number_isNatural(min)) pipeline.push(v.minLength(min)) -// if (Number_isNatural(max)) pipeline.push(v.maxLength(max)) -// } -// return pipeline.length === 0 ? schema : v.pipe(schema, ...pipeline) -// } - -// export function v_array(elementSchema: T, bounds?: Bounds.array): v.ArraySchema -// export function v_array(elementSchema: T, bounds: Bounds.array = Bounds.defaults.array) { -// const [min, max, exactLength] = bounds -// const schema: v.ArraySchema = v.array(elementSchema) -// let pipeline = Array.of>() -// if (Number_isNatural(exactLength)) { -// pipeline.push(v.minLength(exactLength), v.maxLength(exactLength)) -// } else { -// if (Number_isNatural(min)) pipeline.push(v.minLength(min)) -// if (Number_isNatural(max)) pipeline.push(v.maxLength(max)) -// } -// return pipeline.length === 0 ? schema : v.pipe(schema, ...pipeline) -// } - -// const unboundedSeed = { -// bigint: () => fc.constant([byTag.bigint, [null, null, null]]), -// number: () => fc.constant([byTag.number, [null, null, null, false, false]]), -// string: () => fc.constant([byTag.string, [null, null]]), -// array: (tie) => fc.tuple(fc.constant(byTag.array), tie('*'), fc.constant([null, null])), -// } satisfies Record fc.Arbitrary> - -// export interface Builder extends inline<{ [K in Tag]+?: fc.Arbitrary }> { -// root?: fc.Arbitrary -// invalid?: fc.Arbitrary -// ['*']: fc.Arbitrary -// } - -// export function Builder(base: Gen.Base): -// >( -// options?: Options, -// overrides?: Partial> -// ) => (tie: fc.LetrecLooselyTypedTie) => Builder - -// export function Builder(base: Gen.Base) { -// return >(options?: Options, overrides?: Partial>) => { -// const $ = Config.parseOptions(options) -// return (tie: fc.LetrecLooselyTypedTie) => { -// const builder: { [x: string]: fc.Arbitrary } = fn.pipe( -// { -// ...base, -// ...$.bigint.unbounded && 'bigint' in base && { bigint: unboundedSeed.bigint }, -// ...$.number.unbounded && 'number' in base && { number: unboundedSeed.number }, -// ...$.string.unbounded && 'string' in base && { string: unboundedSeed.string }, -// ...$.array.unbounded && 'array' in base && { array: unboundedSeed.array }, -// ...overrides, -// }, -// (x) => pick(x, $.include), -// (x) => omit(x, $.exclude), -// (x) => fn.map(x, (f, k) => f(tie, $[k as never])), -// ) -// const nodes = pickAndSortNodes(Object_entries(builder) as [k: keyof SeedMap, unknown][])($) -// builder['*'] = fc.oneof($['*'], ...nodes.map((k) => builder[k])) -// const root = isKeyOf(builder, $.root) && builder[$.root] -// let leaf = builder['*'] - -// return Object_assign( -// builder, { -// ...root && { root }, -// ['*']: leaf -// }) -// } -// } -// } - -// export declare namespace Gen { -// type Base = { [K in keyof T]: (tie: fc.LetrecLooselyTypedTie, constraints: $[K & keyof $]) => fc.Arbitrary } -// type Values = never | T[Exclude] -// type InferArb = S extends fc.Arbitrary ? T : never -// type Builder = T & BuilderStar -// interface BuilderStar { ['*']: fc.Arbitrary>> } -// type BuildBuilder, Out extends {} = BuilderBase> = never | Builder -// type BuilderBase, $ extends ParseOptions = ParseOptions> = never | -// & ([$['root']] extends [never] ? unknown : { root: fc.Arbitrary<$['root']> }) -// & { [K in Exclude<$['include'], $['exclude']>]: fc.Arbitrary } -// type ParseOptions> = never | { -// include: Options['include'] extends readonly unknown[] ? Options['include'][number] : keyof T -// exclude: Options['exclude'] extends readonly unknown[] ? Options['exclude'][number] : never -// root: Options['root'] extends keyof T ? T[Options['root']] : never -// } -// } - -// /** -// * ## {@link Gen `Gen`} -// */ -// export function Gen(base: Gen.Base): -// >( -// options?: Options, -// overrides?: Partial> -// ) => Gen.BuildBuilder - -// export function Gen(base: Gen.Base) { -// return >( -// options?: Options, -// overrides?: Partial> -// ): Builder => { -// return fc.letrec(Builder(base)(options, overrides)) -// } -// } - -// const typedArray = fc.oneof( -// fc.int8Array(), -// fc.uint8Array(), -// fc.uint8ClampedArray(), -// fc.int16Array(), -// fc.uint16Array(), -// fc.int32Array(), -// fc.uint32Array(), -// fc.float32Array(), -// fc.float64Array(), -// fc.bigInt64Array(), -// fc.bigUint64Array(), -// ) - -// const pathName = fc.webUrl().map((webUrl) => new URL(webUrl).pathname) -// const ext = fc.string({ minLength: 2, maxLength: 3 }) -// const fileName = fc.tuple(pathName, ext).map(([pathName, ext]) => `${pathName}.${ext}` as const) -// const fileBits = fc.array(typedArray) -// const file = fc.tuple(fileBits, fileName).map(([fileBits, filename]) => new File(fileBits, filename)) -// const blob = fc.tuple(fileBits, fileName).map(([fileBits, filename]) => new Blob(fileBits)) -// const arbitrarySymbol = fc.oneof(fc.constant(Symbol()), fc.string().map((s) => Symbol.for(s))) - -// function entries(model: fc.Arbitrary, options?: fc.UniqueArrayConstraintsRecommended<[k: string, T], unknown>) { -// return fc.uniqueArray( -// fc.tuple(identifier, model), -// { selector: options?.selector || (([k]) => k), ...options }, -// ) -// } - -// // const is = { -// // null: (x: unknown): x is [byTag['null']] => Array_isArray(x) && x[0] === byTag.null, -// // undefined: (x: unknown): x is [byTag['undefined']] => Array_isArray(x) && x[0] === byTag.undefined, -// // boolean: (x: unknown): x is [byTag['boolean']] => Array_isArray(x) && x[0] === byTag.boolean, -// // int: (x: unknown): x is [byTag['int'], Bounds.int] => Array_isArray(x) && x[0] === byTag.int, -// // number: (x: unknown): x is [byTag['number'], Bounds.number] => Array_isArray(x) && x[0] === byTag.number, -// // string: (x: unknown): x is [byTag['number'], Bounds.string] => Array_isArray(x) && x[0] === byTag.string, -// // literal: (x: unknown): x is [byTag['literal'], z.core.util.Literal] => Array_isArray(x) && x[0] === byTag.literal, -// // bigint: (x: unknown): x is [byTag['number'], Bounds.bigint] => Array_isArray(x) && x[0] === byTag.bigint, -// // } - -// function intersect(x: unknown, y: unknown) { -// return !isObject(x) ? y : !isObject(y) ? x : Object_assign(x, y) -// } - -// const GeneratorByTag = { -// any: () => fc.anything(), -// custom: () => fc.anything(), -// boolean: () => fc.boolean(), -// date: () => fc.date({ noInvalidDate: true }), -// file: () => file, -// blob: () => blob, -// nan: () => fc.constant(Number.NaN), -// never: () => fc.constant(void 0 as never), -// null: () => fc.constant(null), -// symbol: () => arbitrarySymbol, -// undefined: () => fc.constant(undefined), -// unknown: () => fc.anything(), -// void: () => fc.constant(void 0 as void), -// bigint: (x) => fc.bigInt(Bounds.bigintBoundsToBigIntConstraints(x[1])), -// number: (x) => fc.double(Bounds.numberBoundsToDoubleConstraints(x[1])), -// string: (x) => fc.string(Bounds.stringBoundsToStringConstraints(x[1])), -// enum: (x) => fc.constantFrom(...Object_values(x[1])), -// literal: (x) => fc.constant(x[1]), -// array: (x) => fc.array(x[1], Bounds.arrayBoundsToArrayConstraints(x[2])), -// optional: (x, _$, isProperty) => isProperty ? x[1] : fc.oneof(x[1], fc.constant(undefined)), -// non_optional: (x) => x[1].map((_) => _ === undefined ? {} : _), -// undefinedable: (x) => fc.oneof(x[1], fc.constant(undefined)), -// nullable: (x) => fc.oneof(x[1], fc.constant(null)), -// non_nullable: (x) => x[1].map((_) => _ === null ? {} : _), -// nullish: (x) => fc.oneof(x[1], fc.constant(undefined), fc.constant(null)), -// non_nullish: (x) => x[1].map((_) => _ == null ? {} : _), -// set: (x) => x[1].map((v) => new globalThis.Set([v])), -// map: (x) => fc.tuple(x[1][0], x[1][1]).map(([k, v]) => new Map([[k, v]])), -// record: (x) => fc.dictionary(identifier, x[1]), -// tuple: (x) => fc.tuple(...x[1]), -// loose_tuple: (x) => fc.tuple(...x[1]), -// strict_tuple: (x) => fc.tuple(...x[1]), -// tuple_with_rest: (x) => fc.tuple(fc.tuple(...x[1]), fc.array(x[2])).map(([xs, rest]) => [...xs, ...rest]), -// union: (x) => fc.oneof(...(x[1] || [fc.constant(void 0 as never)])), -// lazy: (x) => x[1](), -// object: (x) => fc.record(Object.fromEntries(x[1])), -// strict_object: (x) => fc.record(Object.fromEntries(x[1])), -// loose_object: (x) => fc.record(Object.fromEntries(x[1])), -// object_with_rest: (x) => fc.tuple(fc.record(Object.fromEntries(x[1])), fc.dictionary(identifier, x[2])).map(([xs, rest]) => ({ ...rest, ...xs })), -// intersect: (x) => fc.tuple(...x[1]).map(([x, y]) => intersect(x, y)), -// variant: (x) => fc.oneof( -// ...x[1].map( -// ([tag, entries]) => fc.record( -// Object_assign( -// Object_create(null), -// Object_fromEntries(entries), -// { [x[2]]: fc.constant(tag) } -// ) -// ) -// ) -// ), -// /** -// * variant: (tie, $) => fc.tuple( -// * fc.constant(byTag.variant), -// * identifier, -// * fc.uniqueArray( -// * fc.tuple( -// * identifier, -// * fc.dictionary(identifier, tie('*'), $), -// * ), -// * { selector: ([uniqueTag]) => uniqueTag } -// * ) -// * ).map( -// * ([seedTag, discriminator, xs]) => [ -// * seedTag, -// * xs.map(([uniqueTag, x]) => ({ ...x, [discriminator]: uniqueTag })), -// * ] satisfies [any, any] -// * ), -// */ -// // fc.oneof(...(x[1] || [fc.constant(void 0 as never)])) -// promise: () => PromiseSchemaIsUnsupported('GeneratorByTag'), -// } satisfies { -// [K in keyof Seed]: (x: Seed>[K], $: Config, isProperty: boolean) => fc.Arbitrary -// } - -// /** -// * ## {@link seedToValidDataGenerator `seedToValidDataGenerator`} -// * -// * Convert a seed into an valid data generator. -// * -// * Valid in this context means that it will always satisfy the valibot schema that the seed produces. -// * -// * To convert a seed to a valibot schema, use {@link seedToSchema `seedToSchema`}. -// * -// * To convert a seed to an _invalid_ data generator, use {@link seedToInvalidDataGenerator `seedToInvalidDataGenerator`}. -// */ -// export function seedToValidDataGenerator(seed: Seed.F, options?: Config.Options): fc.Arbitrary -// export function seedToValidDataGenerator(seed: Seed.F, options?: Config.Options): fc.Arbitrary { -// const $ = Config.parseOptions(options) -// return fold>((x, isProperty) => GeneratorByTag[bySeed[x[0]]](x as never, $, isProperty || x[0] === 7500))(seed as never) -// } - -// /** -// * ## {@link seedToInvalidDataGenerator `seedToInvalidDataGenerator`} -// * -// * Convert a seed into an invalid data generator. -// * -// * Invalid in this context means that it will never satisfy the valibot schema that the seed produces. -// * -// * To convert a seed to a valibot schema, use {@link seedToSchema `seedToSchema`}. -// * -// * To convert a seed to an _valid_ data generator, use -// * {@link seedToValidDataGenerator `seedToValidDataGenerator`}. -// */ -// export function seedToInvalidDataGenerator(seed: T, options?: Config.Options): fc.Arbitrary -// export function seedToInvalidDataGenerator(seed: Seed.F, options?: Config.Options): fc.Arbitrary -// export function seedToInvalidDataGenerator(seed: Seed.F, options?: Config.Options): fc.Arbitrary { -// const $ = Config.parseOptions(options) -// return fold>((x) => { -// switch (x[0]) { -// case byTag.record: return GeneratorByTag.record(x).map(mutateRandomValueOf) -// case byTag.array: return GeneratorByTag.array(x).map(mutateRandomElementOf) -// case byTag.tuple: return GeneratorByTag.tuple(x).map(mutateRandomElementOf) -// case byTag.object: return GeneratorByTag.object(x).map(mutateRandomValueOf) -// default: return GeneratorByTag[bySeed[x[0]]](x as never, $, false) -// } -// })(seed as never) -// } - -// /** -// * ## {@link SeedGenerator `SeedGenerator`} -// * -// * Pseudo-random seed generator. -// * -// * The generator supports a wide range of discoverable configuration options via the -// * optional `options` argument. -// * -// * Many of those options are forwarded to the corresponding `fast-check` arbitrary. -// * -// * See also: -// * - {@link SeedGenerator `SeedGenerator`} -// * -// * @example -// * import * as fc from 'fast-check' -// * import * as v from 'valibot' -// * import { vxTest } from '@traversable/valibot-test' -// * -// * const Json = vxTest.SeedGenerator({ include: ['null', 'boolean', 'number', 'string', 'array', 'object'] }) -// * const [jsonNumber, jsonObject, anyJson] = [ -// * fc.sample(Json.number, 1)[0], -// * fc.sample(Json.object, 1)[0], -// * fc.sample(Json['*'], 1)[0], -// * ] as const -// * -// * console.log(JSON.stringify(jsonNumber)) -// * // => [200,[2.96e-322,1,null,false,true]] -// * -// * console.log(vxTest.toString(vxTest.seedToSchema(jsonNumber))) -// * // => v.pipe(v.number(), v.maxValue(2.96e-322), v.ltValue(1) -// * -// * console.log(JSON.stringify(jsonObject)) -// * // => [7500,[["n;}289K~",[250,[null,null]]]]] -// * -// * console.log(vxTest.toString(vxTest.seedToSchema(jsonObject))) -// * // => v.object({ "n;}289K~": v.string() }) -// * -// * console.log(anyJson) -// * // => [250,[23,64]] -// * -// * console.log(vxTest.toString(vxTest.seedToSchema(anyJson))) -// * // => v.pipe(v.string(), v.minValue(23), v.maxValue(64)) -// */ -// export const SeedGenerator = Gen(SeedMap) - -// const seedsThatPreventGeneratingValidData = [ -// 'custom', -// 'never', -// 'non_optional', -// 'non_nullable', -// 'non_nullish', -// 'promise', -// ] satisfies SchemaGenerator.Options['exclude'] - -// const seedsThatPreventGeneratingInvalidData = [ -// ...seedsThatPreventGeneratingValidData, -// 'any', -// 'promise', -// 'symbol', -// 'unknown', -// ] satisfies SchemaGenerator.Options['exclude'] - -// /** -// * ## {@link SeedValidDataGenerator `SeedValidDataGenerator`} -// * -// * A seed generator that can be interpreted to produce reliably valid data. -// * -// * This was originally developed to test for parity between various schema libraries. -// * -// * Note that certain schemas make generating valid data impossible -// * (like {@link v.never `v.never`}). For this reason, those schemas are not seeded. -// * -// * To see the list of excluded schemas, see -// * {@link seedsThatPreventGeneratingValidData `seedsThatPreventGeneratingValidData`}. -// * -// * See also: -// * - {@link SeedInvalidDataGenerator `SeedInvalidDataGenerator`} -// * -// * @example -// * import * as fc from 'fast-check' -// * import * as v from 'valibot' -// * import { vxTest } from '@traversable/valibot-test' -// * -// * const [seed] = fc.sample(vxTest.SeedValidDataGenerator, 1) -// * const ValibotSchema = vxTest.seedToSchema(seed) -// * const dataset = fc.sample(vxTest.seedToValidData(seed), 5) -// * -// * const results = dataset.map((pt) => ValibotSchema.safeParse(pt).success) -// * -// * console.log(results) // => [true, true, true, true, true] -// */ - -// export const SeedValidDataGenerator = SeedGenerator({ exclude: seedsThatPreventGeneratingValidData })['*'] - -// /** -// * ## {@link SeedInvalidDataGenerator `vxTest.SeedInvalidDataGenerator`} -// * -// * A seed generator that can be interpreted to produce reliably invalid data. -// * -// * This was originally developed to test for parity between various schema libraries. -// * -// * Note that certain schemas make generating invalid data impossible -// * (like {@link v.any `v.any`}). For this reason, those schemas are not seeded. -// * -// * To see the list of excluded schemas, see -// * {@link seedsThatPreventGeneratingInvalidData `vxTest.seedsThatPreventGeneratingInvalidData`}. -// * -// * See also: -// * - {@link SeedValidDataGenerator `vxTest.SeedValidDataGenerator`} -// * -// * @example -// * import * as fc from 'fast-check' -// * import * as v from 'valibot' -// * import { vxTest } from '@traversable/valibot-test' -// * -// * const [seed] = fc.sample(vxTest.SeedInvalidDataGenerator, 1) -// * const ValibotSchema = vxTest.seedToSchema(seed) -// * const dataset = fc.sample(vxTest.seedToInvalidData(seed), 5) -// * -// * const results = dataset.map((pt) => ValibotSchema.safeParse(pt).success) -// * -// * console.log(results) // => [false, false, false, false, false] -// */ -// export const SeedInvalidDataGenerator = fn.pipe( -// SeedGenerator({ exclude: seedsThatPreventGeneratingInvalidData }), -// ($) => fc.oneof( -// $.object, -// $.tuple, -// $.array, -// $.record, -// ) -// ) - -// /** -// * ## {@link SchemaGenerator `vxTest.SchemaGenerator`} -// * -// * A valibot schema generator that can be interpreted to produce an arbitrary valibot schema. -// * -// * The generator supports a wide range of configuration options that are discoverable via the -// * optional `options` argument. -// * -// * Many of those options are forwarded to the corresponding `fast-check` arbitrary. -// * -// * See also: -// * - {@link SeedGenerator `vxTest.SeedGenerator`} -// * -// * @example -// * import * as fc from 'fast-check' -// * import * as v from 'valibot' -// * import { vxTest } from '@traversable/valibot-test' -// * -// * const tenSchemas = fc.sample(vxTest.SchemaGenerator({ -// * include: ['null', 'boolean', 'number', 'string', 'array', 'object'] -// * }), 10) -// * -// * tenSchemas.forEach((s) => console.log(vxTest.toString(s))) -// * // => v.number() -// * // => v.pipe(v.string(), v.minValue(9.1e-53)) -// * // => v.null() -// * // => v.array(v.boolean()) -// * // => v.boolean() -// * // => v.object({ "": v.object({ "/d2P} {/": v.boolean() }), "svH2]L'x": v.pipe(v.number(), v.ltValue(-65536)) }) -// * // => v.null() -// * // => v.string() -// * // => v.array(v.array(v.null())) -// * // => v.object({ "y(Qza": v.boolean(), "G1S\\U 4Y6i": v.object({ "YtO3]ia0cM": v.boolean() }) }) -// */ - -// export const SchemaGenerator = fn.flow( -// SeedGenerator, -// builder => builder['*'], -// (arb) => arb.map(seedToSchema), -// ) - -// export declare namespace SchemaGenerator { -// type Options = Config.Options -// } - -// /** -// * ## {@link seedToSchema `vxTest.seedToSchema`} -// * -// * Interpreter that converts a seed value into its corresponding valibot schema. -// * -// * To get a seed, use {@link SeedGenerator `vxTest.SeedGenerator`}. -// */ -// export function seedToSchema(seed: T): Seed.schemaFromComposite[T[0]] -// export function seedToSchema(seed: Seed.F): v.BaseSchema> -// export function seedToSchema(seed: Seed.F) { -// return fold((x) => { -// switch (true) { -// default: return fn.exhaustive(x) -// case x[0] === byTag.any: return v.any() -// case x[0] === byTag.custom: return v.custom(() => true) -// case x[0] === byTag.boolean: return v.boolean() -// case x[0] === byTag.date: return v.date() -// case x[0] === byTag.file: return v.file() -// case x[0] === byTag.blob: return v.blob() -// case x[0] === byTag.nan: return v.nan() -// case x[0] === byTag.never: return v.never() -// case x[0] === byTag.null: return v.null() -// case x[0] === byTag.symbol: return v.symbol() -// case x[0] === byTag.undefined: return v.undefined() -// case x[0] === byTag.unknown: return v.unknown() -// case x[0] === byTag.void: return v.void() -// case x[0] === byTag.bigint: return v_bigint(x[1]) -// case x[0] === byTag.number: return v_number(x[1]) -// case x[0] === byTag.string: return v_string(x[1]) -// case x[0] === byTag.enum: return v.enum(x[1]) -// case x[0] === byTag.literal: return v.literal(x[1]) -// case x[0] === byTag.array: return v.array(x[1]) -// case x[0] === byTag.optional: return v.optional(x[1]) -// case x[0] === byTag.non_optional: return v.nonOptional(x[1]) -// case x[0] === byTag.undefinedable: return v.undefinedable(x[1]) -// case x[0] === byTag.nullable: return v.nullable(x[1]) -// case x[0] === byTag.non_nullable: return v.nonNullable(x[1]) -// case x[0] === byTag.nullish: return v.nullish(x[1]) -// case x[0] === byTag.non_nullish: return v.nonNullish(x[1]) -// case x[0] === byTag.set: return v.set(x[1]) -// case x[0] === byTag.intersect: return v.intersect(x[1]) -// case x[0] === byTag.lazy: return v.lazy(x[1]) -// case x[0] === byTag.map: return v.map(x[1][0], x[1][1]) -// case x[0] === byTag.record: return v.record(v.string(), x[1]) -// case x[0] === byTag.object: return v.object(Object.fromEntries(x[1])) -// case x[0] === byTag.loose_object: return v.looseObject(Object.fromEntries(x[1])) -// case x[0] === byTag.strict_object: return v.strictObject(Object.fromEntries(x[1])) -// case x[0] === byTag.object_with_rest: return v.objectWithRest(Object.fromEntries(x[1]), x[2]) -// case x[0] === byTag.tuple: return v.tuple(x[1]) -// case x[0] === byTag.loose_tuple: return v.looseTuple(x[1]) -// case x[0] === byTag.strict_tuple: return v.strictTuple(x[1]) -// case x[0] === byTag.tuple_with_rest: return v.tupleWithRest(x[1], x[2]) -// case x[0] === byTag.strict_object: return v.strictObject(Object.fromEntries(x[1])) -// case x[0] === byTag.union: return v.union(x[1]) -// case x[0] === byTag.variant: return v.variant(x[2], x[1].map( -// ([tag, entries]) => v.object({ ...Object_fromEntries(entries), [x[2]]: v.literal(tag) })) -// ) -// case x[0] === byTag.promise: return PromiseSchemaIsUnsupported('seedToSchema') -// } -// })(seed as never) -// } - -// /** -// * ## {@link seedToValidData `seedToValidData`} -// * -// * Given a seed, generates an single example of valid data. -// * -// * Valid in this context means that it will always satisfy the valibot schema that the seed produces. -// * -// * To use it, you'll need to have [fast-check](https://github.com/dubzzz/fast-check) installed. -// * -// * To convert a seed to a valibot schema, use {@link seedToSchema `seedToSchema`}. -// * -// * To convert a seed to a single example of _invalid_ data, use {@link seedToInvalidData `seedToInvalidData`}. -// */ -// export const seedToValidData = fn.flow( -// seedToValidDataGenerator, -// (model) => fc.sample(model, 1)[0], -// ) - -// /** -// * ## {@link seedToInvalidData `seedToInvalidData`} -// * -// * Given a seed, generates an single example of invalid data. -// * -// * Invalid in this context means that it will never satisfy the valibot schema that the seed produces. -// * -// * To use it, you'll need to have [fast-check](https://github.com/dubzzz/fast-check) installed. -// * -// * To convert a seed to a valibot schema, use {@link seedToSchema `seedToSchema`}. -// * -// * To convert a seed to a single example of _valid_ data, use {@link seedToValidData `seedToValidData`}. -// */ -// export const seedToInvalidData = fn.flow( -// seedToInvalidDataGenerator, -// (model) => fc.sample(model, 1)[0], -// ) +import { Json } from '@traversable/json' + +type Config = import('./generator-options.js').Config +import * as Config from './generator-options.js' +import * as Bounds from './generator-bounds.js' + +import { AST, Kind, NamedType } from '@traversable/graphql-types' + +import type { Tag } from './generator-seed.js' +import { byTag, bySeed, Seed } from './generator-seed.js' +import { fold } from './functor.js' + +const identifier = fc.stringMatching(new RegExp(PATTERN.identifier, 'u')) + +const nameNode = (name: T): AST.NameNode => ({ + kind: 'Name', + value: name, +}) + +const namedTypeNode = (name: T) => ({ + kind: Kind.NamedType, + name: nameNode(name), +}) satisfies AST.NamedTypeNode + +const nullValueNode = () => ({ + kind: Kind.NullValue, +}) satisfies AST.NullValueNode + +const booleanValueNode = (value: boolean) => ({ + kind: Kind.BooleanValue, + value, +}) satisfies AST.BooleanValueNode + +const intValueNode = (value: number) => ({ + kind: Kind.IntValue, + value, +}) satisfies AST.IntValueNode + +const floatValueNode = (value: number) => ({ + kind: Kind.FloatValue, + value, +}) satisfies AST.FloatValueNode + +const stringValueNode = (value: string, block?: boolean) => ({ + kind: Kind.StringValue, + value, + block: block ?? new RegExp(PATTERN.newline).test(value), +}) satisfies AST.StringValueNode + +const listValueNode = (values: readonly AST.ValueNode[]) => ({ + kind: Kind.ListValue, + values, +}) satisfies AST.ListValueNode + +const objectFieldNodes = (fields: { [x: string]: Json }) => Object_entries(fields).map( + ([k, json]) => ({ + kind: Kind.ObjectField, + name: nameNode(k), + value: valueNodeFromJson(json), + }) +) satisfies AST.ObjectFieldNode[] + +const variableNode = (name: string): AST.VariableNode => ({ + kind: Kind.Variable, + name: nameNode(name), +}) + +const enumValueDefinition = (name: string): AST.EnumValueDefinitionNode => ({ + kind: Kind.EnumValueDefinition, + name: nameNode(name), +}) + +const operationTypeDefinition = (name: string, operation: T): AST.OperationTypeDefinitionNode => ({ + kind: Kind.OperationTypeDefinition, + type: namedTypeNode(name), + operation, +}) + +const valueNodeFromJson = Json.fold((x) => { + switch (true) { + default: return x satisfies never + case x == null: return nullValueNode() + case x === true: + case x === false: return booleanValueNode(x) + case Number_isSafeInteger(x): return intValueNode(x) + case Number_isFinite(x): return floatValueNode(x) + case typeof x === 'string': return stringValueNode(x) + case Array_isArray(x): return listValueNode(x) + case !!x && typeof x === 'object': return { + kind: Kind.ObjectValue, + fields: Object_entries(x).map(([k, v]) => ({ + kind: Kind.ObjectField, + name: nameNode(k), + value: v, + })) + } + } +}) + +export function pickAndSortNodes(nodes: readonly ([keyof Seed, unknown])[]): ($: Config) => (keyof Seed)[] { + return ({ include, exclude, sortBias }) => nodes + .map(([k]) => k) + .filter((x) => + (include ? include.includes(x as never) : true) && + (exclude ? !exclude.includes(x as never) : true) + ) + .sort((l, r) => sortBias[l]! < sortBias[r]! ? -1 : sortBias[l]! > sortBias[r]! ? 1 : 0) +} + +export declare namespace Gen { + type Base = { [K in keyof T]: (tie: fc.LetrecLooselyTypedTie, constraints: $[K & keyof $]) => fc.Arbitrary } + type Values = never | T[Exclude] + type InferArb = S extends fc.Arbitrary ? T : never + type Builder = T & BuilderStar + interface BuilderStar { ['*']: fc.Arbitrary>> } + type BuildBuilder, Out extends {} = BuilderBase> = never | Builder + type BuilderBase, $ extends ParseOptions = ParseOptions> = never | + & ([$['root']] extends [never] ? unknown : { root: fc.Arbitrary<$['root']> }) + & { [K in Exclude<$['include'], $['exclude']>]: fc.Arbitrary } + type ParseOptions> = never | { + include: Options['include'] extends readonly unknown[] ? Options['include'][number] : keyof T + exclude: Options['exclude'] extends readonly unknown[] ? Options['exclude'][number] : never + root: Options['root'] extends keyof T ? T[Options['root']] : never + } +} + +export function Gen(base: Gen.Base>): + >( + options?: Options, + overrides?: Partial>> + ) => Gen.BuildBuilder + +export function Gen(base: Gen.Base>) { + return >( + options?: Options, + overrides?: Partial>> + ): Builder => { + return fc.letrec(Builder(base)(options, overrides)) + } +} + +export interface Builder extends inline<{ [K in Tag]+?: fc.Arbitrary }> { + root?: fc.Arbitrary + ['*']: fc.Arbitrary +} + +export function Builder(base: Gen.Base>): + >( + options?: Options, + overrides?: Partial>> + ) => (tie: fc.LetrecLooselyTypedTie) => Builder + +export function Builder(base: Gen.Base>) { + return >(options?: Options, overrides?: Partial>>) => { + const $ = Config.defaultOptions(options) + return (tie: fc.LetrecLooselyTypedTie) => { + const builder: { [x: string]: fc.Arbitrary } = fn.pipe( + { ...base, ...overrides }, + (x) => pick(x, $.include), + (x) => omit(x, $.exclude), + (x) => fn.map(x, (f, k) => f(tie, $[k as never])), + ) + const nodes = pickAndSortNodes(Object_entries(builder) as [k: keyof Seed, unknown][])($) + builder['*'] = fc.oneof(...nodes.map((k) => builder[k])) + const root = isKeyOf(builder, $.root) && builder[$.root] + let leaf = builder['*'] + + return Object_assign( + builder, { + ...root && { root }, + ['*']: leaf + }) + } + } +} + +const FromSeed = { + Name: ([, name]) => nameNode(name), + Null: (_) => namedTypeNode(NamedType.Null), + Boolean: (_) => namedTypeNode(NamedType.Boolean), + Int: (_) => namedTypeNode(NamedType.Int), + Float: (_) => namedTypeNode(NamedType.Float), + Number: (_) => namedTypeNode(NamedType.Number), + String: (_) => namedTypeNode(NamedType.String), + ID: (_) => namedTypeNode(NamedType.ID), + NullValue: (_) => ({ kind: Kind.NullValue }), + BooleanValue: ([, value]) => ({ + kind: Kind.BooleanValue, + value, + }), + FloatValue: ([, value]) => ({ + kind: Kind.FloatValue, + value, + }), + IntValue: ([, value]) => ({ + kind: Kind.IntValue, + value, + }), + StringValue: ([, value, block]) => ({ + kind: Kind.StringValue, + value, + block, + }), + ScalarTypeDefinition: ([, name, description]) => ({ + kind: Kind.ScalarTypeDefinition, + name: nameNode(name), + description: stringValueNode(...description), + }), + EnumValue: ([, value]) => ({ + kind: Kind.EnumValue, + value, + }), + EnumValueDefinition: ([, name]) => ({ + kind: Kind.EnumValueDefinition, + name: nameNode(name), + }), + ListValue: ([, values]) => ({ + kind: Kind.ListValue, + values: fn.map(values, (value) => valueNodeFromJson(value)), + }), + ObjectValue: ([, fields]) => ({ + kind: Kind.ObjectValue, + fields: objectFieldNodes(fields), + }), + Argument: ([, name, value]) => ({ + kind: Kind.Argument, + name: nameNode(name), + value, + }), + Document: ([, definitions]) => ({ + kind: Kind.Document, + definitions + }), + Directive: ([, name, args]) => ({ + kind: Kind.Directive, + name: nameNode(name), + arguments: args, + }), + DirectiveDefinition: ([, name, description, repeatable, locations, args]) => ({ + kind: Kind.DirectiveDefinition, + name: nameNode(name), + repeatable, + description: stringValueNode(...description), + locations: locations.map((location) => nameNode(location)), + ...args.length && { arguments: args }, + }), + EnumTypeDefinition: ([, name, description, values]) => ({ + kind: Kind.EnumTypeDefinition, + name: nameNode(name), + description: stringValueNode(...description), + values: values.map(enumValueDefinition), + }), + Field: ([, name, alias, selectionSet, args, directives]) => ({ + kind: Kind.Field, + name: nameNode(name), + alias: nameNode(alias), + ...selectionSet != null && { selectionSet }, + ...args.length && { arguments: args }, + ...directives.length && { directives }, + }), + FieldDefinition: ([, name, description, type, args, directives]) => ({ + kind: Kind.FieldDefinition, + name: nameNode(name), + type, + description: stringValueNode(...description), + ...args.length && { arguments: args }, + ...directives.length && { directives }, + }), + FragmentDefinition: ([, name, typeCondition, selectionSet, directives]) => ({ + kind: Kind.FragmentDefinition, + name: nameNode(name), + typeCondition: namedTypeNode(typeCondition), + selectionSet, + ...directives.length && { directives }, + }), + FragmentSpread: ([, name, directives]) => ({ + kind: Kind.FragmentSpread, + name: nameNode(name), + ...directives.length && { directives }, + }), + InlineFragment: ([, typeCondition, selectionSet, directives]) => ({ + kind: Kind.InlineFragment, + selectionSet, + typeCondition: namedTypeNode(typeCondition), + ...directives.length && { directives }, + }), + InputObjectTypeDefinition: ([, name, description, fields, directives]) => ({ + kind: Kind.InputObjectTypeDefinition, + name: nameNode(name), + fields, + description: stringValueNode(...description), + ...directives.length && { directives }, + }), + InputValueDefinition: ([, name, description, type, defaultValue, directives]) => ({ + kind: Kind.InputValueDefinition, + name: nameNode(name), + type, + ...defaultValue != null && { defaultValue }, + description: stringValueNode(...description), + ...directives.length && { directives }, + }), + InterfaceTypeDefinition: ([, name, description, fields, interfaces, directives]) => ({ + kind: Kind.InterfaceTypeDefinition, + name: nameNode(name), + fields, + interfaces, + description: stringValueNode(...description), + ...directives.length && { directives }, + }), + ListType: ([, type]) => ({ + kind: Kind.ListType, + type, + }), + NamedType: ([, name]) => ({ + kind: Kind.NamedType, + name: nameNode(name as never), // <-- TODO + }), + NonNullType: ([, type]) => ({ + kind: Kind.NonNullType, + type, + }), + ObjectField: ([, name, value]) => ({ + kind: Kind.ObjectField, + name: nameNode(name), + value: valueNodeFromJson(value), + }), + ObjectTypeDefinition: ([, name, description, fields, interfaces, directives]) => ({ + kind: Kind.ObjectTypeDefinition, + name: nameNode(name), + fields, + interfaces, + description: stringValueNode(...description), + ...directives.length && { directives }, + }), + OperationDefinition: ([, name, operationType, selectionSet, variableDefinitions, directives]) => ({ + kind: Kind.OperationDefinition, + name: nameNode(name), // <-- TODO: optional? + operation: operationType, + selectionSet, + ...variableDefinitions.length && { variableDefinitions }, + ...directives.length && { directives }, + }), + OperationTypeDefinition: ([, type, operation]) => ({ + kind: Kind.OperationTypeDefinition, + type: namedTypeNode(type), + operation, + }), + SchemaDefinition: ([, description, operationTypes, directives]) => ({ + kind: Kind.SchemaDefinition, + operationTypes: operationTypes.map((ot) => operationTypeDefinition(...ot)), + description: stringValueNode(...description), + ...directives.length && { directives }, + }), + SelectionSet: ([, selections]) => ({ + kind: Kind.SelectionSet, + selections, + }), + UnionTypeDefinition: ([, name, types, directives]) => ({ + kind: Kind.UnionTypeDefinition, + name: nameNode(name), + types, + ...directives.length && { directives }, + }), + Variable: ([, name, description, directives]) => ({ + kind: Kind.Variable, + name: nameNode(name), + description: stringValueNode(...description), + ...directives.length && { directives }, + }), + VariableDefinition: ([, name, type, defaultValue, directives]) => ({ + kind: Kind.VariableDefinition, + variable: variableNode(name), + type, + ...defaultValue != null && { defaultValue }, + ...directives.length && { directives }, + }), +} satisfies ( + & { [K in keyof AST.Catalog.byKind]: (x: Seed[K]) => AST.Catalog.byKind[K] } + & { [K in keyof AST.Catalog.byNamedType]: (x: Seed[K]) => AST.Catalog.byNamedType[K] } + ) + +export const seedToSchema = fold((x) => FromSeed[bySeed[x[0]]](x as never)) diff --git a/packages/graphql-test/tsconfig.build.json b/packages/graphql-test/tsconfig.build.json index ee34f8a2..23cce151 100644 --- a/packages/graphql-test/tsconfig.build.json +++ b/packages/graphql-test/tsconfig.build.json @@ -9,6 +9,7 @@ }, "references": [ { "path": "../graphql-types" }, + { "path": "../json" }, { "path": "../registry" } ] } diff --git a/packages/graphql-test/tsconfig.src.json b/packages/graphql-test/tsconfig.src.json index fed5dd75..851d36a7 100644 --- a/packages/graphql-test/tsconfig.src.json +++ b/packages/graphql-test/tsconfig.src.json @@ -8,6 +8,7 @@ }, "references": [ { "path": "../graphql-types" }, + { "path": "../json" }, { "path": "../registry" } ], "include": ["src"] diff --git a/packages/graphql-test/tsconfig.test.json b/packages/graphql-test/tsconfig.test.json index e318a1ea..298a1b08 100644 --- a/packages/graphql-test/tsconfig.test.json +++ b/packages/graphql-test/tsconfig.test.json @@ -9,6 +9,7 @@ "references": [ { "path": "tsconfig.src.json" }, { "path": "../graphql-types" }, + { "path": "../json" }, { "path": "../registry" } ], "include": ["test"] diff --git a/packages/graphql-types/src/exports.ts b/packages/graphql-types/src/exports.ts index d484d5d4..aeee3105 100644 --- a/packages/graphql-types/src/exports.ts +++ b/packages/graphql-types/src/exports.ts @@ -14,6 +14,7 @@ export { Kinds, NamedType, NamedTypes, + OperationType, Tag, Tags, createIndex, diff --git a/packages/graphql-types/src/functor.ts b/packages/graphql-types/src/functor.ts index cb7fdb3e..e16cf58f 100644 --- a/packages/graphql-types/src/functor.ts +++ b/packages/graphql-types/src/functor.ts @@ -1,7 +1,7 @@ import type * as GQL from 'graphql' import type * as T from '@traversable/registry' -import { fn, has, topologicalSort } from '@traversable/registry' +import { fn, has, Object_create, Object_values, topologicalSort } from '@traversable/registry' export type Algebra = { (src: AST.DocumentNode, ix?: Index): { order: string[], byName: Record T> } @@ -36,7 +36,7 @@ export const Kind = { IntValue: 'IntValue', ListType: 'ListType', ListValue: 'ListValue', - Name: 'Name', + // Name: 'Name', NamedType: 'NamedType', NonNullType: 'NonNullType', NullValue: 'NullValue', @@ -47,7 +47,7 @@ export const Kind = { OperationTypeDefinition: 'OperationTypeDefinition', ScalarTypeDefinition: 'ScalarTypeDefinition', SchemaDefinition: 'SchemaDefinition', - SchemaExtension: 'SchemaExtension', + // SchemaExtension: 'SchemaExtension', SelectionSet: 'SelectionSet', StringValue: 'StringValue', UnionTypeDefinition: 'UnionTypeDefinition', @@ -56,7 +56,7 @@ export const Kind = { } as const export type Kind = typeof Kinds[number] -export const Kinds = Object.values(Kind) +export const Kinds = Object_values(Kind) export declare namespace Kind { type Argument = typeof Kind.Argument @@ -79,7 +79,7 @@ export declare namespace Kind { type IntValue = typeof Kind.IntValue type ListType = typeof Kind.ListType type ListValue = typeof Kind.ListValue - type Name = typeof Kind.Name + // type Name = typeof Kind.Name type NamedType = typeof Kind.NamedType type NonNullType = typeof Kind.NonNullType type NullValue = typeof Kind.NullValue @@ -90,7 +90,7 @@ export declare namespace Kind { type OperationTypeDefinition = typeof Kind.OperationTypeDefinition type ScalarTypeDefinition = typeof Kind.ScalarTypeDefinition type SchemaDefinition = typeof Kind.SchemaDefinition - type SchemaExtension = typeof Kind.SchemaExtension + // type SchemaExtension = typeof Kind.SchemaExtension type SelectionSet = typeof Kind.SelectionSet type StringValue = typeof Kind.StringValue type UnionTypeDefinition = typeof Kind.UnionTypeDefinition @@ -99,19 +99,20 @@ export declare namespace Kind { } export const NamedType = { + Null: 'Null', Boolean: 'Boolean', Float: 'Float', ID: 'ID', Int: 'Int', Number: 'Number', String: 'String', - Null: 'Null', } as const export type NamedType = typeof NamedTypes[number] -export const NamedTypes = Object.values(NamedType) +export const NamedTypes = Object_values(NamedType) export declare namespace NamedType { + type Null = typeof NamedType.Null type Boolean = typeof NamedType.Boolean type Float = typeof NamedType.Float type ID = typeof NamedType.ID @@ -127,7 +128,7 @@ export const OperationType = { } as const export type OperationType = typeof OperationTypes[number] -export const OperationTypes = Object.values(OperationType) +export const OperationTypes = Object_values(OperationType) export declare namespace OperationType { type Query = typeof OperationType.Query @@ -137,28 +138,40 @@ export declare namespace OperationType { export const Tag = { ...Kind, ...NamedType } export type Tag = typeof Tags[number] -export const Tags = Object.values(Tag) - -export type Catalog = { [Node in F as Node['kind']]: Node } +export const Tags = Object_values(Tag) + +export declare namespace Catalog { + type byKind = { [Node in F as Node['kind']]: Node } + type byNamedType = { + [NamedType.Null]: AST.NullNode + [NamedType.Int]: AST.IntNode + [NamedType.Float]: AST.FloatNode + [NamedType.Number]: AST.NumberNode + [NamedType.Boolean]: AST.BooleanNode + [NamedType.String]: AST.StringNode + [NamedType.ID]: AST.IDNode + } +} export interface Location { start: number end: number } -export interface SelectionSetNode { +export interface SelectionSetNode { kind: Kind.SelectionSet selections: readonly T[] loc?: Location } export interface NameNode { - kind: Kind.Name + kind: 'Name' value: Value loc?: Location } export interface NamedTypeNode { + kind: Kind.NamedType name: NameNode loc?: Location } @@ -174,20 +187,20 @@ export interface RefNode { loc?: Location } -export interface ArgumentNode { +export interface ArgumentNode { kind: Kind.Argument loc?: Location name: NameNode value: T } -export interface DocumentNode { +export interface DocumentNode { kind: Kind.Document definitions: readonly T[] loc?: Location } -export interface InputValueDefinitionNode { +export interface InputValueDefinitionNode { kind: Kind.InputValueDefinition name: NameNode type: T @@ -197,7 +210,7 @@ export interface InputValueDefinitionNode { loc?: Location } -export interface InputObjectTypeDefinitionNode { +export interface InputObjectTypeDefinitionNode { kind: Kind.InputObjectTypeDefinition name: NameNode fields: readonly T[] @@ -212,7 +225,7 @@ export interface VariableNode { loc?: Location } -export interface VariableDefinitionNode { +export interface VariableDefinitionNode { kind: Kind.VariableDefinition variable: VariableNode type: T @@ -221,7 +234,7 @@ export interface VariableDefinitionNode { loc?: Location } -export interface ScalarTypeDefinitionNode { +export interface ScalarTypeDefinitionNode { kind: Kind.ScalarTypeDefinition name: NameNode directives?: T[] @@ -229,6 +242,12 @@ export interface ScalarTypeDefinitionNode { loc?: Location } +export interface NullNode { + kind: Kind.NamedType + name: NameNode + loc?: Location +} + export interface BooleanNode { kind: Kind.NamedType name: NameNode @@ -278,7 +297,7 @@ export interface EnumValueNode { loc?: Location } -export interface EnumTypeDefinitionNode { +export interface EnumTypeDefinitionNode { kind: Kind.EnumTypeDefinition name: NameNode values: readonly EnumValueDefinitionNode[] @@ -287,19 +306,19 @@ export interface EnumTypeDefinitionNode { loc?: Location } -export interface NonNullTypeNode { +export interface NonNullTypeNode { kind: Kind.NonNullType type: T loc?: Location } -export interface ListNode { +export interface ListNode { kind: Kind.ListType type: T loc?: Location } -export interface FieldDefinitionNode { +export interface FieldDefinitionNode { kind: Kind.FieldDefinition name: NameNode type: T @@ -309,8 +328,8 @@ export interface FieldDefinitionNode { loc?: Location } -export interface FieldNode { - kind: Kind.FieldDefinition +export interface FieldNode { + kind: Kind.Field alias: NameNode | undefined name: NameNode selectionSet?: T @@ -320,7 +339,7 @@ export interface FieldNode { loc?: Location } -export interface ObjectTypeDefinitionNode { +export interface ObjectTypeDefinitionNode { kind: Kind.ObjectTypeDefinition name: NameNode fields: readonly T[] @@ -330,7 +349,7 @@ export interface ObjectTypeDefinitionNode { loc?: Location } -export interface InterfaceTypeDefinitionNode { +export interface InterfaceTypeDefinitionNode { kind: Kind.InterfaceTypeDefinition name: NameNode fields: readonly T[] @@ -340,7 +359,7 @@ export interface InterfaceTypeDefinitionNode { loc?: Location } -export interface UnionTypeDefinitionNode { +export interface UnionTypeDefinitionNode { kind: Kind.UnionTypeDefinition name: NameNode types: readonly T[] @@ -349,7 +368,7 @@ export interface UnionTypeDefinitionNode { loc?: Location } -export interface FragmentDefinitionNode { +export interface FragmentDefinitionNode { kind: Kind.FragmentDefinition name: NameNode typeCondition: NamedTypeNode @@ -358,14 +377,14 @@ export interface FragmentDefinitionNode { loc?: Location } -export interface FragmentSpreadNode { +export interface FragmentSpreadNode { kind: Kind.FragmentSpread name: NameNode directives?: readonly T[] loc?: Location } -export interface InlineFragmentNode { +export interface InlineFragmentNode { kind: Kind.InlineFragment typeCondition?: NamedTypeNode directives?: readonly T[] @@ -375,13 +394,13 @@ export interface InlineFragmentNode { export interface IntValueNode { kind: Kind.IntValue - value: string + value: number loc?: Location } export interface FloatValueNode { kind: Kind.FloatValue - value: string + value: number loc?: Location } @@ -422,14 +441,14 @@ export interface ObjectFieldNode { loc?: Location } -export interface DirectiveNode { +export interface DirectiveNode { kind: Kind.Directive name: NameNode arguments?: readonly T[] loc?: Location } -export interface DirectiveDefinitionNode { +export interface DirectiveDefinitionNode { kind: Kind.DirectiveDefinition name: NameNode arguments?: readonly T[] @@ -439,27 +458,27 @@ export interface DirectiveDefinitionNode { loc?: Location } -export interface QueryOperation { +export interface QueryOperation { kind: Kind.OperationDefinition operation: OperationType.Query + selectionSet: T name?: NameNode variableDefinitions?: readonly T[] directives?: readonly T[] - selectionSet: T loc?: Location } -export interface MutationOperation { +export interface MutationOperation { kind: Kind.OperationDefinition operation: OperationType.Mutation + selectionSet: T name?: NameNode variableDefinitions?: readonly T[] directives?: readonly T[] - selectionSet: T loc?: Location } -export interface SubscriptionOperation { +export interface SubscriptionOperation { kind: Kind.OperationDefinition operation: OperationType.Subscription name?: NameNode @@ -469,17 +488,17 @@ export interface SubscriptionOperation { loc?: Location } -export interface OperationTypeDefinitionNode { +export interface OperationTypeDefinitionNode { kind: Kind.OperationTypeDefinition - operation: OperationType type: NamedTypeNode + operation: T loc?: Location } -export interface SchemaDefinitionNode { +export interface SchemaDefinitionNode { kind: Kind.SchemaDefinition directives?: readonly T[] - operationTypes: readonly OperationTypeDefinitionNode[] + operationTypes: readonly OperationTypeDefinitionNode[] description?: StringValueNode loc?: Location } @@ -496,6 +515,8 @@ export type ValueNode = | EnumValueNode export type Nullary = + | NullNode + | NameNode | BooleanNode | IntNode | NumberNode @@ -503,9 +524,9 @@ export type Nullary = | StringNode | IDNode | EnumValueDefinitionNode - | ValueNode + | ObjectFieldNode -export type OperationDefinitionNode = +export type OperationDefinitionNode = | QueryOperation | MutationOperation | SubscriptionOperation @@ -531,19 +552,54 @@ export type Unary = | DirectiveDefinitionNode | SchemaDefinitionNode | VariableDefinitionNode + | OperationDefinitionNode + | OperationTypeDefinitionNode + | DocumentNode export type F = - | RefNode | Nullary + | RefNode + | ValueNode | Unary - | OperationDefinitionNode - | DocumentNode -/** - * ## {@link AST `AST`} - */ +export type Fixpoint = + | Nullary + | RefNode + | ValueNode + | NonNullTypeNode + | ListNode + | FieldNode + | FieldDefinitionNode + | ScalarTypeDefinitionNode + | ObjectTypeDefinitionNode + | InterfaceTypeDefinitionNode + | UnionTypeDefinitionNode + | ArgumentNode + | InputValueDefinitionNode + | InputObjectTypeDefinitionNode + | SelectionSetNode + | FragmentDefinitionNode + | FragmentSpreadNode + | InlineFragmentNode + | DirectiveNode + | EnumTypeDefinitionNode + | DirectiveDefinitionNode + | SchemaDefinitionNode + | VariableDefinitionNode + | OperationDefinitionNode + | OperationTypeDefinitionNode + | DocumentNode + export declare namespace AST { export { + NameNode, + Nullary, + Unary, + F, + Fixpoint, + Catalog, + RefNode, + // nodes: ArgumentNode, BooleanNode, BooleanValueNode, @@ -571,14 +627,15 @@ export declare namespace AST { MutationOperation, NamedTypeNode, NonNullTypeNode, + NullNode, NullValueNode, NumberNode, ObjectTypeDefinitionNode, + ObjectFieldNode, ObjectValueNode, OperationDefinitionNode, OperationTypeDefinitionNode, QueryOperation, - RefNode, ScalarTypeDefinitionNode, SchemaDefinitionNode, SelectionSetNode, @@ -589,10 +646,6 @@ export declare namespace AST { ValueNode, VariableNode, VariableDefinitionNode, - Nullary, - Unary, - F, - Catalog, } } @@ -600,6 +653,11 @@ export function isScalarTypeDefinition(x: unknown): x is AST.ScalarTypeDefini return has('kind', (kind) => kind === Kind.ScalarTypeDefinition)(x) } +export function isNullNode(x: unknown): x is AST.NullNode { + return has('kind', (kind) => kind === Kind.NamedType)(x) + && has('name', 'value', (value) => value === NamedType.Null)(x) +} + export function isBooleanNode(x: unknown): x is AST.BooleanNode { return has('kind', (kind) => kind === Kind.NamedType)(x) && has('name', 'value', (value) => value === NamedType.Boolean)(x) @@ -630,6 +688,10 @@ export function isIDNode(x: unknown): x is AST.IDNode { && has('name', 'value', (value) => value === NamedType.ID)(x) } +export function isObjectFieldNode(x: unknown): x is AST.ObjectFieldNode { + return has('kind', (kind) => kind === Kind.ObjectField)(x) +} + export function isEnumTypeDefinitionNode(x: unknown): x is AST.EnumTypeDefinitionNode { return has('kind', (kind) => kind === Kind.EnumTypeDefinition)(x) } @@ -718,6 +780,10 @@ export function isInputObjectTypeDefinitionNode(x: unknown): x is AST.InputOb return has('kind', (kind) => kind === Kind.InputObjectTypeDefinition)(x) } +export function isNameNode(x: unknown): x is AST.NameNode { + return has('kind', (kind) => kind == 'Name')(x) +} + export function isNamedTypeNode(x: unknown): x is AST.NamedTypeNode { return has('name', 'value', (value) => typeof value === 'string')(x) } @@ -773,36 +839,37 @@ export function isRefNode(x: unknown): x is AST.RefNode { } export function isValueNode(x: unknown): x is AST.ValueNode { - return isVariableNode(x) + return isNullValueNode(x) || isIntValueNode(x) || isFloatValueNode(x) || isStringValueNode(x) || isBooleanValueNode(x) - || isNullValueNode(x) || isEnumValueNode(x) || isListValueNode(x) || isObjectValueNode(x) || isEnumValueNode(x) || isEnumValueDefinitionNode(x) + || isVariableNode(x) } export function isNullaryNode(x: unknown): x is AST.Nullary { - return isBooleanNode(x) + return isNullNode(x) + || isNameNode(x) + || isBooleanNode(x) || isIntNode(x) || isNumberNode(x) || isFloatNode(x) || isStringNode(x) || isIDNode(x) - || isScalarTypeDefinition(x) || isEnumValueDefinitionNode(x) - || isOperationTypeDefinitionNode(x) + // || isScalarTypeDefinition(x) } export function isUnaryNode(x: unknown): x is AST.Unary { return isNonNullTypeNode(x) || isListNode(x) || isFieldNode(x) - || isScalarTypeDefinition(x) + || isFieldDefinitionNode(x) || isFieldDefinitionNode(x) || isObjectTypeDefinitionNode(x) || isInterfaceTypeDefinitionNode(x) @@ -819,13 +886,16 @@ export function isUnaryNode(x: unknown): x is AST.Unary { || isDirectiveDefinitionNode(x) || isSchemaDefinitionNode(x) || isVariableDefinitionNode(x) + || isOperationDefinitionNode(x) + || isOperationTypeDefinitionNode(x) + || isDocumentNode(x) } export function isOperationDefinitionNode(x: unknown): x is AST.OperationDefinitionNode { return has('kind', (kind) => kind === Kind.OperationDefinition)(x) } -export function isOperationTypeDefinitionNode(x: unknown): x is AST.OperationTypeDefinitionNode { +export function isOperationTypeDefinitionNode(x: unknown): x is AST.OperationTypeDefinitionNode { return has('kind', (kind) => kind === Kind.OperationTypeDefinition)(x) } @@ -834,7 +904,7 @@ export interface Index { } export const createIndex: () => Index = () => ({ - namedTypes: Object.create(null), + namedTypes: Object_create(null), }) export interface Functor extends T.HKT { [-1]: AST.F } @@ -847,6 +917,7 @@ export const Functor: T.Functor.Ix = { default: return fn.exhaustive(x) case isRefNode(x): return x case isNullaryNode(x): return x + case isValueNode(x): return x case isListNode(x): return { ...x, type: g(x.type) } case isNonNullTypeNode(x): return { ...x, type: g(x.type) } case isSelectionSetNode(x): return { ...x, selections: x.selections.map(g) } @@ -982,12 +1053,18 @@ export const Functor: T.Functor.Ix = { selectionSet: g(selectionSet) } } + case isOperationTypeDefinitionNode(x): { + return { + ...x, + operation: g(x.operation), + } + } case isSchemaDefinitionNode(x): { const { directives, operationTypes, ...xs } = x return { ...xs, ...directives && { directives: directives.map(g) }, - ...operationTypes && { operationTypes }, + operationTypes: fn.map(operationTypes, (ot) => ({ ...ot, operation: g(ot.operation) })), } } } @@ -996,10 +1073,11 @@ export const Functor: T.Functor.Ix = { mapWithIndex(g) { return (x, ix) => { switch (true) { - default: return x satisfies never // default: return fn.exhaustive(x) + default: return (console.log('EXHAUSTIVE, x:', x), x) satisfies never case isRefNode(x): return x case isNullaryNode(x): return x + case isValueNode(x): return x case isArgumentNode(x): return { ...x, value: g(x.value, ix, x) } case isListNode(x): return { ...x, type: g(x.type, ix, x) } case isNonNullTypeNode(x): return { ...x, type: g(x.type, ix, x) } @@ -1137,12 +1215,18 @@ export const Functor: T.Functor.Ix = { selectionSet: g(x.selectionSet, ix, x) } } + case isOperationTypeDefinitionNode(x): { + return { + ...x, + operation: g(x.operation, ix, x), + } + } case isSchemaDefinitionNode(x): { const { directives, operationTypes, ...xs } = x return { ...xs, ...directives && { directives: directives.map((_) => g(_, ix, x)) }, - ...operationTypes && { operationTypes }, + operationTypes: fn.map(operationTypes, (ot) => ({ ...ot, operation: g(ot.operation, ix, x) })), } } } @@ -1154,11 +1238,7 @@ const fold_ = fn.catamorphism(Functor, createIndex()) function getDependencies(node: GQL.ASTNode, ix: Index): Set { const deps = new Set() - - void fold_( - (x) => isRefNode(x) ? (void deps.add(x.name.value), x) : x - )(node, ix) - + void fold_((x) => isRefNode(x) ? (void deps.add(x.name.value), x) : x)(node, ix) return deps } diff --git a/packages/graphql-types/src/to-string.ts b/packages/graphql-types/src/to-string.ts index b4d80953..bbf12c55 100644 --- a/packages/graphql-types/src/to-string.ts +++ b/packages/graphql-types/src/to-string.ts @@ -1,47 +1,49 @@ -import { escape, fn } from '@traversable/registry' +import { escape } from '@traversable/registry' import * as F from './functor.js' import type * as gql from 'graphql' -import type { AST } from './functor.js' import { Kind } from './functor.js' function directives(x: { directives?: readonly string[] }): string { return !x.directives ? '' : ` ${x.directives.join(' ')} ` } -function defaultValue(x: { defaultValue?: AST.ValueNode | string }): string { +function defaultValue(x: { defaultValue?: F.ValueNode | string }): string { return x.defaultValue ? ` = ${serializeValueNode(x.defaultValue)}` : '' } -function description(x: { description?: AST.StringValueNode }): string { +function description(x: { description?: F.StringValueNode }): string { return !x.description ? '' : x.description.block ? `"""\n${x.description.value}\n"""` : ` "${x.description.value}" ` } -function serializeValueNode(x?: AST.ValueNode | string): string { +function serializeValueNode(x?: F.ValueNode | string): string { if (!x) return '' else if (typeof x === 'string') return x else { - switch (x.kind) { + switch (true) { default: return x satisfies never - case Kind.NullValue: return 'null' - case Kind.BooleanValue: return `${x.value}` - case Kind.IntValue: return `${x.value}` - case Kind.FloatValue: return `${x.value}` - case Kind.StringValue: return `"${escape(x.value)}"` - case Kind.EnumValue: return `${x.value}` - case Kind.ListValue: return `[${x.values.map(serializeValueNode).join(', ')}]` - case Kind.Variable: return `$${x.name.value}` - case Kind.ObjectValue: return `{ ${x.fields.map((n) => `${n.name.value}: ${serializeValueNode(n.value)}`).join(', ')} }` + case F.isNullValueNode(x): return 'Null' + case F.isBooleanValueNode(x): return `${x.value}` + case F.isIntValueNode(x): return `${x.value}` + case F.isFloatValueNode(x): return `${x.value}` + case F.isStringValueNode(x): return `"${escape(x.value)}"` + case F.isEnumValueNode(x): return `${x.value}` + case F.isListValueNode(x): return `[${x.values.map(serializeValueNode).join(', ')}]` + case F.isVariableNode(x): return `$${x.name.value}` + case F.isObjectValueNode(x): return `{ ${x.fields.map((n) => `${n.name.value}: ${serializeValueNode(n.value)}`).join(', ')} }` } } } const fold = F.fold((x) => { switch (true) { - default: return fn.exhaustive(x) - case F.isEnumValueDefinitionNode(x): throw Error('Not sure what an enum value definition node is...') + // default: return fn.exhaustive(x) + default: return x satisfies never + case F.isEnumValueDefinitionNode(x): throw Error('Not sure what an `EnumValueDefinitionNode` is...') + case F.isObjectFieldNode(x): throw Error('Not sure what an `ObjectFieldNode` is...') + case F.isOperationTypeDefinitionNode(x): throw Error('Not sure what an `OperationTypeDefinitionNode` is...') case F.isValueNode(x): return serializeValueNode(x) case F.isSelectionSetNode(x): return `{ ${x.selections.join('\n')} }` case F.isScalarTypeDefinition(x): return `scalar ${directives(x)}${x.name.value}` @@ -51,11 +53,14 @@ const fold = F.fold((x) => { case F.isFloatNode(x): return 'Float' case F.isStringNode(x): return 'String' case F.isIDNode(x): return 'ID' + case F.isNullNode(x): return 'Null' case F.isListNode(x): return `[${x.type}]` case F.isNonNullTypeNode(x): return `${x.type}!` case F.isArgumentNode(x): return `${x.name.value}: ${x.value}` case F.isDirectiveNode(x): return `@${x.name.value}(${(x.arguments ?? []).join(', ')})` - case F.isInputObjectTypeDefinitionNode(x): return `${description(x)}input ${x.name.value} { ${x.fields.join('\n')} } ` + case F.isInputObjectTypeDefinitionNode(x): { + return `${description(x)}input ${x.name.value} { ${x.fields.join('\n')} } ` + } case F.isUnionTypeDefinitionNode(x): return `${description(x)}union ${x.name.value}${directives(x)} = ${x.types.join(' | ')}` case F.isEnumTypeDefinitionNode(x): return `enum ${x.name.value}${directives(x)} { ${x.values.map((v) => v.name.value).join('\n')} }` case F.isDirectiveDefinitionNode(x): { @@ -100,6 +105,7 @@ const fold = F.fold((x) => { return `${description(x)}schema ${directives(x)}{ ${x.operationTypes.map((op) => `${op.operation}: ${op.type.name.value}`).join('\n')} }` } case F.isRefNode(x): return x.name.value + case F.isNameNode(x): return x.value } }) diff --git a/packages/graphql-types/src/to-type.ts b/packages/graphql-types/src/to-type.ts index 8b065f5f..0f39c382 100644 --- a/packages/graphql-types/src/to-type.ts +++ b/packages/graphql-types/src/to-type.ts @@ -1,8 +1,8 @@ import { fn, has, parseKey } from '@traversable/registry' import * as F from './functor.js' import type * as gql from 'graphql' -import type { AST } from './functor.js' -import { Kind } from './functor.js' +import { Kind, NamedType } from './functor.js' +import * as AST from './functor.js' const unsupported = [ 'Directive', @@ -13,13 +13,15 @@ const unsupported = [ 'InputValueDefinition', 'SelectionSet', 'OperationDefinition', + 'OperationTypeDefinition', 'Argument', 'SchemaDefinition', 'VariableDefinition', 'DirectiveDefinition', + 'ObjectField', ] as const satisfies Array -type UnsupportedNodeMap = Pick +type UnsupportedNodeMap = Pick type UnsupportedNode = UnsupportedNodeMap[keyof UnsupportedNodeMap] function isUnsupportedNode(x: unknown): x is UnsupportedNode { @@ -29,20 +31,22 @@ function isUnsupportedNode(x: unknown): x is UnsupportedNode { } function valueNodeToString(x: AST.ValueNode): string { - switch (x.kind) { + switch (true) { default: return fn.exhaustive(x) - case Kind.NullValue: return 'null' - case Kind.BooleanValue: return `${x.value}` - case Kind.IntValue: return `${x.value}` - case Kind.FloatValue: return `${x.value}` - case Kind.StringValue: return `"${x.value}"` - case Kind.EnumValue: return `${x.value}` - case Kind.ListValue: return `[${x.values.map(valueNodeToString).join(', ')}]` - case Kind.ObjectValue: return '' + case AST.isNullNode(x): return 'null' + case AST.isNullValueNode(x): return 'null' + case AST.isBooleanValueNode(x): return `${x.value}` + case AST.isIntValueNode(x): return `${x.value}` + case AST.isFloatValueNode(x): return `${x.value}` + case AST.isStringValueNode(x): return `"${x.value}"` + case AST.isEnumValueNode(x): return `${x.value}` + case AST.isListValueNode(x): return `[${x.values.map(valueNodeToString).join(', ')}]` + case AST.isObjectValueNode(x): return '' + '{ ' + x.fields.map((node) => `${parseKey(node.name.value)}: ${valueNodeToString(node.value)}`).join(', ') + ' }' - case Kind.Variable: return `${x.name.value}` + case AST.isVariableNode(x): return `${x.name.value}` + case AST.isNamedTypeNode(x): return (x as AST.NamedTypeNode).name.value } } @@ -50,8 +54,9 @@ const fold = F.fold((x) => { switch (true) { default: return fn.exhaustive(x) case isUnsupportedNode(x): return '' - case F.isEnumValueDefinitionNode(x): throw Error('Not sure what an enum value definition node is...') case F.isRefNode(x): return x.name.value + case F.isNameNode(x): return x.value + case F.isEnumValueDefinitionNode(x): throw Error('Not sure what an enum value definition node is...') case F.isValueNode(x): return valueNodeToString(x) case F.isScalarTypeDefinition(x): return x.name.value case F.isBooleanNode(x): return 'boolean' diff --git a/packages/registry/src/pattern.ts b/packages/registry/src/pattern.ts index ba071780..6e81c252 100644 --- a/packages/registry/src/pattern.ts +++ b/packages/registry/src/pattern.ts @@ -2,4 +2,5 @@ export const PATTERN = { alphanumeric: '^[a-zA-Z0-9]*$', identifier: '^[$_a-zA-Z][$_a-zA-Z0-9]*$', exponential: 'e[-|+]?', + newline: '[\r|\n]', } as const diff --git a/packages/zod-test/src/generator.ts b/packages/zod-test/src/generator.ts index 1caa6190..77a1d8db 100644 --- a/packages/zod-test/src/generator.ts +++ b/packages/zod-test/src/generator.ts @@ -294,7 +294,7 @@ export declare namespace Gen { type Builder = T & BuilderStar interface BuilderStar { ['*']: fc.Arbitrary>> } type BuildBuilder, Out extends {} = BuilderBase> = Builder - type BuilderBase, $ extends ParseOptions = ParseOptions> = + type BuilderBase, $ extends ParseOptions = ParseOptions> = & ([$['root']] extends [never] ? unknown : { root: fc.Arbitrary<$['root']> }) & { [K in Exclude<$['include'], $['exclude']>]: fc.Arbitrary } type ParseOptions> = { @@ -519,7 +519,7 @@ const GeneratorByTag = { export function seedToValidDataGenerator(seed: Seed.F, options?: Config.Options): fc.Arbitrary export function seedToValidDataGenerator(seed: Seed.F, options?: Config.Options): fc.Arbitrary { const $ = Config.parseOptions(options) - return fold>((x, isProperty) => GeneratorByTag[bySeed[x[0]]](x as never, $, isProperty || x[0] === 7500))(seed as never) + return fold>((x, isProperty) => GeneratorByTag[bySeed[x[0]]](x as never, $, isProperty || x[0] === 7500))(seed) } /** @@ -799,7 +799,7 @@ export function seedToSchema(seed: Seed.F) { case x[0] === byTag.lazy: return z.lazy(x[1]) case x[0] === byTag.promise: return PromiseSchemaIsUnsupported('seedToSchema') } - })(seed as never) + })(seed) } /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c57cbd1b..ff781684 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -346,6 +346,9 @@ importers: '@traversable/graphql-types': specifier: workspace:^ version: link:../graphql-types/dist + '@traversable/json': + specifier: workspace:^ + version: link:../json/dist '@traversable/registry': specifier: workspace:^ version: link:../registry/dist @@ -1399,36 +1402,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.5.1': resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} @@ -1514,56 +1523,67 @@ packages: resolution: {integrity: sha512-JWnrj8qZgLWRNHr7NbpdnrQ8kcg09EBBq8jVOjmtlB3c8C6IrynAJSMhMVGME4YfTJzIkJqvSUSVJRqkDnu/aA==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.48.0': resolution: {integrity: sha512-9xu92F0TxuMH0tD6tG3+GtngwdgSf8Bnz+YcsPG91/r5Vgh5LNofO48jV55priA95p3c92FLmPM7CvsVlnSbGQ==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.48.0': resolution: {integrity: sha512-NLtvJB5YpWn7jlp1rJiY0s+G1Z1IVmkDuiywiqUhh96MIraC0n7XQc2SZ1CZz14shqkM+XN2UrfIo7JB6UufOA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.48.0': resolution: {integrity: sha512-QJ4hCOnz2SXgCh+HmpvZkM+0NSGcZACyYS8DGbWn2PbmA0e5xUk4bIP8eqJyNXLtyB4gZ3/XyvKtQ1IFH671vQ==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.48.0': resolution: {integrity: sha512-Pk0qlGJnhILdIC5zSKQnprFjrGmjfDM7TPZ0FKJxRkoo+kgMRAg4ps1VlTZf8u2vohSicLg7NP+cA5qE96PaFg==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.48.0': resolution: {integrity: sha512-/dNFc6rTpoOzgp5GKoYjT6uLo8okR/Chi2ECOmCZiS4oqh3mc95pThWma7Bgyk6/WTEvjDINpiBCuecPLOgBLQ==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.48.0': resolution: {integrity: sha512-YBwXsvsFI8CVA4ej+bJF2d9uAeIiSkqKSPQNn0Wyh4eMDY4wxuSp71BauPjQNCKK2tD2/ksJ7uhJ8X/PVY9bHQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.48.0': resolution: {integrity: sha512-FI3Rr2aGAtl1aHzbkBIamsQyuauYtTF9SDUJ8n2wMXuuxwchC3QkumZa1TEXYIv/1AUp1a25Kwy6ONArvnyeVQ==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.48.0': resolution: {integrity: sha512-Dx7qH0/rvNNFmCcIRe1pyQ9/H0XO4v/f0SDoafwRYwc2J7bJZ5N4CHL/cdjamISZ5Cgnon6iazAVRFlxSoHQnQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.48.0': resolution: {integrity: sha512-GUdZKTeKBq9WmEBzvFYuC88yk26vT66lQV8D5+9TgkfbewhLaTHRNATyzpQwwbHIfJvDJ3N9WJ90wK/uR3cy3Q==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.48.0': resolution: {integrity: sha512-ao58Adz/v14MWpQgYAb4a4h3fdw73DrDGtaiF7Opds5wNyEQwtO6M9dBh89nke0yoZzzaegq6J/EXs7eBebG8A==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.48.0': resolution: {integrity: sha512-kpFno46bHtjZVdRIOxqaGeiABiToo2J+st7Yce+aiAoo1H0xPi2keyQIP04n2JjDVuxBN6bSz9R6RdTK5hIppw==} From 9c6359ce6ac3f37d88a2032061959721c458d30c Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 4 Oct 2025 01:20:39 -0500 Subject: [PATCH 25/32] optimize(registry): removes unnecessary type instantiation in `has` signature --- packages/graphql-test/src/generator.ts | 16 +++++++++++++--- packages/registry/src/has.ts | 2 +- packages/schema/src/has.ts | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/graphql-test/src/generator.ts b/packages/graphql-test/src/generator.ts index 4d4bba6e..c6e9b6a9 100644 --- a/packages/graphql-test/src/generator.ts +++ b/packages/graphql-test/src/generator.ts @@ -5,6 +5,7 @@ import type { inline } from '@traversable/registry' import { Array_isArray, fn, + has, isKeyOf, Number_isFinite, Number_isSafeInteger, @@ -118,14 +119,23 @@ const valueNodeFromJson = Json.fold((x) => { } }) -export function pickAndSortNodes(nodes: readonly ([keyof Seed, unknown])[]): ($: Config) => (keyof Seed)[] { +export function pickAndSortNodes(nodes: readonly ([K, fc.Arbitrary])[]): ($: Config) => K[] { return ({ include, exclude, sortBias }) => nodes .map(([k]) => k) .filter((x) => (include ? include.includes(x as never) : true) && (exclude ? !exclude.includes(x as never) : true) ) - .sort((l, r) => sortBias[l]! < sortBias[r]! ? -1 : sortBias[l]! > sortBias[r]! ? 1 : 0) + .sort((l, r) => { + if ( + has(l, (_) => typeof _ === 'number')(sortBias) && + has(r, (_) => typeof _ === 'number')(sortBias) + ) { + return sortBias[l] < sortBias[r] ? -1 : sortBias[l] > sortBias[r] ? 1 : 0 + } else { + return 0 + } + }) } export declare namespace Gen { @@ -181,7 +191,7 @@ export function Builder(base: Gen.Base>) { (x) => omit(x, $.exclude), (x) => fn.map(x, (f, k) => f(tie, $[k as never])), ) - const nodes = pickAndSortNodes(Object_entries(builder) as [k: keyof Seed, unknown][])($) + const nodes = pickAndSortNodes(Object_entries(builder))($) builder['*'] = fc.oneof(...nodes.map((k) => builder[k])) const root = isKeyOf(builder, $.root) && builder[$.root] let leaf = builder['*'] diff --git a/packages/registry/src/has.ts b/packages/registry/src/has.ts index cef8d427..ef3c223a 100644 --- a/packages/registry/src/has.ts +++ b/packages/registry/src/has.ts @@ -54,7 +54,7 @@ export declare namespace has { export type loop = KS extends readonly [...infer Todo, infer K extends keyof any] ? has.loop - : T extends infer U extends {} ? U : never + : T } /** diff --git a/packages/schema/src/has.ts b/packages/schema/src/has.ts index 23e8ce9c..0bfbc4aa 100644 --- a/packages/schema/src/has.ts +++ b/packages/schema/src/has.ts @@ -13,6 +13,6 @@ export interface has { export function has(...args: readonly [...path: KS, leafSchema?: S]): has> export function has(...args: readonly [...path: KS, leafSchema?: S]): has export function has(...path: readonly [...KS]): has> -export function has(...args: readonly [...KS]) { +export function has(...args: readonly [...KS]): unknown { return has_(...args) } From ae3f30e0a738eca87eb40f9dc1fc240083783f73 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 4 Oct 2025 01:53:56 -0500 Subject: [PATCH 26/32] fix(zod-test): fixes type-level bug where `Seed.Promise` was `never`, which caused `Builder["*"]` to return `fc.Arbitrary` --- packages/zod-test/src/generator.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/zod-test/src/generator.ts b/packages/zod-test/src/generator.ts index 9fe65cea..d561b4cd 100644 --- a/packages/zod-test/src/generator.ts +++ b/packages/zod-test/src/generator.ts @@ -136,7 +136,7 @@ const UnaryMap = { map: (tie) => fc.tuple(fc.constant(byTag.map), fc.tuple(tie('*'), tie('*'))), pipe: (tie) => fc.tuple(fc.constant(byTag.pipe), fc.tuple(tie('*'), tie('*'))), transform: (tie) => fc.tuple(fc.constant(byTag.transform), tie('*')), - promise: () => PromiseSchemaIsUnsupported('SeedMap'), + promise: (tie) => fc.tuple(fc.constant(byTag.promise), tie('*')), } satisfies { [K in keyof Seed.UnaryMap]: SeedBuilder } const TerminalSeeds = fn.map(Object_keys(TerminalMap), (tag) => byTag[tag]) @@ -657,7 +657,7 @@ const seedsThatPreventGeneratingInvalidData = [ * const ZodSchema = zxTest.seedToSchema(seed) * const dataset = fc.sample(zxTest.seedToValidData(seed), 5) * - * const results = dataset.map((pt) => ZodSchema.safeParse(pt).success) + * const results = dataset.map((data) => ZodSchema.safeParse(data).success) * * console.log(results) // => [true, true, true, true, true] */ @@ -692,7 +692,7 @@ export const SeedValidDataGenerator = SeedGenerator({ exclude: seedsThatPreventG * const ZodSchema = zxTest.seedToSchema(seed) * const dataset = fc.sample(zxTest.seedToInvalidData(seed), 5) * - * const results = dataset.map((pt) => ZodSchema.safeParse(pt).success) + * const results = dataset.map((data) => ZodSchema.safeParse(data).success) * * console.log(results) // => [false, false, false, false, false] */ From 6e52d8ea7d3b4c7fa1e6dc27b21b8a62a6c12674 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 4 Oct 2025 21:39:25 -0500 Subject: [PATCH 27/32] feat(graphql-tes): generator partially working --- packages/graphql-test/package.json | 1 + .../src/__generated__/__manifest__.ts | 1 + packages/graphql-test/src/exports.ts | 12 +- packages/graphql-test/src/functor.ts | 31 +- .../graphql-test/src/generator-options.ts | 315 +++- packages/graphql-test/src/generator-seed.ts | 1299 ++++++++++++++--- packages/graphql-test/src/generator.ts | 110 +- packages/graphql-test/test/generator.test.ts | 41 + packages/graphql-types/src/exports.ts | 2 + packages/graphql-types/src/functor.ts | 41 +- packages/graphql-types/src/to-string.ts | 29 +- packages/registry/src/globalThis.ts | 12 +- packages/registry/src/optics.ts | 3 +- packages/registry/src/pattern.ts | 1 + packages/registry/src/topological-sort.ts | 4 +- packages/schema-errors/src/json.ts | 3 +- packages/schema-errors/src/validators.ts | 5 +- packages/schema/src/schema.ts | 3 +- packages/zod-test/src/generator-options.ts | 1 - pnpm-lock.yaml | 3 + 20 files changed, 1582 insertions(+), 335 deletions(-) create mode 100644 packages/graphql-test/test/generator.test.ts diff --git a/packages/graphql-test/package.json b/packages/graphql-test/package.json index 33c2ca7c..b424e938 100644 --- a/packages/graphql-test/package.json +++ b/packages/graphql-test/package.json @@ -56,6 +56,7 @@ "graphql": { "optional": true } }, "devDependencies": { + "@prettier/sync": "catalog:", "@traversable/graphql-types": "workspace:^", "@traversable/json": "workspace:^", "@traversable/registry": "workspace:^", diff --git a/packages/graphql-test/src/__generated__/__manifest__.ts b/packages/graphql-test/src/__generated__/__manifest__.ts index 735f0baf..823f5593 100644 --- a/packages/graphql-test/src/__generated__/__manifest__.ts +++ b/packages/graphql-test/src/__generated__/__manifest__.ts @@ -52,6 +52,7 @@ export default { "graphql": { "optional": true } }, "devDependencies": { + "@prettier/sync": "catalog:", "@traversable/graphql-types": "workspace:^", "@traversable/json": "workspace:^", "@traversable/registry": "workspace:^", diff --git a/packages/graphql-test/src/exports.ts b/packages/graphql-test/src/exports.ts index 04783bce..7e30d4e3 100644 --- a/packages/graphql-test/src/exports.ts +++ b/packages/graphql-test/src/exports.ts @@ -1 +1,11 @@ -export * from './version.js' \ No newline at end of file +export { VERSION } from './version.js' + +export { Config } from './generator-options.js' +export * from './generator-seed.js' +export { + Builder, + Gen, + SeedGenerator, + pickAndSortNodes, + seedToSchema, +} from './generator.js' diff --git a/packages/graphql-test/src/functor.ts b/packages/graphql-test/src/functor.ts index 83488029..3dbfb0a2 100644 --- a/packages/graphql-test/src/functor.ts +++ b/packages/graphql-test/src/functor.ts @@ -33,6 +33,8 @@ export const Functor: T.Functor.Ix = { case x[0] === byTag.ListValue: return x case x[0] === byTag.ObjectValue: return x case x[0] === byTag.ObjectField: return x + case x[0] === byTag.Variable: return x + case x[0] === byTag.OperationTypeDefinition: return x case x[0] === byTag.ListType: return [x[0], g(x[1])] case x[0] === byTag.NonNullType: return [x[0], g(x[1])] case x[0] === byTag.SelectionSet: return [x[0], fn.map(x[1], g)] @@ -50,11 +52,9 @@ export const Functor: T.Functor.Ix = { case x[0] === byTag.InputObjectTypeDefinition: return [x[0], x[1], x[2], fn.map(x[3], g), fn.map(x[4], g)] case x[0] === byTag.InterfaceTypeDefinition: return [x[0], x[1], x[2], fn.map(x[3], g), fn.map(x[4], g), fn.map(x[5], g)] case x[0] === byTag.ObjectTypeDefinition: return [x[0], x[1], x[2], fn.map(x[3], g), fn.map(x[4], g), fn.map(x[5], g)] - case x[0] === byTag.OperationDefinition: return [x[0], x[1], x[2], g(x[3]), fn.map(x[4], g), fn.map(x[5], g)] - case x[0] === byTag.OperationTypeDefinition: return [x[0], x[1], g(x[2])] - case x[0] === byTag.SchemaDefinition: return [x[0], x[1], fn.map(x[2], ([n, o]) => [n, g(o)] as const), fn.map(x[3], g)] + case x[0] === byTag.OperationDefinition: return [x[0], x[1], x[2], g(x[3]!), fn.map(x[4], g), fn.map(x[5], g)] + case x[0] === byTag.SchemaDefinition: return [x[0], x[1], x[2], x[3], fn.map(x[4], g)] case x[0] === byTag.UnionTypeDefinition: return [x[0], x[1], fn.map(x[2], g), fn.map(x[3], g)] - case x[0] === byTag.Variable: return [x[0], x[1], x[2], fn.map(x[3], g)] case x[0] === byTag.VariableDefinition: return [x[0], x[1], g(x[2]), g(x[3]), fn.map(x[4], g)] // case x[0] === byTag.SchemaExtension: return [x[0], fn.map(x[1], g), fn.map(x[2], g)] } @@ -62,6 +62,12 @@ export const Functor: T.Functor.Ix = { }, mapWithIndex(g) { return (x, ix) => { + + console.debug('\n') + console.group('mapWithIndex') + console.debug('x:', x) + console.groupEnd() + switch (true) { default: return x satisfies never case x[0] === byTag.Name: return x @@ -84,6 +90,8 @@ export const Functor: T.Functor.Ix = { case x[0] === byTag.ListValue: return x case x[0] === byTag.ObjectValue: return x case x[0] === byTag.ObjectField: return x + case x[0] === byTag.Variable: return x + case x[0] === byTag.OperationTypeDefinition: return x case x[0] === byTag.ListType: return [x[0], g(x[1], ix, x)] case x[0] === byTag.NonNullType: return [x[0], g(x[1], ix, x)] case x[0] === byTag.SelectionSet: return [x[0], fn.map(x[1], (_) => g(_, ix, x))] @@ -101,11 +109,18 @@ export const Functor: T.Functor.Ix = { case x[0] === byTag.InputObjectTypeDefinition: return [x[0], x[1], x[2], fn.map(x[3], (_) => g(_, ix, x)), fn.map(x[4], (_) => g(_, ix, x))] case x[0] === byTag.InterfaceTypeDefinition: return [x[0], x[1], x[2], fn.map(x[3], (_) => g(_, ix, x)), fn.map(x[4], (_) => g(_, ix, x)), fn.map(x[5], (_) => g(_, ix, x))] case x[0] === byTag.ObjectTypeDefinition: return [x[0], x[1], x[2], fn.map(x[3], (_) => g(_, ix, x)), fn.map(x[4], (_) => g(_, ix, x)), fn.map(x[5], (_) => g(_, ix, x))] - case x[0] === byTag.OperationDefinition: return [x[0], x[1], x[2], g(x[3], ix, x), fn.map(x[4], (_) => g(_, ix, x)), fn.map(x[5], (_) => g(_, ix, x))] - case x[0] === byTag.OperationTypeDefinition: return [x[0], x[1], g(x[2], ix, x)] - case x[0] === byTag.SchemaDefinition: return [x[0], x[1], fn.map(x[2], ([n, o]) => [n, g(o, ix, x)] as const), fn.map(x[3], (_) => g(_, ix, x))] + + case x[0] === byTag.OperationDefinition: return [ + x[0], + x[1], + x[2], + g(x[3], ix, x), + fn.map(x[4], (_) => g(_, ix, x)), + fn.map(x[5], (_) => g(_, ix, x)) + ] + + case x[0] === byTag.SchemaDefinition: return [x[0], x[1], x[2], x[3], fn.map(x[4], (_) => g(_, ix, x))] case x[0] === byTag.UnionTypeDefinition: return [x[0], x[1], fn.map(x[2], (_) => g(_, ix, x)), fn.map(x[3], (_) => g(_, ix, x))] - case x[0] === byTag.Variable: return [x[0], x[1], x[2], fn.map(x[3], (_) => g(_, ix, x))] case x[0] === byTag.VariableDefinition: return [x[0], x[1], g(x[2], ix, x), g(x[3], ix, x), fn.map(x[4], (_) => g(_, ix, x))] // case x[0] === byTag.SchemaExtension: return [x[0], fn.map(x[1], (_) => g(_, ix, x)), fn.map(x[2], (_) => g(_, ix, x))] } diff --git a/packages/graphql-test/src/generator-options.ts b/packages/graphql-test/src/generator-options.ts index 84d54259..7393760d 100644 --- a/packages/graphql-test/src/generator-options.ts +++ b/packages/graphql-test/src/generator-options.ts @@ -1,11 +1,23 @@ -import * as fc from 'fast-check' -import { fn } from '@traversable/registry' -import { Tags } from '@traversable/graphql-types' +import type * as fc from 'fast-check' +import { + Object_assign, + Object_create, + Object_keys, +} from '@traversable/registry' import type { Seed } from './generator-seed.js' import { byTag } from './generator-seed.js' -export interface Options extends Partial> {} +export interface Options extends Partial>, Constraints {} + +export interface Config extends OptionsBase, Constraints {} + +export declare namespace Config { + export { + Options, + Constraints, + } +} export type InferConfigType = S extends Options ? T : never @@ -20,25 +32,296 @@ export interface OptionsBase< root: '*' | K sortBias: { [K in keyof Seed]+?: number } forceInvalid: boolean + noDescriptions: boolean } -export interface Config extends OptionsBase {} +export declare namespace Constraints { + type Argument = fc.UniqueArrayConstraints + type Directive = fc.UniqueArrayConstraints + type Document = fc.UniqueArrayConstraints + type InputValueDefinition = fc.UniqueArrayConstraints + type FieldDefinition = fc.UniqueArrayConstraints + type NamedType = fc.UniqueArrayConstraints + type ObjectValue = fc.UniqueArrayConstraints + type SchemaDefinition = fc.UniqueArrayConstraints + type SelectionSet = fc.UniqueArrayConstraints + type VariableDefinition = fc.UniqueArrayConstraints +} + +export type Constraints = { + Argument?: Constraints.Argument + Boolean?: {} + BooleanValue?: {} + Directive?: Constraints.Directive + DirectiveDefinition?: {} + Document?: Constraints.Document + EnumTypeDefinition?: {} + EnumValue?: {} + EnumValueDefinition?: {} + Field?: {} + FieldDefinition?: Constraints.FieldDefinition + Float?: {} + FloatValue?: {} + FragmentDefinition?: {} + FragmentSpread?: {} + ID?: {} + InlineFragment?: {} + InputObjectTypeDefinition?: {} + InputValueDefinition?: Constraints.InputValueDefinition + Int?: {} + InterfaceTypeDefinition?: {} + IntValue?: {} + ListType?: {} + ListValue?: {} + Name?: {} + NamedType?: Constraints.NamedType + NonNullType?: {} + Null?: {} + NullValue?: {} + Number?: {} + ObjectField?: {} + ObjectTypeDefinition?: {} + ObjectValue?: Constraints.ObjectValue + OperationDefinition?: {} + OperationTypeDefinition?: {} + ScalarTypeDefinition?: {} + SchemaDefinition?: Constraints.SchemaDefinition + SelectionSet?: Constraints.SelectionSet + String?: {} + StringValue?: {} + UnionTypeDefinition?: {} + Variable?: {} + VariableDefinition?: Constraints.VariableDefinition +} -export const defaultSortBias = (x?: T) => Object.fromEntries( - Object.entries(x ?? Object.create(null)).map(([k], i) => [k, i]) -) satisfies Config['sortBias'] +export const defaultConstraints = { + Argument: { + minLength: 0, + maxLength: 2, + selector: ([, name]) => name, + size: 'xsmall', + }, + Boolean: {}, + BooleanValue: {}, + Directive: { + minLength: 0, + maxLength: 1, + selector: ([, name]) => name, + size: 'xsmall', + }, + DirectiveDefinition: {}, + Document: { + minLength: 1, + maxLength: 1, + selector: ([, name]) => name, + size: 'xsmall', + }, + EnumTypeDefinition: {}, + EnumValue: {}, + EnumValueDefinition: {}, + Field: {}, + FieldDefinition: { + minLength: 1, + maxLength: 3, + selector: ([, name]) => name, + size: 'xsmall', + }, + Float: {}, + FloatValue: {}, + FragmentDefinition: {}, + FragmentSpread: {}, + ID: {}, + InlineFragment: {}, + InputObjectTypeDefinition: {}, + InputValueDefinition: { + minLength: 1, + maxLength: 3, + selector: ([, name]) => name, + size: 'xsmall', + }, + Int: {}, + InterfaceTypeDefinition: {}, + IntValue: {}, + ListType: {}, + ListValue: {}, + Name: {}, + NamedType: { + minLength: 1, + maxLength: 3, + selector: ([, name]) => name, + size: 'xsmall', + }, + NonNullType: {}, + Null: {}, + NullValue: {}, + Number: {}, + ObjectField: {}, + ObjectTypeDefinition: {}, + ObjectValue: { + minLength: 1, + maxLength: 3, + selector: ([, name]) => name, + size: 'xsmall', + }, + OperationDefinition: { + }, + OperationTypeDefinition: {}, + ScalarTypeDefinition: {}, + SchemaDefinition: { + minLength: 1, + maxLength: 3, + selector: ([, , operationType]) => operationType, + size: 'xsmall', + }, + SelectionSet: { + minLength: 0, + maxLength: 3, + selector: ([, name]) => name, + size: 'xsmall', + }, + String: {}, + StringValue: {}, + UnionTypeDefinition: {}, + Variable: {}, + VariableDefinition: { + minLength: 0, + maxLength: 2, + selector: ([, name]) => name, + size: 'xsmall', + }, +} satisfies Required + +export const defaultOptions: OptionsBase = { + include: Object_keys(defaultConstraints), + exclude: [], + root: '*', + sortBias: byTag, + forceInvalid: false, + noDescriptions: false, +} + +export const defaults = Object_assign( + Object_create(null), + defaultConstraints, + defaultOptions, +) satisfies Config + +export function parseOptions(options?: Opts): Config> +export function parseOptions(options: Options = defaults): Config { + const { + // options: + exclude = defaults.exclude, + forceInvalid = defaults.forceInvalid, + include = defaults.include, + root = defaults.root, + sortBias = defaults.sortBias, + noDescriptions = defaults.noDescriptions, + // constraints: + Argument = defaults.Argument, + Boolean = defaults.Boolean, + BooleanValue = defaults.BooleanValue, + Directive = defaults.Directive, + DirectiveDefinition = defaults.DirectiveDefinition, + Document = defaults.Document, + EnumTypeDefinition = defaults.EnumTypeDefinition, + EnumValue = defaults.EnumValue, + EnumValueDefinition = defaults.EnumValueDefinition, + Field = defaults.Field, + FieldDefinition = defaults.FieldDefinition, + Float = defaults.Float, + FloatValue = defaults.FloatValue, + FragmentDefinition = defaults.FragmentDefinition, + FragmentSpread = defaults.FragmentSpread, + ID = defaults.ID, + InlineFragment = defaults.InlineFragment, + InputObjectTypeDefinition = defaults.InputObjectTypeDefinition, + InputValueDefinition = defaults.InputValueDefinition, + Int = defaults.Int, + IntValue = defaults.IntValue, + InterfaceTypeDefinition = defaults.InterfaceTypeDefinition, + ListType = defaults.ListType, + ListValue = defaults.ListValue, + Name = defaults.Name, + NamedType = defaults.NamedType, + NonNullType = defaults.NonNullType, + Null = defaults.Null, + NullValue = defaults.NullValue, + Number = defaults.Number, + ObjectField = defaults.ObjectField, + ObjectTypeDefinition = defaults.ObjectTypeDefinition, + ObjectValue = defaults.ObjectValue, + OperationDefinition = defaults.OperationDefinition, + OperationTypeDefinition = defaults.OperationTypeDefinition, + ScalarTypeDefinition = defaults.ScalarTypeDefinition, + SchemaDefinition = defaults.SchemaDefinition, + SelectionSet = defaults.SelectionSet, + String = defaults.String, + StringValue = defaults.StringValue, + UnionTypeDefinition = defaults.UnionTypeDefinition, + Variable = defaults.Variable, + VariableDefinition = defaults.VariableDefinition, + } = options -export function defaultOptions(x?: T): Config -export function defaultOptions(x?: T) { return { - include: Object.keys(x ?? Object.create(null)), - exclude: [], - forceInvalid: false, - root: '*', - sortBias: defaultSortBias(x), - } satisfies Config + include: include.length === 0 || include[0] === ('*' as never) + ? defaults.include + : include, + exclude, + root, + sortBias, + forceInvalid, + noDescriptions, + // nodes: + Argument, + Boolean, + BooleanValue, + Directive, + DirectiveDefinition, + Document, + EnumTypeDefinition, + EnumValue, + EnumValueDefinition, + Field, + FieldDefinition, + Float, + FloatValue, + FragmentDefinition, + FragmentSpread, + ID, + InlineFragment, + InputObjectTypeDefinition, + InputValueDefinition, + Int, + IntValue, + InterfaceTypeDefinition, + ListType, + ListValue, + Name, + NamedType, + NonNullType, + Null, + NullValue, + Number, + ObjectField, + ObjectTypeDefinition, + ObjectValue, + OperationDefinition, + OperationTypeDefinition, + ScalarTypeDefinition, + SchemaDefinition, + SelectionSet, + String, + StringValue, + UnionTypeDefinition, + Variable, + VariableDefinition, + } } +export function Config() {} +Config.defaults = defaults +Config.parseOptions = parseOptions + // export type ObjectConstraints = // & Omit, 'minLength' | 'maxLength'> // & { diff --git a/packages/graphql-test/src/generator-seed.ts b/packages/graphql-test/src/generator-seed.ts index 6a231c78..beac2b99 100644 --- a/packages/graphql-test/src/generator-seed.ts +++ b/packages/graphql-test/src/generator-seed.ts @@ -1,16 +1,65 @@ import * as fc from 'fast-check' - import type * as T from '@traversable/registry' import { Object_keys, PATTERN } from '@traversable/registry' -import { Json } from '@traversable/json' import type { OperationType } from '@traversable/graphql-types' -import { Kind, NamedType } from '@traversable/graphql-types' +import * as F from '@traversable/graphql-types' + +import { Config } from './generator-options.js' + +type Constraints = Config.Options +// type Constraints = Required export function invert>(x: T): { [K in keyof T as T[K]]: K } export function invert(x: Record) { return Object_keys(x).reduce((acc, k) => (acc[x[k]] = k, acc), {} as typeof x) } +const EXAMPLE = [ + 360, [ + [ + 400, // OperationDefinition + "ornare", + "query", + [ + 420, + [] + ], + [ + [ + 330, + "suscipit", + [ + 270, + [ + 20, + "turpis" + ] + ], + [ + 110, + true + ], + [] + ] + ], + [ + 340, + "fermentum", + [ + [ + 290, + "egestas", + [ + 130, + -4.442196362483118e+172 + ] + ] + ] + ] + ] + ] +] + export type Tag = byTag[keyof byTag] export type byTag = typeof byTag export const byTag = { @@ -64,16 +113,30 @@ export const byTag = { SelectionSet: 420, SchemaDefinition: 430, // SchemaExtension: 440, -} as const satisfies Record<'Name' | Kind | NamedType, number> +} as const satisfies Record<'Name' | F.Kind | F.NamedType, number> export type bySeed = typeof bySeed export const bySeed = invert(byTag) -export type Seed = ( - & Seed.TerminalMap - & Seed.ValueMap - & Seed.UnaryMap -) +export const identifier = fc.stringMatching(new RegExp(PATTERN.identifierNoDollar, 'u')).map((x) => `${x.charAt(0).toUpperCase()}${x.slice(1)}`) +export const name = fc.lorem({ maxCount: 1 }) +export const alias = name +export const target = fc.constantFrom(...F.DirectiveTargets) + +export const description = ($: Constraints) => $.noDescriptions ? fc.constant(null) : fc.oneof( + fc.constant(null), + fc.constant(null), + fc.tuple( + fc.lorem(), + fc.boolean(), + ) +) satisfies fc.Arbitrary + +export const operationType = fc.constantFrom( + 'query', + 'mutation', + 'subscription', +) satisfies fc.Arbitrary export declare namespace Seed { type Name = [ @@ -86,7 +149,7 @@ export declare namespace Seed { name: string, ] - type Description = [description: string, block: boolean] + type Description = null | [description: string, block: boolean] //////////////////////// /// Terminal nodes /// @@ -143,14 +206,14 @@ export declare namespace Seed { value: string, ] - type ListValue = [ + type ListValue = [ ListValue: byTag['ListValue'], - value: readonly Json[], + value: readonly T[], ] - type ObjectValue = [ + type ObjectValue = [ ObjectValue: byTag['ObjectValue'], - value: { [x: string]: Json }, + value: readonly T[], ] /// Value nodes /// ///////////////////// @@ -172,11 +235,10 @@ export declare namespace Seed { directives: readonly T[] ] - type Variable = [ + type Variable = [ Variable: byTag['Variable'], name: string, description: Description, - directives: readonly T[], ] type EnumTypeDefinition = [ @@ -205,10 +267,10 @@ export declare namespace Seed { directives: readonly T[], ] - type ObjectField = [ + type ObjectField = [ ObjectField: byTag['ObjectField'], name: string, - value: Json + value: T ] type ObjectTypeDefinition = [ @@ -271,7 +333,7 @@ export declare namespace Seed { name: string, description: Description, repeatable: boolean, - locations: readonly string[], + locations: readonly F.DirectiveTarget[], arguments: readonly T[], ] @@ -296,11 +358,56 @@ export declare namespace Seed { type InlineFragment = [ InlineFragment: byTag['InlineFragment'], - selectionSet: string, - typeCondition: T, + typeCondition: string, + selectionSet: T, directives: readonly T[], ] + /** + * @example + * [ + * 400, // OperationDefinition + * "ornare", + * "query", + * [ + * 420, + * [] + * ], + * [ + * [ + * 330, + * "suscipit", + * [ + * 270, + * [ + * 20, + * "turpis" + * ] + * ], + * [ + * 110, + * true + * ], + * [] + * ] + * ], + * [ + * 340, + * "fermentum", + * [ + * [ + * 290, + * "egestas", + * [ + * 130, + * -4.442196362483118e+172 + * ] + * ] + * ] + * ] + * ] + */ + type OperationDefinition = [ OperationDefinition: byTag['OperationDefinition'], name: string, @@ -310,10 +417,10 @@ export declare namespace Seed { directives: readonly T[], ] - type OperationTypeDefinition = [ + type OperationTypeDefinition = [ OperationTypeDefinition: byTag['OperationTypeDefinition'], type: string, - operation: T, + operation: F.OperationType, ] type SelectionSet = [ @@ -323,8 +430,9 @@ export declare namespace Seed { type SchemaDefinition = [ SchemaDefinition: byTag['SchemaDefinition'], + _unused: string, description: Description, - operationTypes: readonly (readonly [name: string, operation: T])[], + operationTypes: readonly Seed.OperationTypeDefinition[], directives: readonly T[], ] @@ -346,6 +454,8 @@ export declare namespace Seed { Number: Number ScalarTypeDefinition: ScalarTypeDefinition String: String + Variable: Variable + OperationTypeDefinition: OperationTypeDefinition } type Value = ValueMap[keyof ValueMap] @@ -366,7 +476,6 @@ export declare namespace Seed { ListType: ListType NonNullType: NonNullType UnionTypeDefinition: UnionTypeDefinition - Variable: Variable EnumTypeDefinition: EnumTypeDefinition Field: Field FieldDefinition: FieldDefinition @@ -383,7 +492,6 @@ export declare namespace Seed { FragmentSpread: FragmentSpread InlineFragment: InlineFragment OperationDefinition: OperationDefinition - OperationTypeDefinition: OperationTypeDefinition SelectionSet: SelectionSet SchemaDefinition: SchemaDefinition // SchemaExtension: SchemaExtension @@ -398,7 +506,6 @@ export declare namespace Seed { | Seed.ListType | Seed.NonNullType | Seed.UnionTypeDefinition - | Seed.Variable | Seed.EnumTypeDefinition | Seed.Field | Seed.FieldDefinition @@ -415,7 +522,6 @@ export declare namespace Seed { | Seed.FragmentSpread | Seed.InlineFragment | Seed.OperationDefinition - | Seed.OperationTypeDefinition | Seed.SelectionSet | Seed.SchemaDefinition // | Seed.SchemaExtension @@ -456,228 +562,981 @@ export declare namespace Seed { interface Free extends T.HKT { [-1]: Seed.F } } -export const identifier = fc.stringMatching(new RegExp(PATTERN.identifier, 'u')) -export const name = fc.lorem({ maxCount: 1 }) +export type Seed = ( + & Seed.TerminalMap + & Seed.ValueMap + & Seed.UnaryMap +) + +const NamedType = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['NamedType']), + name, +) -export const description = fc.tuple( +const Boolean = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag['Boolean']]) +const Float = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag['Float']]) +const Int = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag['Int']]) +const ID = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag['ID']]) +const Null = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag['Null']]) +const Number = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag['Number']]) +const String = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag['String']]) + +const NullValue = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag['NullValue']]) +const BooleanValue = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.tuple(fc.constant(byTag['BooleanValue']), fc.boolean()) +const FloatValue = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.tuple(fc.constant(byTag['FloatValue']), fc.double()) +const IntValue = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.tuple(fc.constant(byTag['IntValue']), fc.integer()) +const StringValue = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['StringValue']), fc.string(), fc.boolean(), -) satisfies fc.Arbitrary +) -export const operationType = fc.constantFrom( - 'query', - 'mutation', - 'subscription', -) satisfies fc.Arbitrary +const ScalarTypeDefinition = (_tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['ScalarTypeDefinition']), + identifier, + description($), +) -export const directives = (model: fc.Arbitrary) => fc.array(model, { maxLength: 2 }) +const EnumValue = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['EnumValue']), + name, +) -export const Seed = { - Boolean: () => fc.constant([byTag['Boolean']]), - Float: () => fc.constant([byTag['Float']]), - Int: () => fc.constant([byTag['Int']]), - ID: () => fc.constant([byTag['ID']]), - Null: () => fc.constant([byTag['Null']]), - Number: () => fc.constant([byTag['Number']]), - String: () => fc.constant([byTag['String']]), - NamedType: () => fc.tuple( - fc.constant(byTag['NamedType']), - name, - ), - NullValue: () => fc.constant([byTag['NullValue']]), - BooleanValue: () => fc.tuple( - fc.constant(byTag['BooleanValue']), - fc.boolean(), - ), - FloatValue: () => fc.tuple( - fc.constant(byTag['FloatValue']), - fc.double(), - ), - IntValue: () => fc.tuple( - fc.constant(byTag['IntValue']), - fc.integer(), - ), - StringValue: () => fc.tuple( - fc.constant(byTag['StringValue']), - fc.string(), - fc.boolean(), - ), - ScalarTypeDefinition: () => fc.tuple( - fc.constant(byTag['ScalarTypeDefinition']), - name, - description, - ), - EnumValue: () => fc.tuple( - fc.constant(byTag['EnumValue']), - name, - ), - EnumValueDefinition: () => fc.tuple( - fc.constant(byTag['EnumValueDefinition']), - name, - ), - ListValue: () => fc.tuple( - fc.constant(byTag['ListValue']), - fc.array(fc.jsonValue()), - ), - ObjectValue: () => fc.tuple( - fc.constant(byTag['ObjectValue']), - fc.dictionary(identifier, fc.jsonValue()), - ), - ObjectField: () => fc.tuple( - fc.constant(byTag['ObjectField']), - name, - fc.jsonValue(), - ), - ListType: (model) => fc.tuple( - fc.constant(byTag['ListType']), - model, +const EnumValueDefinition = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['EnumValueDefinition']), + name, +) + +/** + * @example + * type ListValue = [ + * ListValue: byTag['ListValue'], + * value: readonly unknown[], + * ] + * export interface ListValueNode { + * readonly kind: Kind.LIST; + * readonly loc?: Location; + * readonly values: ReadonlyArray; + * } + */ +const ListValue = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['ListValue']), + fc.array(ValueNode(tie, $)), +) + +/** + * @example + * type ListType = [ + * ListType: byTag['ListType'], + * type: T, + * ] + * export interface ListTypeNode { + * readonly kind: Kind.LIST_TYPE; + * readonly loc?: Location; + * readonly type: TypeNode; + * } + */ +const ListType = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['ListType']), + TypeNode(tie, $), +) + +/** + * @example + * export interface NonNullTypeNode { + * readonly kind: Kind.NON_NULL_TYPE; + * readonly loc?: Location; + * readonly type: NamedTypeNode | ListTypeNode; + * } + * type NonNullType = [ + * NonNullType: byTag['NonNullType'], + * type: T, + * ] + */ +const NonNullType = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['NonNullType']), + fc.oneof( + NamedType(tie, $), + tie('ListType'), + ) +) + +/** + * @example + * type UnionTypeDefinition = [ + * UnionTypeDefinition: byTag['UnionTypeDefinition'], + * name: string, + * types: readonly T[], + * directives: readonly T[] + * ] + * export interface UnionTypeDefinitionNode { + * readonly kind: Kind.UNION_TYPE_DEFINITION; + * readonly loc?: Location; + * readonly description?: StringValueNode; + * readonly name: NameNode; + * readonly directives?: ReadonlyArray; + * readonly types?: ReadonlyArray; + * } + */ +const UnionTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['UnionTypeDefinition']), + name, + fc.uniqueArray( + NamedType(tie, $), + $.NamedType! ), - NonNullType: (model) => fc.tuple( - fc.constant(byTag['NonNullType']), - model, + fc.uniqueArray( + // TODO: + // ConstDirective(tie, $), + Directive(tie, $), + $.Directive!, + ) +) + +/** + * @example + * type Variable = [ + * Variable: byTag['Variable'], + * name: string, + * description: Description, + * directives: readonly T[], + * ] + * export interface VariableNode { + * readonly kind: Kind.VARIABLE; + * readonly loc?: Location; + * readonly name: NameNode; + * } + */ +const Variable = (_tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['Variable']), + name, + description($), +) + +/** + * @example + * type EnumTypeDefinition = [ + * EnumTypeDefinition: byTag['EnumTypeDefinition'], + * name: string, + * description: Description, + * values: readonly string[], + * directives: readonly T[], + * ] + * export interface EnumTypeDefinitionNode { + * readonly kind: Kind.ENUM_TYPE_DEFINITION; + * readonly loc?: Location; + * readonly description?: StringValueNode; + * readonly name: NameNode; + * readonly directives?: ReadonlyArray; + * readonly values?: ReadonlyArray; + * } + */ +const EnumTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['EnumTypeDefinition']), + name, + description($), + fc.uniqueArray(name), + fc.uniqueArray( + // TODO: + // ConstDirective(tie, $), + Directive(tie, $), + $.Directive! ), - UnionTypeDefinition: (model) => fc.tuple( - fc.constant(byTag['UnionTypeDefinition']), - name, - fc.uniqueArray(model), - directives(model), +) + +/** + * @example + * type Field = [ + * Field: byTag['Field'], + * name: string, + * alias: string, + * selectionSet: T, + * arguments: readonly T[], + * directives: readonly T[], + * ] + * export interface FieldNode { + * readonly kind: Kind.FIELD; + * readonly loc?: Location; + * readonly alias?: NameNode; + * readonly name: NameNode; + * readonly arguments?: ReadonlyArray; + * readonly directives?: ReadonlyArray; + * readonly selectionSet?: SelectionSetNode; + * } + */ +const Field = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['Field']), + name, + alias, + tie('SelectionSet'), + fc.uniqueArray( + tie('Argument'), + $.Argument! ), - Variable: (model) => fc.tuple( - fc.constant(byTag['Variable']), - name, - description, - directives(model) + fc.uniqueArray( + tie('Directive'), + $.Directive! ), - EnumTypeDefinition: (model) => fc.tuple( - fc.constant(byTag['EnumTypeDefinition']), - name, - description, - fc.uniqueArray(name), - directives(model), +) + +/** + * @example + * type FieldDefinition = [ + * FieldDefinition: byTag['FieldDefinition'], + * name: string, + * description: Description, + * type: T, + * arguments: readonly T[], + * directives: readonly T[], + * ] + * export interface FieldDefinitionNode { + * readonly kind: Kind.FIELD_DEFINITION; + * readonly loc?: Location; + * readonly description?: StringValueNode; + * readonly name: NameNode; + * readonly arguments?: ReadonlyArray; + * readonly type: TypeNode; + * readonly directives?: ReadonlyArray; + * } + */ +const FieldDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['FieldDefinition']), + name, + description($), + TypeNode(tie, $), + fc.uniqueArray( + tie('InputValueDefinition'), + $.InputValueDefinition! ), - Field: (model) => fc.tuple( - fc.constant(byTag['Field']), - name, - name, - model, - fc.uniqueArray(model), - directives(model), + fc.uniqueArray( + // TODO: + // ConstDirective(tie, $), + Directive(tie, $), + $.Directive! + ) +) + +/** + * @example + * type ObjectTypeDefinition = [ + * ObjectTypeDefinition: byTag['ObjectTypeDefinition'], + * name: string, + * description: Description, + * fields: readonly T[], + * interfaces: readonly T[], + * directives: readonly T[], + * ] + * export interface ObjectTypeDefinitionNode { + * readonly kind: Kind.OBJECT_TYPE_DEFINITION; + * readonly loc?: Location; + * readonly description?: StringValueNode; + * readonly name: NameNode; + * readonly interfaces?: ReadonlyArray; + * readonly directives?: ReadonlyArray; + * readonly fields?: ReadonlyArray; + * } + */ +const ObjectTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['ObjectTypeDefinition']), + identifier, + description($), + fc.uniqueArray( + tie('FieldDefinition'), + $.FieldDefinition! ), - FieldDefinition: (model) => fc.tuple( - fc.constant(byTag['FieldDefinition']), - name, - description, - model, - fc.uniqueArray(model), - directives(model), + fc.uniqueArray( + NamedType(tie, $), + $.NamedType! ), - ObjectTypeDefinition: (model) => fc.tuple( - fc.constant(byTag['ObjectTypeDefinition']), - name, - description, - fc.uniqueArray(model), - fc.uniqueArray(model), - directives(model), + fc.uniqueArray( + // TODO: ConstDirective(tie, $), + Directive(tie, $), + $.Directive! + ) +) + +/** + * @example + * type InterfaceTypeDefinition = [ + * InterfaceTypeDefinition: byTag['InterfaceTypeDefinition'], + * name: string, + * description: Description, + * fields: readonly T[], + * interfaces: readonly T[], + * directives: readonly T[], + * ] + * export interface InterfaceTypeDefinitionNode { + * readonly kind: Kind.INTERFACE_TYPE_DEFINITION; + * readonly loc?: Location; + * readonly description?: StringValueNode; + * readonly name: NameNode; + * readonly interfaces?: ReadonlyArray; + * readonly directives?: ReadonlyArray; + * readonly fields?: ReadonlyArray; + * } + */ +const InterfaceTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['InterfaceTypeDefinition']), + identifier, + description($), + fc.uniqueArray( + tie('FieldDefinition'), + $.FieldDefinition! ), - InterfaceTypeDefinition: (model) => fc.tuple( - fc.constant(byTag['InterfaceTypeDefinition']), - name, - description, - fc.uniqueArray(model), - fc.uniqueArray(model), - directives(model), + fc.uniqueArray( + NamedType(tie, $), + $.NamedType! ), - Argument: (model) => fc.tuple( - fc.constant(byTag['Argument']), - name, - model, + fc.uniqueArray( + // TODO: + // ConstDirective(tie, $), + Directive(tie, $), + $.Directive! + ) +) + +/** + * @example + * type Argument = [ + * Argument: byTag['Argument'], + * name: string, + * value: T, + * ] + * export interface ArgumentNode { + * readonly kind: Kind.ARGUMENT; + * readonly loc?: Location; + * readonly name: NameNode; + * readonly value: ValueNode; + * } + */ +const Argument = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['Argument']), + name, + ValueNode(tie, $), +) + +/** + * @example + * type InputObjectTypeDefinition = [ + * InputObjectTypeDefinition: byTag['InputObjectTypeDefinition'], + * name: string, + * description: Description, + * fields: readonly T[], + * directives: readonly T[], + * ] + * export interface InputObjectTypeDefinitionNode { + * readonly kind: Kind.INPUT_OBJECT_TYPE_DEFINITION; + * readonly loc?: Location; + * readonly description?: StringValueNode; + * readonly name: NameNode; + * readonly directives?: ReadonlyArray; + * readonly fields?: ReadonlyArray; + * } + */ +const InputObjectTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['InputObjectTypeDefinition']), + name, + description($), + fc.uniqueArray( + tie('InputValueDefinition'), + $.InputValueDefinition! ), - InputObjectTypeDefinition: (model) => fc.tuple( - fc.constant(byTag['InputObjectTypeDefinition']), - name, - description, - fc.uniqueArray(model), - directives(model), + fc.uniqueArray( + // TODO: + // ConstDirective(tie, $), + Directive(tie, $), + $.Directive! ), - InputValueDefinition: (model) => fc.tuple( - fc.constant(byTag['InputValueDefinition']), - name, - description, - model, - model, - directives(model), +) + +/** + * @example + * type InputValueDefinition = [ + * InputValueDefinition: byTag['InputValueDefinition'], + * name: string, + * description: Description, + * type: T, + * defaultValue: T, + * directives: readonly T[], + * ] + * export interface InputValueDefinitionNode { + * readonly kind: Kind.INPUT_VALUE_DEFINITION; + * readonly loc?: Location; + * readonly description?: StringValueNode; + * readonly name: NameNode; + * readonly type: TypeNode; + * readonly defaultValue?: ConstValueNode; + * readonly directives?: ReadonlyArray; + * } + */ +const InputValueDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['InputValueDefinition']), + name, + description($), + TypeNode(tie, $), + ConstValueNode(tie, $), + fc.uniqueArray( + // TODO: + // ConstDirective(tie, $), + Directive(tie, $), + $.Directive! ), - VariableDefinition: (model) => fc.tuple( - fc.constant(byTag['VariableDefinition']), - name, - model, - model, - directives(model), +) + +/** + * @example + * type VariableDefinition = [ + * kind: byTag['VariableDefinition'], + * variable: string, + * type: T, + * defaultValue: T, + * directives: readonly T[], + * ] + * export interface VariableDefinitionNode { + * readonly kind: Kind.VARIABLE_DEFINITION; + * readonly variable: VariableNode; + * readonly type: TypeNode; + * readonly defaultValue?: ConstValueNode; + * readonly directives?: ReadonlyArray; + * } + */ +const VariableDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['VariableDefinition']), + name, + TypeNode(tie, $), + ConstValueNode(tie, $), + fc.uniqueArray( + // TODO: + // ConstDirective(tie, $), + Directive(tie, $), + $.Directive! ), - Directive: (model) => fc.tuple( +) + +/** + * @example + * type Directive = [ + * Directive: byTag['Directive'], + * name: string, + * arguments: readonly T[], + * ] + * export interface DirectiveNode { + * readonly kind: Kind.DIRECTIVE; + * readonly loc?: Location; + * readonly name: NameNode; + * readonly arguments?: ReadonlyArray; + * } + */ +const Directive = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => { + return fc.tuple( fc.constant(byTag['Directive']), name, - fc.uniqueArray(model), + fc.uniqueArray( + tie('Argument'), + $.Argument! + ), + ) +} + +/** + * @example + * type DirectiveDefinition = [ + * DirectiveDefinition: byTag['DirectiveDefinition'], + * name: string, + * description: Description, + * repeatable: boolean, + * locations: readonly F.Target[], + * arguments: readonly T[], + * ] + * export interface DirectiveDefinitionNode { + * readonly kind: Kind.DIRECTIVE_DEFINITION; + * readonly loc?: Location; + * readonly description?: StringValueNode; + * readonly name: NameNode; + * readonly arguments?: ReadonlyArray; + * readonly repeatable: boolean; + * readonly locations: ReadonlyArray; + * } + */ +const DirectiveDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['DirectiveDefinition']), + name, + description($), + fc.boolean(), + fc.uniqueArray( + target, + { minLength: 1, maxLength: 3 }, ), - DirectiveDefinition: (model) => fc.tuple( - fc.constant(byTag['DirectiveDefinition']), - name, - description, - fc.boolean(), - fc.uniqueArray(name), - fc.uniqueArray(model), + fc.uniqueArray( + tie('InputValueDefinition'), + $.InputValueDefinition! ), - Document: (model) => fc.tuple( - fc.constant(byTag['Document']), - fc.uniqueArray(model), +) + +/** + * @example + * type FragmentDefinition = [ + * FragmentDefinition: byTag['FragmentDefinition'], + * name: string, + * typeCondition: string, + * selectionSet: T, + * directives: readonly T[], + * ] + * export interface FragmentDefinitionNode { + * readonly kind: Kind.FRAGMENT_DEFINITION; + * readonly loc?: Location; + * readonly name: NameNode; + * readonly typeCondition: NamedTypeNode; + * readonly directives?: ReadonlyArray; + * readonly selectionSet: SelectionSetNode; + * } + */ +const FragmentDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['FragmentDefinition']), + name, + name, + tie('SelectionSet'), + fc.uniqueArray( + tie('Directive'), + $.Directive! ), - FragmentDefinition: (model) => fc.tuple( - fc.constant(byTag['FragmentDefinition']), - name, - name, - model, - directives(model), +) + +/** + * @example + * + * type FragmentSpread = [ + * FragmentSpread: byTag['FragmentSpread'], + * name: string, + * directives: readonly T[], + * ] + * export interface FragmentSpreadNode { + * readonly kind: Kind.FRAGMENT_SPREAD; + * readonly loc?: Location; + * readonly name: NameNode; + * readonly directives?: ReadonlyArray; + * } + */ +const FragmentSpread = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['FragmentSpread']), + name, + fc.uniqueArray( + tie('Directive'), + $.Directive! ), - FragmentSpread: (model) => fc.tuple( - fc.constant(byTag['FragmentSpread']), - name, - directives(model), +) + +/** + * @example + * type InlineFragment = [ + * InlineFragment: byTag['InlineFragment'], + * typeCondition: string, + * selectionSet: string, + * directives: readonly T[], + * ] + * export interface InlineFragmentNode { + * readonly kind: Kind.INLINE_FRAGMENT; + * readonly loc?: Location; + * readonly typeCondition?: NamedTypeNode; + * readonly directives?: ReadonlyArray; + * readonly selectionSet: SelectionSetNode; + * } + */ +const InlineFragment = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['InlineFragment']), + name, + tie('SelectionSet'), + fc.uniqueArray( + tie('Directive'), + $.Directive! ), - InlineFragment: (model) => fc.tuple( - fc.constant(byTag['InlineFragment']), - name, - model, - directives(model), +) + +/** + * @example + * type OperationDefinition = [ + * OperationDefinition: byTag['OperationDefinition'], + * name: string, + * operation: OperationType, + * selectionSet: T, + * variableDefinitions: readonly T[], + * directives: readonly T[], + * ] + * export interface OperationDefinitionNode { + * readonly kind: Kind.OPERATION_DEFINITION; + * readonly loc?: Location; + * readonly operation: OperationTypeNode; + * readonly name?: NameNode; + * readonly variableDefinitions?: ReadonlyArray; + * readonly directives?: ReadonlyArray; + * readonly selectionSet: SelectionSetNode; + * } + */ +const OperationDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['OperationDefinition']), + name, + operationType, + tie('SelectionSet'), + fc.uniqueArray( + tie('VariableDefinition'), + $.VariableDefinition! ), - OperationDefinition: (model) => fc.tuple( - fc.constant(byTag['OperationDefinition']), - name, - operationType, - model, - fc.uniqueArray(model), - directives(model), + fc.uniqueArray( + tie('Directive'), + $.Directive! ), - OperationTypeDefinition: (model) => fc.tuple( - fc.constant(byTag['OperationTypeDefinition']), - operationType, - model, + // tie('Directive'), +) + +/** + * @example + * type OperationTypeDefinition = [ + * OperationTypeDefinition: byTag['OperationTypeDefinition'], + * type: string, + * operation: T, + * ] + * export interface OperationTypeDefinitionNode { + * readonly kind: Kind.OPERATION_TYPE_DEFINITION; + * readonly loc?: Location; + * readonly operation: OperationTypeNode; + * readonly type: NamedTypeNode; + * } + */ +const OperationTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['OperationTypeDefinition']), + name, + operationType, + // tie('OperationT'), +) + +/** + * @example + * type SelectionSet = [ + * SelectionSet: byTag['SelectionSet'], + * selections: readonly T[], + * ] + * export interface SelectionSetNode { + * kind: Kind.SELECTION_SET; + * loc?: Location; + * selections: ReadonlyArray; + * } + */ +const SelectionSet = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['SelectionSet']), + fc.uniqueArray( + Selection(tie, $), + $.SelectionSet! ), - SelectionSet: (model) => fc.tuple( - fc.constant(byTag['SelectionSet']), - fc.uniqueArray(model), +) + +/** + * @example + * type SchemaDefinition = [ + * SchemaDefinition: byTag['SchemaDefinition'], + * description: Description, + * operationTypes: readonly (readonly [name: string, operation: T])[], + * directives: readonly T[], + * ] + * export interface SchemaDefinitionNode { + * readonly kind: Kind.SCHEMA_DEFINITION; + * readonly loc?: Location; + * readonly description?: StringValueNode; + * readonly directives?: ReadonlyArray; + * readonly operationTypes: ReadonlyArray; + * } + */ +const SchemaDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['SchemaDefinition']), + name, + description($), + fc.uniqueArray( + OperationTypeDefinition(tie, $), + $.SchemaDefinition!, ), - SchemaDefinition: (model) => fc.tuple( - fc.constant(byTag['SchemaDefinition']), - description, + fc.uniqueArray( + // TODO: + // ConstDirective(tie, $), + Directive(tie, $), + $.Directive! + ) +) + +/** + * @example + * type Document = [ + * Document: byTag['Document'], + * definition: readonly T[], + * ] + * export interface DocumentNode { + * readonly kind: Kind.DOCUMENT; + * readonly loc?: Location; + * readonly definitions: ReadonlyArray; + * readonly tokenCount?: number | undefined; + * } + */ +const Document = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => { + return fc.tuple( + fc.constant(byTag['Document']), fc.uniqueArray( - fc.tuple(name, model), - { selector: ([name]) => name }, + Definition(tie, $), + $.Document! ), - directives(model), + ) +} + +///////////////// +/// DERIVED /// +///////////////// + +/** + * @example + * type Argument = [ + * Argument: byTag['Argument'], + * name: string, + * value: T, + * ] + * export interface ConstArgumentNode { + * readonly kind: Kind.ARGUMENT; + * readonly loc?: Location; + * readonly name: NameNode; + * readonly value: ConstValueNode; + * } + */ +const ConstArgument = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['Argument']), + name, + ConstValueNode(tie, $), +) + +/** + * @example + * type Directive = [ + * Directive: byTag['Directive'], + * name: string, + * arguments: readonly T[], + * ] + * export interface ConstDirectiveNode { + * readonly kind: Kind.DIRECTIVE; + * readonly loc?: Location; + * readonly name: NameNode; + * readonly arguments?: ReadonlyArray; + * } + */ +const ConstDirective = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['Directive']), + name, + fc.uniqueArray( + ConstArgument(tie, $), + $.Argument! + ), +) + +/** + * ## {@link TypeNode `TypeNode`} + * See also: + * - https://github.com/graphql/graphql-js/blob/16.x.x/src/language/ast.ts#L524 + */ +const TypeNode = (tie: fc.LetrecTypedTie, $: Constraints) => fc.oneof( + NamedType(tie, $), + tie('ListType'), + tie('NonNullType'), +) + +/** + * ## {@link Selection `Selection`} + * See also: + * - https://github.com/graphql/graphql-js/blob/16.x.x/src/language/ast.ts#L355 + */ +const Selection = (tie: fc.LetrecTypedTie, $: Constraints) => fc.oneof( + tie('Field'), + tie('FragmentSpread'), + tie('InlineFragment'), +) + +/** + * @example + * type ObjectValue = [ + * ObjectValue: byTag['ObjectValue'], + * value: { [x: string]: unknown }, + * ] + * export interface ObjectValueNode { + * readonly kind: Kind.OBJECT; + * readonly loc?: Location; + * readonly fields: ReadonlyArray; + * } + */ +const ObjectValue = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['ObjectValue']), + fc.uniqueArray( + ObjectField(tie, $), + $.ObjectValue! ), - // SchemaExtension: (model: fc.Arbitrary) => fc.tuple( - // fc.constant(byTag['SchemaExtension']), - // fc.uniqueArray(model), - // directives(model), - // ) satisfies fc.Arbitrary, -} satisfies { [K in Kind | NamedType]: (model: fc.Arbitrary) => fc.Arbitrary } +) + +/** + * ## {@link ValueNode `ValueNode`} + * See also: + * - https://github.com/graphql/graphql-js/blob/16.x.x/src/language/ast.ts#L411-L420 + */ +const ValueNode = (_tie: fc.LetrecTypedTie, $: Constraints) => fc.oneof( + IntValue(_tie, $), + FloatValue(_tie, $), + StringValue(_tie, $), + BooleanValue(_tie, $), + NullValue(_tie, $), + EnumValue(_tie, $), + Variable(_tie, $), + // ObjectValue(), + // ListValue(), +) + +/** + * @example + * type ObjectField = [ + * ObjectField: byTag['ObjectField'], + * name: string, + * value: T + * ] + * export interface ObjectFieldNode { + * readonly kind: Kind.OBJECT_FIELD; + * readonly loc?: Location; + * readonly name: NameNode; + * readonly value: ValueNode; + * } + */ +const ObjectField = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['ObjectField']), + name, + ValueNode(tie, $), +) + +/** + * @example + * type ListValue = [ + * ListValue: byTag['ListValue'], + * value: readonly Json[], + * ] + * export interface ConstListValueNode { + * readonly kind: Kind.LIST; + * readonly loc?: Location; + * readonly values: ReadonlyArray; + * } + */ +const ConstListValue = (tie: fc.LetrecTypedTie, $: Constraints) => fc.tuple( + fc.constant(byTag['ListValue']), + fc.array(ConstValueNode(tie, $)), +) + +/** + * ## {@link ConstValueNode `ConstValueNode`} + * See also: + * - https://github.com/graphql/graphql-js/blob/16.x.x/src/language/ast.ts#L422 + */ +const ConstValueNode = (tie: fc.LetrecTypedTie, $: Constraints) => fc.oneof( + IntValue(tie, $), + FloatValue(tie, $), + StringValue(tie, $), + BooleanValue(tie, $), + NullValue(tie, $), + EnumValue(tie, $), + // tie('ConstListValue'), + // tie('ConstObjectValue'), +) + +/** + * ## {@link ExecutableDefinition `ExecutableDefinition`} + * See also: + * - https://github.com/graphql/graphql-js/blob/16.x.x/src/language/ast.ts#L313-L315 + */ +const ExecutableDefinition = (tie: fc.LetrecTypedTie, $: Constraints) => fc.oneof( + tie('OperationDefinition'), + // tie('FragmentDefinition'), +) + +/** + * ## {@link TypeDefinition `TypeDefinition`} + * See also: + * - https://github.com/graphql/graphql-js/blob/16.x.x/src/language/ast.ts#L568-L574 + */ +const TypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints) => fc.oneof( + tie('ScalarTypeDefinition'), + tie('ObjectTypeDefinition'), + tie('InterfaceTypeDefinition'), + tie('UnionTypeDefinition'), + tie('EnumTypeDefinition'), + tie('InputObjectTypeDefinition'), +) + +/** + * ## {@link TypeSystemDefinition `TypeSystemDefinition`} + * See also: + * - https://github.com/graphql/graphql-js/blob/16.x.x/src/language/ast.ts#L546-L549 + */ +const TypeSystemDefinition = (tie: fc.LetrecTypedTie, $: Constraints) => fc.oneof( + TypeDefinition(tie, $), + tie('SchemaDefinition'), + tie('DirectiveDefinition'), + // TypeSystemExtension(tie), +) + +/** + * ## {@link Definition `Definition`} + * See also: + * - https://github.com/graphql/graphql-js/blob/16.x.x/src/language/ast.ts#L308-L311 + */ +const Definition = (tie: fc.LetrecTypedTie, $: Constraints) => fc.oneof( + ExecutableDefinition(tie, $), + TypeSystemDefinition(tie, $), +) + +export const Seed = { + Boolean, + Float, + Int, + ID, + Null, + Number, + String, + NamedType, + NullValue, + BooleanValue, + FloatValue, + IntValue, + StringValue, + ScalarTypeDefinition, + EnumValue, + Variable, + EnumValueDefinition, + ObjectValue, + ObjectField, + ListValue, + ListType, + NonNullType, + UnionTypeDefinition, + EnumTypeDefinition, + Field, + FieldDefinition, + ObjectTypeDefinition, + InterfaceTypeDefinition, + Argument, + InputObjectTypeDefinition, + InputValueDefinition, + VariableDefinition, + Directive, + DirectiveDefinition, + Document, + FragmentDefinition, + FragmentSpread, + InlineFragment, + OperationDefinition, + OperationTypeDefinition, + SelectionSet, + SchemaDefinition, + // SchemaExtension, +} satisfies { [K in F.Kind | F.NamedType]: (tie: fc.LetrecTypedTie, $: Constraints) => fc.Arbitrary } + +// const SchemaExtension = (tie: fc.LetrecTypedTie) => fc.tuple( +// fc.constant(byTag['SchemaExtension']), +// fc.uniqueArray(tie('*')), +// directives(tie('*')), +// ) diff --git a/packages/graphql-test/src/generator.ts b/packages/graphql-test/src/generator.ts index c6e9b6a9..abde211d 100644 --- a/packages/graphql-test/src/generator.ts +++ b/packages/graphql-test/src/generator.ts @@ -11,25 +11,19 @@ import { Number_isSafeInteger, Object_assign, Object_entries, - Object_fromEntries, - Object_keys, - Object_values, omit, - pair, PATTERN, pick, - symbol, } from '@traversable/registry' import { Json } from '@traversable/json' -type Config = import('./generator-options.js').Config -import * as Config from './generator-options.js' +import { Config } from './generator-options.js' import * as Bounds from './generator-bounds.js' -import { AST, Kind, NamedType } from '@traversable/graphql-types' +import { AST, Kind, NamedType, OperationType } from '@traversable/graphql-types' import type { Tag } from './generator-seed.js' -import { byTag, bySeed, Seed } from './generator-seed.js' +import { bySeed, Seed } from './generator-seed.js' import { fold } from './functor.js' const identifier = fc.stringMatching(new RegExp(PATTERN.identifier, 'u')) @@ -92,7 +86,7 @@ const enumValueDefinition = (name: string): AST.EnumValueDefinitionNode => ({ name: nameNode(name), }) -const operationTypeDefinition = (name: string, operation: T): AST.OperationTypeDefinitionNode => ({ +const operationTypeDefinition = (name: string, operation: OperationType): AST.OperationTypeDefinitionNode => ({ kind: Kind.OperationTypeDefinition, type: namedTypeNode(name), operation, @@ -126,12 +120,12 @@ export function pickAndSortNodes(nodes: readonly ([K, fc.Ar (include ? include.includes(x as never) : true) && (exclude ? !exclude.includes(x as never) : true) ) - .sort((l, r) => { + .sort((lk, rk) => { if ( - has(l, (_) => typeof _ === 'number')(sortBias) && - has(r, (_) => typeof _ === 'number')(sortBias) + has(lk, (bias) => typeof bias === 'number')(sortBias) && + has(rk, (bias) => typeof bias === 'number')(sortBias) ) { - return sortBias[l] < sortBias[r] ? -1 : sortBias[l] > sortBias[r] ? 1 : 0 + return sortBias[lk] < sortBias[rk] ? -1 : sortBias[lk] > sortBias[rk] ? 1 : 0 } else { return 0 } @@ -139,7 +133,7 @@ export function pickAndSortNodes(nodes: readonly ([K, fc.Ar } export declare namespace Gen { - type Base = { [K in keyof T]: (tie: fc.LetrecLooselyTypedTie, constraints: $[K & keyof $]) => fc.Arbitrary } + type Base = { [K in keyof T]: (tie: fc.LetrecLooselyTypedTie, constraints: $) => fc.Arbitrary } type Values = never | T[Exclude] type InferArb = S extends fc.Arbitrary ? T : never type Builder = T & BuilderStar @@ -181,31 +175,30 @@ export function Builder(base: Gen.Base>): overrides?: Partial>> ) => (tie: fc.LetrecLooselyTypedTie) => Builder -export function Builder(base: Gen.Base>) { - return >(options?: Options, overrides?: Partial>>) => { - const $ = Config.defaultOptions(options) +export function Builder(base: Gen.Base>) { + return (options?: Config.Options, overrides?: Partial>) => { + const $ = Config.parseOptions(options) return (tie: fc.LetrecLooselyTypedTie) => { - const builder: { [x: string]: fc.Arbitrary } = fn.pipe( + const builder = fn.pipe( { ...base, ...overrides }, (x) => pick(x, $.include), - (x) => omit(x, $.exclude), - (x) => fn.map(x, (f, k) => f(tie, $[k as never])), + (x) => omit(x, $.exclude as []), + (x) => fn.map(x, (f) => f(tie, $)), ) const nodes = pickAndSortNodes(Object_entries(builder))($) - builder['*'] = fc.oneof(...nodes.map((k) => builder[k])) + const star = fc.oneof(...fn.map(nodes, (k) => builder[k])) const root = isKeyOf(builder, $.root) && builder[$.root] - let leaf = builder['*'] return Object_assign( builder, { ...root && { root }, - ['*']: leaf + ['*']: star, }) } } } -const FromSeed = { +const SchemaMap = { Name: ([, name]) => nameNode(name), Null: (_) => namedTypeNode(NamedType.Null), Boolean: (_) => namedTypeNode(NamedType.Boolean), @@ -235,7 +228,7 @@ const FromSeed = { ScalarTypeDefinition: ([, name, description]) => ({ kind: Kind.ScalarTypeDefinition, name: nameNode(name), - description: stringValueNode(...description), + ...description && { description: stringValueNode(...description) }, }), EnumValue: ([, value]) => ({ kind: Kind.EnumValue, @@ -247,11 +240,13 @@ const FromSeed = { }), ListValue: ([, values]) => ({ kind: Kind.ListValue, - values: fn.map(values, (value) => valueNodeFromJson(value)), + values: values as readonly AST.ValueNode[], // <- TODO + // values: fn.map(values, (value) => valueNodeFromJson(value)), }), ObjectValue: ([, fields]) => ({ kind: Kind.ObjectValue, - fields: objectFieldNodes(fields), + fields: fields as readonly AST.ObjectFieldNode[], // <- TODO + // fields: objectFieldNodes(fields), }), Argument: ([, name, value]) => ({ kind: Kind.Argument, @@ -262,24 +257,31 @@ const FromSeed = { kind: Kind.Document, definitions }), - Directive: ([, name, args]) => ({ - kind: Kind.Directive, - name: nameNode(name), - arguments: args, - }), + Directive: ([__kind, name, args]) => { + + console.log('SchemaMap Directive, kind:', __kind) + console.log('SchemaMap Directive, name:', name) + console.log('SchemaMap Directive, args:', JSON.stringify(args, null, 2)) + + return { + kind: Kind.Directive, + name: nameNode(name), + arguments: args, + } + }, DirectiveDefinition: ([, name, description, repeatable, locations, args]) => ({ kind: Kind.DirectiveDefinition, name: nameNode(name), repeatable, - description: stringValueNode(...description), locations: locations.map((location) => nameNode(location)), + ...description && { description: stringValueNode(...description) }, ...args.length && { arguments: args }, }), EnumTypeDefinition: ([, name, description, values]) => ({ kind: Kind.EnumTypeDefinition, name: nameNode(name), - description: stringValueNode(...description), values: values.map(enumValueDefinition), + ...description && { description: stringValueNode(...description) }, }), Field: ([, name, alias, selectionSet, args, directives]) => ({ kind: Kind.Field, @@ -293,7 +295,7 @@ const FromSeed = { kind: Kind.FieldDefinition, name: nameNode(name), type, - description: stringValueNode(...description), + ...description && { description: stringValueNode(...description) }, ...args.length && { arguments: args }, ...directives.length && { directives }, }), @@ -319,7 +321,7 @@ const FromSeed = { kind: Kind.InputObjectTypeDefinition, name: nameNode(name), fields, - description: stringValueNode(...description), + ...description && { description: stringValueNode(...description) }, ...directives.length && { directives }, }), InputValueDefinition: ([, name, description, type, defaultValue, directives]) => ({ @@ -327,7 +329,7 @@ const FromSeed = { name: nameNode(name), type, ...defaultValue != null && { defaultValue }, - description: stringValueNode(...description), + ...description && { description: stringValueNode(...description) }, ...directives.length && { directives }, }), InterfaceTypeDefinition: ([, name, description, fields, interfaces, directives]) => ({ @@ -335,7 +337,7 @@ const FromSeed = { name: nameNode(name), fields, interfaces, - description: stringValueNode(...description), + ...description && { description: stringValueNode(...description) }, ...directives.length && { directives }, }), ListType: ([, type]) => ({ @@ -353,14 +355,15 @@ const FromSeed = { ObjectField: ([, name, value]) => ({ kind: Kind.ObjectField, name: nameNode(name), - value: valueNodeFromJson(value), + value: value as AST.ValueNode, // <-- TODO + // value: valueNodeFromJson(value), }), ObjectTypeDefinition: ([, name, description, fields, interfaces, directives]) => ({ kind: Kind.ObjectTypeDefinition, name: nameNode(name), fields, interfaces, - description: stringValueNode(...description), + ...description && { description: stringValueNode(...description) }, ...directives.length && { directives }, }), OperationDefinition: ([, name, operationType, selectionSet, variableDefinitions, directives]) => ({ @@ -376,10 +379,10 @@ const FromSeed = { type: namedTypeNode(type), operation, }), - SchemaDefinition: ([, description, operationTypes, directives]) => ({ + SchemaDefinition: ([, _, description, operationTypes, directives]) => ({ kind: Kind.SchemaDefinition, - operationTypes: operationTypes.map((ot) => operationTypeDefinition(...ot)), - description: stringValueNode(...description), + operationTypes: operationTypes.map(([, name, operationType]) => operationTypeDefinition(name, operationType)), + ...description && { description: stringValueNode(...description) }, ...directives.length && { directives }, }), SelectionSet: ([, selections]) => ({ @@ -392,11 +395,10 @@ const FromSeed = { types, ...directives.length && { directives }, }), - Variable: ([, name, description, directives]) => ({ + Variable: ([, name, description]) => ({ kind: Kind.Variable, name: nameNode(name), - description: stringValueNode(...description), - ...directives.length && { directives }, + ...description && { description: stringValueNode(...description) }, }), VariableDefinition: ([, name, type, defaultValue, directives]) => ({ kind: Kind.VariableDefinition, @@ -410,4 +412,16 @@ const FromSeed = { & { [K in keyof AST.Catalog.byNamedType]: (x: Seed[K]) => AST.Catalog.byNamedType[K] } ) -export const seedToSchema = fold((x) => FromSeed[bySeed[x[0]]](x as never)) +export const SeedGenerator = Gen(Seed) + +export const seedToSchema = fold((x) => { + console.log('seedToSchema, x:', x) + try { + return SchemaMap[bySeed[x[0]]](x as never) + } catch (e) { + console.error('SchemaMap[bySeed[x[0]]] is not a function') + console.error('x:', x) + throw e + } +}) + diff --git a/packages/graphql-test/test/generator.test.ts b/packages/graphql-test/test/generator.test.ts new file mode 100644 index 00000000..580f56b0 --- /dev/null +++ b/packages/graphql-test/test/generator.test.ts @@ -0,0 +1,41 @@ +import * as vi from 'vitest' +import * as fc from 'fast-check' +import prettier from '@prettier/sync' +import * as gql from 'graphql' +import { SeedGenerator, seedToSchema } from '@traversable/graphql-test' +import * as F from '@traversable/graphql-types' + +function format(src: string) { + return prettier.format(src, { parser: 'graphql', printWidth: 60 }) +} + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/graphql-test❳', () => { + + vi.test('〖⛳️〗› ❲SeedGenerator❳', () => { + const Builder = SeedGenerator({ + noDescriptions: true, + exclude: [ + // 'EnumValueDefinition', + // 'ObjectField', + // 'OperationTypeDefinition', + ], + }) + + try { + const [seed] = fc.sample(Builder['Document'], 1) + try { + const schema = seedToSchema(seed) + console.log(F.toString(schema)) + } catch (e) { + console.error('\n\nFAILED: seedToSchema', JSON.stringify(seed, null, 2), '\n\n') + throw e + } + } catch (e) { + console.error('\n\nFAILED: fc.sample(Builder["Document"])\n\n') + throw e + } + + + vi.assert.isTrue(true) + }) +}) diff --git a/packages/graphql-types/src/exports.ts b/packages/graphql-types/src/exports.ts index aeee3105..9107cc06 100644 --- a/packages/graphql-types/src/exports.ts +++ b/packages/graphql-types/src/exports.ts @@ -10,6 +10,8 @@ export type { export { Functor, + DirectiveTarget, + DirectiveTargets, Kind, Kinds, NamedType, diff --git a/packages/graphql-types/src/functor.ts b/packages/graphql-types/src/functor.ts index e16cf58f..5e8da8a4 100644 --- a/packages/graphql-types/src/functor.ts +++ b/packages/graphql-types/src/functor.ts @@ -55,6 +55,15 @@ export const Kind = { VariableDefinition: 'VariableDefinition', } as const +export type DirectiveTarget = typeof DirectiveTargets[number] +export const DirectiveTarget = { + ARGUMENT_DEFINITION: 'ARGUMENT_DEFINITION', + FIELD_DEFINITION: 'FIELD_DEFINITION', + ENUM_VALUE: 'ENUM_VALUE', + INPUT_FIELD_DEFINITION: 'INPUT_FIELD_DEFINITION', +} as const +export const DirectiveTargets = Object_values(DirectiveTarget) + export type Kind = typeof Kinds[number] export const Kinds = Object_values(Kind) @@ -488,17 +497,17 @@ export interface SubscriptionOperation { loc?: Location } -export interface OperationTypeDefinitionNode { +export interface OperationTypeDefinitionNode { kind: Kind.OperationTypeDefinition type: NamedTypeNode - operation: T + operation: OperationType loc?: Location } export interface SchemaDefinitionNode { kind: Kind.SchemaDefinition directives?: readonly T[] - operationTypes: readonly OperationTypeDefinitionNode[] + operationTypes: readonly OperationTypeDefinitionNode[] description?: StringValueNode loc?: Location } @@ -525,6 +534,7 @@ export type Nullary = | IDNode | EnumValueDefinitionNode | ObjectFieldNode + | OperationTypeDefinitionNode export type OperationDefinitionNode = | QueryOperation @@ -553,7 +563,6 @@ export type Unary = | SchemaDefinitionNode | VariableDefinitionNode | OperationDefinitionNode - | OperationTypeDefinitionNode | DocumentNode export type F = @@ -862,6 +871,7 @@ export function isNullaryNode(x: unknown): x is AST.Nullary { || isStringNode(x) || isIDNode(x) || isEnumValueDefinitionNode(x) + || isOperationTypeDefinitionNode(x) // || isScalarTypeDefinition(x) } @@ -895,7 +905,7 @@ export function isOperationDefinitionNode(x: unknown): x is AST.OperationDefi return has('kind', (kind) => kind === Kind.OperationDefinition)(x) } -export function isOperationTypeDefinitionNode(x: unknown): x is AST.OperationTypeDefinitionNode { +export function isOperationTypeDefinitionNode(x: unknown): x is AST.OperationTypeDefinitionNode { return has('kind', (kind) => kind === Kind.OperationTypeDefinition)(x) } @@ -1053,18 +1063,12 @@ export const Functor: T.Functor.Ix = { selectionSet: g(selectionSet) } } - case isOperationTypeDefinitionNode(x): { - return { - ...x, - operation: g(x.operation), - } - } case isSchemaDefinitionNode(x): { - const { directives, operationTypes, ...xs } = x + const { directives, ...xs } = x return { ...xs, ...directives && { directives: directives.map(g) }, - operationTypes: fn.map(operationTypes, (ot) => ({ ...ot, operation: g(ot.operation) })), + // operationTypes: fn.map(operationTypes, (ot) => ({ ...ot, operation: g(ot.operation) })), } } } @@ -1215,18 +1219,11 @@ export const Functor: T.Functor.Ix = { selectionSet: g(x.selectionSet, ix, x) } } - case isOperationTypeDefinitionNode(x): { - return { - ...x, - operation: g(x.operation, ix, x), - } - } case isSchemaDefinitionNode(x): { - const { directives, operationTypes, ...xs } = x + const { directives, ...xs } = x return { ...xs, ...directives && { directives: directives.map((_) => g(_, ix, x)) }, - operationTypes: fn.map(operationTypes, (ot) => ({ ...ot, operation: g(ot.operation, ix, x) })), } } } @@ -1276,6 +1273,8 @@ export function fold(g: (src: AST.F, ix: Index, x: GQL.ASTNode) => T): Alg const order = graph.chunks.flat() return { order, byName } } catch (e) { + console.error('Error:', e) + console.debug('Groups:', groups) throw Error( 'Dependency graph contains unknown nodes: { ' + Array.from(groups).map(([k, v]) => `${k}: [${v.join(', ')}]`).join(', ') diff --git a/packages/graphql-types/src/to-string.ts b/packages/graphql-types/src/to-string.ts index bbf12c55..1012665e 100644 --- a/packages/graphql-types/src/to-string.ts +++ b/packages/graphql-types/src/to-string.ts @@ -1,6 +1,6 @@ -import { escape } from '@traversable/registry' +import { escape, has } from '@traversable/registry' import * as F from './functor.js' -import type * as gql from 'graphql' +import * as gql from 'graphql' import { Kind } from './functor.js' function directives(x: { directives?: readonly string[] }): string { @@ -14,8 +14,8 @@ function defaultValue(x: { defaultValue?: F.ValueNode | string }): string { function description(x: { description?: F.StringValueNode }): string { return !x.description ? '' : x.description.block ? - `"""\n${x.description.value}\n"""` - : ` "${x.description.value}" ` + `\n"""\n${x.description.value}\n"""\n` + : `"${x.description.value}"\n` } function serializeValueNode(x?: F.ValueNode | string): string { @@ -43,7 +43,7 @@ const fold = F.fold((x) => { default: return x satisfies never case F.isEnumValueDefinitionNode(x): throw Error('Not sure what an `EnumValueDefinitionNode` is...') case F.isObjectFieldNode(x): throw Error('Not sure what an `ObjectFieldNode` is...') - case F.isOperationTypeDefinitionNode(x): throw Error('Not sure what an `OperationTypeDefinitionNode` is...') + case F.isOperationTypeDefinitionNode(x): return `OperationTypeDefinition: ${x.operation}` // throw Error('Not sure what an `OperationTypeDefinitionNode` is...') case F.isValueNode(x): return serializeValueNode(x) case F.isSelectionSetNode(x): return `{ ${x.selections.join('\n')} }` case F.isScalarTypeDefinition(x): return `scalar ${directives(x)}${x.name.value}` @@ -89,11 +89,13 @@ const fold = F.fold((x) => { return `${description(x)}${x.name.value}${directives(x)}: ${x.type}${defaultValue(x)}` } case F.isObjectTypeDefinitionNode(x): { - const IMPLEMENTS = x.interfaces.length ? ` implements ${x.interfaces.join(' & ')}` : '' + // const IMPLEMENTS = x.interfaces.length ? ` implements ${x.interfaces.join(' & ')}` : '' + const IMPLEMENTS = '' return `${description(x)}type ${x.name.value}${directives(x)}${IMPLEMENTS} { ${x.fields.join('\n')} } ` } case F.isInterfaceTypeDefinitionNode(x): { - const IMPLEMENTS = x.interfaces.length ? ` implements ${x.interfaces.join(' & ')}` : '' + // const IMPLEMENTS = x.interfaces.length ? ` implements ${x.interfaces.join(' & ')}` : '' + const IMPLEMENTS = '' return `${description(x)}interface ${x.name.value}${directives(x)}${IMPLEMENTS} { ${x.fields.join('\n')} } ` } case F.isOperationDefinitionNode(x): { @@ -116,6 +118,15 @@ export declare namespace toString {} * * Convert a GraphQL AST into its corresponding TypeScript type. */ -export function toString(doc: gql.DocumentNode): string { - return Object.values(fold(doc).byName).map((thunk) => thunk()).join('\n\r') +export function toString(doc: gql.DocumentNode): string +export function toString(doc: F.AST.Fixpoint): string +export function toString(doc: F.AST.Fixpoint | gql.DocumentNode): string { + const ast: F.AST.DocumentNode + = has('kind', (kind) => kind === gql.Kind.DOCUMENT)(doc) + ? doc + : { kind: 'Document', definitions: [doc as F.F] } + + return Object + .values(fold(ast as F.AST.DocumentNode).byName) + .map((thunk) => thunk()).join('\n\n\r') } diff --git a/packages/registry/src/globalThis.ts b/packages/registry/src/globalThis.ts index 4d92191e..4bb4947b 100644 --- a/packages/registry/src/globalThis.ts +++ b/packages/registry/src/globalThis.ts @@ -76,7 +76,17 @@ export const Object_create: { (x: T): T } = globalThis.Object.create -export const Object_entries = globalThis.Object.entries +type Key = `${T & (string | number)}` + +type Object_entries< + T, + K extends keyof T = keyof T, + E = K extends K ? [Key, T[K]] : [string, unknown] +> = (T extends readonly any[] ? [number, T[number]] : E)[] + +export const Object_entries + : (o: T) => Object_entries + = globalThis.Object.entries export const Object_fromEntries = globalThis.Object.fromEntries diff --git a/packages/registry/src/optics.ts b/packages/registry/src/optics.ts index 4be31794..eeb94de7 100644 --- a/packages/registry/src/optics.ts +++ b/packages/registry/src/optics.ts @@ -1,4 +1,3 @@ -import type { HKT, Kind } from './hkt.js' import { Number_isSafeInteger, Number_parseInt, @@ -147,7 +146,7 @@ const FallbackSemigroup = (fallback: unknown) => ({ return acc } else { - Object_entries(xs).reduce( + Object.entries(xs).reduce( (acc, [k, v]) => k in acc ? acc : { ...acc, [k]: f(isMissing(v) ? fallback : v) }, {} as { [x: number]: unknown } ) diff --git a/packages/registry/src/pattern.ts b/packages/registry/src/pattern.ts index 6e81c252..f942ecb7 100644 --- a/packages/registry/src/pattern.ts +++ b/packages/registry/src/pattern.ts @@ -1,6 +1,7 @@ export const PATTERN = { alphanumeric: '^[a-zA-Z0-9]*$', identifier: '^[$_a-zA-Z][$_a-zA-Z0-9]*$', + identifierNoDollar: '^[_a-zA-Z][_a-zA-Z0-9]*$', exponential: 'e[-|+]?', newline: '[\r|\n]', } as const diff --git a/packages/registry/src/topological-sort.ts b/packages/registry/src/topological-sort.ts index ca22f1b8..1e493e6a 100644 --- a/packages/registry/src/topological-sort.ts +++ b/packages/registry/src/topological-sort.ts @@ -108,7 +108,9 @@ export function topologicalSort(graph: Graph, includedNodes: T[] = [...gra while (queue.length) { const [id, cycle] = queue.shift()! - for (const to of graph.get(id)!) { + const target = graph.get(id) + if (target === undefined) continue + for (const to of target) { if (to === startNode) { cycleVisited.add(to) cycles.push([...cycle]) diff --git a/packages/schema-errors/src/json.ts b/packages/schema-errors/src/json.ts index ce81c783..06d5d1f0 100644 --- a/packages/schema-errors/src/json.ts +++ b/packages/schema-errors/src/json.ts @@ -1,7 +1,6 @@ import type { Mutable, Unknown } from '@traversable/registry' import { fn, - Object_entries, Object_hasOwn, NS, } from '@traversable/registry' @@ -64,7 +63,7 @@ export function object( function validateJsonObject(got: T | Unknown, path: (keyof any)[] = defaultPath): ValidationError[] { return !JSON.isObject(got) ? ERRORS(got, path) - : Object_entries(validators).flatMap(([k, validator]) => Object_hasOwn(got, k) + : Object.entries(validators).flatMap(([k, validator]) => Object_hasOwn(got, k) ? validator(got[k], [...path, k]) : KEY_ERRORS(k, got, path) ) diff --git a/packages/schema-errors/src/validators.ts b/packages/schema-errors/src/validators.ts index 14ceed1f..ffeeca61 100644 --- a/packages/schema-errors/src/validators.ts +++ b/packages/schema-errors/src/validators.ts @@ -3,7 +3,6 @@ import { getConfig, Number_isFinite, Number_isSafeInteger, - Object_entries, Object_hasOwn, symbol, URI, @@ -228,7 +227,7 @@ export function record( const ARRAYS_ARE_OK = getConfig().schema.treatArraysAsObjects return (!got || typeof got !== 'object' || (ARRAYS_ARE_OK ? false : Array_isArray(got))) ? ERRORS(got, path) - : Object_entries(got).flatMap(([k, v]) => x.def(v, [...path, k])) + : Object.entries(got).flatMap(([k, v]) => x.def(v, [...path, k])) } validateRecord.tag = URI.record return validateRecord @@ -319,7 +318,7 @@ export function object( || typeof got !== 'object' || NON_ARRAY_CHECK(got) ) ? ERRORS(got, path) - : Object_entries(x.def).flatMap(([k, validator]): ValidationError[] => { + : Object.entries(x.def).flatMap(([k, validator]): ValidationError[] => { const IS_OPTIONAL = symbol.optional in validator && typeof validator[symbol.optional] === 'number' let LOCAL_PATH = [...path, k] return !Object_hasOwn(got, k) diff --git a/packages/schema/src/schema.ts b/packages/schema/src/schema.ts index 83766713..83fe630d 100644 --- a/packages/schema/src/schema.ts +++ b/packages/schema/src/schema.ts @@ -20,7 +20,6 @@ import { Number_isNatural, Number_isSafeInteger, Object_assign, - Object_entries, Object_keys, parseArgs, parseKey, @@ -160,7 +159,7 @@ const serialize = (json: unknown) => { case typeof x === 'symbol': return String(x) case Array_isArray(x): return x.length === 0 ? '[]' : `[${x.map(go).join(', ')}]` case !!x && typeof x === 'object': { - const xs = Object_entries(x).map(([k, v]) => `${parseKey(k)}: ${go(v)}`) + const xs = Object.entries(x).map(([k, v]) => `${parseKey(k)}: ${go(v)}`) return xs.length === 0 ? `{}` : `{ ${xs.join(', ')} }` } } diff --git a/packages/zod-test/src/generator-options.ts b/packages/zod-test/src/generator-options.ts index 6237d6c2..ce6628d9 100644 --- a/packages/zod-test/src/generator-options.ts +++ b/packages/zod-test/src/generator-options.ts @@ -273,7 +273,6 @@ export const defaults = { } as const satisfies OptionsBase export function parseOptions(options?: Opts): Config> -export function parseOptions(options?: Options): Config export function parseOptions(options: Options = defaults as never): Config { const { exclude = defaults.exclude, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 028c4f1c..c68a4b0b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -343,6 +343,9 @@ importers: packages/graphql-test: devDependencies: + '@prettier/sync': + specifier: 'catalog:' + version: 0.5.5(prettier@3.6.2) '@traversable/graphql-types': specifier: workspace:^ version: link:../graphql-types/dist From 16aa4f6b9da9d36a23452e8d75839d70c5c2e177 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 4 Oct 2025 22:21:00 -0500 Subject: [PATCH 28/32] feat(graphql-test): generator generates valid gql documents :tada: --- packages/graphql-test/src/functor.ts | 23 ++---- packages/graphql-test/src/generator-seed.ts | 76 +++++++++++--------- packages/graphql-test/src/generator.ts | 30 ++------ packages/graphql-test/test/generator.test.ts | 5 +- packages/graphql-types/src/to-string.ts | 9 ++- 5 files changed, 63 insertions(+), 80 deletions(-) diff --git a/packages/graphql-test/src/functor.ts b/packages/graphql-test/src/functor.ts index 3dbfb0a2..defa9654 100644 --- a/packages/graphql-test/src/functor.ts +++ b/packages/graphql-test/src/functor.ts @@ -4,6 +4,8 @@ import { fn } from '@traversable/registry' import type { Seed } from './generator-seed.js' import { byTag } from './generator-seed.js' +const isNull = (x: unknown) => x === null + export interface Index {} export const defaultIndex = Object.create(null) satisfies Index @@ -43,7 +45,7 @@ export const Functor: T.Functor.Ix = { case x[0] === byTag.DirectiveDefinition: return [x[0], x[1], x[2], x[3], x[4], fn.map(x[5], g)] case x[0] === byTag.Document: return [x[0], fn.map(x[1], g)] case x[0] === byTag.EnumTypeDefinition: return [x[0], x[1], x[2], x[3], fn.map(x[4], g)] - case x[0] === byTag.Field: return [x[0], x[1], x[2], g(x[3]), fn.map(x[4], g), fn.map(x[5], g)] + case x[0] === byTag.Field: return [x[0], x[1], x[2], isNull(x[3]) ? x[3] : g(x[3]), fn.map(x[4], g), fn.map(x[5], g)] case x[0] === byTag.FieldDefinition: return [x[0], x[1], x[2], g(x[3]), fn.map(x[4], g), fn.map(x[5], g)] case x[0] === byTag.FragmentDefinition: return [x[0], x[1], x[2], g(x[3]), fn.map(x[4], g)] case x[0] === byTag.FragmentSpread: return [x[0], x[1], fn.map(x[2], g)] @@ -62,12 +64,6 @@ export const Functor: T.Functor.Ix = { }, mapWithIndex(g) { return (x, ix) => { - - console.debug('\n') - console.group('mapWithIndex') - console.debug('x:', x) - console.groupEnd() - switch (true) { default: return x satisfies never case x[0] === byTag.Name: return x @@ -100,7 +96,7 @@ export const Functor: T.Functor.Ix = { case x[0] === byTag.DirectiveDefinition: return [x[0], x[1], x[2], x[3], x[4], fn.map(x[5], (_) => g(_, ix, x))] case x[0] === byTag.Document: return [x[0], fn.map(x[1], (_) => g(_, ix, x))] case x[0] === byTag.EnumTypeDefinition: return [x[0], x[1], x[2], x[3], fn.map(x[4], (_) => g(_, ix, x))] - case x[0] === byTag.Field: return [x[0], x[1], x[2], g(x[3], ix, x), fn.map(x[4], (_) => g(_, ix, x)), fn.map(x[5], (_) => g(_, ix, x))] + case x[0] === byTag.Field: return [x[0], x[1], x[2], isNull(x[3]) ? x[3] : g(x[3], ix, x), fn.map(x[4], (_) => g(_, ix, x)), fn.map(x[5], (_) => g(_, ix, x))] case x[0] === byTag.FieldDefinition: return [x[0], x[1], x[2], g(x[3], ix, x), fn.map(x[4], (_) => g(_, ix, x)), fn.map(x[5], (_) => g(_, ix, x))] case x[0] === byTag.FragmentDefinition: return [x[0], x[1], x[2], g(x[3], ix, x), fn.map(x[4], (_) => g(_, ix, x))] case x[0] === byTag.FragmentSpread: return [x[0], x[1], fn.map(x[2], (_) => g(_, ix, x))] @@ -109,16 +105,7 @@ export const Functor: T.Functor.Ix = { case x[0] === byTag.InputObjectTypeDefinition: return [x[0], x[1], x[2], fn.map(x[3], (_) => g(_, ix, x)), fn.map(x[4], (_) => g(_, ix, x))] case x[0] === byTag.InterfaceTypeDefinition: return [x[0], x[1], x[2], fn.map(x[3], (_) => g(_, ix, x)), fn.map(x[4], (_) => g(_, ix, x)), fn.map(x[5], (_) => g(_, ix, x))] case x[0] === byTag.ObjectTypeDefinition: return [x[0], x[1], x[2], fn.map(x[3], (_) => g(_, ix, x)), fn.map(x[4], (_) => g(_, ix, x)), fn.map(x[5], (_) => g(_, ix, x))] - - case x[0] === byTag.OperationDefinition: return [ - x[0], - x[1], - x[2], - g(x[3], ix, x), - fn.map(x[4], (_) => g(_, ix, x)), - fn.map(x[5], (_) => g(_, ix, x)) - ] - + case x[0] === byTag.OperationDefinition: return [x[0], x[1], x[2], g(x[3], ix, x), fn.map(x[4], (_) => g(_, ix, x)), fn.map(x[5], (_) => g(_, ix, x))] case x[0] === byTag.SchemaDefinition: return [x[0], x[1], x[2], x[3], fn.map(x[4], (_) => g(_, ix, x))] case x[0] === byTag.UnionTypeDefinition: return [x[0], x[1], fn.map(x[2], (_) => g(_, ix, x)), fn.map(x[3], (_) => g(_, ix, x))] case x[0] === byTag.VariableDefinition: return [x[0], x[1], g(x[2], ix, x), g(x[3], ix, x), fn.map(x[4], (_) => g(_, ix, x))] diff --git a/packages/graphql-test/src/generator-seed.ts b/packages/graphql-test/src/generator-seed.ts index beac2b99..2e045df5 100644 --- a/packages/graphql-test/src/generator-seed.ts +++ b/packages/graphql-test/src/generator-seed.ts @@ -119,8 +119,8 @@ export type bySeed = typeof bySeed export const bySeed = invert(byTag) export const identifier = fc.stringMatching(new RegExp(PATTERN.identifierNoDollar, 'u')).map((x) => `${x.charAt(0).toUpperCase()}${x.slice(1)}`) -export const name = fc.lorem({ maxCount: 1 }) -export const alias = name +// export const name = fc.lorem({ maxCount: 1 }) +export const alias = identifier export const target = fc.constantFrom(...F.DirectiveTargets) export const description = ($: Constraints) => $.noDescriptions ? fc.constant(null) : fc.oneof( @@ -253,7 +253,7 @@ export declare namespace Seed { Field: byTag['Field'], name: string, alias: string, - selectionSet: T, + selectionSet: T | null, arguments: readonly T[], directives: readonly T[], ] @@ -570,7 +570,7 @@ export type Seed = ( const NamedType = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['NamedType']), - name, + identifier, ) const Boolean = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag['Boolean']]) @@ -599,12 +599,12 @@ const ScalarTypeDefinition = (_tie: fc.LetrecTypedTie, $: Constraints): fc const EnumValue = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['EnumValue']), - name, + identifier, ) const EnumValueDefinition = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['EnumValueDefinition']), - name, + identifier, ) /** @@ -680,7 +680,7 @@ const NonNullType = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary */ const UnionTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['UnionTypeDefinition']), - name, + identifier, fc.uniqueArray( NamedType(tie, $), $.NamedType! @@ -709,7 +709,7 @@ const UnionTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.A */ const Variable = (_tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['Variable']), - name, + identifier, description($), ) @@ -733,9 +733,9 @@ const Variable = (_tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['EnumTypeDefinition']), - name, + identifier, description($), - fc.uniqueArray(name), + fc.uniqueArray(identifier), fc.uniqueArray( // TODO: // ConstDirective(tie, $), @@ -766,9 +766,11 @@ const EnumTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Ar */ const Field = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['Field']), - name, + identifier, alias, - tie('SelectionSet'), + fc.oneof( + fc.constant(null), NonEmptySelectionSet(tie, $) + ), fc.uniqueArray( tie('Argument'), $.Argument! @@ -801,7 +803,7 @@ const Field = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['FieldDefinition']), - name, + identifier, description($), TypeNode(tie, $), fc.uniqueArray( @@ -911,7 +913,7 @@ const InterfaceTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): */ const Argument = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['Argument']), - name, + identifier, ValueNode(tie, $), ) @@ -935,7 +937,7 @@ const Argument = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['InputObjectTypeDefinition']), - name, + identifier, description($), fc.uniqueArray( tie('InputValueDefinition'), @@ -971,7 +973,7 @@ const InputObjectTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints) */ const InputValueDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['InputValueDefinition']), - name, + identifier, description($), TypeNode(tie, $), ConstValueNode(tie, $), @@ -1002,7 +1004,7 @@ const InputValueDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc. */ const VariableDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['VariableDefinition']), - name, + identifier, TypeNode(tie, $), ConstValueNode(tie, $), fc.uniqueArray( @@ -1030,7 +1032,7 @@ const VariableDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Ar const Directive = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => { return fc.tuple( fc.constant(byTag['Directive']), - name, + identifier, fc.uniqueArray( tie('Argument'), $.Argument! @@ -1060,7 +1062,7 @@ const Directive = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['DirectiveDefinition']), - name, + identifier, description($), fc.boolean(), fc.uniqueArray( @@ -1093,8 +1095,8 @@ const DirectiveDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.A */ const FragmentDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['FragmentDefinition']), - name, - name, + identifier, + identifier, tie('SelectionSet'), fc.uniqueArray( tie('Directive'), @@ -1119,7 +1121,7 @@ const FragmentDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Ar */ const FragmentSpread = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['FragmentSpread']), - name, + identifier, fc.uniqueArray( tie('Directive'), $.Directive! @@ -1144,14 +1146,23 @@ const FragmentSpread = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitr */ const InlineFragment = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['InlineFragment']), - name, - tie('SelectionSet'), + identifier, + NonEmptySelectionSet(tie, $), fc.uniqueArray( tie('Directive'), $.Directive! ), ) +// used in OperationDefinition +const NonEmptySelectionSet = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['SelectionSet']), + fc.uniqueArray( + Selection(tie, $), + { ...$.SelectionSet, minLength: 1 }, + ), +) + /** * @example * type OperationDefinition = [ @@ -1174,9 +1185,9 @@ const InlineFragment = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitr */ const OperationDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['OperationDefinition']), - name, + identifier, operationType, - tie('SelectionSet'), + NonEmptySelectionSet(tie, $), fc.uniqueArray( tie('VariableDefinition'), $.VariableDefinition! @@ -1185,7 +1196,6 @@ const OperationDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.A tie('Directive'), $.Directive! ), - // tie('Directive'), ) /** @@ -1204,7 +1214,7 @@ const OperationDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.A */ const OperationTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['OperationTypeDefinition']), - name, + identifier, operationType, // tie('OperationT'), ) @@ -1247,7 +1257,7 @@ const SelectionSet = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrar */ const SchemaDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['SchemaDefinition']), - name, + identifier, description($), fc.uniqueArray( OperationTypeDefinition(tie, $), @@ -1304,7 +1314,7 @@ const Document = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['Argument']), - name, + identifier, ConstValueNode(tie, $), ) @@ -1324,7 +1334,7 @@ const ConstArgument = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitra */ const ConstDirective = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['Directive']), - name, + identifier, fc.uniqueArray( ConstArgument(tie, $), $.Argument! @@ -1385,7 +1395,7 @@ const ValueNode = (_tie: fc.LetrecTypedTie, $: Constraints) => fc.oneof( BooleanValue(_tie, $), NullValue(_tie, $), EnumValue(_tie, $), - Variable(_tie, $), + // Variable(_tie, $), // ObjectValue(), // ListValue(), ) @@ -1406,7 +1416,7 @@ const ValueNode = (_tie: fc.LetrecTypedTie, $: Constraints) => fc.oneof( */ const ObjectField = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['ObjectField']), - name, + identifier, ValueNode(tie, $), ) diff --git a/packages/graphql-test/src/generator.ts b/packages/graphql-test/src/generator.ts index abde211d..3d97a107 100644 --- a/packages/graphql-test/src/generator.ts +++ b/packages/graphql-test/src/generator.ts @@ -257,18 +257,11 @@ const SchemaMap = { kind: Kind.Document, definitions }), - Directive: ([__kind, name, args]) => { - - console.log('SchemaMap Directive, kind:', __kind) - console.log('SchemaMap Directive, name:', name) - console.log('SchemaMap Directive, args:', JSON.stringify(args, null, 2)) - - return { - kind: Kind.Directive, - name: nameNode(name), - arguments: args, - } - }, + Directive: ([__kind, name, args]) => ({ + kind: Kind.Directive, + name: nameNode(name), + arguments: args, + }), DirectiveDefinition: ([, name, description, repeatable, locations, args]) => ({ kind: Kind.DirectiveDefinition, name: nameNode(name), @@ -287,7 +280,7 @@ const SchemaMap = { kind: Kind.Field, name: nameNode(name), alias: nameNode(alias), - ...selectionSet != null && { selectionSet }, + ...selectionSet !== null && { selectionSet }, ...args.length && { arguments: args }, ...directives.length && { directives }, }), @@ -414,14 +407,5 @@ const SchemaMap = { export const SeedGenerator = Gen(Seed) -export const seedToSchema = fold((x) => { - console.log('seedToSchema, x:', x) - try { - return SchemaMap[bySeed[x[0]]](x as never) - } catch (e) { - console.error('SchemaMap[bySeed[x[0]]] is not a function') - console.error('x:', x) - throw e - } -}) +export const seedToSchema = fold((x) => SchemaMap[bySeed[x[0]]](x as never)) diff --git a/packages/graphql-test/test/generator.test.ts b/packages/graphql-test/test/generator.test.ts index 580f56b0..bd7930ec 100644 --- a/packages/graphql-test/test/generator.test.ts +++ b/packages/graphql-test/test/generator.test.ts @@ -25,7 +25,10 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/graphql-test❳', () => { const [seed] = fc.sample(Builder['Document'], 1) try { const schema = seedToSchema(seed) - console.log(F.toString(schema)) + console.log('seed:\n', JSON.stringify(seed, null, 2)) + console.log('schema:\n', schema) + + console.log('SDL:\n', format(F.toString(schema))) } catch (e) { console.error('\n\nFAILED: seedToSchema', JSON.stringify(seed, null, 2), '\n\n') throw e diff --git a/packages/graphql-types/src/to-string.ts b/packages/graphql-types/src/to-string.ts index 1012665e..338aac5c 100644 --- a/packages/graphql-types/src/to-string.ts +++ b/packages/graphql-types/src/to-string.ts @@ -1,7 +1,6 @@ import { escape, has } from '@traversable/registry' import * as F from './functor.js' import * as gql from 'graphql' -import { Kind } from './functor.js' function directives(x: { directives?: readonly string[] }): string { return !x.directives ? '' : ` ${x.directives.join(' ')} ` @@ -57,7 +56,7 @@ const fold = F.fold((x) => { case F.isListNode(x): return `[${x.type}]` case F.isNonNullTypeNode(x): return `${x.type}!` case F.isArgumentNode(x): return `${x.name.value}: ${x.value}` - case F.isDirectiveNode(x): return `@${x.name.value}(${(x.arguments ?? []).join(', ')})` + case F.isDirectiveNode(x): return `@${x.name.value}${(!x.arguments?.length ? '' : `(${x.arguments.join(', ')})`)}` case F.isInputObjectTypeDefinitionNode(x): { return `${description(x)}input ${x.name.value} { ${x.fields.join('\n')} } ` } @@ -78,15 +77,15 @@ const fold = F.fold((x) => { const KEY = !x.alias?.value ? `${x.name.value} ` : `${x.alias.value}: ${x.name.value}` const ARGS = !x.arguments?.length ? '' : `(${x.arguments.join(', ')})` return x.selectionSet - ? `${description(x)}${KEY}${directives(x)}${ARGS}${x.selectionSet}` - : `${description(x)}${KEY}${directives(x)}${ARGS}` + ? `${description(x)}${KEY}${ARGS}${directives(x)}${x.selectionSet}` + : `${description(x)}${KEY}${ARGS}${directives(x)}` } case F.isFieldDefinitionNode(x): { const ARGS = !x.arguments?.length ? '' : `(${x.arguments.join(', ')})` return `${description(x)}${x.name.value}${directives(x)}${ARGS}: ${x.type}` } case F.isInputValueDefinitionNode(x): { - return `${description(x)}${x.name.value}${directives(x)}: ${x.type}${defaultValue(x)}` + return `${description(x)}${x.name.value}: ${x.type}${defaultValue(x)}${directives(x)}` } case F.isObjectTypeDefinitionNode(x): { // const IMPLEMENTS = x.interfaces.length ? ` implements ${x.interfaces.join(' & ')}` : '' From 6192e6fb4ff9442a635daf4fdcb72692f63042aa Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 4 Oct 2025 22:34:49 -0500 Subject: [PATCH 29/32] feat(graphql-types): AST.toString correctly stringifies nodes --- packages/graphql-test/src/generator-options.ts | 8 ++++++-- packages/graphql-test/src/generator-seed.ts | 10 ++++++---- packages/graphql-test/test/generator.test.ts | 1 + packages/graphql-types/src/to-string.ts | 2 +- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/graphql-test/src/generator-options.ts b/packages/graphql-test/src/generator-options.ts index 7393760d..9f27baf4 100644 --- a/packages/graphql-test/src/generator-options.ts +++ b/packages/graphql-test/src/generator-options.ts @@ -39,6 +39,7 @@ export declare namespace Constraints { type Argument = fc.UniqueArrayConstraints type Directive = fc.UniqueArrayConstraints type Document = fc.UniqueArrayConstraints + type EnumTypeDefinition = fc.UniqueArrayConstraints type InputValueDefinition = fc.UniqueArrayConstraints type FieldDefinition = fc.UniqueArrayConstraints type NamedType = fc.UniqueArrayConstraints @@ -55,7 +56,7 @@ export type Constraints = { Directive?: Constraints.Directive DirectiveDefinition?: {} Document?: Constraints.Document - EnumTypeDefinition?: {} + EnumTypeDefinition?: Constraints.EnumTypeDefinition EnumValue?: {} EnumValueDefinition?: {} Field?: {} @@ -116,7 +117,10 @@ export const defaultConstraints = { selector: ([, name]) => name, size: 'xsmall', }, - EnumTypeDefinition: {}, + EnumTypeDefinition: { + minLength: 1, + maxLength: 3, + }, EnumValue: {}, EnumValueDefinition: {}, Field: {}, diff --git a/packages/graphql-test/src/generator-seed.ts b/packages/graphql-test/src/generator-seed.ts index 2e045df5..773dee20 100644 --- a/packages/graphql-test/src/generator-seed.ts +++ b/packages/graphql-test/src/generator-seed.ts @@ -735,7 +735,10 @@ const EnumTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Ar fc.constant(byTag['EnumTypeDefinition']), identifier, description($), - fc.uniqueArray(identifier), + fc.uniqueArray( + identifier, + $.EnumTypeDefinition! + ), fc.uniqueArray( // TODO: // ConstDirective(tie, $), @@ -1097,7 +1100,7 @@ const FragmentDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Ar fc.constant(byTag['FragmentDefinition']), identifier, identifier, - tie('SelectionSet'), + NonEmptySelectionSet(tie, $), fc.uniqueArray( tie('Directive'), $.Directive! @@ -1395,7 +1398,6 @@ const ValueNode = (_tie: fc.LetrecTypedTie, $: Constraints) => fc.oneof( BooleanValue(_tie, $), NullValue(_tie, $), EnumValue(_tie, $), - // Variable(_tie, $), // ObjectValue(), // ListValue(), ) @@ -1460,7 +1462,7 @@ const ConstValueNode = (tie: fc.LetrecTypedTie, $: Constraints) => fc.oneo */ const ExecutableDefinition = (tie: fc.LetrecTypedTie, $: Constraints) => fc.oneof( tie('OperationDefinition'), - // tie('FragmentDefinition'), + tie('FragmentDefinition'), ) /** diff --git a/packages/graphql-test/test/generator.test.ts b/packages/graphql-test/test/generator.test.ts index bd7930ec..555c698c 100644 --- a/packages/graphql-test/test/generator.test.ts +++ b/packages/graphql-test/test/generator.test.ts @@ -11,6 +11,7 @@ function format(src: string) { vi.describe('〖⛳️〗‹‹‹ ❲@traversable/graphql-test❳', () => { + vi.test('〖⛳️〗› ❲SeedGenerator❳', () => { const Builder = SeedGenerator({ noDescriptions: true, diff --git a/packages/graphql-types/src/to-string.ts b/packages/graphql-types/src/to-string.ts index 338aac5c..f346a843 100644 --- a/packages/graphql-types/src/to-string.ts +++ b/packages/graphql-types/src/to-string.ts @@ -82,7 +82,7 @@ const fold = F.fold((x) => { } case F.isFieldDefinitionNode(x): { const ARGS = !x.arguments?.length ? '' : `(${x.arguments.join(', ')})` - return `${description(x)}${x.name.value}${directives(x)}${ARGS}: ${x.type}` + return `${description(x)}${x.name.value}${ARGS}: ${x.type}${directives(x)}` } case F.isInputValueDefinitionNode(x): { return `${description(x)}${x.name.value}: ${x.type}${defaultValue(x)}${directives(x)}` From 477a6a9840dd3ba1466f0c1a74e462d9e33d347f Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 4 Oct 2025 23:48:37 -0500 Subject: [PATCH 30/32] chore(graphql-test): cleans up impl --- .../graphql-test/src/generator-options.ts | 30 +- packages/graphql-test/src/generator-seed.ts | 708 +++--------------- packages/graphql-test/src/generator.ts | 18 +- packages/graphql-test/test/generator.test.ts | 46 +- packages/graphql-types/src/functor.ts | 2 +- packages/graphql-types/src/to-string.ts | 15 +- 6 files changed, 144 insertions(+), 675 deletions(-) diff --git a/packages/graphql-test/src/generator-options.ts b/packages/graphql-test/src/generator-options.ts index 9f27baf4..7abe5e84 100644 --- a/packages/graphql-test/src/generator-options.ts +++ b/packages/graphql-test/src/generator-options.ts @@ -41,7 +41,9 @@ export declare namespace Constraints { type Document = fc.UniqueArrayConstraints type EnumTypeDefinition = fc.UniqueArrayConstraints type InputValueDefinition = fc.UniqueArrayConstraints + type FloatValue = fc.DoubleConstraints type FieldDefinition = fc.UniqueArrayConstraints + type ListValue = fc.ArrayConstraints type NamedType = fc.UniqueArrayConstraints type ObjectValue = fc.UniqueArrayConstraints type SchemaDefinition = fc.UniqueArrayConstraints @@ -62,7 +64,7 @@ export type Constraints = { Field?: {} FieldDefinition?: Constraints.FieldDefinition Float?: {} - FloatValue?: {} + FloatValue?: Constraints.FloatValue FragmentDefinition?: {} FragmentSpread?: {} ID?: {} @@ -73,7 +75,7 @@ export type Constraints = { InterfaceTypeDefinition?: {} IntValue?: {} ListType?: {} - ListValue?: {} + ListValue?: Constraints.ListValue Name?: {} NamedType?: Constraints.NamedType NonNullType?: {} @@ -98,7 +100,7 @@ export type Constraints = { export const defaultConstraints = { Argument: { minLength: 0, - maxLength: 2, + maxLength: 3, selector: ([, name]) => name, size: 'xsmall', }, @@ -106,14 +108,14 @@ export const defaultConstraints = { BooleanValue: {}, Directive: { minLength: 0, - maxLength: 1, + maxLength: 2, selector: ([, name]) => name, size: 'xsmall', }, DirectiveDefinition: {}, Document: { minLength: 1, - maxLength: 1, + maxLength: 5, selector: ([, name]) => name, size: 'xsmall', }, @@ -126,12 +128,15 @@ export const defaultConstraints = { Field: {}, FieldDefinition: { minLength: 1, - maxLength: 3, + maxLength: 5, selector: ([, name]) => name, size: 'xsmall', }, Float: {}, - FloatValue: {}, + FloatValue: { + noNaN: true, + noDefaultInfinity: true, + }, FragmentDefinition: {}, FragmentSpread: {}, ID: {}, @@ -145,9 +150,14 @@ export const defaultConstraints = { }, Int: {}, InterfaceTypeDefinition: {}, - IntValue: {}, + IntValue: { + }, ListType: {}, - ListValue: {}, + ListValue: { + minLength: 0, + maxLength: 5, + size: 'xsmall', + }, Name: {}, NamedType: { minLength: 1, @@ -164,7 +174,7 @@ export const defaultConstraints = { ObjectValue: { minLength: 1, maxLength: 3, - selector: ([, name]) => name, + selector: ([k]) => k, size: 'xsmall', }, OperationDefinition: { diff --git a/packages/graphql-test/src/generator-seed.ts b/packages/graphql-test/src/generator-seed.ts index 773dee20..a24109af 100644 --- a/packages/graphql-test/src/generator-seed.ts +++ b/packages/graphql-test/src/generator-seed.ts @@ -1,65 +1,19 @@ import * as fc from 'fast-check' import type * as T from '@traversable/registry' import { Object_keys, PATTERN } from '@traversable/registry' +import type { Json } from '@traversable/json' import type { OperationType } from '@traversable/graphql-types' import * as F from '@traversable/graphql-types' import { Config } from './generator-options.js' type Constraints = Config.Options -// type Constraints = Required export function invert>(x: T): { [K in keyof T as T[K]]: K } export function invert(x: Record) { return Object_keys(x).reduce((acc, k) => (acc[x[k]] = k, acc), {} as typeof x) } -const EXAMPLE = [ - 360, [ - [ - 400, // OperationDefinition - "ornare", - "query", - [ - 420, - [] - ], - [ - [ - 330, - "suscipit", - [ - 270, - [ - 20, - "turpis" - ] - ], - [ - 110, - true - ], - [] - ] - ], - [ - 340, - "fermentum", - [ - [ - 290, - "egestas", - [ - 130, - -4.442196362483118e+172 - ] - ] - ] - ] - ] - ] -] - export type Tag = byTag[keyof byTag] export type byTag = typeof byTag export const byTag = { @@ -206,14 +160,14 @@ export declare namespace Seed { value: string, ] - type ListValue = [ + type ListValue = [ ListValue: byTag['ListValue'], - value: readonly T[], + value: readonly Json[], ] - type ObjectValue = [ + type ObjectValue = [ ObjectValue: byTag['ObjectValue'], - value: readonly T[], + value: readonly (readonly [k: string, v: Json])[], ] /// Value nodes /// ///////////////////// @@ -363,51 +317,6 @@ export declare namespace Seed { directives: readonly T[], ] - /** - * @example - * [ - * 400, // OperationDefinition - * "ornare", - * "query", - * [ - * 420, - * [] - * ], - * [ - * [ - * 330, - * "suscipit", - * [ - * 270, - * [ - * 20, - * "turpis" - * ] - * ], - * [ - * 110, - * true - * ], - * [] - * ] - * ], - * [ - * 340, - * "fermentum", - * [ - * [ - * 290, - * "egestas", - * [ - * 130, - * -4.442196362483118e+172 - * ] - * ] - * ] - * ] - * ] - */ - type OperationDefinition = [ OperationDefinition: byTag['OperationDefinition'], name: string, @@ -573,6 +482,46 @@ const NamedType = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary identifier, ) +const jsonScalar = fc.oneof( + fc.constant(null), + fc.integer(), + fc.double({ noNaN: true, noDefaultInfinity: true }), + identifier, +) + +type JsonValue = { + null: null + number: number + string: string + array: readonly JsonValue[] + object: { [x: string]: JsonValue } + "*": JsonValue +} + +const jsonValue = fc.letrec((tie: fc.LetrecTypedTie) => { + return { + null: fc.constant(null), + number: fc.integer(), + // fc.double({ noNaN: true, noDefaultInfinity: true }), + string: identifier, + array: fc.array(tie('*')), + object: fc.uniqueArray( + fc.tuple( + identifier, + tie('*'), + ), + { selector: ([k]) => k } + ).map((xs) => Object.fromEntries(xs)), + ['*']: fc.oneof( + tie('null'), + tie('number'), + tie('string'), + tie('array'), + tie('object'), + ) + } +}) + const Boolean = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag['Boolean']]) const Float = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag['Float']]) const Int = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag['Int']]) @@ -583,7 +532,10 @@ const String = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary, _$: Constraints): fc.Arbitrary => fc.constant([byTag['NullValue']]) const BooleanValue = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.tuple(fc.constant(byTag['BooleanValue']), fc.boolean()) -const FloatValue = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.tuple(fc.constant(byTag['FloatValue']), fc.double()) +const FloatValue = (_tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag['FloatValue']), fc.double($.FloatValue) +) + const IntValue = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.tuple(fc.constant(byTag['IntValue']), fc.integer()) const StringValue = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['StringValue']), @@ -607,77 +559,27 @@ const EnumValueDefinition = (_tie: fc.LetrecTypedTie, _$: Constraints): fc identifier, ) -/** - * @example - * type ListValue = [ - * ListValue: byTag['ListValue'], - * value: readonly unknown[], - * ] - * export interface ListValueNode { - * readonly kind: Kind.LIST; - * readonly loc?: Location; - * readonly values: ReadonlyArray; - * } - */ -const ListValue = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( +const ListValue = (_tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['ListValue']), - fc.array(ValueNode(tie, $)), + fc.array( + jsonValue['*'], + $.ListValue + ), ) -/** - * @example - * type ListType = [ - * ListType: byTag['ListType'], - * type: T, - * ] - * export interface ListTypeNode { - * readonly kind: Kind.LIST_TYPE; - * readonly loc?: Location; - * readonly type: TypeNode; - * } - */ const ListType = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['ListType']), TypeNode(tie, $), ) -/** - * @example - * export interface NonNullTypeNode { - * readonly kind: Kind.NON_NULL_TYPE; - * readonly loc?: Location; - * readonly type: NamedTypeNode | ListTypeNode; - * } - * type NonNullType = [ - * NonNullType: byTag['NonNullType'], - * type: T, - * ] - */ const NonNullType = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['NonNullType']), fc.oneof( NamedType(tie, $), - tie('ListType'), + ListType(tie, $), ) ) -/** - * @example - * type UnionTypeDefinition = [ - * UnionTypeDefinition: byTag['UnionTypeDefinition'], - * name: string, - * types: readonly T[], - * directives: readonly T[] - * ] - * export interface UnionTypeDefinitionNode { - * readonly kind: Kind.UNION_TYPE_DEFINITION; - * readonly loc?: Location; - * readonly description?: StringValueNode; - * readonly name: NameNode; - * readonly directives?: ReadonlyArray; - * readonly types?: ReadonlyArray; - * } - */ const UnionTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['UnionTypeDefinition']), identifier, @@ -686,51 +588,17 @@ const UnionTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.A $.NamedType! ), fc.uniqueArray( - // TODO: - // ConstDirective(tie, $), - Directive(tie, $), + ConstDirective(tie, $), $.Directive!, ) ) -/** - * @example - * type Variable = [ - * Variable: byTag['Variable'], - * name: string, - * description: Description, - * directives: readonly T[], - * ] - * export interface VariableNode { - * readonly kind: Kind.VARIABLE; - * readonly loc?: Location; - * readonly name: NameNode; - * } - */ const Variable = (_tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['Variable']), identifier, description($), ) -/** - * @example - * type EnumTypeDefinition = [ - * EnumTypeDefinition: byTag['EnumTypeDefinition'], - * name: string, - * description: Description, - * values: readonly string[], - * directives: readonly T[], - * ] - * export interface EnumTypeDefinitionNode { - * readonly kind: Kind.ENUM_TYPE_DEFINITION; - * readonly loc?: Location; - * readonly description?: StringValueNode; - * readonly name: NameNode; - * readonly directives?: ReadonlyArray; - * readonly values?: ReadonlyArray; - * } - */ const EnumTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['EnumTypeDefinition']), identifier, @@ -740,33 +608,11 @@ const EnumTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Ar $.EnumTypeDefinition! ), fc.uniqueArray( - // TODO: - // ConstDirective(tie, $), - Directive(tie, $), + ConstDirective(tie, $), $.Directive! ), ) -/** - * @example - * type Field = [ - * Field: byTag['Field'], - * name: string, - * alias: string, - * selectionSet: T, - * arguments: readonly T[], - * directives: readonly T[], - * ] - * export interface FieldNode { - * readonly kind: Kind.FIELD; - * readonly loc?: Location; - * readonly alias?: NameNode; - * readonly name: NameNode; - * readonly arguments?: ReadonlyArray; - * readonly directives?: ReadonlyArray; - * readonly selectionSet?: SelectionSetNode; - * } - */ const Field = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['Field']), identifier, @@ -775,78 +621,36 @@ const Field = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary = [ - * FieldDefinition: byTag['FieldDefinition'], - * name: string, - * description: Description, - * type: T, - * arguments: readonly T[], - * directives: readonly T[], - * ] - * export interface FieldDefinitionNode { - * readonly kind: Kind.FIELD_DEFINITION; - * readonly loc?: Location; - * readonly description?: StringValueNode; - * readonly name: NameNode; - * readonly arguments?: ReadonlyArray; - * readonly type: TypeNode; - * readonly directives?: ReadonlyArray; - * } - */ const FieldDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['FieldDefinition']), identifier, description($), TypeNode(tie, $), fc.uniqueArray( - tie('InputValueDefinition'), + InputValueDefinition(tie, $), $.InputValueDefinition! ), fc.uniqueArray( - // TODO: - // ConstDirective(tie, $), - Directive(tie, $), + ConstDirective(tie, $), $.Directive! ) ) -/** - * @example - * type ObjectTypeDefinition = [ - * ObjectTypeDefinition: byTag['ObjectTypeDefinition'], - * name: string, - * description: Description, - * fields: readonly T[], - * interfaces: readonly T[], - * directives: readonly T[], - * ] - * export interface ObjectTypeDefinitionNode { - * readonly kind: Kind.OBJECT_TYPE_DEFINITION; - * readonly loc?: Location; - * readonly description?: StringValueNode; - * readonly name: NameNode; - * readonly interfaces?: ReadonlyArray; - * readonly directives?: ReadonlyArray; - * readonly fields?: ReadonlyArray; - * } - */ const ObjectTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['ObjectTypeDefinition']), identifier, description($), fc.uniqueArray( - tie('FieldDefinition'), + FieldDefinition(tie, $), $.FieldDefinition! ), fc.uniqueArray( @@ -854,38 +658,17 @@ const ObjectTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc. $.NamedType! ), fc.uniqueArray( - // TODO: ConstDirective(tie, $), - Directive(tie, $), + ConstDirective(tie, $), $.Directive! ) ) -/** - * @example - * type InterfaceTypeDefinition = [ - * InterfaceTypeDefinition: byTag['InterfaceTypeDefinition'], - * name: string, - * description: Description, - * fields: readonly T[], - * interfaces: readonly T[], - * directives: readonly T[], - * ] - * export interface InterfaceTypeDefinitionNode { - * readonly kind: Kind.INTERFACE_TYPE_DEFINITION; - * readonly loc?: Location; - * readonly description?: StringValueNode; - * readonly name: NameNode; - * readonly interfaces?: ReadonlyArray; - * readonly directives?: ReadonlyArray; - * readonly fields?: ReadonlyArray; - * } - */ const InterfaceTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['InterfaceTypeDefinition']), identifier, description($), fc.uniqueArray( - tie('FieldDefinition'), + FieldDefinition(tie, $), $.FieldDefinition! ), fc.uniqueArray( @@ -893,87 +676,31 @@ const InterfaceTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): $.NamedType! ), fc.uniqueArray( - // TODO: - // ConstDirective(tie, $), - Directive(tie, $), + ConstDirective(tie, $), $.Directive! ) ) -/** - * @example - * type Argument = [ - * Argument: byTag['Argument'], - * name: string, - * value: T, - * ] - * export interface ArgumentNode { - * readonly kind: Kind.ARGUMENT; - * readonly loc?: Location; - * readonly name: NameNode; - * readonly value: ValueNode; - * } - */ const Argument = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['Argument']), identifier, ValueNode(tie, $), ) -/** - * @example - * type InputObjectTypeDefinition = [ - * InputObjectTypeDefinition: byTag['InputObjectTypeDefinition'], - * name: string, - * description: Description, - * fields: readonly T[], - * directives: readonly T[], - * ] - * export interface InputObjectTypeDefinitionNode { - * readonly kind: Kind.INPUT_OBJECT_TYPE_DEFINITION; - * readonly loc?: Location; - * readonly description?: StringValueNode; - * readonly name: NameNode; - * readonly directives?: ReadonlyArray; - * readonly fields?: ReadonlyArray; - * } - */ const InputObjectTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['InputObjectTypeDefinition']), identifier, description($), fc.uniqueArray( - tie('InputValueDefinition'), + InputValueDefinition(tie, $), $.InputValueDefinition! ), fc.uniqueArray( - // TODO: - // ConstDirective(tie, $), - Directive(tie, $), + ConstDirective(tie, $), $.Directive! ), ) -/** - * @example - * type InputValueDefinition = [ - * InputValueDefinition: byTag['InputValueDefinition'], - * name: string, - * description: Description, - * type: T, - * defaultValue: T, - * directives: readonly T[], - * ] - * export interface InputValueDefinitionNode { - * readonly kind: Kind.INPUT_VALUE_DEFINITION; - * readonly loc?: Location; - * readonly description?: StringValueNode; - * readonly name: NameNode; - * readonly type: TypeNode; - * readonly defaultValue?: ConstValueNode; - * readonly directives?: ReadonlyArray; - * } - */ const InputValueDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['InputValueDefinition']), identifier, @@ -981,88 +708,33 @@ const InputValueDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc. TypeNode(tie, $), ConstValueNode(tie, $), fc.uniqueArray( - // TODO: - // ConstDirective(tie, $), - Directive(tie, $), + ConstDirective(tie, $), $.Directive! ), ) -/** - * @example - * type VariableDefinition = [ - * kind: byTag['VariableDefinition'], - * variable: string, - * type: T, - * defaultValue: T, - * directives: readonly T[], - * ] - * export interface VariableDefinitionNode { - * readonly kind: Kind.VARIABLE_DEFINITION; - * readonly variable: VariableNode; - * readonly type: TypeNode; - * readonly defaultValue?: ConstValueNode; - * readonly directives?: ReadonlyArray; - * } - */ const VariableDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['VariableDefinition']), identifier, TypeNode(tie, $), ConstValueNode(tie, $), fc.uniqueArray( - // TODO: - // ConstDirective(tie, $), - Directive(tie, $), + ConstDirective(tie, $), $.Directive! ), ) -/** - * @example - * type Directive = [ - * Directive: byTag['Directive'], - * name: string, - * arguments: readonly T[], - * ] - * export interface DirectiveNode { - * readonly kind: Kind.DIRECTIVE; - * readonly loc?: Location; - * readonly name: NameNode; - * readonly arguments?: ReadonlyArray; - * } - */ const Directive = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => { return fc.tuple( fc.constant(byTag['Directive']), identifier, fc.uniqueArray( - tie('Argument'), + Argument(tie, $), $.Argument! ), ) } -/** - * @example - * type DirectiveDefinition = [ - * DirectiveDefinition: byTag['DirectiveDefinition'], - * name: string, - * description: Description, - * repeatable: boolean, - * locations: readonly F.Target[], - * arguments: readonly T[], - * ] - * export interface DirectiveDefinitionNode { - * readonly kind: Kind.DIRECTIVE_DEFINITION; - * readonly loc?: Location; - * readonly description?: StringValueNode; - * readonly name: NameNode; - * readonly arguments?: ReadonlyArray; - * readonly repeatable: boolean; - * readonly locations: ReadonlyArray; - * } - */ const DirectiveDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['DirectiveDefinition']), identifier, @@ -1073,91 +745,41 @@ const DirectiveDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.A { minLength: 1, maxLength: 3 }, ), fc.uniqueArray( - tie('InputValueDefinition'), + InputValueDefinition(tie, $), $.InputValueDefinition! ), ) -/** - * @example - * type FragmentDefinition = [ - * FragmentDefinition: byTag['FragmentDefinition'], - * name: string, - * typeCondition: string, - * selectionSet: T, - * directives: readonly T[], - * ] - * export interface FragmentDefinitionNode { - * readonly kind: Kind.FRAGMENT_DEFINITION; - * readonly loc?: Location; - * readonly name: NameNode; - * readonly typeCondition: NamedTypeNode; - * readonly directives?: ReadonlyArray; - * readonly selectionSet: SelectionSetNode; - * } - */ const FragmentDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['FragmentDefinition']), identifier, identifier, NonEmptySelectionSet(tie, $), fc.uniqueArray( - tie('Directive'), + Directive(tie, $), $.Directive! ), ) -/** - * @example - * - * type FragmentSpread = [ - * FragmentSpread: byTag['FragmentSpread'], - * name: string, - * directives: readonly T[], - * ] - * export interface FragmentSpreadNode { - * readonly kind: Kind.FRAGMENT_SPREAD; - * readonly loc?: Location; - * readonly name: NameNode; - * readonly directives?: ReadonlyArray; - * } - */ const FragmentSpread = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['FragmentSpread']), identifier, fc.uniqueArray( - tie('Directive'), + Directive(tie, $), $.Directive! ), ) -/** - * @example - * type InlineFragment = [ - * InlineFragment: byTag['InlineFragment'], - * typeCondition: string, - * selectionSet: string, - * directives: readonly T[], - * ] - * export interface InlineFragmentNode { - * readonly kind: Kind.INLINE_FRAGMENT; - * readonly loc?: Location; - * readonly typeCondition?: NamedTypeNode; - * readonly directives?: ReadonlyArray; - * readonly selectionSet: SelectionSetNode; - * } - */ const InlineFragment = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['InlineFragment']), identifier, NonEmptySelectionSet(tie, $), fc.uniqueArray( - tie('Directive'), + Directive(tie, $), $.Directive! ), ) -// used in OperationDefinition const NonEmptySelectionSet = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['SelectionSet']), fc.uniqueArray( @@ -1166,74 +788,27 @@ const NonEmptySelectionSet = (tie: fc.LetrecTypedTie, $: Constraints): fc. ), ) -/** - * @example - * type OperationDefinition = [ - * OperationDefinition: byTag['OperationDefinition'], - * name: string, - * operation: OperationType, - * selectionSet: T, - * variableDefinitions: readonly T[], - * directives: readonly T[], - * ] - * export interface OperationDefinitionNode { - * readonly kind: Kind.OPERATION_DEFINITION; - * readonly loc?: Location; - * readonly operation: OperationTypeNode; - * readonly name?: NameNode; - * readonly variableDefinitions?: ReadonlyArray; - * readonly directives?: ReadonlyArray; - * readonly selectionSet: SelectionSetNode; - * } - */ const OperationDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['OperationDefinition']), identifier, operationType, NonEmptySelectionSet(tie, $), fc.uniqueArray( - tie('VariableDefinition'), + VariableDefinition(tie, $), $.VariableDefinition! ), fc.uniqueArray( - tie('Directive'), + Directive(tie, $), $.Directive! ), ) -/** - * @example - * type OperationTypeDefinition = [ - * OperationTypeDefinition: byTag['OperationTypeDefinition'], - * type: string, - * operation: T, - * ] - * export interface OperationTypeDefinitionNode { - * readonly kind: Kind.OPERATION_TYPE_DEFINITION; - * readonly loc?: Location; - * readonly operation: OperationTypeNode; - * readonly type: NamedTypeNode; - * } - */ const OperationTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['OperationTypeDefinition']), identifier, operationType, - // tie('OperationT'), ) -/** - * @example - * type SelectionSet = [ - * SelectionSet: byTag['SelectionSet'], - * selections: readonly T[], - * ] - * export interface SelectionSetNode { - * kind: Kind.SELECTION_SET; - * loc?: Location; - * selections: ReadonlyArray; - * } - */ const SelectionSet = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['SelectionSet']), fc.uniqueArray( @@ -1242,22 +817,6 @@ const SelectionSet = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrar ), ) -/** - * @example - * type SchemaDefinition = [ - * SchemaDefinition: byTag['SchemaDefinition'], - * description: Description, - * operationTypes: readonly (readonly [name: string, operation: T])[], - * directives: readonly T[], - * ] - * export interface SchemaDefinitionNode { - * readonly kind: Kind.SCHEMA_DEFINITION; - * readonly loc?: Location; - * readonly description?: StringValueNode; - * readonly directives?: ReadonlyArray; - * readonly operationTypes: ReadonlyArray; - * } - */ const SchemaDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['SchemaDefinition']), identifier, @@ -1267,26 +826,11 @@ const SchemaDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbi $.SchemaDefinition!, ), fc.uniqueArray( - // TODO: - // ConstDirective(tie, $), - Directive(tie, $), + ConstDirective(tie, $), $.Directive! ) ) -/** - * @example - * type Document = [ - * Document: byTag['Document'], - * definition: readonly T[], - * ] - * export interface DocumentNode { - * readonly kind: Kind.DOCUMENT; - * readonly loc?: Location; - * readonly definitions: ReadonlyArray; - * readonly tokenCount?: number | undefined; - * } - */ const Document = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => { return fc.tuple( fc.constant(byTag['Document']), @@ -1299,42 +843,13 @@ const Document = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary = [ - * Argument: byTag['Argument'], - * name: string, - * value: T, - * ] - * export interface ConstArgumentNode { - * readonly kind: Kind.ARGUMENT; - * readonly loc?: Location; - * readonly name: NameNode; - * readonly value: ConstValueNode; - * } - */ const ConstArgument = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['Argument']), identifier, ConstValueNode(tie, $), ) -/** - * @example - * type Directive = [ - * Directive: byTag['Directive'], - * name: string, - * arguments: readonly T[], - * ] - * export interface ConstDirectiveNode { - * readonly kind: Kind.DIRECTIVE; - * readonly loc?: Location; - * readonly name: NameNode; - * readonly arguments?: ReadonlyArray; - * } - */ const ConstDirective = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['Directive']), identifier, @@ -1344,11 +859,6 @@ const ConstDirective = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitr ), ) -/** - * ## {@link TypeNode `TypeNode`} - * See also: - * - https://github.com/graphql/graphql-js/blob/16.x.x/src/language/ast.ts#L524 - */ const TypeNode = (tie: fc.LetrecTypedTie, $: Constraints) => fc.oneof( NamedType(tie, $), tie('ListType'), @@ -1366,22 +876,13 @@ const Selection = (tie: fc.LetrecTypedTie, $: Constraints) => fc.oneof( tie('InlineFragment'), ) -/** - * @example - * type ObjectValue = [ - * ObjectValue: byTag['ObjectValue'], - * value: { [x: string]: unknown }, - * ] - * export interface ObjectValueNode { - * readonly kind: Kind.OBJECT; - * readonly loc?: Location; - * readonly fields: ReadonlyArray; - * } - */ -const ObjectValue = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( +const ObjectValue = (_tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['ObjectValue']), fc.uniqueArray( - ObjectField(tie, $), + fc.tuple( + identifier, + jsonValue['*'], + ), $.ObjectValue! ), ) @@ -1391,49 +892,23 @@ const ObjectValue = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary * See also: * - https://github.com/graphql/graphql-js/blob/16.x.x/src/language/ast.ts#L411-L420 */ -const ValueNode = (_tie: fc.LetrecTypedTie, $: Constraints) => fc.oneof( - IntValue(_tie, $), - FloatValue(_tie, $), - StringValue(_tie, $), - BooleanValue(_tie, $), - NullValue(_tie, $), - EnumValue(_tie, $), - // ObjectValue(), - // ListValue(), +const ValueNode = (tie: fc.LetrecTypedTie, $: Constraints) => fc.oneof( + IntValue(tie, $), + FloatValue(tie, $), + StringValue(tie, $), + BooleanValue(tie, $), + NullValue(tie, $), + EnumValue(tie, $), + ListValue(tie, $), + ObjectValue(tie, $), ) -/** - * @example - * type ObjectField = [ - * ObjectField: byTag['ObjectField'], - * name: string, - * value: T - * ] - * export interface ObjectFieldNode { - * readonly kind: Kind.OBJECT_FIELD; - * readonly loc?: Location; - * readonly name: NameNode; - * readonly value: ValueNode; - * } - */ const ObjectField = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( fc.constant(byTag['ObjectField']), identifier, ValueNode(tie, $), ) -/** - * @example - * type ListValue = [ - * ListValue: byTag['ListValue'], - * value: readonly Json[], - * ] - * export interface ConstListValueNode { - * readonly kind: Kind.LIST; - * readonly loc?: Location; - * readonly values: ReadonlyArray; - * } - */ const ConstListValue = (tie: fc.LetrecTypedTie, $: Constraints) => fc.tuple( fc.constant(byTag['ListValue']), fc.array(ConstValueNode(tie, $)), @@ -1451,6 +926,7 @@ const ConstValueNode = (tie: fc.LetrecTypedTie, $: Constraints) => fc.oneo BooleanValue(tie, $), NullValue(tie, $), EnumValue(tie, $), + // TODO: // tie('ConstListValue'), // tie('ConstObjectValue'), ) diff --git a/packages/graphql-test/src/generator.ts b/packages/graphql-test/src/generator.ts index 3d97a107..e8bb0de6 100644 --- a/packages/graphql-test/src/generator.ts +++ b/packages/graphql-test/src/generator.ts @@ -187,13 +187,11 @@ export function Builder(base: Gen.Base>) { ) const nodes = pickAndSortNodes(Object_entries(builder))($) const star = fc.oneof(...fn.map(nodes, (k) => builder[k])) - const root = isKeyOf(builder, $.root) && builder[$.root] return Object_assign( - builder, { - ...root && { root }, - ['*']: star, - }) + builder, + { ['*']: star } + ) } } } @@ -240,13 +238,15 @@ const SchemaMap = { }), ListValue: ([, values]) => ({ kind: Kind.ListValue, - values: values as readonly AST.ValueNode[], // <- TODO - // values: fn.map(values, (value) => valueNodeFromJson(value)), + values: values.map((value) => valueNodeFromJson(value)), }), ObjectValue: ([, fields]) => ({ kind: Kind.ObjectValue, - fields: fields as readonly AST.ObjectFieldNode[], // <- TODO - // fields: objectFieldNodes(fields), + fields: fields.map(([name, value]) => ({ + kind: Kind.ObjectField, + name: nameNode(name), + value: valueNodeFromJson(value), + })) }), Argument: ([, name, value]) => ({ kind: Kind.Argument, diff --git a/packages/graphql-test/test/generator.test.ts b/packages/graphql-test/test/generator.test.ts index 555c698c..b2693249 100644 --- a/packages/graphql-test/test/generator.test.ts +++ b/packages/graphql-test/test/generator.test.ts @@ -1,7 +1,6 @@ import * as vi from 'vitest' import * as fc from 'fast-check' import prettier from '@prettier/sync' -import * as gql from 'graphql' import { SeedGenerator, seedToSchema } from '@traversable/graphql-test' import * as F from '@traversable/graphql-types' @@ -9,37 +8,22 @@ function format(src: string) { return prettier.format(src, { parser: 'graphql', printWidth: 60 }) } -vi.describe('〖⛳️〗‹‹‹ ❲@traversable/graphql-test❳', () => { - - - vi.test('〖⛳️〗› ❲SeedGenerator❳', () => { - const Builder = SeedGenerator({ - noDescriptions: true, - exclude: [ - // 'EnumValueDefinition', - // 'ObjectField', - // 'OperationTypeDefinition', - ], - }) - - try { - const [seed] = fc.sample(Builder['Document'], 1) - try { - const schema = seedToSchema(seed) - console.log('seed:\n', JSON.stringify(seed, null, 2)) - console.log('schema:\n', schema) +const Builder = SeedGenerator() - console.log('SDL:\n', format(F.toString(schema))) - } catch (e) { - console.error('\n\nFAILED: seedToSchema', JSON.stringify(seed, null, 2), '\n\n') - throw e +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/graphql-test❳', () => { + vi.test('〖⛳️〗› ❲SeedGenerator❳: generates valid seeds', () => { + fc.assert( + fc.property( + Builder.Document, + (seed) => { + const schema = seedToSchema(seed) + vi.assert.doesNotThrow(() => format(F.toString(schema))) + } + ), + { + endOnFailure: true, + // numRuns: 4_000, } - } catch (e) { - console.error('\n\nFAILED: fc.sample(Builder["Document"])\n\n') - throw e - } - - - vi.assert.isTrue(true) + ) }) }) diff --git a/packages/graphql-types/src/functor.ts b/packages/graphql-types/src/functor.ts index 5e8da8a4..c85f019f 100644 --- a/packages/graphql-types/src/functor.ts +++ b/packages/graphql-types/src/functor.ts @@ -99,12 +99,12 @@ export declare namespace Kind { type OperationTypeDefinition = typeof Kind.OperationTypeDefinition type ScalarTypeDefinition = typeof Kind.ScalarTypeDefinition type SchemaDefinition = typeof Kind.SchemaDefinition - // type SchemaExtension = typeof Kind.SchemaExtension type SelectionSet = typeof Kind.SelectionSet type StringValue = typeof Kind.StringValue type UnionTypeDefinition = typeof Kind.UnionTypeDefinition type Variable = typeof Kind.Variable type VariableDefinition = typeof Kind.VariableDefinition + // type SchemaExtension = typeof Kind.SchemaExtension } export const NamedType = { diff --git a/packages/graphql-types/src/to-string.ts b/packages/graphql-types/src/to-string.ts index f346a843..7b3ae97e 100644 --- a/packages/graphql-types/src/to-string.ts +++ b/packages/graphql-types/src/to-string.ts @@ -42,7 +42,8 @@ const fold = F.fold((x) => { default: return x satisfies never case F.isEnumValueDefinitionNode(x): throw Error('Not sure what an `EnumValueDefinitionNode` is...') case F.isObjectFieldNode(x): throw Error('Not sure what an `ObjectFieldNode` is...') - case F.isOperationTypeDefinitionNode(x): return `OperationTypeDefinition: ${x.operation}` // throw Error('Not sure what an `OperationTypeDefinitionNode` is...') + case F.isOperationTypeDefinitionNode(x): return `${x.operation}: ${x.type.name.value}` + // return `OperationTypeDefinition: ${x.operation}` // throw Error('Not sure what an `OperationTypeDefinitionNode` is...') case F.isValueNode(x): return serializeValueNode(x) case F.isSelectionSetNode(x): return `{ ${x.selections.join('\n')} }` case F.isScalarTypeDefinition(x): return `scalar ${directives(x)}${x.name.value}` @@ -88,14 +89,12 @@ const fold = F.fold((x) => { return `${description(x)}${x.name.value}: ${x.type}${defaultValue(x)}${directives(x)}` } case F.isObjectTypeDefinitionNode(x): { - // const IMPLEMENTS = x.interfaces.length ? ` implements ${x.interfaces.join(' & ')}` : '' - const IMPLEMENTS = '' - return `${description(x)}type ${x.name.value}${directives(x)}${IMPLEMENTS} { ${x.fields.join('\n')} } ` + const IMPLEMENTS = x.interfaces.length ? ` implements ${x.interfaces.join(' & ')}` : '' + return `${description(x)}type ${x.name.value}${IMPLEMENTS}${directives(x)} { ${x.fields.join('\n')} } ` } case F.isInterfaceTypeDefinitionNode(x): { - // const IMPLEMENTS = x.interfaces.length ? ` implements ${x.interfaces.join(' & ')}` : '' - const IMPLEMENTS = '' - return `${description(x)}interface ${x.name.value}${directives(x)}${IMPLEMENTS} { ${x.fields.join('\n')} } ` + const IMPLEMENTS = x.interfaces.length ? ` implements ${x.interfaces.join(' & ')}` : '' + return `${description(x)}interface ${x.name.value}${IMPLEMENTS}${directives(x)} { ${x.fields.join('\n')} } ` } case F.isOperationDefinitionNode(x): { const NAME = x.name?.value ? ` ${x.name.value} ` : '' @@ -127,5 +126,5 @@ export function toString(doc: F.AST.Fixpoint | gql.DocumentNode): string { return Object .values(fold(ast as F.AST.DocumentNode).byName) - .map((thunk) => thunk()).join('\n\n\r') + .map((thunk) => thunk()).join('\n\r') } From f5afddf80ba4d88cf46b83689a83189a19688811 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sun, 5 Oct 2025 00:10:47 -0500 Subject: [PATCH 31/32] chore(graphql-test): adds more configuration --- .../graphql-test/src/generator-options.ts | 10 +- packages/graphql-test/src/generator-seed.ts | 122 +++++++++--------- packages/graphql-types/test/to-string.test.ts | 1 + 3 files changed, 69 insertions(+), 64 deletions(-) diff --git a/packages/graphql-test/src/generator-options.ts b/packages/graphql-test/src/generator-options.ts index 7abe5e84..cc5f2d6b 100644 --- a/packages/graphql-test/src/generator-options.ts +++ b/packages/graphql-test/src/generator-options.ts @@ -38,6 +38,7 @@ export interface OptionsBase< export declare namespace Constraints { type Argument = fc.UniqueArrayConstraints type Directive = fc.UniqueArrayConstraints + type DirectiveDefinition = fc.UniqueArrayConstraints type Document = fc.UniqueArrayConstraints type EnumTypeDefinition = fc.UniqueArrayConstraints type InputValueDefinition = fc.UniqueArrayConstraints @@ -56,7 +57,7 @@ export type Constraints = { Boolean?: {} BooleanValue?: {} Directive?: Constraints.Directive - DirectiveDefinition?: {} + DirectiveDefinition?: Constraints.DirectiveDefinition Document?: Constraints.Document EnumTypeDefinition?: Constraints.EnumTypeDefinition EnumValue?: {} @@ -112,7 +113,10 @@ export const defaultConstraints = { selector: ([, name]) => name, size: 'xsmall', }, - DirectiveDefinition: {}, + DirectiveDefinition: { + minLength: 1, + maxLength: 3, + }, Document: { minLength: 1, maxLength: 5, @@ -174,7 +178,7 @@ export const defaultConstraints = { ObjectValue: { minLength: 1, maxLength: 3, - selector: ([k]) => k, + selector: ([key]) => key, size: 'xsmall', }, OperationDefinition: { diff --git a/packages/graphql-test/src/generator-seed.ts b/packages/graphql-test/src/generator-seed.ts index a24109af..4cca0433 100644 --- a/packages/graphql-test/src/generator-seed.ts +++ b/packages/graphql-test/src/generator-seed.ts @@ -5,7 +5,7 @@ import type { Json } from '@traversable/json' import type { OperationType } from '@traversable/graphql-types' import * as F from '@traversable/graphql-types' -import { Config } from './generator-options.js' +import type { Config } from './generator-options.js' type Constraints = Config.Options @@ -478,14 +478,7 @@ export type Seed = ( ) const NamedType = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['NamedType']), - identifier, -) - -const jsonScalar = fc.oneof( - fc.constant(null), - fc.integer(), - fc.double({ noNaN: true, noDefaultInfinity: true }), + fc.constant(byTag.NamedType), identifier, ) @@ -522,45 +515,51 @@ const jsonValue = fc.letrec((tie: fc.LetrecTypedTie) => { } }) -const Boolean = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag['Boolean']]) -const Float = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag['Float']]) -const Int = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag['Int']]) -const ID = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag['ID']]) -const Null = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag['Null']]) -const Number = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag['Number']]) -const String = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag['String']]) - -const NullValue = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag['NullValue']]) -const BooleanValue = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.tuple(fc.constant(byTag['BooleanValue']), fc.boolean()) +const Boolean = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag.Boolean]) +const Float = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag.Float]) +const Int = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag.Int]) +const ID = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag.ID]) +const Null = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag.Null]) +const Number = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag.Number]) +const String = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag.String]) + +const NullValue = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.constant([byTag.NullValue]) +const BooleanValue = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag.BooleanValue), + fc.boolean(), +) +const IntValue = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.tuple( + fc.constant(byTag.IntValue), + fc.integer(), +) const FloatValue = (_tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['FloatValue']), fc.double($.FloatValue) + fc.constant(byTag.FloatValue), + fc.double($.FloatValue), ) - -const IntValue = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.tuple(fc.constant(byTag['IntValue']), fc.integer()) const StringValue = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['StringValue']), + fc.constant(byTag.StringValue), fc.string(), fc.boolean(), ) const ScalarTypeDefinition = (_tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['ScalarTypeDefinition']), + fc.constant(byTag.ScalarTypeDefinition), identifier, description($), ) const EnumValue = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['EnumValue']), + fc.constant(byTag.EnumValue), identifier, ) const EnumValueDefinition = (_tie: fc.LetrecTypedTie, _$: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['EnumValueDefinition']), + fc.constant(byTag.EnumValueDefinition), identifier, ) const ListValue = (_tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['ListValue']), + fc.constant(byTag.ListValue), fc.array( jsonValue['*'], $.ListValue @@ -568,12 +567,12 @@ const ListValue = (_tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary< ) const ListType = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['ListType']), + fc.constant(byTag.ListType), TypeNode(tie, $), ) const NonNullType = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['NonNullType']), + fc.constant(byTag.NonNullType), fc.oneof( NamedType(tie, $), ListType(tie, $), @@ -581,7 +580,7 @@ const NonNullType = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary ) const UnionTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['UnionTypeDefinition']), + fc.constant(byTag.UnionTypeDefinition), identifier, fc.uniqueArray( NamedType(tie, $), @@ -589,18 +588,18 @@ const UnionTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.A ), fc.uniqueArray( ConstDirective(tie, $), - $.Directive!, + $.Directive! ) ) const Variable = (_tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['Variable']), + fc.constant(byTag.Variable), identifier, description($), ) const EnumTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['EnumTypeDefinition']), + fc.constant(byTag.EnumTypeDefinition), identifier, description($), fc.uniqueArray( @@ -614,11 +613,12 @@ const EnumTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Ar ) const Field = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['Field']), + fc.constant(byTag.Field), identifier, alias, fc.oneof( - fc.constant(null), NonEmptySelectionSet(tie, $) + fc.constant(null), + NonEmptySelectionSet(tie, $), ), fc.uniqueArray( Argument(tie, $), @@ -631,7 +631,7 @@ const Field = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['FieldDefinition']), + fc.constant(byTag.FieldDefinition), identifier, description($), TypeNode(tie, $), @@ -646,7 +646,7 @@ const FieldDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbit ) const ObjectTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['ObjectTypeDefinition']), + fc.constant(byTag.ObjectTypeDefinition), identifier, description($), fc.uniqueArray( @@ -664,7 +664,7 @@ const ObjectTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc. ) const InterfaceTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['InterfaceTypeDefinition']), + fc.constant(byTag.InterfaceTypeDefinition), identifier, description($), fc.uniqueArray( @@ -682,13 +682,13 @@ const InterfaceTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): ) const Argument = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['Argument']), + fc.constant(byTag.Argument), identifier, ValueNode(tie, $), ) const InputObjectTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['InputObjectTypeDefinition']), + fc.constant(byTag.InputObjectTypeDefinition), identifier, description($), fc.uniqueArray( @@ -702,7 +702,7 @@ const InputObjectTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints) ) const InputValueDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['InputValueDefinition']), + fc.constant(byTag.InputValueDefinition), identifier, description($), TypeNode(tie, $), @@ -714,7 +714,7 @@ const InputValueDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc. ) const VariableDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['VariableDefinition']), + fc.constant(byTag.VariableDefinition), identifier, TypeNode(tie, $), ConstValueNode(tie, $), @@ -726,7 +726,7 @@ const VariableDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Ar const Directive = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => { return fc.tuple( - fc.constant(byTag['Directive']), + fc.constant(byTag.Directive), identifier, fc.uniqueArray( Argument(tie, $), @@ -736,13 +736,13 @@ const Directive = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['DirectiveDefinition']), + fc.constant(byTag.DirectiveDefinition), identifier, description($), fc.boolean(), fc.uniqueArray( target, - { minLength: 1, maxLength: 3 }, + $.DirectiveDefinition! ), fc.uniqueArray( InputValueDefinition(tie, $), @@ -751,7 +751,7 @@ const DirectiveDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.A ) const FragmentDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['FragmentDefinition']), + fc.constant(byTag.FragmentDefinition), identifier, identifier, NonEmptySelectionSet(tie, $), @@ -762,7 +762,7 @@ const FragmentDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Ar ) const FragmentSpread = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['FragmentSpread']), + fc.constant(byTag.FragmentSpread), identifier, fc.uniqueArray( Directive(tie, $), @@ -771,7 +771,7 @@ const FragmentSpread = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitr ) const InlineFragment = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['InlineFragment']), + fc.constant(byTag.InlineFragment), identifier, NonEmptySelectionSet(tie, $), fc.uniqueArray( @@ -781,15 +781,15 @@ const InlineFragment = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitr ) const NonEmptySelectionSet = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['SelectionSet']), + fc.constant(byTag.SelectionSet), fc.uniqueArray( Selection(tie, $), - { ...$.SelectionSet, minLength: 1 }, + { ...$.SelectionSet, minLength: 1 } ), ) const OperationDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['OperationDefinition']), + fc.constant(byTag.OperationDefinition), identifier, operationType, NonEmptySelectionSet(tie, $), @@ -804,13 +804,13 @@ const OperationDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.A ) const OperationTypeDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['OperationTypeDefinition']), + fc.constant(byTag.OperationTypeDefinition), identifier, operationType, ) const SelectionSet = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['SelectionSet']), + fc.constant(byTag.SelectionSet), fc.uniqueArray( Selection(tie, $), $.SelectionSet! @@ -818,7 +818,7 @@ const SelectionSet = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrar ) const SchemaDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['SchemaDefinition']), + fc.constant(byTag.SchemaDefinition), identifier, description($), fc.uniqueArray( @@ -833,7 +833,7 @@ const SchemaDefinition = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbi const Document = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => { return fc.tuple( - fc.constant(byTag['Document']), + fc.constant(byTag.Document), fc.uniqueArray( Definition(tie, $), $.Document! @@ -845,13 +845,13 @@ const Document = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['Argument']), + fc.constant(byTag.Argument), identifier, ConstValueNode(tie, $), ) const ConstDirective = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['Directive']), + fc.constant(byTag.Directive), identifier, fc.uniqueArray( ConstArgument(tie, $), @@ -877,7 +877,7 @@ const Selection = (tie: fc.LetrecTypedTie, $: Constraints) => fc.oneof( ) const ObjectValue = (_tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['ObjectValue']), + fc.constant(byTag.ObjectValue), fc.uniqueArray( fc.tuple( identifier, @@ -904,13 +904,13 @@ const ValueNode = (tie: fc.LetrecTypedTie, $: Constraints) => fc.oneof( ) const ObjectField = (tie: fc.LetrecTypedTie, $: Constraints): fc.Arbitrary => fc.tuple( - fc.constant(byTag['ObjectField']), + fc.constant(byTag.ObjectField), identifier, ValueNode(tie, $), ) const ConstListValue = (tie: fc.LetrecTypedTie, $: Constraints) => fc.tuple( - fc.constant(byTag['ListValue']), + fc.constant(byTag.ListValue), fc.array(ConstValueNode(tie, $)), ) @@ -1024,7 +1024,7 @@ export const Seed = { } satisfies { [K in F.Kind | F.NamedType]: (tie: fc.LetrecTypedTie, $: Constraints) => fc.Arbitrary } // const SchemaExtension = (tie: fc.LetrecTypedTie) => fc.tuple( -// fc.constant(byTag['SchemaExtension']), +// fc.constant(byTag.SchemaExtension), // fc.uniqueArray(tie('*')), // directives(tie('*')), // ) diff --git a/packages/graphql-types/test/to-string.test.ts b/packages/graphql-types/test/to-string.test.ts index a1ee2dba..f6ef044e 100644 --- a/packages/graphql-types/test/to-string.test.ts +++ b/packages/graphql-types/test/to-string.test.ts @@ -683,6 +683,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/graphql-types❳', () => { """ type Query { me: User! + """ Fetches the hero of a specified Star Wars film. """ From 2151bdb5f60441c61d1eb9b3cd796258bd08a8f1 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sun, 5 Oct 2025 00:15:35 -0500 Subject: [PATCH 32/32] chore(graphql-test): deletes unused code --- .../graphql-test/src/generator-options.ts | 396 ------------------ 1 file changed, 396 deletions(-) diff --git a/packages/graphql-test/src/generator-options.ts b/packages/graphql-test/src/generator-options.ts index cc5f2d6b..baffa051 100644 --- a/packages/graphql-test/src/generator-options.ts +++ b/packages/graphql-test/src/generator-options.ts @@ -339,399 +339,3 @@ export function parseOptions(options: Options = defaults): Config { export function Config() {} Config.defaults = defaults Config.parseOptions = parseOptions - -// export type ObjectConstraints = -// & Omit, 'minLength' | 'maxLength'> -// & { -// minKeys?: number -// maxKeys?: number -// size?: fc.SizeForArbitrary -// } - -// const objectDefaults = { -// minKeys: 1, -// maxKeys: 3, -// size: 'xsmall', -// selector: ([k]) => k, -// comparator: 'SameValueZero', -// depthIdentifier: fc.createDepthIdentifier(), -// } satisfies ObjectConstraints - -// export const defaultConstraints = { -// object: objectDefaults, -// loose_object: objectDefaults, -// strict_object: objectDefaults, -// object_with_rest: objectDefaults, -// any: {}, -// array: { -// minLength: 0, -// maxLength: 0x10 -// }, -// bigint: { -// unbounded: false, -// min: undefined, -// max: undefined, -// multipleOf: null, -// }, -// boolean: {}, -// custom: {}, -// date: {}, -// enum: {}, -// file: {}, -// blob: {}, -// intersect: {}, -// lazy: {}, -// literal: {}, -// map: {}, -// nan: {}, -// never: {}, -// null: {}, -// number: { -// unbounded: false, -// min: -0x10000, -// max: 0x10000, -// multipleOf: Number.NaN, -// noNaN: true, -// noDefaultInfinity: true, -// minExcluded: false, -// maxExcluded: false, -// noInteger: false, -// }, -// optional: {}, -// non_optional: {}, -// undefinedable: {}, -// nullish: {}, -// non_nullish: {}, -// nullable: {}, -// non_nullable: {}, -// record: { -// depthIdentifier: fc.createDepthIdentifier(), -// maxKeys: 3, -// minKeys: 1, -// noNullPrototype: false, -// size: 'xsmall', -// } satisfies fc.DictionaryConstraints, -// set: {}, -// string: { -// unbounded: false, -// minLength: 0, -// maxLength: 0x100, -// size: 'xsmall', -// unit: 'grapheme-ascii', -// }, -// symbol: {}, -// tuple: { -// minLength: 1, -// maxLength: 3, -// size: 'xsmall', -// depthIdentifier: fc.createDepthIdentifier(), -// } satisfies fc.ArrayConstraints, -// tuple_with_rest: { -// minLength: 1, -// maxLength: 3, -// size: 'xsmall', -// depthIdentifier: fc.createDepthIdentifier(), -// } satisfies fc.ArrayConstraints, -// strict_tuple: { -// minLength: 1, -// maxLength: 3, -// size: 'xsmall', -// depthIdentifier: fc.createDepthIdentifier(), -// } satisfies fc.ArrayConstraints, -// loose_tuple: { -// minLength: 1, -// maxLength: 3, -// size: 'xsmall', -// depthIdentifier: fc.createDepthIdentifier(), -// } satisfies fc.ArrayConstraints, -// undefined: {}, -// union: { -// depthIdentifier: fc.createDepthIdentifier(), -// minLength: 1, -// maxLength: 3, -// size: 'xsmall', -// } satisfies fc.ArrayConstraints, -// variant: { -// depthIdentifier: fc.createDepthIdentifier(), -// minLength: 1, -// maxLength: 3, -// size: 'xsmall', -// } satisfies fc.ArrayConstraints, -// unknown: {}, -// void: {}, -// promise: {}, -// ['*']: { -// maxDepth: 3, -// depthIdentifier: fc.createDepthIdentifier(), -// depthSize: 'xsmall', -// withCrossShrink: true, -// } satisfies fc.OneOfConstraints, -// } as const satisfies { [K in keyof Constraints]-?: Constraints[K] } - -// export const unsupportedSchemas = ['promise'] satisfies (keyof SeedMap)[] - -// export const defaults = { -// exclude: unsupportedSchemas, -// forceInvalid: false, -// include: Tags, -// root: '*', -// sortBias: byTag, -// } as const satisfies OptionsBase - -// export function parseOptions(options?: Opts): Config> -// export function parseOptions(options?: Options): Config -// export function parseOptions(options: Options = defaults as never): Config { -// const { -// exclude = defaults.exclude, -// forceInvalid = defaults.forceInvalid, -// include = defaults.include, -// root = defaults.root, -// sortBias = defaults.sortBias, -// ['*']: { -// maxDepth: starMaxDepth = defaultConstraints['*'].maxDepth, -// depthSize: starDepthSize = defaultConstraints['*'].depthSize, -// ...STAR -// } = defaultConstraints['*'], -// any = defaultConstraints.any, -// array: { -// maxLength: arrayMax = defaultConstraints.array.maxLength, -// minLength: arrayMin = defaultConstraints.array.minLength, -// ...ARRAY -// } = defaultConstraints.array, -// bigint: { -// unbounded: bigIntUnbounded, -// max: bigIntMax, -// min: bigIntMin, -// ...BIGINT -// } = defaultConstraints.bigint, -// boolean = defaultConstraints.boolean, -// custom = defaultConstraints.custom, -// date = defaultConstraints.date, -// enum: enum_ = defaultConstraints.enum, -// file = defaultConstraints.file, -// intersect = defaultConstraints.intersect, -// lazy = defaultConstraints.lazy, -// literal = defaultConstraints.literal, -// map = defaultConstraints.map, -// nan = defaultConstraints.nan, -// never = defaultConstraints.never, -// non_optional = defaultConstraints.non_optional, -// non_nullable = defaultConstraints.non_nullable, -// non_nullish = defaultConstraints.non_nullable, -// undefinedable = defaultConstraints.undefinedable, -// nullish = defaultConstraints.nullish, -// null: null_ = defaultConstraints.null, -// nullable = defaultConstraints.nullable, -// number: { -// unbounded: numberUnbounded, -// max: numberMax, -// maxExcluded: numberMaxExcluded, -// min: numberMin, -// minExcluded: numberMinExcluded, -// // ...NUMBER -// } = defaultConstraints.number, -// optional = defaultConstraints.optional, -// record: { -// maxKeys: recordMaxKeys = defaultConstraints.record.maxKeys, -// minKeys: recordMinKeys = defaultConstraints.record.minKeys, -// size: recordSize = defaultConstraints.record.size, -// ...RECORD -// } = defaultConstraints.record, -// set = defaultConstraints.set, -// string: { -// unbounded: stringUnbounded, -// minLength: stringMinLength, -// maxLength: stringMaxLength, -// size: stringSize = defaultConstraints.string.size, -// // ...STRING -// } = defaultConstraints.string, -// symbol = defaultConstraints.symbol, -// undefined: undefined_ = defaultConstraints.undefined, -// union: { -// minLength: unionMinLength = defaultConstraints.union.minLength, -// maxLength: unionMaxLength = defaultConstraints.union.maxLength, -// size: unionSize = defaultConstraints.union.size, -// ...UNION -// } = defaultConstraints.union, -// variant: { -// minLength: variantMinLength = defaultConstraints.variant.minLength, -// maxLength: variantMaxLength = defaultConstraints.variant.maxLength, -// size: variantSize = defaultConstraints.variant.size, -// ...VARIANT -// } = defaultConstraints.variant, -// unknown = defaultConstraints.unknown, -// void: void_ = defaultConstraints.void, -// promise = defaultConstraints.promise, -// object: { -// maxKeys: objectMaxKeys = defaultConstraints.object.maxKeys, -// minKeys: objectMinKeys = defaultConstraints.object.minKeys, -// size: objectSize = defaultConstraints.object.size, -// ...OBJECT -// } = defaultConstraints.object, -// blob = defaultConstraints.blob, -// strict_object: { -// maxKeys: strictObjectMaxKeys = defaultConstraints.strict_object.maxKeys, -// minKeys: strictObjectMinKeys = defaultConstraints.strict_object.minKeys, -// size: strictObjectSize = defaultConstraints.strict_object.size, -// ...STRICT_OBJECT -// } = defaultConstraints.strict_object, -// loose_object: { -// maxKeys: looseObjectMaxKeys = defaultConstraints.loose_object.maxKeys, -// minKeys: looseObjectMinKeys = defaultConstraints.loose_object.minKeys, -// size: looseObjectSize = defaultConstraints.loose_object.size, -// ...LOOSE_OBJECT -// } = defaultConstraints.loose_object, -// object_with_rest: { -// maxKeys: objectWithRestMaxKeys = defaultConstraints.object_with_rest.maxKeys, -// minKeys: objectWithRestMinKeys = defaultConstraints.object_with_rest.minKeys, -// size: objectWithRestSize = defaultConstraints.object_with_rest.size, -// ...OBJECT_WITH_REST -// } = defaultConstraints.object_with_rest, -// tuple: { -// maxLength: tupleMaxLength = defaultConstraints.tuple.maxLength, -// minLength: tupleMinLength = defaultConstraints.tuple.minLength, -// ...TUPLE -// } = defaultConstraints.tuple, -// strict_tuple: { -// maxLength: strictTupleMaxLength = defaultConstraints.strict_tuple.maxLength, -// minLength: strictTupleMinLength = defaultConstraints.strict_tuple.minLength, -// ...STRICT_TUPLE -// } = defaultConstraints.strict_tuple, -// loose_tuple: { -// maxLength: looseTupleMaxLength = defaultConstraints.loose_tuple.maxLength, -// minLength: looseTupleMinLength = defaultConstraints.loose_tuple.minLength, -// ...LOOSE_TUPLE -// } = defaultConstraints.loose_tuple, -// tuple_with_rest: { -// maxLength: tupleWithRestMaxLength = defaultConstraints.tuple_with_rest.maxLength, -// minLength: tupleWithRestMinLength = defaultConstraints.tuple_with_rest.minLength, -// ...TUPLE_WITH_REST -// } = defaultConstraints.tuple_with_rest, -// } = options -// return { -// exclude, -// forceInvalid, -// include: include.length === 0 || include[0] === '*' ? defaults.include : include, -// root, -// sortBias: { ...defaults.sortBias, ...sortBias }, -// ['*']: { -// ...STAR, -// depthSize: starDepthSize, -// maxDepth: starMaxDepth, -// }, -// object: { -// ...OBJECT, -// minLength: objectMinKeys, -// maxLength: objectMaxKeys, -// size: objectSize, -// }, -// strict_object: { -// ...STRICT_OBJECT, -// maxKeys: strictObjectMaxKeys, -// minKeys: strictObjectMinKeys, -// size: strictObjectSize, -// }, -// loose_object: { -// ...LOOSE_OBJECT, -// maxKeys: looseObjectMaxKeys, -// minKeys: looseObjectMinKeys, -// size: looseObjectSize, -// }, -// object_with_rest: { -// ...OBJECT_WITH_REST, -// maxKeys: objectWithRestMaxKeys, -// minKeys: objectWithRestMinKeys, -// size: objectWithRestSize, -// }, -// any, -// array: { -// ...ARRAY, -// min: arrayMin, -// max: arrayMax, -// }, -// bigint: { -// ...BIGINT, -// unbounded: bigIntUnbounded, -// max: bigIntMax, -// min: bigIntMin, -// }, -// boolean, -// date, -// enum: enum_, -// file, -// intersect, -// lazy, -// literal, -// map, -// nan, -// never, -// non_optional, -// null: null_, -// nullable, -// number: { -// unbounded: numberUnbounded, -// max: numberMax, -// min: numberMin, -// maxExcluded: numberMaxExcluded, -// minExcluded: numberMinExcluded, -// }, -// optional, -// record: { -// ...RECORD, -// maxKeys: recordMaxKeys, -// minKeys: recordMinKeys, -// size: recordSize, -// }, -// set, -// string: { -// // ...STRING, -// unbounded: stringUnbounded, -// minLength: stringMinLength, -// maxLength: stringMaxLength, -// size: stringSize, -// }, -// symbol, -// tuple: { -// ...TUPLE, -// minLength: tupleMinLength, -// maxLength: tupleMaxLength, -// }, -// loose_tuple: { -// ...LOOSE_TUPLE, -// minLength: looseTupleMinLength, -// maxLength: looseTupleMaxLength, -// }, -// strict_tuple: { -// ...STRICT_TUPLE, -// minLength: strictTupleMinLength, -// maxLength: strictTupleMaxLength, -// }, -// tuple_with_rest: { -// ...TUPLE_WITH_REST, -// minLength: tupleWithRestMinLength, -// maxLength: tupleWithRestMaxLength, -// }, -// blob, -// custom, -// non_nullable, -// nullish, -// non_nullish, -// undefinedable, -// variant: { -// ...VARIANT, -// minLength: variantMinLength, -// maxLength: variantMaxLength, -// size: variantSize, -// }, -// undefined: undefined_, -// union: { -// ...UNION, -// minLength: unionMinLength, -// maxLength: unionMaxLength, -// size: unionSize, -// }, -// unknown, -// void: void_, -// promise, -// } -// }