@ultimabase/rest 0.0.0-stage → 0.1.1
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 +24 -0
- package/README.md +4 -2
- package/dist/entries.d.ts +38 -0
- package/dist/entries.d.ts.map +1 -0
- package/dist/entries.js +47 -0
- package/dist/fields.d.ts +77 -0
- package/dist/fields.d.ts.map +1 -0
- package/dist/fields.js +288 -0
- package/dist/functions.d.ts +28 -0
- package/dist/functions.d.ts.map +1 -0
- package/dist/functions.js +156 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +62 -0
- package/dist/json-schema.d.ts +8 -0
- package/dist/json-schema.d.ts.map +1 -0
- package/dist/json-schema.js +44 -0
- package/dist/limits.d.ts +6 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +17 -0
- package/dist/live.d.ts +15 -0
- package/dist/live.d.ts.map +1 -0
- package/dist/live.js +168 -0
- package/dist/models.d.ts +8 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +136 -0
- package/dist/openapi-functions.d.ts +11 -0
- package/dist/openapi-functions.d.ts.map +1 -0
- package/dist/openapi-functions.js +85 -0
- package/dist/openapi-inputs.d.ts +30 -0
- package/dist/openapi-inputs.d.ts.map +1 -0
- package/dist/openapi-inputs.js +316 -0
- package/dist/openapi-models.d.ts +14 -0
- package/dist/openapi-models.d.ts.map +1 -0
- package/dist/openapi-models.js +280 -0
- package/dist/openapi.d.ts +47 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +150 -0
- package/dist/params.d.ts +11 -0
- package/dist/params.d.ts.map +1 -0
- package/dist/params.js +27 -0
- package/dist/sse.d.ts +17 -0
- package/dist/sse.d.ts.map +1 -0
- package/dist/sse.js +63 -0
- package/dist/streams.d.ts +73 -0
- package/dist/streams.d.ts.map +1 -0
- package/dist/streams.js +218 -0
- package/dist/values.d.ts +7 -0
- package/dist/values.d.ts.map +1 -0
- package/dist/values.js +123 -0
- package/package.json +47 -3
- package/src/entries.ts +82 -0
- package/src/fields.ts +391 -0
- package/src/functions.ts +205 -0
- package/src/index.ts +105 -0
- package/src/json-schema.ts +51 -0
- package/src/limits.ts +28 -0
- package/src/live.ts +223 -0
- package/src/models.ts +180 -0
- package/src/openapi-functions.ts +127 -0
- package/src/openapi-inputs.ts +399 -0
- package/src/openapi-models.ts +367 -0
- package/src/openapi.ts +204 -0
- package/src/params.ts +39 -0
- package/src/sse.ts +79 -0
- package/src/streams.ts +301 -0
- package/src/values.ts +163 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
// The inputs of the document (D-144), named and typed as GraphQL's (D-129):
|
|
2
|
+
// each field's value, the filters, `where`, `orderBy`, the unique target
|
|
3
|
+
// in REST's flat form, and the data a write takes.
|
|
4
|
+
import { jsonSchemaOf, readable } from '@ultimabase/http/internal'
|
|
5
|
+
import type { SchemaField } from 'ultimabase'
|
|
6
|
+
import { Refused } from 'ultimabase'
|
|
7
|
+
import { decimalPattern } from 'ultimabase/internal'
|
|
8
|
+
import type { Api, Entry } from './entries.ts'
|
|
9
|
+
import { entryOf } from './entries.ts'
|
|
10
|
+
import type { Components, Schema } from './openapi.ts'
|
|
11
|
+
import {
|
|
12
|
+
arrayOf,
|
|
13
|
+
describe,
|
|
14
|
+
described,
|
|
15
|
+
hoisted,
|
|
16
|
+
objectOf,
|
|
17
|
+
orNull,
|
|
18
|
+
refTo,
|
|
19
|
+
} from './openapi.ts'
|
|
20
|
+
|
|
21
|
+
type Scalar = Extract<SchemaField, { readonly kind: 'scalar' }>
|
|
22
|
+
type Relation = Exclude<SchemaField, Scalar>
|
|
23
|
+
|
|
24
|
+
/** What the document is built from: its components and REST's models. */
|
|
25
|
+
export type Doc = { readonly components: Components; readonly api: Api }
|
|
26
|
+
|
|
27
|
+
const upper = (name: string): string =>
|
|
28
|
+
name.charAt(0).toUpperCase() + name.slice(1)
|
|
29
|
+
|
|
30
|
+
/** Whether a model has fields its `_search` matches (D-148). */
|
|
31
|
+
const searched = (fields: readonly SchemaField[]): boolean =>
|
|
32
|
+
fields.some((field) => field.kind === 'scalar' && field.search === true)
|
|
33
|
+
|
|
34
|
+
const id: Schema = { type: 'string' }
|
|
35
|
+
const dateTime: Schema = { type: 'string', format: 'date-time' }
|
|
36
|
+
|
|
37
|
+
/** One value of `field`, a list's item, by GraphQL's type name. */
|
|
38
|
+
function item(
|
|
39
|
+
doc: Doc,
|
|
40
|
+
model: string,
|
|
41
|
+
field: Scalar,
|
|
42
|
+
side: 'input' | 'output',
|
|
43
|
+
): { readonly name: string; readonly schema: Schema } {
|
|
44
|
+
const named = `${model}${upper(field.name)}`
|
|
45
|
+
switch (field.type) {
|
|
46
|
+
case 'string':
|
|
47
|
+
return { name: 'String', schema: { type: 'string' } }
|
|
48
|
+
case 'number':
|
|
49
|
+
return field.integer === true
|
|
50
|
+
? { name: 'Int', schema: { type: 'integer', format: 'int32' } }
|
|
51
|
+
: { name: 'Float', schema: { type: 'number' } }
|
|
52
|
+
case 'decimal':
|
|
53
|
+
return {
|
|
54
|
+
name: 'Decimal',
|
|
55
|
+
schema: { type: 'string', pattern: decimalPattern },
|
|
56
|
+
}
|
|
57
|
+
case 'boolean':
|
|
58
|
+
return { name: 'Boolean', schema: { type: 'boolean' } }
|
|
59
|
+
case 'dateTime':
|
|
60
|
+
return { name: 'DateTime', schema: dateTime }
|
|
61
|
+
case 'date':
|
|
62
|
+
return { name: 'Date', schema: { type: 'string', format: 'date' } }
|
|
63
|
+
case 'enum': {
|
|
64
|
+
const values = field.values ?? []
|
|
65
|
+
const shared = entryOf(doc.api, model).exposed.enums[field.name]
|
|
66
|
+
if (shared !== undefined) {
|
|
67
|
+
const { name, description } = shared
|
|
68
|
+
const schema = describe({ type: 'string', enum: values }, description)
|
|
69
|
+
const owner = `${model}.${field.name}`
|
|
70
|
+
return { name, schema: doc.components.shared(name, schema, owner) }
|
|
71
|
+
}
|
|
72
|
+
const schema = doc.components.ref(named, () => ({
|
|
73
|
+
type: 'string',
|
|
74
|
+
enum: values,
|
|
75
|
+
}))
|
|
76
|
+
return { name: named, schema }
|
|
77
|
+
}
|
|
78
|
+
case 'json':
|
|
79
|
+
break
|
|
80
|
+
}
|
|
81
|
+
return { name: 'JSON', schema: jsonOf(doc, model, field, side) }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A json field's value: its validator's JSON Schema, any JSON without
|
|
86
|
+
* (D-125); a plugin field's read value, its shape's (D-152).
|
|
87
|
+
*/
|
|
88
|
+
function jsonOf(
|
|
89
|
+
doc: Doc,
|
|
90
|
+
model: string,
|
|
91
|
+
field: Scalar,
|
|
92
|
+
side: 'input' | 'output',
|
|
93
|
+
): Schema {
|
|
94
|
+
const { validators, fieldTypes } = entryOf(doc.api, model).exposed
|
|
95
|
+
const validator = validators[field.name] ?? fieldTypes[field.name]?.shape
|
|
96
|
+
if (validator === undefined) return {}
|
|
97
|
+
const owner = `${model}.${field.name}`
|
|
98
|
+
let node
|
|
99
|
+
try {
|
|
100
|
+
node = jsonSchemaOf(validator, side, owner)
|
|
101
|
+
} catch (error) {
|
|
102
|
+
// As GraphQL types it: `JSON` without an export.
|
|
103
|
+
if (error instanceof Refused) return {}
|
|
104
|
+
throw error
|
|
105
|
+
}
|
|
106
|
+
const root = `${model}${upper(field.name)}${side === 'input' ? 'Input' : ''}`
|
|
107
|
+
return hoisted(doc.components, node, side, owner, root)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** A field's value, a plugin field's as its shape says (D-122, D-152). */
|
|
111
|
+
export function valueOf(
|
|
112
|
+
doc: Doc,
|
|
113
|
+
entry: Entry,
|
|
114
|
+
field: Scalar,
|
|
115
|
+
side: 'input' | 'output',
|
|
116
|
+
): Schema {
|
|
117
|
+
if (entry.exposed.fieldTypes[field.name] !== undefined) {
|
|
118
|
+
// What it takes is the field type's own.
|
|
119
|
+
return side === 'input' ? {} : jsonOf(doc, entry.model.name, field, side)
|
|
120
|
+
}
|
|
121
|
+
const { schema } = item(doc, entry.model.name, field, side)
|
|
122
|
+
return field.list ? arrayOf(schema) : schema
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const base = ['equals', 'not', 'in', 'notIn']
|
|
126
|
+
const ordered = [...base, 'lt', 'lte', 'gt', 'gte']
|
|
127
|
+
const text = [...base, 'contains', 'startsWith', 'endsWith', 'mode']
|
|
128
|
+
const operators: { readonly [T in Scalar['type']]: readonly string[] } = {
|
|
129
|
+
string: text,
|
|
130
|
+
number: ordered,
|
|
131
|
+
decimal: ordered,
|
|
132
|
+
dateTime: ordered,
|
|
133
|
+
date: ordered,
|
|
134
|
+
boolean: base,
|
|
135
|
+
// Any JSON, a path into it or not, typed field or not (D-149).
|
|
136
|
+
json: [
|
|
137
|
+
'path',
|
|
138
|
+
...ordered,
|
|
139
|
+
'stringContains',
|
|
140
|
+
'stringStartsWith',
|
|
141
|
+
'stringEndsWith',
|
|
142
|
+
'mode',
|
|
143
|
+
'arrayContains',
|
|
144
|
+
'arrayStartsWith',
|
|
145
|
+
'arrayEndsWith',
|
|
146
|
+
],
|
|
147
|
+
enum: base,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** `StringFilter`, `PostStatusFilter`: `equals` and `not` take `null` too. */
|
|
151
|
+
function filter(
|
|
152
|
+
doc: Doc,
|
|
153
|
+
name: string,
|
|
154
|
+
value: Schema,
|
|
155
|
+
list: readonly string[],
|
|
156
|
+
): Schema {
|
|
157
|
+
return doc.components.ref(`${name}Filter`, () =>
|
|
158
|
+
objectOf(
|
|
159
|
+
Object.fromEntries(
|
|
160
|
+
list.map((operator) => {
|
|
161
|
+
if (operator === 'mode') {
|
|
162
|
+
const mode = doc.components.ref('QueryMode', () => ({
|
|
163
|
+
type: 'string',
|
|
164
|
+
enum: ['insensitive'],
|
|
165
|
+
}))
|
|
166
|
+
return [operator, mode]
|
|
167
|
+
}
|
|
168
|
+
if (operator === 'in' || operator === 'notIn') {
|
|
169
|
+
return [operator, arrayOf(value)]
|
|
170
|
+
}
|
|
171
|
+
if (operator === 'path')
|
|
172
|
+
return [operator, arrayOf({ type: 'string' })]
|
|
173
|
+
if (operator.startsWith('string')) {
|
|
174
|
+
return [operator, { type: 'string' }]
|
|
175
|
+
}
|
|
176
|
+
const nullable = operator === 'equals' || operator === 'not'
|
|
177
|
+
return [operator, nullable ? orNull(value) : value]
|
|
178
|
+
}),
|
|
179
|
+
),
|
|
180
|
+
),
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function scalarFilter(doc: Doc, model: string, field: Scalar): Schema {
|
|
185
|
+
// A typed json field's operands are any JSON at a path (D-149).
|
|
186
|
+
if (field.type === 'json' && !field.list) {
|
|
187
|
+
return filter(doc, 'JSON', {}, operators.json)
|
|
188
|
+
}
|
|
189
|
+
const { name, schema } = item(doc, model, field, 'input')
|
|
190
|
+
if (field.list) {
|
|
191
|
+
return doc.components.ref(`${name}ListFilter`, () =>
|
|
192
|
+
objectOf({
|
|
193
|
+
has: schema,
|
|
194
|
+
hasSome: arrayOf(schema),
|
|
195
|
+
hasEvery: arrayOf(schema),
|
|
196
|
+
isEmpty: { type: 'boolean' },
|
|
197
|
+
}),
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
return filter(doc, name, schema, operators[field.type])
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** One value or a list: GraphQL takes either for a list argument. */
|
|
204
|
+
const oneOrMore = (schema: Schema): Schema => ({
|
|
205
|
+
anyOf: [schema, arrayOf(schema)],
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
/** `PostWhereInput`, its relation filters included. */
|
|
209
|
+
export function whereOf(doc: Doc, model: string): Schema {
|
|
210
|
+
return doc.components.ref(`${model}WhereInput`, () => {
|
|
211
|
+
const self = refTo(`${model}WhereInput`)
|
|
212
|
+
const entry = entryOf(doc.api, model)
|
|
213
|
+
const dates = filter(doc, 'DateTime', dateTime, ordered)
|
|
214
|
+
const fields: { [name: string]: Schema } = {
|
|
215
|
+
AND: oneOrMore(self),
|
|
216
|
+
OR: oneOrMore(self),
|
|
217
|
+
NOT: oneOrMore(self),
|
|
218
|
+
id: filter(doc, 'ID', id, base),
|
|
219
|
+
createdAt: dates,
|
|
220
|
+
updatedAt: dates,
|
|
221
|
+
}
|
|
222
|
+
if (searched(readable(entry))) fields['_search'] = { type: 'string' }
|
|
223
|
+
for (const field of readable(entry)) {
|
|
224
|
+
// Only a beforeFind filters a plugin field (D-122).
|
|
225
|
+
if (entry.exposed.fieldTypes[field.name] !== undefined) continue
|
|
226
|
+
fields[field.name] =
|
|
227
|
+
field.kind === 'scalar'
|
|
228
|
+
? scalarFilter(doc, model, field)
|
|
229
|
+
: relationFilter(doc, field)
|
|
230
|
+
}
|
|
231
|
+
return objectOf(fields)
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function relationFilter(doc: Doc, field: Relation): Schema {
|
|
236
|
+
const one = field.kind === 'one'
|
|
237
|
+
const name = `${field.target}${one ? 'Relation' : 'ListRelation'}Filter`
|
|
238
|
+
return doc.components.ref(name, () => {
|
|
239
|
+
const where = whereOf(doc, field.target)
|
|
240
|
+
const keys = one ? ['is', 'isNot'] : ['some', 'every', 'none']
|
|
241
|
+
return objectOf(Object.fromEntries(keys.map((key) => [key, where])))
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** `PostOrderByInput`, an object or a list of them (D-94, D-134). */
|
|
246
|
+
export function orderByOf(doc: Doc, model: string): Schema {
|
|
247
|
+
return doc.components.ref(`${model}OrderByInput`, () => {
|
|
248
|
+
const order = doc.components.ref('SortOrder', () => ({
|
|
249
|
+
type: 'string',
|
|
250
|
+
enum: ['asc', 'desc'],
|
|
251
|
+
}))
|
|
252
|
+
const entry = entryOf(doc.api, model)
|
|
253
|
+
const fields: { [name: string]: Schema } = {
|
|
254
|
+
id: order,
|
|
255
|
+
createdAt: order,
|
|
256
|
+
updatedAt: order,
|
|
257
|
+
}
|
|
258
|
+
// The kernel refuses it in a relation's: a read ranks by its own search.
|
|
259
|
+
if (searched(readable(entry))) fields['_relevance'] = order
|
|
260
|
+
for (const field of readable(entry)) {
|
|
261
|
+
if (field.kind === 'one')
|
|
262
|
+
fields[field.name] = orderByOf(doc, field.target)
|
|
263
|
+
else if (field.kind === 'many') {
|
|
264
|
+
fields[field.name] = doc.components.ref('CountOrderByInput', () =>
|
|
265
|
+
objectOf({ _count: order }),
|
|
266
|
+
)
|
|
267
|
+
} else if (
|
|
268
|
+
field.type !== 'json' &&
|
|
269
|
+
!field.list &&
|
|
270
|
+
entry.exposed.fieldTypes[field.name] === undefined
|
|
271
|
+
) {
|
|
272
|
+
fields[field.name] = order
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return objectOf(fields)
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* `PostWhereUniqueInput`, as REST takes it: the fields of one unique set,
|
|
281
|
+
* `{ id }` or `{ author, slug }`, a relation by its target's id.
|
|
282
|
+
*/
|
|
283
|
+
export function uniqueOf(doc: Doc, model: string): Schema {
|
|
284
|
+
return doc.components.ref(`${model}WhereUniqueInput`, () => {
|
|
285
|
+
const entry = entryOf(doc.api, model)
|
|
286
|
+
const { readRules, uniques } = entry.exposed
|
|
287
|
+
// A field only Master reads is absent, as from the filters (D-44).
|
|
288
|
+
const sets = uniques.filter((unique) =>
|
|
289
|
+
unique.every((name) => readRules[name] !== 'private'),
|
|
290
|
+
)
|
|
291
|
+
const typeOf = (name: string): Schema => {
|
|
292
|
+
const field = entry.model.fields.find((each) => each.name === name)
|
|
293
|
+
return field?.kind === 'scalar' ? valueOf(doc, entry, field, 'input') : id
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
anyOf: sets.map((unique) =>
|
|
297
|
+
objectOf(
|
|
298
|
+
Object.fromEntries(unique.map((name) => [name, typeOf(name)])),
|
|
299
|
+
unique,
|
|
300
|
+
),
|
|
301
|
+
),
|
|
302
|
+
}
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** Exactly one of an object's keys, as a GraphQL `@oneOf` input. */
|
|
307
|
+
const oneKey = (schema: Schema): Schema => ({
|
|
308
|
+
...schema,
|
|
309
|
+
minProperties: 1,
|
|
310
|
+
maxProperties: 1,
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* A relation's write, as GraphQL names it (D-128, D-138): a hidden target is
|
|
315
|
+
* connected, disconnected or set, never created, updated or deleted.
|
|
316
|
+
*/
|
|
317
|
+
function relationInput(doc: Doc, field: Relation, creating: boolean): Schema {
|
|
318
|
+
const many = field.kind === 'many'
|
|
319
|
+
const shape = many ? 'Many' : field.optional ? 'One' : 'OneRequired'
|
|
320
|
+
const facing = `Without${upper(field.inverse)}`
|
|
321
|
+
const name = creating
|
|
322
|
+
? `${field.target}CreateNested${many ? 'Many' : 'One'}${facing}Input`
|
|
323
|
+
: `${field.target}Update${shape}${facing}NestedInput`
|
|
324
|
+
return doc.components.ref(name, () => {
|
|
325
|
+
const listed = (schema: Schema) => (many ? arrayOf(schema) : schema)
|
|
326
|
+
const unique = uniqueOf(doc, field.target)
|
|
327
|
+
const { visible } = entryOf(doc.api, field.target)
|
|
328
|
+
const updating = !creating && visible
|
|
329
|
+
const fields: { [key: string]: Schema } = { connect: listed(unique) }
|
|
330
|
+
if (visible) {
|
|
331
|
+
const created = dataOf(doc, field.target, true, field.inverse)
|
|
332
|
+
const orCreate = doc.components.ref(
|
|
333
|
+
`${field.target}CreateOrConnect${facing}Input`,
|
|
334
|
+
() => objectOf({ where: unique, create: created }, ['where', 'create']),
|
|
335
|
+
)
|
|
336
|
+
fields['connectOrCreate'] = listed(orCreate)
|
|
337
|
+
fields['create'] = listed(created)
|
|
338
|
+
}
|
|
339
|
+
if (updating && shape !== 'OneRequired') {
|
|
340
|
+
fields['delete'] = many ? listed(unique) : { type: 'boolean' }
|
|
341
|
+
}
|
|
342
|
+
if (!creating && shape !== 'OneRequired') {
|
|
343
|
+
fields['disconnect'] = many ? listed(unique) : { type: 'boolean' }
|
|
344
|
+
}
|
|
345
|
+
// A filtered relation takes no `set` (D-151).
|
|
346
|
+
if (!creating && many && field.filtered !== true) {
|
|
347
|
+
fields['set'] = listed(unique)
|
|
348
|
+
}
|
|
349
|
+
if (updating) {
|
|
350
|
+
const data = dataOf(doc, field.target, false, field.inverse)
|
|
351
|
+
fields['update'] = many
|
|
352
|
+
? listed(
|
|
353
|
+
doc.components.ref(
|
|
354
|
+
`${field.target}UpdateWithWhereUnique${facing}Input`,
|
|
355
|
+
() => objectOf({ where: unique, data }, ['where', 'data']),
|
|
356
|
+
),
|
|
357
|
+
)
|
|
358
|
+
: data
|
|
359
|
+
}
|
|
360
|
+
return many ? objectOf(fields) : oneKey(objectOf(fields))
|
|
361
|
+
})
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* `PostCreateInput` and `PostUpdateInput`: every field, those only Master
|
|
366
|
+
* reads included; a create needs the fields `create` needs (D-53). Through
|
|
367
|
+
* a relation, `CommentCreateWithoutPostInput`, without it (D-138).
|
|
368
|
+
*/
|
|
369
|
+
export function dataOf(
|
|
370
|
+
doc: Doc,
|
|
371
|
+
model: string,
|
|
372
|
+
creating: boolean,
|
|
373
|
+
without?: string,
|
|
374
|
+
): Schema {
|
|
375
|
+
const facing = without === undefined ? '' : `Without${upper(without)}`
|
|
376
|
+
const name = `${model}${creating ? 'Create' : 'Update'}${facing}Input`
|
|
377
|
+
return doc.components.ref(name, () => {
|
|
378
|
+
const entry = entryOf(doc.api, model)
|
|
379
|
+
const fields: { [name: string]: Schema } = {}
|
|
380
|
+
for (const field of entry.model.fields) {
|
|
381
|
+
// A view is read-only (D-151).
|
|
382
|
+
if (
|
|
383
|
+
field.name === without ||
|
|
384
|
+
(field.kind === 'many' && field.from !== undefined)
|
|
385
|
+
)
|
|
386
|
+
continue
|
|
387
|
+
if (field.kind !== 'scalar') {
|
|
388
|
+
fields[field.name] = relationInput(doc, field, creating)
|
|
389
|
+
continue
|
|
390
|
+
}
|
|
391
|
+
const value = valueOf(doc, entry, field, 'input')
|
|
392
|
+
fields[field.name] = field.optional && !field.list ? orNull(value) : value
|
|
393
|
+
}
|
|
394
|
+
const required = creating
|
|
395
|
+
? entry.exposed.required.filter((each) => each !== without)
|
|
396
|
+
: []
|
|
397
|
+
return objectOf(described(fields, entry.exposed.descriptions), required)
|
|
398
|
+
})
|
|
399
|
+
}
|