@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/Execution.ts
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { formatResult, type FormattedExecutionResult } from "./Formatting.ts"
|
|
2
|
+
import { ScalarInputFailure } from "./ScalarBoundary.ts"
|
|
3
|
+
export type { FormattedExecutionResult } from "./Formatting.ts"
|
|
4
|
+
import type { Context } from "effect"
|
|
5
|
+
import { Cause, Effect, Stream } from "effect"
|
|
6
|
+
import {
|
|
7
|
+
GraphQLError,
|
|
8
|
+
type GraphQLSchema,
|
|
9
|
+
type DocumentNode,
|
|
10
|
+
execute as graphqlExecute,
|
|
11
|
+
subscribe as graphqlSubscribe,
|
|
12
|
+
parse,
|
|
13
|
+
validate,
|
|
14
|
+
specifiedRules,
|
|
15
|
+
SingleFieldSubscriptionsRule,
|
|
16
|
+
type ExecutionResult as GraphQLExecutionResult,
|
|
17
|
+
} from "graphql"
|
|
18
|
+
import {
|
|
19
|
+
type ErrorPolicy,
|
|
20
|
+
defaultErrorPolicy,
|
|
21
|
+
isBoundaryError,
|
|
22
|
+
recoverCause,
|
|
23
|
+
} from "./Errors.ts"
|
|
24
|
+
import { type RequestContext, type Contextual } from "./FieldTypes.ts"
|
|
25
|
+
import { singleRootFieldSubscriptionsRule } from "./internal/SubscriptionRule.ts"
|
|
26
|
+
|
|
27
|
+
// Replace the native rule: v15 counts selections, while v16 evaluates root
|
|
28
|
+
// directives without variables. Both execution paths must use the same semantics.
|
|
29
|
+
const validationRules = specifiedRules.map((rule) =>
|
|
30
|
+
rule === SingleFieldSubscriptionsRule
|
|
31
|
+
? singleRootFieldSubscriptionsRule
|
|
32
|
+
: rule,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
export const schemaRequirements: unique symbol = Symbol(
|
|
36
|
+
"GraphQL schema requirements",
|
|
37
|
+
)
|
|
38
|
+
export const schemaErrorPolicy: unique symbol = Symbol(
|
|
39
|
+
"GraphQL schema error policy",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
export interface BuiltSchema<out R, C = {}> extends Contextual<C> {
|
|
43
|
+
readonly schema: GraphQLSchema
|
|
44
|
+
readonly [schemaRequirements]: R | undefined
|
|
45
|
+
readonly [schemaErrorPolicy]?: ErrorPolicy
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type ExecutionResult = GraphQLExecutionResult<
|
|
49
|
+
{ readonly [key: string]: unknown },
|
|
50
|
+
{ readonly [key: string]: unknown }
|
|
51
|
+
>
|
|
52
|
+
|
|
53
|
+
export interface Request {
|
|
54
|
+
readonly source: string
|
|
55
|
+
readonly variableValues?: { readonly [name: string]: unknown }
|
|
56
|
+
readonly operationName?: string
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type Json =
|
|
60
|
+
| null
|
|
61
|
+
| boolean
|
|
62
|
+
| number
|
|
63
|
+
| string
|
|
64
|
+
| readonly Json[]
|
|
65
|
+
| { readonly [key: string]: Json }
|
|
66
|
+
|
|
67
|
+
/** Structural transport contract: no dependency on a particular HTTP/WS adapter. */
|
|
68
|
+
export interface Execution<C> {
|
|
69
|
+
readonly document: DocumentNode
|
|
70
|
+
readonly request: {
|
|
71
|
+
readonly query: string
|
|
72
|
+
readonly variables?: Record<string, Json> | null
|
|
73
|
+
readonly operationName?: string | null
|
|
74
|
+
readonly extensions?: Record<string, Json> | null
|
|
75
|
+
}
|
|
76
|
+
readonly context: C
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface Executor<C, E = never, R = never> {
|
|
80
|
+
/** Standard schema validation runs here so scalar failures use the executor error policy. */
|
|
81
|
+
readonly validation: "executor"
|
|
82
|
+
readonly schema: GraphQLSchema
|
|
83
|
+
readonly execute: (
|
|
84
|
+
input: Execution<C>,
|
|
85
|
+
) => Effect.Effect<FormattedExecutionResult, E, R>
|
|
86
|
+
readonly subscribe: (
|
|
87
|
+
input: Execution<C>,
|
|
88
|
+
) => Stream.Stream<FormattedExecutionResult, E, R>
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Required when the builder declares a context type; optional for builders without one. */
|
|
92
|
+
export type ContextArgument<C> = {} extends C ? [context?: C] : [context: C]
|
|
93
|
+
|
|
94
|
+
function bridge<R>(
|
|
95
|
+
services: Context.Context<R>,
|
|
96
|
+
context: unknown,
|
|
97
|
+
signal?: AbortSignal,
|
|
98
|
+
pending?: Set<Promise<unknown>>,
|
|
99
|
+
sources?: Set<AsyncIterator<unknown, unknown>>,
|
|
100
|
+
): RequestContext {
|
|
101
|
+
// The builder collects heterogeneous resolver requirements in BuiltSchema.R.
|
|
102
|
+
// Restore that union only here, when crossing into graphql-js's Promise API.
|
|
103
|
+
return {
|
|
104
|
+
context,
|
|
105
|
+
stream: (stream) => {
|
|
106
|
+
// GraphQL consumes one iterator per subscription. Own it before handing
|
|
107
|
+
// it off so closing the lifetime can join its actual source scope cleanup.
|
|
108
|
+
const source = Stream.toAsyncIterableWith(
|
|
109
|
+
stream as Stream.Stream<unknown, never, R>,
|
|
110
|
+
services,
|
|
111
|
+
)[Symbol.asyncIterator]()
|
|
112
|
+
sources?.add(source)
|
|
113
|
+
return { [Symbol.asyncIterator]: () => source }
|
|
114
|
+
},
|
|
115
|
+
run: (effect) => {
|
|
116
|
+
const promise = Effect.runPromiseWith(services)(
|
|
117
|
+
effect as Effect.Effect<unknown, never, R>,
|
|
118
|
+
{ signal },
|
|
119
|
+
)
|
|
120
|
+
pending?.add(promise)
|
|
121
|
+
return promise.finally(() => pending?.delete(promise))
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** graphql-js can also fail in scalar serialization or abstract resolution. */
|
|
127
|
+
const sanitize = (
|
|
128
|
+
result: ExecutionResult,
|
|
129
|
+
policy: ErrorPolicy,
|
|
130
|
+
requestStage = false,
|
|
131
|
+
): Effect.Effect<ExecutionResult> =>
|
|
132
|
+
Effect.gen(function* () {
|
|
133
|
+
if (!result.errors) return result
|
|
134
|
+
const errors = yield* Effect.forEach(result.errors, (error) =>
|
|
135
|
+
Effect.gen(function* () {
|
|
136
|
+
let original: unknown = error
|
|
137
|
+
let scalarFailure = false
|
|
138
|
+
while (true) {
|
|
139
|
+
if (original instanceof ScalarInputFailure) {
|
|
140
|
+
scalarFailure = true
|
|
141
|
+
original = original.original
|
|
142
|
+
} else if (
|
|
143
|
+
original instanceof GraphQLError &&
|
|
144
|
+
!isBoundaryError(original) &&
|
|
145
|
+
original.originalError
|
|
146
|
+
) {
|
|
147
|
+
original = original.originalError
|
|
148
|
+
} else break
|
|
149
|
+
}
|
|
150
|
+
// Only request-stage diagnostics without application callback provenance
|
|
151
|
+
// are graphql-js's own messages. Execution errors never gain trust from
|
|
152
|
+
// a missing originalError (prelocated errors are forwarded unchanged).
|
|
153
|
+
if (
|
|
154
|
+
(requestStage || !error.path) &&
|
|
155
|
+
!scalarFailure &&
|
|
156
|
+
original instanceof GraphQLError &&
|
|
157
|
+
!isBoundaryError(original)
|
|
158
|
+
)
|
|
159
|
+
return error
|
|
160
|
+
const mapped = yield* recoverCause(Cause.die(original), [], policy, {
|
|
161
|
+
coordinate: error.path?.join(".") ?? "operation",
|
|
162
|
+
phase: "execute",
|
|
163
|
+
})
|
|
164
|
+
if (!(mapped instanceof GraphQLError))
|
|
165
|
+
throw new Error("Error policy must return a GraphQL error")
|
|
166
|
+
return new GraphQLError(mapped.message, {
|
|
167
|
+
nodes: error.nodes,
|
|
168
|
+
source: error.source,
|
|
169
|
+
positions: error.positions,
|
|
170
|
+
path: error.path,
|
|
171
|
+
originalError: mapped,
|
|
172
|
+
extensions: mapped.extensions,
|
|
173
|
+
})
|
|
174
|
+
}),
|
|
175
|
+
)
|
|
176
|
+
return { ...result, errors }
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
const unexpected = (
|
|
180
|
+
cause: Cause.Cause<unknown>,
|
|
181
|
+
policy: ErrorPolicy,
|
|
182
|
+
): Effect.Effect<ExecutionResult> =>
|
|
183
|
+
Effect.map(
|
|
184
|
+
recoverCause(cause, [], policy, {
|
|
185
|
+
coordinate: "operation",
|
|
186
|
+
phase: "execute",
|
|
187
|
+
}),
|
|
188
|
+
(error) => {
|
|
189
|
+
if (!(error instanceof GraphQLError))
|
|
190
|
+
throw new Error("Error policy must return a GraphQL error")
|
|
191
|
+
return { errors: [error] }
|
|
192
|
+
},
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
function executeDocument<R, C>(
|
|
196
|
+
built: BuiltSchema<R, C>,
|
|
197
|
+
document: DocumentNode,
|
|
198
|
+
request: Request,
|
|
199
|
+
context: unknown,
|
|
200
|
+
): Effect.Effect<ExecutionResult, never, R> {
|
|
201
|
+
const policy = built[schemaErrorPolicy] ?? defaultErrorPolicy
|
|
202
|
+
return Effect.gen(function* () {
|
|
203
|
+
const errors = validate(built.schema, document, validationRules)
|
|
204
|
+
if (errors.length) return yield* sanitize({ errors }, policy, true)
|
|
205
|
+
const services = yield* Effect.context<R>()
|
|
206
|
+
const result = yield* Effect.promise((signal) =>
|
|
207
|
+
Promise.resolve(
|
|
208
|
+
graphqlExecute({
|
|
209
|
+
schema: built.schema,
|
|
210
|
+
document,
|
|
211
|
+
variableValues: request.variableValues,
|
|
212
|
+
operationName: request.operationName,
|
|
213
|
+
contextValue: bridge(services, context, signal),
|
|
214
|
+
}),
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
return yield* sanitize(result, policy)
|
|
218
|
+
}).pipe(
|
|
219
|
+
Effect.catchCause((cause) => unexpected(cause, policy)),
|
|
220
|
+
Effect.withSpan("GraphQL.execute"),
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function subscribeDocument<R, C>(
|
|
225
|
+
built: BuiltSchema<R, C>,
|
|
226
|
+
document: DocumentNode,
|
|
227
|
+
request: Request,
|
|
228
|
+
context: unknown,
|
|
229
|
+
): Stream.Stream<ExecutionResult, never, R> {
|
|
230
|
+
const policy = built[schemaErrorPolicy] ?? defaultErrorPolicy
|
|
231
|
+
return Stream.unwrap(
|
|
232
|
+
Effect.gen(function* () {
|
|
233
|
+
const errors = validate(built.schema, document, validationRules)
|
|
234
|
+
if (errors.length)
|
|
235
|
+
return Stream.fromEffect(sanitize({ errors }, policy, true))
|
|
236
|
+
const lifetime = yield* Effect.acquireRelease(
|
|
237
|
+
Effect.sync(() => ({
|
|
238
|
+
controller: new AbortController(),
|
|
239
|
+
pending: new Set<Promise<unknown>>(),
|
|
240
|
+
sources: new Set<AsyncIterator<unknown, unknown>>(),
|
|
241
|
+
})),
|
|
242
|
+
({ controller, pending, sources }) =>
|
|
243
|
+
Effect.promise(async () => {
|
|
244
|
+
controller.abort()
|
|
245
|
+
// fromAsyncIterable does not await return() during a pending pull.
|
|
246
|
+
// Closing the owned Effect iterators joins their scope finalizers,
|
|
247
|
+
// including any close already started by graphql-js or the consumer.
|
|
248
|
+
const closing = Array.from(sources, (source) => source.return?.())
|
|
249
|
+
await Promise.allSettled([...pending, ...closing])
|
|
250
|
+
}),
|
|
251
|
+
)
|
|
252
|
+
const services = yield* Effect.context<R>()
|
|
253
|
+
const result: ExecutionResult | AsyncIterable<ExecutionResult> =
|
|
254
|
+
yield* Effect.promise(() =>
|
|
255
|
+
graphqlSubscribe({
|
|
256
|
+
schema: built.schema,
|
|
257
|
+
document,
|
|
258
|
+
variableValues: request.variableValues,
|
|
259
|
+
operationName: request.operationName,
|
|
260
|
+
contextValue: bridge(
|
|
261
|
+
services,
|
|
262
|
+
context,
|
|
263
|
+
lifetime.controller.signal,
|
|
264
|
+
lifetime.pending,
|
|
265
|
+
lifetime.sources,
|
|
266
|
+
),
|
|
267
|
+
}),
|
|
268
|
+
)
|
|
269
|
+
return (
|
|
270
|
+
Symbol.asyncIterator in result
|
|
271
|
+
? Stream.fromAsyncIterable(result, (error) => error)
|
|
272
|
+
: Stream.succeed(result)
|
|
273
|
+
).pipe(Stream.mapEffect((result) => sanitize(result, policy)))
|
|
274
|
+
}),
|
|
275
|
+
).pipe(
|
|
276
|
+
Stream.catchCause((cause) => Stream.fromEffect(unexpected(cause, policy))),
|
|
277
|
+
Stream.withSpan("GraphQL.subscribe"),
|
|
278
|
+
)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function parsed(source: string): DocumentNode | ExecutionResult {
|
|
282
|
+
try {
|
|
283
|
+
return parse(source)
|
|
284
|
+
} catch (error) {
|
|
285
|
+
if (!(error instanceof GraphQLError)) throw error
|
|
286
|
+
return { errors: [error] }
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export const execute = <R, C>(
|
|
291
|
+
built: BuiltSchema<R, C>,
|
|
292
|
+
request: Request,
|
|
293
|
+
...context: ContextArgument<NoInfer<C>>
|
|
294
|
+
): Effect.Effect<ExecutionResult, never, R> =>
|
|
295
|
+
Effect.suspend(() => {
|
|
296
|
+
const document = parsed(request.source)
|
|
297
|
+
return "kind" in document
|
|
298
|
+
? executeDocument(
|
|
299
|
+
built,
|
|
300
|
+
document,
|
|
301
|
+
request,
|
|
302
|
+
context.length === 0 ? {} : context[0],
|
|
303
|
+
)
|
|
304
|
+
: Effect.succeed(document)
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
export const subscribe = <R, C>(
|
|
308
|
+
built: BuiltSchema<R, C>,
|
|
309
|
+
request: Request,
|
|
310
|
+
...context: ContextArgument<NoInfer<C>>
|
|
311
|
+
): Stream.Stream<ExecutionResult, never, R> =>
|
|
312
|
+
Stream.suspend(() => {
|
|
313
|
+
const document = parsed(request.source)
|
|
314
|
+
return "kind" in document
|
|
315
|
+
? subscribeDocument(
|
|
316
|
+
built,
|
|
317
|
+
document,
|
|
318
|
+
request,
|
|
319
|
+
context.length === 0 ? {} : context[0],
|
|
320
|
+
)
|
|
321
|
+
: Stream.succeed(document)
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
export const executor = <R, C>(
|
|
325
|
+
built: BuiltSchema<R, C>,
|
|
326
|
+
): Executor<C, never, R> => {
|
|
327
|
+
const request = (input: Execution<C>): Request => ({
|
|
328
|
+
source: input.request.query,
|
|
329
|
+
...(input.request.variables
|
|
330
|
+
? { variableValues: input.request.variables }
|
|
331
|
+
: {}),
|
|
332
|
+
...(input.request.operationName != null
|
|
333
|
+
? { operationName: input.request.operationName }
|
|
334
|
+
: {}),
|
|
335
|
+
})
|
|
336
|
+
return {
|
|
337
|
+
validation: "executor",
|
|
338
|
+
schema: built.schema,
|
|
339
|
+
execute: (input) =>
|
|
340
|
+
executeDocument(
|
|
341
|
+
built,
|
|
342
|
+
input.document,
|
|
343
|
+
request(input),
|
|
344
|
+
input.context,
|
|
345
|
+
).pipe(Effect.flatMap((result) => formatResult(result))),
|
|
346
|
+
subscribe: (input) =>
|
|
347
|
+
subscribeDocument(
|
|
348
|
+
built,
|
|
349
|
+
input.document,
|
|
350
|
+
request(input),
|
|
351
|
+
input.context,
|
|
352
|
+
).pipe(Stream.mapEffect((result) => formatResult(result))),
|
|
353
|
+
}
|
|
354
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Option } from "effect"
|
|
2
|
+
import type { RuntimeRef, OutputRef, Description } from "./FieldTypes.ts"
|
|
3
|
+
|
|
4
|
+
export function wrap<A>(
|
|
5
|
+
kind: "list" | "nullable",
|
|
6
|
+
of: RuntimeRef,
|
|
7
|
+
): OutputRef<A> {
|
|
8
|
+
// A is a phantom value tracked by the field overloads, never runtime data.
|
|
9
|
+
return { kind, of } as OutputRef<A>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function normalize(ref: RuntimeRef, value: unknown): unknown {
|
|
13
|
+
if (typeof ref === "string") return value
|
|
14
|
+
if (ref.kind === "nullable") {
|
|
15
|
+
const unwrapped = Option.isOption(value) ? Option.getOrNull(value) : value
|
|
16
|
+
return unwrapped == null ? null : normalize(ref.of, unwrapped)
|
|
17
|
+
}
|
|
18
|
+
return Array.isArray(value)
|
|
19
|
+
? value.map((item) => normalize(ref.of, item))
|
|
20
|
+
: value
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function describe(options: Description): Description {
|
|
24
|
+
return {
|
|
25
|
+
...(options.description === undefined
|
|
26
|
+
? {}
|
|
27
|
+
: { description: options.description }),
|
|
28
|
+
...(options.deprecationReason === undefined
|
|
29
|
+
? {}
|
|
30
|
+
: { deprecationReason: options.deprecationReason }),
|
|
31
|
+
}
|
|
32
|
+
}
|