@model-ts/graphql 0.0.0-stage → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +1280 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/Assembly.d.ts +24 -0
- package/dist/Assembly.d.ts.map +1 -0
- package/dist/Assembly.js +236 -0
- package/dist/Assembly.js.map +1 -0
- package/dist/Builder.d.ts +27 -0
- package/dist/Builder.d.ts.map +1 -0
- package/dist/Builder.js +40 -0
- package/dist/Builder.js.map +1 -0
- package/dist/Connections.d.ts +41 -0
- package/dist/Connections.d.ts.map +1 -0
- package/dist/Connections.js +243 -0
- package/dist/Connections.js.map +1 -0
- package/dist/Definitions.d.ts +139 -0
- package/dist/Definitions.d.ts.map +1 -0
- package/dist/Definitions.js +117 -0
- package/dist/Definitions.js.map +1 -0
- package/dist/Errors.d.ts +39 -0
- package/dist/Errors.d.ts.map +1 -0
- package/dist/Errors.js +103 -0
- package/dist/Errors.js.map +1 -0
- package/dist/Execution.d.ts +52 -0
- package/dist/Execution.d.ts.map +1 -0
- package/dist/Execution.js +174 -0
- package/dist/Execution.js.map +1 -0
- package/dist/FieldRuntime.d.ts +5 -0
- package/dist/FieldRuntime.d.ts.map +1 -0
- package/dist/FieldRuntime.js +27 -0
- package/dist/FieldRuntime.js.map +1 -0
- package/dist/FieldTypes.d.ts +232 -0
- package/dist/FieldTypes.d.ts.map +1 -0
- package/dist/FieldTypes.js +6 -0
- package/dist/FieldTypes.js.map +1 -0
- package/dist/Fields.d.ts +8 -0
- package/dist/Fields.d.ts.map +1 -0
- package/dist/Fields.js +72 -0
- package/dist/Fields.js.map +1 -0
- package/dist/Formatting.d.ts +25 -0
- package/dist/Formatting.d.ts.map +1 -0
- package/dist/Formatting.js +29 -0
- package/dist/Formatting.js.map +1 -0
- package/dist/GraphQL.d.ts +10 -0
- package/dist/GraphQL.d.ts.map +1 -0
- package/dist/GraphQL.js +5 -0
- package/dist/GraphQL.js.map +1 -0
- package/dist/InputTypes.d.ts +27 -0
- package/dist/InputTypes.d.ts.map +1 -0
- package/dist/InputTypes.js +203 -0
- package/dist/InputTypes.js.map +1 -0
- package/dist/Registry.d.ts +70 -0
- package/dist/Registry.d.ts.map +1 -0
- package/dist/Registry.js +3 -0
- package/dist/Registry.js.map +1 -0
- package/dist/Resolver.d.ts +30 -0
- package/dist/Resolver.d.ts.map +1 -0
- package/dist/Resolver.js +76 -0
- package/dist/Resolver.js.map +1 -0
- package/dist/Results.d.ts +20 -0
- package/dist/Results.d.ts.map +1 -0
- package/dist/Results.js +79 -0
- package/dist/Results.js.map +1 -0
- package/dist/ScalarBoundary.d.ts +9 -0
- package/dist/ScalarBoundary.d.ts.map +1 -0
- package/dist/ScalarBoundary.js +31 -0
- package/dist/ScalarBoundary.js.map +1 -0
- package/dist/Scalars.d.ts +9 -0
- package/dist/Scalars.d.ts.map +1 -0
- package/dist/Scalars.js +104 -0
- package/dist/Scalars.js.map +1 -0
- package/dist/SchemaTypes.d.ts +55 -0
- package/dist/SchemaTypes.d.ts.map +1 -0
- package/dist/SchemaTypes.js +47 -0
- package/dist/SchemaTypes.js.map +1 -0
- package/dist/Subscription.d.ts +6 -0
- package/dist/Subscription.d.ts.map +1 -0
- package/dist/Subscription.js +60 -0
- package/dist/Subscription.js.map +1 -0
- package/dist/Testing.d.ts +52 -0
- package/dist/Testing.d.ts.map +1 -0
- package/dist/Testing.js +74 -0
- package/dist/Testing.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/SubscriptionRule.d.ts +8 -0
- package/dist/internal/SubscriptionRule.d.ts.map +1 -0
- package/dist/internal/SubscriptionRule.js +73 -0
- package/dist/internal/SubscriptionRule.js.map +1 -0
- package/package.json +55 -4
- package/src/Assembly.ts +352 -0
- package/src/Builder.ts +110 -0
- package/src/Connections.ts +374 -0
- package/src/Definitions.ts +465 -0
- package/src/Errors.ts +188 -0
- package/src/Execution.ts +354 -0
- package/src/FieldRuntime.ts +32 -0
- package/src/FieldTypes.ts +667 -0
- package/src/Fields.ts +117 -0
- package/src/Formatting.ts +54 -0
- package/src/GraphQL.ts +50 -0
- package/src/InputTypes.ts +266 -0
- package/src/Registry.ts +157 -0
- package/src/Resolver.ts +176 -0
- package/src/Results.ts +128 -0
- package/src/ScalarBoundary.ts +30 -0
- package/src/Scalars.ts +143 -0
- package/src/SchemaTypes.ts +90 -0
- package/src/Subscription.ts +93 -0
- package/src/Testing.ts +144 -0
- package/src/index.ts +1 -0
- package/src/internal/SubscriptionRule.ts +90 -0
package/src/Testing.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/** Test operations keep their Effect requirements so @effect/vitest can supply any layer. */
|
|
2
|
+
import { Effect, Stream } from "effect"
|
|
3
|
+
import { GraphQLError, stripIgnoredCharacters } from "graphql"
|
|
4
|
+
import {
|
|
5
|
+
type BuiltSchema,
|
|
6
|
+
type ContextArgument,
|
|
7
|
+
type Request,
|
|
8
|
+
execute as run,
|
|
9
|
+
subscribe as events,
|
|
10
|
+
} from "./Execution.ts"
|
|
11
|
+
import {
|
|
12
|
+
snapshot,
|
|
13
|
+
type FormattedExecutionResult as Result,
|
|
14
|
+
} from "./Formatting.ts"
|
|
15
|
+
export { snapshot } from "./Formatting.ts"
|
|
16
|
+
export type { FormattedExecutionResult as Result } from "./Formatting.ts"
|
|
17
|
+
|
|
18
|
+
export const execute = <R, C>(
|
|
19
|
+
built: BuiltSchema<R, C>,
|
|
20
|
+
request: Request,
|
|
21
|
+
...context: ContextArgument<NoInfer<C>>
|
|
22
|
+
): Effect.Effect<Result, never, R> =>
|
|
23
|
+
run(built, request, ...context).pipe(Effect.map(snapshot))
|
|
24
|
+
|
|
25
|
+
export const subscribe = <R, C>(
|
|
26
|
+
built: BuiltSchema<R, C>,
|
|
27
|
+
request: Request,
|
|
28
|
+
...context: ContextArgument<NoInfer<C>>
|
|
29
|
+
): Stream.Stream<Result, never, R> =>
|
|
30
|
+
events(built, request, ...context).pipe(Stream.map(snapshot))
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Tags a GraphQL document for editor highlighting and removes ignored
|
|
34
|
+
* characters, so snapshots and error locations do not depend on indentation.
|
|
35
|
+
* String literals are kept verbatim. Like an editor, it reads backslash
|
|
36
|
+
* escapes as GraphQL escapes.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* const QUERY = graphql`
|
|
40
|
+
* query List($id: ID!) { list(id: $id) { id name } }
|
|
41
|
+
* `
|
|
42
|
+
*/
|
|
43
|
+
export function graphql(
|
|
44
|
+
strings: TemplateStringsArray,
|
|
45
|
+
...values: ReadonlyArray<string | number | boolean>
|
|
46
|
+
): string {
|
|
47
|
+
const source = String.raw({ raw: strings.raw }, ...values)
|
|
48
|
+
try {
|
|
49
|
+
return stripIgnoredCharacters(source)
|
|
50
|
+
} catch (error) {
|
|
51
|
+
// Leave invalid documents to execution, which reports a located syntax error.
|
|
52
|
+
if (error instanceof GraphQLError) return source
|
|
53
|
+
throw error
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface RequestOptions {
|
|
58
|
+
readonly variables?: { readonly [name: string]: unknown }
|
|
59
|
+
readonly operationName?: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Request options are optional unless the context factory requires one. */
|
|
63
|
+
export type RequestArgument<O> = {} extends O
|
|
64
|
+
? [options?: O & RequestOptions]
|
|
65
|
+
: [options: O & RequestOptions]
|
|
66
|
+
|
|
67
|
+
export interface ClientOptions<C, O, R> {
|
|
68
|
+
/** Builds each request's context from its options, as a value or an Effect. */
|
|
69
|
+
readonly context: (options: O) => C | Effect.Effect<C, never, R>
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** A context factory is required whenever the schema has a request context. */
|
|
73
|
+
export type ClientArgument<C, O, R> = {} extends C
|
|
74
|
+
? [options?: ClientOptions<C, O, R>]
|
|
75
|
+
: [options: ClientOptions<C, O, R>]
|
|
76
|
+
|
|
77
|
+
export interface Client<O, R> {
|
|
78
|
+
readonly request: (
|
|
79
|
+
source: string,
|
|
80
|
+
...options: RequestArgument<O>
|
|
81
|
+
) => Effect.Effect<Result, never, R>
|
|
82
|
+
readonly subscribe: (
|
|
83
|
+
source: string,
|
|
84
|
+
...options: RequestArgument<O>
|
|
85
|
+
) => Stream.Stream<Result, never, R>
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// A context is never itself an Effect, so Effect.isEffect separates the two
|
|
89
|
+
// cases exactly; TypeScript cannot exclude an Effect from a generic A.
|
|
90
|
+
const lift = <A, R>(
|
|
91
|
+
value: A | Effect.Effect<A, never, R>,
|
|
92
|
+
): Effect.Effect<A, never, R> =>
|
|
93
|
+
Effect.isEffect(value)
|
|
94
|
+
? (value as Effect.Effect<A, never, R>)
|
|
95
|
+
: Effect.succeed(value)
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Binds a schema to a default request context so each test only states what
|
|
99
|
+
* differs, such as the identity.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* export const { request, subscribe } = Testing.client(schema, {
|
|
103
|
+
* context: (options: { identity?: Identity }) => ({
|
|
104
|
+
* requestId: "test",
|
|
105
|
+
* identity: options.identity ?? anonymous,
|
|
106
|
+
* }),
|
|
107
|
+
* })
|
|
108
|
+
*/
|
|
109
|
+
export const client = <R, C, O = {}, R2 = never>(
|
|
110
|
+
built: BuiltSchema<R, C>,
|
|
111
|
+
...[options]: ClientArgument<NoInfer<C>, O, R2>
|
|
112
|
+
): Client<O, R | R2> => {
|
|
113
|
+
// RequestArgument and ClientArgument make an argument optional only when
|
|
114
|
+
// `{}` satisfies its type, and both ContextArgument branches accept [C].
|
|
115
|
+
// TypeScript cannot narrow these conditionals for generic O and C.
|
|
116
|
+
const prepare = (source: string, input?: O & RequestOptions) => {
|
|
117
|
+
const request = input ?? ({} as O & RequestOptions)
|
|
118
|
+
const context = options
|
|
119
|
+
? lift(options.context(request))
|
|
120
|
+
: Effect.succeed({} as C)
|
|
121
|
+
return Effect.map(context, (context) => ({
|
|
122
|
+
request: {
|
|
123
|
+
source,
|
|
124
|
+
...(request.variables ? { variableValues: request.variables } : {}),
|
|
125
|
+
...(request.operationName === undefined
|
|
126
|
+
? {}
|
|
127
|
+
: { operationName: request.operationName }),
|
|
128
|
+
},
|
|
129
|
+
context: [context] as ContextArgument<C>,
|
|
130
|
+
}))
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
request: (source, ...[input]) =>
|
|
134
|
+
Effect.flatMap(prepare(source, input), ({ request, context }) =>
|
|
135
|
+
execute(built, request, ...context),
|
|
136
|
+
),
|
|
137
|
+
subscribe: (source, ...[input]) =>
|
|
138
|
+
Stream.unwrap(
|
|
139
|
+
Effect.map(prepare(source, input), ({ request, context }) =>
|
|
140
|
+
subscribe(built, request, ...context),
|
|
141
|
+
),
|
|
142
|
+
),
|
|
143
|
+
}
|
|
144
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./GraphQL.ts"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GraphQLError,
|
|
3
|
+
Kind,
|
|
4
|
+
isAbstractType,
|
|
5
|
+
typeFromAST,
|
|
6
|
+
type FieldNode,
|
|
7
|
+
type FragmentDefinitionNode,
|
|
8
|
+
type InlineFragmentNode,
|
|
9
|
+
type DirectiveNode,
|
|
10
|
+
type SelectionSetNode,
|
|
11
|
+
type ValidationRule,
|
|
12
|
+
} from "graphql"
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Replaces graphql-js's SingleFieldSubscriptionsRule, which evaluates root
|
|
16
|
+
* skip/include without variables and throws for a variable condition. Collect
|
|
17
|
+
* root response keys through fragments and reject root conditionals instead.
|
|
18
|
+
*/
|
|
19
|
+
export const singleRootFieldSubscriptionsRule: ValidationRule = (context) => ({
|
|
20
|
+
OperationDefinition(operation) {
|
|
21
|
+
if (operation.operation !== "subscription") return
|
|
22
|
+
const schema = context.getSchema()
|
|
23
|
+
const root = schema.getSubscriptionType()
|
|
24
|
+
if (!root) return
|
|
25
|
+
const fields = new Map<string, FieldNode[]>()
|
|
26
|
+
const visited = new Set<string>()
|
|
27
|
+
const forbidden: DirectiveNode[] = []
|
|
28
|
+
const matches = (fragment: FragmentDefinitionNode | InlineFragmentNode) => {
|
|
29
|
+
if (!fragment.typeCondition) return true
|
|
30
|
+
const type = typeFromAST(schema, fragment.typeCondition)
|
|
31
|
+
return (
|
|
32
|
+
type === root || (isAbstractType(type) && schema.isSubType(type, root))
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
const collect = (selectionSet: SelectionSetNode): void => {
|
|
36
|
+
for (const selection of selectionSet.selections) {
|
|
37
|
+
// Validation cannot evaluate variables. Like GraphQL 17, root selections are
|
|
38
|
+
// unconditional and skip/include are forbidden; nested fields are not visited.
|
|
39
|
+
for (const directive of selection.directives ?? [])
|
|
40
|
+
if (
|
|
41
|
+
directive.name.value === "skip" ||
|
|
42
|
+
directive.name.value === "include"
|
|
43
|
+
)
|
|
44
|
+
forbidden.push(directive)
|
|
45
|
+
if (selection.kind === Kind.FIELD) {
|
|
46
|
+
const key = selection.alias?.value ?? selection.name.value
|
|
47
|
+
const group = fields.get(key)
|
|
48
|
+
if (group) group.push(selection)
|
|
49
|
+
else fields.set(key, [selection])
|
|
50
|
+
} else if (selection.kind === Kind.INLINE_FRAGMENT) {
|
|
51
|
+
if (matches(selection)) collect(selection.selectionSet)
|
|
52
|
+
} else {
|
|
53
|
+
const name = selection.name.value
|
|
54
|
+
if (visited.has(name)) continue
|
|
55
|
+
visited.add(name)
|
|
56
|
+
const fragment = context.getFragment(name)
|
|
57
|
+
// Standard validation owns missing/cyclic fragments; this traversal must terminate.
|
|
58
|
+
if (fragment && matches(fragment)) collect(fragment.selectionSet)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
collect(operation.selectionSet)
|
|
63
|
+
const name = operation.name
|
|
64
|
+
? `Subscription "${operation.name.value}"`
|
|
65
|
+
: "Anonymous Subscription"
|
|
66
|
+
if (forbidden.length > 0) {
|
|
67
|
+
context.reportError(
|
|
68
|
+
new GraphQLError(
|
|
69
|
+
`${name} must not use \`@skip\` or \`@include\` directives in the top level selection.`,
|
|
70
|
+
{ nodes: forbidden },
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
if (fields.size > 1)
|
|
76
|
+
context.reportError(
|
|
77
|
+
new GraphQLError(`${name} must select only one top level field.`, {
|
|
78
|
+
nodes: [...fields.values()].slice(1).flat(),
|
|
79
|
+
}),
|
|
80
|
+
)
|
|
81
|
+
for (const nodes of fields.values())
|
|
82
|
+
if (nodes[0]!.name.value.startsWith("__"))
|
|
83
|
+
context.reportError(
|
|
84
|
+
new GraphQLError(
|
|
85
|
+
`${name} must not select an introspection top level field.`,
|
|
86
|
+
{ nodes },
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
},
|
|
90
|
+
})
|