@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.
Files changed (114) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +1280 -2
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/Assembly.d.ts +24 -0
  5. package/dist/Assembly.d.ts.map +1 -0
  6. package/dist/Assembly.js +236 -0
  7. package/dist/Assembly.js.map +1 -0
  8. package/dist/Builder.d.ts +27 -0
  9. package/dist/Builder.d.ts.map +1 -0
  10. package/dist/Builder.js +40 -0
  11. package/dist/Builder.js.map +1 -0
  12. package/dist/Connections.d.ts +41 -0
  13. package/dist/Connections.d.ts.map +1 -0
  14. package/dist/Connections.js +243 -0
  15. package/dist/Connections.js.map +1 -0
  16. package/dist/Definitions.d.ts +139 -0
  17. package/dist/Definitions.d.ts.map +1 -0
  18. package/dist/Definitions.js +117 -0
  19. package/dist/Definitions.js.map +1 -0
  20. package/dist/Errors.d.ts +39 -0
  21. package/dist/Errors.d.ts.map +1 -0
  22. package/dist/Errors.js +103 -0
  23. package/dist/Errors.js.map +1 -0
  24. package/dist/Execution.d.ts +52 -0
  25. package/dist/Execution.d.ts.map +1 -0
  26. package/dist/Execution.js +174 -0
  27. package/dist/Execution.js.map +1 -0
  28. package/dist/FieldRuntime.d.ts +5 -0
  29. package/dist/FieldRuntime.d.ts.map +1 -0
  30. package/dist/FieldRuntime.js +27 -0
  31. package/dist/FieldRuntime.js.map +1 -0
  32. package/dist/FieldTypes.d.ts +232 -0
  33. package/dist/FieldTypes.d.ts.map +1 -0
  34. package/dist/FieldTypes.js +6 -0
  35. package/dist/FieldTypes.js.map +1 -0
  36. package/dist/Fields.d.ts +8 -0
  37. package/dist/Fields.d.ts.map +1 -0
  38. package/dist/Fields.js +72 -0
  39. package/dist/Fields.js.map +1 -0
  40. package/dist/Formatting.d.ts +25 -0
  41. package/dist/Formatting.d.ts.map +1 -0
  42. package/dist/Formatting.js +29 -0
  43. package/dist/Formatting.js.map +1 -0
  44. package/dist/GraphQL.d.ts +10 -0
  45. package/dist/GraphQL.d.ts.map +1 -0
  46. package/dist/GraphQL.js +5 -0
  47. package/dist/GraphQL.js.map +1 -0
  48. package/dist/InputTypes.d.ts +27 -0
  49. package/dist/InputTypes.d.ts.map +1 -0
  50. package/dist/InputTypes.js +203 -0
  51. package/dist/InputTypes.js.map +1 -0
  52. package/dist/Registry.d.ts +70 -0
  53. package/dist/Registry.d.ts.map +1 -0
  54. package/dist/Registry.js +3 -0
  55. package/dist/Registry.js.map +1 -0
  56. package/dist/Resolver.d.ts +30 -0
  57. package/dist/Resolver.d.ts.map +1 -0
  58. package/dist/Resolver.js +76 -0
  59. package/dist/Resolver.js.map +1 -0
  60. package/dist/Results.d.ts +20 -0
  61. package/dist/Results.d.ts.map +1 -0
  62. package/dist/Results.js +79 -0
  63. package/dist/Results.js.map +1 -0
  64. package/dist/ScalarBoundary.d.ts +9 -0
  65. package/dist/ScalarBoundary.d.ts.map +1 -0
  66. package/dist/ScalarBoundary.js +31 -0
  67. package/dist/ScalarBoundary.js.map +1 -0
  68. package/dist/Scalars.d.ts +9 -0
  69. package/dist/Scalars.d.ts.map +1 -0
  70. package/dist/Scalars.js +104 -0
  71. package/dist/Scalars.js.map +1 -0
  72. package/dist/SchemaTypes.d.ts +55 -0
  73. package/dist/SchemaTypes.d.ts.map +1 -0
  74. package/dist/SchemaTypes.js +47 -0
  75. package/dist/SchemaTypes.js.map +1 -0
  76. package/dist/Subscription.d.ts +6 -0
  77. package/dist/Subscription.d.ts.map +1 -0
  78. package/dist/Subscription.js +60 -0
  79. package/dist/Subscription.js.map +1 -0
  80. package/dist/Testing.d.ts +52 -0
  81. package/dist/Testing.d.ts.map +1 -0
  82. package/dist/Testing.js +74 -0
  83. package/dist/Testing.js.map +1 -0
  84. package/dist/index.d.ts +2 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/index.js +2 -0
  87. package/dist/index.js.map +1 -0
  88. package/dist/internal/SubscriptionRule.d.ts +8 -0
  89. package/dist/internal/SubscriptionRule.d.ts.map +1 -0
  90. package/dist/internal/SubscriptionRule.js +73 -0
  91. package/dist/internal/SubscriptionRule.js.map +1 -0
  92. package/package.json +55 -4
  93. package/src/Assembly.ts +352 -0
  94. package/src/Builder.ts +110 -0
  95. package/src/Connections.ts +374 -0
  96. package/src/Definitions.ts +465 -0
  97. package/src/Errors.ts +188 -0
  98. package/src/Execution.ts +354 -0
  99. package/src/FieldRuntime.ts +32 -0
  100. package/src/FieldTypes.ts +667 -0
  101. package/src/Fields.ts +117 -0
  102. package/src/Formatting.ts +54 -0
  103. package/src/GraphQL.ts +50 -0
  104. package/src/InputTypes.ts +266 -0
  105. package/src/Registry.ts +157 -0
  106. package/src/Resolver.ts +176 -0
  107. package/src/Results.ts +128 -0
  108. package/src/ScalarBoundary.ts +30 -0
  109. package/src/Scalars.ts +143 -0
  110. package/src/SchemaTypes.ts +90 -0
  111. package/src/Subscription.ts +93 -0
  112. package/src/Testing.ts +144 -0
  113. package/src/index.ts +1 -0
  114. 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
+ })