@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/Fields.ts ADDED
@@ -0,0 +1,117 @@
1
+ import { Schema } from "effect"
2
+ import { connectionField } from "./Connections.ts"
3
+ import { type ErrorPolicy } from "./Errors.ts"
4
+ import { wrap, normalize } from "./FieldRuntime.ts"
5
+ import {
6
+ type ConnectionRef,
7
+ type RuntimeRef,
8
+ type Field,
9
+ type Fields,
10
+ } from "./FieldTypes.ts"
11
+ import {
12
+ type AnyResolve,
13
+ type AnyConfig,
14
+ config,
15
+ resolverField,
16
+ } from "./Resolver.ts"
17
+ import { id, invalid } from "./SchemaTypes.ts"
18
+ import { eventField } from "./Subscription.ts"
19
+
20
+ export function makeFields<Types, G, C>(
21
+ policy: ErrorPolicy,
22
+ maxPageSize: number,
23
+ defaultPageSize: number,
24
+ ) {
25
+ const field = (
26
+ ref: RuntimeRef,
27
+ input: AnyResolve | AnyConfig = {},
28
+ ): Field<unknown> => {
29
+ const options = config(input)
30
+ if (options.subscribe) return eventField(policy, ref, options)
31
+ const members = options.errors ?? []
32
+ if (members.some((member) => policy.internal.includes(member)))
33
+ invalid("Internal errors cannot be exposed as result union members")
34
+ const resolve = options.resolve
35
+ return resolverField(policy, options, {
36
+ type: (context, name) =>
37
+ members.length > 0 || options.result !== undefined
38
+ ? context.result(
39
+ typeof ref === "string"
40
+ ? ref
41
+ : ref.kind === "nullable" && typeof ref.of === "string"
42
+ ? ref.of
43
+ : invalid(
44
+ "Result unions require an object type or object union",
45
+ ),
46
+ members,
47
+ name,
48
+ options.result,
49
+ typeof ref !== "string" && ref.kind === "nullable",
50
+ )
51
+ : context.output(ref, name),
52
+ args: options.args ?? {},
53
+ members,
54
+ call: (source, _raw, decoded, coordinate, context) =>
55
+ resolve
56
+ ? resolve(source, decoded, context)
57
+ : Reflect.get(
58
+ Object(source),
59
+ coordinate.slice(coordinate.lastIndexOf(".") + 1),
60
+ ),
61
+ finish: (value) => normalize(ref, value),
62
+ })
63
+ }
64
+ const wrapped =
65
+ (kind: "list" | "nullable") =>
66
+ (of: RuntimeRef, input?: AnyResolve | AnyConfig) =>
67
+ input === undefined
68
+ ? Object.assign(field(wrap(kind, of)), wrap(kind, of))
69
+ : field(wrap(kind, of), input)
70
+ const scalarHelpers = (nullable: boolean) =>
71
+ Object.fromEntries(
72
+ Object.entries({
73
+ id: "ID",
74
+ string: "String",
75
+ int: "Int",
76
+ float: "Float",
77
+ boolean: "Boolean",
78
+ dateTime: "DateTime",
79
+ json: "JSON",
80
+ }).map(([method, ref]) => [
81
+ method,
82
+ (options?: AnyConfig) =>
83
+ field(nullable ? { kind: "nullable", of: ref } : ref, options),
84
+ ]),
85
+ )
86
+ const connection = (node: string, input?: AnyResolve | AnyConfig) =>
87
+ input === undefined
88
+ ? Object.assign(
89
+ connectionField(policy, maxPageSize, defaultPageSize, node),
90
+ { kind: "connection" as const, node },
91
+ )
92
+ : connectionField(policy, maxPageSize, defaultPageSize, node, input)
93
+ // Public overloads describe these functions before the graphql-js runtime boundary.
94
+ const fields = {
95
+ ...scalarHelpers(false),
96
+ // A schema can never be field options, so the argument alone selects the
97
+ // position; the public overload admits only string schemas.
98
+ id: (input?: AnyConfig | Schema.Top) =>
99
+ Schema.isSchema(input)
100
+ ? id(input as Schema.Top & Schema.Schema<string>)
101
+ : field("ID", input),
102
+ field,
103
+ connection,
104
+ list: wrapped("list"),
105
+ nullable: Object.assign(wrapped("nullable"), scalarHelpers(true)),
106
+ } as unknown as Fields<Types, G, C>
107
+ return {
108
+ fields,
109
+ rootField: (
110
+ ref: RuntimeRef | ConnectionRef<string>,
111
+ input: AnyResolve | AnyConfig,
112
+ ) =>
113
+ typeof ref !== "string" && ref.kind === "connection"
114
+ ? connection(ref.node, input)
115
+ : field(ref, input),
116
+ }
117
+ }
@@ -0,0 +1,54 @@
1
+ import { Effect, Schema } from "effect"
2
+ import { GraphQLError } from "graphql"
3
+ import { recoverCause, defaultErrorPolicy } from "./Errors.ts"
4
+ import type { ExecutionResult } from "./Execution.ts"
5
+
6
+ const Result = Schema.Struct({
7
+ data: Schema.optionalKey(Schema.NullOr(Schema.JsonObject)),
8
+ errors: Schema.optionalKey(
9
+ Schema.Array(
10
+ Schema.Struct({
11
+ message: Schema.String,
12
+ locations: Schema.optionalKey(
13
+ Schema.Array(
14
+ Schema.Struct({ line: Schema.Number, column: Schema.Number }),
15
+ ),
16
+ ),
17
+ path: Schema.optionalKey(
18
+ Schema.Array(Schema.Union([Schema.String, Schema.Number])),
19
+ ),
20
+ extensions: Schema.optionalKey(Schema.JsonObject),
21
+ }),
22
+ ),
23
+ ),
24
+ extensions: Schema.optionalKey(Schema.JsonObject),
25
+ })
26
+ export type FormattedExecutionResult = typeof Result.Type
27
+ const decode = Schema.decodeUnknownSync(Schema.fromJsonString(Result))
28
+
29
+ /** Match the JSON wire response: plain records, no Error stacks or graphql-js prototypes. */
30
+ export const snapshot = (result: ExecutionResult): FormattedExecutionResult =>
31
+ decode(JSON.stringify(result))
32
+
33
+ /** Formatting belongs to the executor; transports receive only JSON values.
34
+ * Serialization defects always become a fixed internal error, including failures
35
+ * in a custom toJSON method, so even the fallback is guaranteed to serialize.
36
+ */
37
+ export const formatResult = (
38
+ result: ExecutionResult,
39
+ ): Effect.Effect<FormattedExecutionResult> =>
40
+ Effect.sync(() => snapshot(result)).pipe(
41
+ Effect.catchCause((cause) =>
42
+ Effect.map(
43
+ recoverCause(cause, [], defaultErrorPolicy, {
44
+ coordinate: "operation",
45
+ phase: "execute",
46
+ }),
47
+ (error) => {
48
+ if (!(error instanceof GraphQLError))
49
+ throw new Error("Error policy must return a GraphQL error")
50
+ return snapshot({ errors: [error] })
51
+ },
52
+ ),
53
+ ),
54
+ )
package/src/GraphQL.ts ADDED
@@ -0,0 +1,50 @@
1
+ export { createBuilder, type Builder, type BuilderOptions } from "./Builder.ts"
2
+ export type { Register, Registration, Scalars, Values } from "./Registry.ts"
3
+ export type {
4
+ Field,
5
+ Fields,
6
+ OutputRef,
7
+ Ref,
8
+ Value,
9
+ Resolved,
10
+ Requirements,
11
+ SourceField,
12
+ DefaultField,
13
+ } from "./FieldTypes.ts"
14
+ export type {
15
+ ObjectDefinition,
16
+ InterfaceDefinition,
17
+ UnionDefinition,
18
+ ScalarDefinition,
19
+ NamedDefinition,
20
+ RootDefinition,
21
+ EnumDefinition,
22
+ EnumOptions,
23
+ InterfaceOptions,
24
+ AbstractOptions,
25
+ } from "./Definitions.ts"
26
+ export type {
27
+ Connection,
28
+ ConnectionArgs,
29
+ Edge,
30
+ PageInfo,
31
+ } from "./Connections.ts"
32
+ export type { ErrorSchema, ResultOptions } from "./Results.ts"
33
+ export {
34
+ ErrorReporter,
35
+ type ErrorReport,
36
+ type ErrorPolicyOptions,
37
+ } from "./Errors.ts"
38
+ export { ID, JSONValue, DateTime, id, input, defaulted } from "./SchemaTypes.ts"
39
+ export {
40
+ executor,
41
+ type Executor,
42
+ type Execution,
43
+ type Json,
44
+ execute,
45
+ subscribe,
46
+ type BuiltSchema,
47
+ type Request,
48
+ type ExecutionResult,
49
+ type FormattedExecutionResult,
50
+ } from "./Execution.ts"
@@ -0,0 +1,266 @@
1
+ import { Schema, SchemaAST } from "effect"
2
+ import {
3
+ GraphQLBoolean,
4
+ GraphQLFloat,
5
+ GraphQLInt,
6
+ GraphQLList,
7
+ GraphQLNonNull,
8
+ GraphQLScalarType,
9
+ GraphQLString,
10
+ type GraphQLNamedType,
11
+ type GraphQLInputType,
12
+ GraphQLInputObjectType,
13
+ GraphQLEnumType,
14
+ isInputType,
15
+ } from "graphql"
16
+ import { dateTime, jsonScalar } from "./Scalars.ts"
17
+ import {
18
+ invalid,
19
+ annotations,
20
+ metadata,
21
+ representation,
22
+ unwrap,
23
+ } from "./SchemaTypes.ts"
24
+
25
+ export function structure(ast: SchemaAST.AST): SchemaAST.Objects | undefined {
26
+ if (ast._tag === "Objects") return ast
27
+ if (ast._tag === "Declaration" && ast.typeParameters[0]?._tag === "Objects")
28
+ return ast.typeParameters[0]
29
+ return undefined
30
+ }
31
+
32
+ /** One mapper per assembled schema: names and AST identity never leak between schemas. */
33
+ export class InputTypes {
34
+ private readonly registered = new Map<SchemaAST.AST, GraphQLInputType>()
35
+ // optionalKey() and annotate() copy Union ASTs but retain their member array.
36
+ // Its identity links model enum schemas without matching unrelated literals by value.
37
+ private readonly enums = new Map<readonly SchemaAST.AST[], GraphQLEnumType>()
38
+ private readonly owners = new Map<string, SchemaAST.Objects>()
39
+ constructor(
40
+ private readonly named: Map<string, GraphQLNamedType>,
41
+ private readonly add: <T extends GraphQLNamedType>(type: T) => T,
42
+ ) {}
43
+ register(ast: SchemaAST.AST, type: GraphQLInputType) {
44
+ if (ast._tag === "Union" && type instanceof GraphQLEnumType) {
45
+ const existing = this.enums.get(ast.types)
46
+ if (existing && existing !== type)
47
+ invalid(
48
+ `Enum schema is registered as both ${existing.name} and ${type.name}; use a separate Schema.Literals instance for each enum`,
49
+ )
50
+ this.enums.set(ast.types, type)
51
+ }
52
+ this.registered.set(ast, type)
53
+ }
54
+ argument(
55
+ schema: Schema.ConstraintDecoder<unknown, never>,
56
+ coordinate: string,
57
+ ) {
58
+ const defaultValue: unknown = annotations(schema.ast).graphqlDefault
59
+ return {
60
+ type: this.type(schema.ast, coordinate),
61
+ ...metadata(schema.ast),
62
+ ...(defaultValue === undefined
63
+ ? {}
64
+ : {
65
+ // defaulted() accepts a codec; the argument map retains its decoder view.
66
+ defaultValue: Schema.encodeUnknownSync(
67
+ schema as Schema.Codec<unknown, unknown>,
68
+ )(defaultValue),
69
+ }),
70
+ }
71
+ }
72
+ type(ast: SchemaAST.AST, coordinate: string): GraphQLInputType {
73
+ const nonnull = !SchemaAST.isOptional(ast)
74
+ const wrap = (type: GraphQLInputType) =>
75
+ nonnull ? new GraphQLNonNull(unwrap(type)) : unwrap(type)
76
+ const registered = this.registered.get(ast)
77
+ if (registered) return wrap(registered)
78
+ const ref =
79
+ annotations(ast).graphqlType ??
80
+ (structure(ast) ? annotations(ast).identifier : undefined)
81
+ if (ref) {
82
+ const existing = this.named.get(ref)
83
+ if (existing) {
84
+ if (!isInputType(existing))
85
+ return invalid(`${coordinate}: ${ref} is not an input type`)
86
+ const owner = this.owners.get(ref)
87
+ if (
88
+ owner &&
89
+ owner.propertySignatures !== structure(ast)?.propertySignatures
90
+ )
91
+ return invalid(`Duplicate input name: ${ref}`)
92
+ if (structure(ast) && !(existing instanceof GraphQLInputObjectType))
93
+ return invalid(`Duplicate input name: ${ref}`)
94
+ return wrap(existing)
95
+ }
96
+ if (ref === "DateTime") return wrap(this.add(dateTime()))
97
+ if (ref === "JSON") return wrap(this.add(jsonScalar()))
98
+ const encoded = structure(ast) ?? SchemaAST.toEncoded(ast)
99
+ if (encoded._tag !== "Objects")
100
+ return invalid(`${coordinate}: unregistered scalar or enum ${ref}`)
101
+ if (encoded.indexSignatures.length)
102
+ return invalid(
103
+ `${coordinate}: input dictionaries require a JSON scalar`,
104
+ )
105
+ this.owners.set(ref, encoded)
106
+ const type = this.add(
107
+ new GraphQLInputObjectType({
108
+ name: ref,
109
+ ...metadata(ast),
110
+ fields: () =>
111
+ Object.fromEntries(
112
+ encoded.propertySignatures.map((p) => {
113
+ if (typeof p.name !== "string")
114
+ return invalid(`${coordinate}: input keys must be strings`)
115
+ return [
116
+ p.name,
117
+ this.argument(Schema.make(p.type), `${ref}.${p.name}`),
118
+ ]
119
+ }),
120
+ ),
121
+ }),
122
+ )
123
+ return wrap(type)
124
+ }
125
+ if (ast._tag === "Union") {
126
+ const enumeration = this.enums.get(ast.types)
127
+ if (enumeration) return wrap(enumeration)
128
+ const members = ast.types.filter(
129
+ (t) => t._tag !== "Null" && t._tag !== "Undefined",
130
+ )
131
+ if (members.length !== 1 || members.length === ast.types.length)
132
+ return invalid(
133
+ `Unregistered literal or union schema at ${coordinate}; register this exact model schema with GraphQL.enum(name, schema) and export the definition in the schema barrel`,
134
+ )
135
+ return unwrap(this.type(members[0]!, coordinate))
136
+ }
137
+ if (
138
+ ast._tag === "Declaration" &&
139
+ representation(ast) === "effect/schema/Date"
140
+ ) {
141
+ const existing = this.named.get("DateTime")
142
+ return wrap(
143
+ existing instanceof GraphQLScalarType ? existing : this.add(dateTime()),
144
+ )
145
+ }
146
+ if (ast.encoding) return this.type(SchemaAST.toEncoded(ast), coordinate)
147
+ switch (ast._tag) {
148
+ case "String":
149
+ return wrap(GraphQLString)
150
+ case "Number":
151
+ return wrap(
152
+ ast.checks?.some((c) => representation(c) === "effect/schema/isInt")
153
+ ? GraphQLInt
154
+ : GraphQLFloat,
155
+ )
156
+ case "Boolean":
157
+ return wrap(GraphQLBoolean)
158
+ case "Literal":
159
+ return wrap(
160
+ typeof ast.literal === "string"
161
+ ? GraphQLString
162
+ : typeof ast.literal === "boolean"
163
+ ? GraphQLBoolean
164
+ : GraphQLFloat,
165
+ )
166
+ case "Arrays":
167
+ if (ast.elements.length || ast.rest.length !== 1)
168
+ return invalid(`Unsupported tuple at ${coordinate}`)
169
+ return wrap(new GraphQLList(this.type(ast.rest[0]!, coordinate)))
170
+ default:
171
+ return invalid(
172
+ `Unsupported Schema ${ast._tag} at ${coordinate}; use Schema.Class or name input objects with GraphQL.input`,
173
+ )
174
+ }
175
+ }
176
+ }
177
+
178
+ /** Report the schema change needed to accept an explicit clear, without echoing input data. */
179
+ export function optionalNullMessage(
180
+ args: Readonly<Record<string, Schema.ConstraintDecoder<unknown, never>>>,
181
+ raw: Readonly<Record<string, unknown>>,
182
+ ): string | undefined {
183
+ const visit = (
184
+ ast: SchemaAST.AST,
185
+ value: unknown,
186
+ path: string,
187
+ ): string | undefined => {
188
+ if (value === null) {
189
+ const nullable = Schema.is(Schema.toEncoded(Schema.make(ast)))(null)
190
+ return SchemaAST.isOptional(ast) && !nullable
191
+ ? `${path}: null is not accepted by Schema.optionalKey(S); omit the field or use Schema.optionalKey(Schema.NullOr(S)) to allow clearing it`
192
+ : undefined
193
+ }
194
+ if (ast._tag === "Union") {
195
+ for (const member of ast.types) {
196
+ const message = visit(member, value, path)
197
+ if (message) return message
198
+ }
199
+ }
200
+ const object = structure(ast)
201
+ if (object && typeof value === "object" && value !== null) {
202
+ for (const field of object.propertySignatures) {
203
+ const message = visit(
204
+ field.type,
205
+ Reflect.get(value, field.name),
206
+ `${path}.${String(field.name)}`,
207
+ )
208
+ if (message) return message
209
+ }
210
+ }
211
+ if (ast._tag === "Arrays" && ast.rest[0] && Array.isArray(value)) {
212
+ for (const [index, item] of value.entries()) {
213
+ const message = visit(ast.rest[0], item, `${path}[${index}]`)
214
+ if (message) return message
215
+ }
216
+ }
217
+ return undefined
218
+ }
219
+ for (const [key, schema] of Object.entries(args)) {
220
+ const message = visit(schema.ast, raw[key], key)
221
+ if (message) return message
222
+ }
223
+ return undefined
224
+ }
225
+
226
+ /** DateTime parses to ISO strings; native Schema.Date inputs still decode to Dates. */
227
+ export function argumentDecoder(
228
+ args: Readonly<Record<string, Schema.ConstraintDecoder<unknown, never>>>,
229
+ ) {
230
+ const schema = Schema.Struct(args)
231
+ const decode = Schema.decodeUnknownSync(schema)
232
+ // Walk the wire shape: a Schema.Class itself has an encoding to its object fields.
233
+ const encoded = SchemaAST.toEncoded(schema.ast)
234
+ const prepare = (ast: SchemaAST.AST, value: unknown): unknown => {
235
+ if (value == null) return value
236
+ if (
237
+ ast._tag === "Declaration" &&
238
+ representation(ast) === "effect/schema/Date" &&
239
+ typeof value === "string"
240
+ )
241
+ return new Date(value)
242
+ if (ast._tag === "Union") {
243
+ const member = ast.types.find(
244
+ (type) => type._tag !== "Null" && type._tag !== "Undefined",
245
+ )
246
+ return member ? prepare(member, value) : value
247
+ }
248
+ const object = structure(ast)
249
+ if (object && typeof value === "object") {
250
+ const fields = new Map(
251
+ object.propertySignatures.map((field) => [field.name, field.type]),
252
+ )
253
+ return Object.fromEntries(
254
+ Object.entries(value).map(([key, item]) => {
255
+ const field = fields.get(key)
256
+ return [key, field ? prepare(field, item) : item]
257
+ }),
258
+ )
259
+ }
260
+ if (ast._tag === "Arrays" && ast.rest[0] && Array.isArray(value))
261
+ return value.map((item) => prepare(ast.rest[0]!, item))
262
+ return value
263
+ }
264
+ return (raw: Readonly<Record<string, unknown>>) =>
265
+ decode(prepare(encoded, raw))
266
+ }
@@ -0,0 +1,157 @@
1
+ import type { Schema } from "effect"
2
+ import { type PageInfo, type Edge, type Connection } from "./Connections.ts"
3
+ import { type FieldMap, type Contextual } from "./FieldTypes.ts"
4
+
5
+ export interface Scalars {
6
+ ID: string
7
+ String: string
8
+ Boolean: boolean
9
+ Int: number
10
+ Float: number
11
+ DateTime: Date | string
12
+ JSON: unknown
13
+ }
14
+
15
+ export type DefinitionKind =
16
+ | "object"
17
+ | "interface"
18
+ | "union"
19
+ | "scalar"
20
+ | "enum"
21
+
22
+ export interface Registration<K extends DefinitionKind, A> {
23
+ readonly kind: K
24
+ readonly model: A
25
+ }
26
+
27
+ export type Model = { readonly Type: unknown }
28
+
29
+ export type Tag<M extends Model> = M["Type"] extends {
30
+ readonly _tag: infer N extends string
31
+ }
32
+ ? N
33
+ : never
34
+
35
+ export type InferKind<M extends Model> =
36
+ M extends Schema.Literals<readonly string[]>
37
+ ? "enum"
38
+ : M extends Schema.Union<readonly Schema.Top[]>
39
+ ? "union"
40
+ : M["Type"] extends string | number | boolean | Date
41
+ ? "scalar"
42
+ : "object"
43
+ /** Tagged models need no name; non-object abstract types specify their kind. */
44
+
45
+ export type Register<
46
+ M extends Model,
47
+ N extends string = Tag<M>,
48
+ K extends DefinitionKind = InferKind<M>,
49
+ > = {
50
+ readonly [Name in N]: Registration<K, M["Type"]>
51
+ }
52
+
53
+ export type NamesOfKind<Types, Kind extends DefinitionKind> = Extract<
54
+ {
55
+ [N in keyof Types]: Types[N] extends Registration<Kind, unknown> ? N : never
56
+ }[keyof Types],
57
+ string
58
+ >
59
+
60
+ export type ModelAt<Types, N> = N extends keyof Types
61
+ ? Types[N] extends Registration<DefinitionKind, infer A>
62
+ ? A
63
+ : never
64
+ : never
65
+
66
+ export type Values<Types> = {
67
+ readonly [N in keyof Types]: ModelAt<Types, N>
68
+ } & {
69
+ readonly [
70
+ N in NamesOfKind<
71
+ Types,
72
+ "object" | "interface" | "union"
73
+ > as `${N}Edge` extends keyof Types ? never : `${N}Edge`
74
+ ]: Edge<ModelAt<Types, N>>
75
+ } & {
76
+ readonly [
77
+ N in NamesOfKind<
78
+ Types,
79
+ "object" | "interface" | "union"
80
+ > as `${N}Connection` extends keyof Types ? never : `${N}Connection`
81
+ ]: Connection<ModelAt<Types, N>>
82
+ } & ("PageInfo" extends keyof Types ? {} : { readonly PageInfo: PageInfo })
83
+
84
+ export type NameOf<T> = T extends {
85
+ readonly "~kind": DefinitionKind
86
+ readonly "~name": infer N extends string
87
+ }
88
+ ? N
89
+ : never
90
+
91
+ export type RegistrationIssue<
92
+ Types,
93
+ N extends string,
94
+ A,
95
+ K extends DefinitionKind,
96
+ > = N extends keyof Types
97
+ ? Types[N] extends Registration<K, infer Expected>
98
+ ? [A] extends [Expected]
99
+ ? [Expected] extends [A]
100
+ ? unknown
101
+ : `Registration model mismatch for "${N}"; register the definition's model`
102
+ : `Registration model mismatch for "${N}"; register the definition's model`
103
+ : `Registration kind mismatch for "${N}"; expected ${K}`
104
+ : `Register "${N}": add declare module "../graphql" { interface Types extends Register<typeof ${N}${K extends "object" ? "" : `, "${N}"${K extends "enum" | "scalar" ? "" : `, "${K}"`}`}> {} }`
105
+
106
+ export type RegistrationCheck<
107
+ Types,
108
+ N extends string,
109
+ A,
110
+ K extends DefinitionKind,
111
+ > =
112
+ RegistrationIssue<Types, N, A, K> extends infer Issue
113
+ ? Issue extends string
114
+ ? { readonly error: Issue }
115
+ : unknown
116
+ : never
117
+
118
+ export type TaggedModel = Schema.Top & {
119
+ readonly Type: { readonly _tag: string }
120
+ readonly fields: { readonly _tag: Schema.Constraint }
121
+ }
122
+
123
+ export type MemberIssue<Types, M extends Schema.Top> = M extends TaggedModel
124
+ ? RegistrationIssue<Types, Tag<M>, M["Type"], "object"> extends infer Issue
125
+ ? Issue extends string
126
+ ? Issue
127
+ : never
128
+ : never
129
+ : "Union members must be tagged object models; use registered objects and pass { members: [...] } for untagged models"
130
+
131
+ export type CheckedMembers<Types, Members extends readonly Schema.Top[]> =
132
+ MemberIssue<Types, Members[number]> extends infer Issue
133
+ ? [Issue] extends [never]
134
+ ? unknown
135
+ : { readonly error: Issue }
136
+ : never
137
+
138
+ export type Complete<Types, M> =
139
+ Exclude<keyof Types, NameOf<M[keyof M]>> extends infer Missing
140
+ ? [Missing] extends [never]
141
+ ? unknown
142
+ : `Missing definition: ${Missing & string}; export it through the schema barrel`
143
+ : never
144
+
145
+ /** Check each definition separately so conflicting contexts cannot widen into a union. */
146
+ export type CompatibleContexts<M, C> = {
147
+ readonly [K in keyof M]: M[K] extends {
148
+ readonly "~kind": string
149
+ readonly fields: infer F
150
+ }
151
+ ? M[K] extends Contextual<C>
152
+ ? F extends FieldMap<never, C, boolean>
153
+ ? unknown
154
+ : "Fields require a different request context; build them with the createBuilder<Types, AppContext>() builder that builds the schema"
155
+ : "Definition requires a different request context; use definitions from createBuilder<Types, AppContext>() with the schema context"
156
+ : unknown
157
+ }