@kubun/graphql 0.4.5 → 0.6.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/lib/schema.js CHANGED
@@ -1,1409 +1 @@
1
- import { AttachmentID, DocumentID, DocumentModelID, decodeID } from '@kubun/id';
2
- import { REFERENCE_PREFIX } from '@kubun/protocol';
3
- import { pascalCase } from 'change-case';
4
- import { GraphQLBoolean, GraphQLEnumType, GraphQLFloat, GraphQLID, GraphQLInputObjectType, GraphQLInt, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLSchema, GraphQLString, isEnumType, isListType, isNonNullType } from 'graphql';
5
- import { connectionArgs, connectionDefinitions, mutationWithClientMutationId, nodeDefinitions } from 'graphql-relay';
6
- import { GraphQLDateTimeISO, GraphQLDID, GraphQLDuration, GraphQLEmailAddress, GraphQLJSON, GraphQLJSONObject, GraphQLURL } from 'graphql-scalars';
7
- import { GraphQLAttachmentID, GraphQLDocID } from './identifiers.js';
8
- import { getUniqueDocValue } from './utils.js';
9
- const STRING_FORMATS = {
10
- date: GraphQLString,
11
- time: GraphQLString,
12
- 'date-time': GraphQLString,
13
- duration: GraphQLDuration,
14
- email: GraphQLEmailAddress,
15
- uri: GraphQLURL
16
- };
17
- const STRING_TITLES = {
18
- attachmentid: GraphQLAttachmentID,
19
- did: GraphQLDID,
20
- docid: GraphQLDocID
21
- };
22
- function getReferenceID(path) {
23
- return path.slice(REFERENCE_PREFIX.length);
24
- }
25
- function isList(type) {
26
- return isListType(type) || isNonNullType(type) && isListType(type.ofType);
27
- }
28
- function collectAllModelIDs(collected, record, interfaceID) {
29
- const implementedBy = record[interfaceID];
30
- if (implementedBy == null) {
31
- throw new Error(`Interface ${interfaceID} not found in record`);
32
- }
33
- for (const id of implementedBy){
34
- // Check if this id is itself an interface (exists in record with implementers)
35
- if (record[id] != null) {
36
- // The id is an interface that has implementers, resolve recursively
37
- collectAllModelIDs(collected, record, id);
38
- } else {
39
- // The id is a concrete model
40
- collected.add(id);
41
- }
42
- }
43
- }
44
- export function toDocumentModelID(id) {
45
- return id instanceof DocumentModelID ? id : DocumentModelID.fromString(id);
46
- }
47
- export function getGlobalID(id, globalID) {
48
- const docModelID = toDocumentModelID(id);
49
- return docModelID.isLocal ? docModelID.toGlobal(toDocumentModelID(globalID)) : docModelID;
50
- }
51
- export function toList(type) {
52
- return new GraphQLList(new GraphQLNonNull(type));
53
- }
54
- export function toOuputList(type) {
55
- return new GraphQLList(new GraphQLNonNull(type));
56
- }
57
- function createEnumValueFilterInput(type) {
58
- return new GraphQLInputObjectType({
59
- name: `${type.name}ValueFilter`,
60
- isOneOf: true,
61
- fields: {
62
- isNull: {
63
- type: GraphQLBoolean
64
- },
65
- equalTo: {
66
- type
67
- },
68
- notEqualTo: {
69
- type
70
- },
71
- in: {
72
- type: toList(type)
73
- },
74
- notIn: {
75
- type: toList(type)
76
- }
77
- }
78
- });
79
- }
80
- function createScalarValueFilterInput(type) {
81
- return new GraphQLInputObjectType({
82
- name: `${type.name}ValueFilter`,
83
- isOneOf: true,
84
- fields: {
85
- isNull: {
86
- type: GraphQLBoolean
87
- },
88
- equalTo: {
89
- type
90
- },
91
- notEqualTo: {
92
- type
93
- },
94
- in: {
95
- type: toList(type)
96
- },
97
- notIn: {
98
- type: toList(type)
99
- },
100
- lessThan: {
101
- type
102
- },
103
- lessThanOrEqualTo: {
104
- type
105
- },
106
- greaterThan: {
107
- type
108
- },
109
- greaterThanOrEqualTo: {
110
- type
111
- }
112
- }
113
- });
114
- }
115
- const VALUE_FILTER_INPUTS = {
116
- AccountValueFilter: new GraphQLInputObjectType({
117
- name: 'AccountValueFilter',
118
- isOneOf: true,
119
- fields: {
120
- equalTo: {
121
- type: GraphQLDID
122
- },
123
- notEqualTo: {
124
- type: GraphQLDID
125
- },
126
- in: {
127
- type: toList(GraphQLDID)
128
- },
129
- notIn: {
130
- type: toList(GraphQLDID)
131
- }
132
- }
133
- }),
134
- BooleanValueFilter: new GraphQLInputObjectType({
135
- name: 'BooleanValueFilter',
136
- isOneOf: true,
137
- fields: {
138
- isNull: {
139
- type: GraphQLBoolean
140
- },
141
- equalTo: {
142
- type: GraphQLBoolean
143
- }
144
- }
145
- }),
146
- FloatValueFilter: createScalarValueFilterInput(GraphQLFloat),
147
- IntValueFilter: createScalarValueFilterInput(GraphQLInt),
148
- StringValueFilter: createScalarValueFilterInput(GraphQLString)
149
- };
150
- const VALUE_FILTER_INPUT_TYPES = {
151
- GraphQLBoolean: 'BooleanValueFilter',
152
- GraphQLDID: 'AccountValueFilter',
153
- GraphQLFloat: 'FloatValueFilter',
154
- GraphQLInt: 'IntValueFilter',
155
- GraphQLString: 'StringValueFilter'
156
- };
157
- const PatchSetOperation = new GraphQLInputObjectType({
158
- name: 'PatchSetOperation',
159
- fields: {
160
- path: {
161
- type: new GraphQLNonNull(GraphQLString)
162
- },
163
- value: {
164
- type: GraphQLJSON
165
- }
166
- }
167
- });
168
- const PatchRemoveOperation = new GraphQLInputObjectType({
169
- name: 'PatchRemoveOperation',
170
- fields: {
171
- path: {
172
- type: new GraphQLNonNull(GraphQLString)
173
- }
174
- }
175
- });
176
- const PatchDestinationOperation = new GraphQLInputObjectType({
177
- name: 'PatchDestinationOperation',
178
- fields: {
179
- from: {
180
- type: new GraphQLNonNull(GraphQLString)
181
- },
182
- path: {
183
- type: new GraphQLNonNull(GraphQLString)
184
- }
185
- }
186
- });
187
- export const PatchOperation = new GraphQLInputObjectType({
188
- name: 'PatchOperation',
189
- isOneOf: true,
190
- fields: {
191
- add: {
192
- type: PatchSetOperation
193
- },
194
- set: {
195
- type: PatchSetOperation
196
- },
197
- remove: {
198
- type: PatchRemoveOperation
199
- },
200
- replace: {
201
- type: PatchSetOperation
202
- },
203
- copy: {
204
- type: PatchDestinationOperation
205
- },
206
- move: {
207
- type: PatchDestinationOperation
208
- }
209
- }
210
- });
211
- const OrderByDirection = new GraphQLEnumType({
212
- name: 'OrderByDirection',
213
- values: {
214
- ASC: {
215
- value: 'asc'
216
- },
217
- DESC: {
218
- value: 'desc'
219
- }
220
- }
221
- });
222
- export class SchemaBuilder {
223
- // Mapping of model or references IDs to their GraphQL object names
224
- _aliases;
225
- _aliasToModelID;
226
- _definitions;
227
- _implemented = {};
228
- _includedModelIDs;
229
- _includeInterfaceDependencies;
230
- _includeMutations;
231
- _includeRelationDependencies;
232
- _includeSubscriptions;
233
- _inputObjects = {
234
- ...VALUE_FILTER_INPUTS,
235
- PatchOperation
236
- };
237
- _mutations = {};
238
- _nodeModelIDs;
239
- _onlyModels;
240
- _record;
241
- _references = {};
242
- _relationsTo = {};
243
- _subscriptions = {};
244
- _types = {
245
- OrderByDirection
246
- };
247
- constructor(params){
248
- this._record = params.record;
249
- this._onlyModels = params.onlyModels ?? null;
250
- this._includeInterfaceDependencies = params.includeInterfaceDependencies ?? true;
251
- this._includeRelationDependencies = params.includeRelationDependencies ?? true;
252
- this._includeMutations = params.includeMutations ?? true;
253
- this._includeSubscriptions = params.includeSubscriptions ?? true;
254
- // params.aliases is { aliasName: modelID }, which is what we need for _aliasToModelID
255
- this._aliasToModelID = params.aliases ?? {};
256
- // Build reverse lookup map (modelID -> alias) for internal use
257
- this._aliases = {};
258
- for (const [alias, modelID] of Object.entries(this._aliasToModelID)){
259
- this._aliases[modelID] = alias;
260
- }
261
- // Compute which models to include
262
- this._includedModelIDs = this._resolveModelIDsToInclude();
263
- const referencedInterfaces = {};
264
- for (const [modelIDstring, model] of Object.entries(params.record)){
265
- const modelID = DocumentModelID.fromString(modelIDstring);
266
- Object.assign(this._references, model.schema.$defs ?? {});
267
- // Extract interfaces
268
- for (const iid of model.interfaces){
269
- const interfaceID = getGlobalID(iid, modelID).toString();
270
- referencedInterfaces[interfaceID] ??= new Set();
271
- referencedInterfaces[interfaceID].add(modelIDstring);
272
- }
273
- // Extract inverse relations
274
- for (const [fieldName, meta] of Object.entries(model.fieldsMeta)){
275
- if (meta.type === 'document' && meta.model != null) {
276
- const relationRefID = getReferenceID(model.schema.properties[fieldName]?.$ref);
277
- const isList = this._getReferenceSchema(relationRefID).type === 'array';
278
- const relationID = getGlobalID(meta.model, modelID).toString();
279
- this._relationsTo[relationID] ??= {};
280
- this._relationsTo[relationID][fieldName] ??= [];
281
- this._relationsTo[relationID][fieldName].push({
282
- model: modelIDstring,
283
- isList
284
- });
285
- }
286
- }
287
- }
288
- // Map interface IDs to all the models implementing them
289
- const nodeModelIDs = new Set();
290
- for (const id of Object.keys(referencedInterfaces)){
291
- const implementedBy = new Set();
292
- collectAllModelIDs(implementedBy, referencedInterfaces, id);
293
- if (implementedBy.size !== 0) {
294
- this._implemented[id] = Array.from(implementedBy);
295
- }
296
- for (const modelID of implementedBy){
297
- nodeModelIDs.add(modelID);
298
- }
299
- }
300
- this._nodeModelIDs = Array.from(nodeModelIDs);
301
- }
302
- _resolveModelIdentifier(identifier) {
303
- // Check if it's a model ID (exists in record)
304
- if (this._record[identifier] != null) {
305
- return identifier;
306
- }
307
- // Check if it's an alias
308
- const modelID = this._aliasToModelID[identifier];
309
- if (modelID != null && this._record[modelID] != null) {
310
- return modelID;
311
- }
312
- throw new Error(`Model identifier "${identifier}" not found. Must be a valid model ID or alias.`);
313
- }
314
- _resolveModelIDsToInclude() {
315
- // If no restriction, include all models
316
- if (this._onlyModels == null || this._onlyModels.length === 0) {
317
- return new Set(Object.keys(this._record));
318
- }
319
- // Resolve identifiers to model IDs
320
- const included = new Set();
321
- for (const identifier of this._onlyModels){
322
- const modelID = this._resolveModelIdentifier(identifier);
323
- included.add(modelID);
324
- }
325
- // If both dependencies are disabled, return early
326
- if (!this._includeInterfaceDependencies && !this._includeRelationDependencies) {
327
- return included;
328
- }
329
- // Recursively collect dependencies
330
- const toProcess = Array.from(included);
331
- const processed = new Set();
332
- while(toProcess.length > 0){
333
- const modelID = toProcess.pop();
334
- if (processed.has(modelID)) continue;
335
- processed.add(modelID);
336
- const model = this._record[modelID];
337
- if (model == null) continue;
338
- // Add interface dependencies if enabled
339
- if (this._includeInterfaceDependencies) {
340
- for (const iid of model.interfaces){
341
- const interfaceID = getGlobalID(iid, modelID).toString();
342
- if (!included.has(interfaceID)) {
343
- included.add(interfaceID);
344
- toProcess.push(interfaceID);
345
- }
346
- }
347
- }
348
- // Add relation dependencies if enabled
349
- if (this._includeRelationDependencies) {
350
- for (const meta of Object.values(model.fieldsMeta)){
351
- if (meta.type === 'document' && meta.model != null && meta.model !== null) {
352
- const relationID = getGlobalID(meta.model, modelID).toString();
353
- if (!included.has(relationID)) {
354
- included.add(relationID);
355
- toProcess.push(relationID);
356
- }
357
- }
358
- }
359
- }
360
- }
361
- return included;
362
- }
363
- getModelIDs(id) {
364
- return id === null ? this._nodeModelIDs : this._implemented[id] ?? [
365
- id
366
- ];
367
- }
368
- build() {
369
- const definitions = this._buildSharedDefinitions();
370
- this._definitions = definitions;
371
- const queryFields = {
372
- nodes: definitions.nodesField,
373
- ...definitions.queryFields
374
- };
375
- for (const id of this._includedModelIDs){
376
- this._buildDocument(id);
377
- const name = this._aliases[id];
378
- const filterArg = {
379
- type: this._inputObjects[`${id}-filter`]
380
- };
381
- queryFields[`all${name}`] = {
382
- type: this._types[`${id}-connection`],
383
- args: {
384
- ...connectionArgs,
385
- filter: filterArg,
386
- orderBy: {
387
- type: toList(this._inputObjects[`${id}-order`])
388
- }
389
- },
390
- resolve: async (_, args, ctx)=>{
391
- return await ctx.resolveConnection(this.getModelIDs(id), args);
392
- }
393
- };
394
- }
395
- // Add access control mutations if mutations are enabled
396
- if (this._includeMutations) {
397
- this._mutations.setModelAccessDefaults = {
398
- type: GraphQLBoolean,
399
- args: {
400
- modelId: {
401
- type: new GraphQLNonNull(GraphQLID)
402
- },
403
- permissionType: {
404
- type: new GraphQLNonNull(GraphQLString)
405
- },
406
- accessLevel: {
407
- type: new GraphQLNonNull(GraphQLString)
408
- },
409
- allowedDIDs: {
410
- type: toList(GraphQLString)
411
- }
412
- },
413
- resolve: async (_, args, ctx)=>{
414
- await ctx.executeSetModelAccessDefaults(args.modelId, args.permissionType, args.accessLevel, args.allowedDIDs ?? null);
415
- return true;
416
- }
417
- };
418
- this._mutations.removeModelAccessDefaults = {
419
- type: GraphQLBoolean,
420
- args: {
421
- modelId: {
422
- type: new GraphQLNonNull(GraphQLID)
423
- },
424
- permissionTypes: {
425
- type: new GraphQLNonNull(toList(GraphQLString))
426
- }
427
- },
428
- resolve: async (_, args, ctx)=>{
429
- await ctx.executeRemoveModelAccessDefaults(args.modelId, args.permissionTypes);
430
- return true;
431
- }
432
- };
433
- this._mutations.setDocumentAccessOverride = {
434
- type: GraphQLBoolean,
435
- args: {
436
- documentId: {
437
- type: new GraphQLNonNull(GraphQLID)
438
- },
439
- permissionType: {
440
- type: new GraphQLNonNull(GraphQLString)
441
- },
442
- accessLevel: {
443
- type: new GraphQLNonNull(GraphQLString)
444
- },
445
- allowedDIDs: {
446
- type: toList(GraphQLString)
447
- }
448
- },
449
- resolve: async (_, args, ctx)=>{
450
- await ctx.executeSetDocumentAccessOverride(args.documentId, args.permissionType, args.accessLevel, args.allowedDIDs ?? null);
451
- return true;
452
- }
453
- };
454
- this._mutations.removeDocumentAccessOverride = {
455
- type: GraphQLBoolean,
456
- args: {
457
- documentId: {
458
- type: new GraphQLNonNull(GraphQLID)
459
- },
460
- permissionTypes: {
461
- type: new GraphQLNonNull(toList(GraphQLString))
462
- }
463
- },
464
- resolve: async (_, args, ctx)=>{
465
- await ctx.executeRemoveDocumentAccessOverride(args.documentId, args.permissionTypes);
466
- return true;
467
- }
468
- };
469
- }
470
- const config = {
471
- query: new GraphQLObjectType({
472
- name: 'Query',
473
- fields: queryFields
474
- })
475
- };
476
- if (this._includeMutations) {
477
- config.mutation = new GraphQLObjectType({
478
- name: 'Mutation',
479
- fields: this._mutations
480
- });
481
- }
482
- if (this._includeSubscriptions) {
483
- config.subscription = new GraphQLObjectType({
484
- name: 'Subscription',
485
- fields: ()=>{
486
- const fields = {
487
- documentChanged: {
488
- type: new GraphQLNonNull(definitions.documentInterface),
489
- args: {
490
- id: {
491
- type: new GraphQLNonNull(GraphQLID)
492
- }
493
- },
494
- resolve: (doc)=>doc,
495
- subscribe: (_, args, ctx)=>{
496
- return ctx.subscribeToDocumentChanged(args.id);
497
- }
498
- },
499
- documentRemoved: {
500
- type: new GraphQLNonNull(GraphQLID),
501
- resolve: (id)=>id,
502
- subscribe: (_src, _args, ctx)=>{
503
- return ctx.subscribeToDocumentRemoved();
504
- }
505
- }
506
- };
507
- for (const [id, model] of Object.entries(this._record)){
508
- if (!this._includedModelIDs.has(id)) continue;
509
- const name = this._aliases[id];
510
- fields[`new${name}Created`] = {
511
- type: new GraphQLNonNull(this._types[id]),
512
- resolve: (doc)=>doc,
513
- subscribe: (_src, _args, ctx)=>{
514
- return ctx.subscribeToDocumentCreated(this.getModelIDs(id));
515
- }
516
- };
517
- for (const iid of model.interfaces ?? []){
518
- const interfaceID = getGlobalID(iid, id).toString();
519
- for (const [fieldName, relations] of Object.entries(this._relationsTo[interfaceID] ?? {})){
520
- for (const relation of relations){
521
- this._buildRelationSubscriptions(fields, interfaceID, fieldName, relation);
522
- }
523
- }
524
- }
525
- for (const [fieldName, relations] of Object.entries(this._relationsTo[id] ?? {})){
526
- for (const relation of relations){
527
- if (!this._includedModelIDs.has(relation.model)) continue;
528
- this._buildRelationSubscriptions(fields, id, fieldName, relation);
529
- }
530
- }
531
- }
532
- return fields;
533
- }
534
- });
535
- }
536
- return new GraphQLSchema(config);
537
- }
538
- _getDefinitions() {
539
- if (this._definitions == null) {
540
- throw new Error('Definitions must be initialized');
541
- }
542
- return this._definitions;
543
- }
544
- _getReference(path, parentName) {
545
- const id = getReferenceID(path);
546
- const type = this._types[id];
547
- if (type != null) {
548
- return type;
549
- }
550
- const schema = this._getReferenceSchema(id);
551
- return this._buildReference(id, schema, parentName);
552
- }
553
- _getReferenceSchema(id) {
554
- const schema = this._references[id];
555
- if (schema == null) {
556
- throw new Error(`Could not find reference: ${id}`);
557
- }
558
- return schema;
559
- }
560
- _getReferenceInput(path, isPartial, parentName = '') {
561
- const id = getReferenceID(path);
562
- const schema = this._getReferenceSchema(id);
563
- if (schema.type === 'array') {
564
- const itemType = this._getReferenceInput(schema.items.$ref, false, schema.title);
565
- return new GraphQLList(new GraphQLNonNull(itemType));
566
- }
567
- if (schema.type === 'object') {
568
- if (schema.additionalProperties) {
569
- return GraphQLJSONObject;
570
- }
571
- const inputID = isPartial ? `${id}-partial` : id;
572
- const existing = this._inputObjects[inputID];
573
- if (existing != null) {
574
- return existing;
575
- }
576
- const fields = {};
577
- for (const [key, value] of Object.entries(schema.properties)){
578
- const type = this._getReferenceInput(value.$ref, isPartial, schema.title);
579
- fields[key] = {
580
- type: isPartial || !schema.required.includes(key) ? type : new GraphQLNonNull(type)
581
- };
582
- }
583
- const name = this._aliases[id] ?? pascalCase(parentName + schema.title);
584
- const input = new GraphQLInputObjectType({
585
- name: isPartial ? `Partial${name}Input` : `${name}Input`,
586
- fields
587
- });
588
- this._inputObjects[inputID] = input;
589
- return input;
590
- }
591
- return this._buildScalar(id, schema, parentName);
592
- }
593
- _buildSharedDefinitions() {
594
- const nodeDefs = nodeDefinitions(async (id, ctx)=>{
595
- if (id.startsWith('did:')) {
596
- return id;
597
- }
598
- const decoded = decodeID(id);
599
- if (decoded instanceof AttachmentID) {
600
- return decoded;
601
- }
602
- if (decoded instanceof DocumentID) {
603
- return await ctx.loadDocument(id);
604
- }
605
- throw new Error(`Unsupported node ID: ${id}`);
606
- }, (node)=>{
607
- return typeof node === 'string' ? 'AccountNode' : node instanceof AttachmentID ? 'AttachmentNode' : this._aliases[node.model];
608
- });
609
- const accountObject = new GraphQLObjectType({
610
- name: 'AccountNode',
611
- interfaces: [
612
- nodeDefs.nodeInterface
613
- ],
614
- fields: ()=>{
615
- const config = {
616
- id: {
617
- type: new GraphQLNonNull(GraphQLID),
618
- description: 'Globally unique identifier of the account (DID string)',
619
- resolve: (did)=>did
620
- },
621
- isViewer: {
622
- type: new GraphQLNonNull(GraphQLBoolean),
623
- description: 'Whether the authenticated request issuer is this account or not',
624
- resolve: (did, _, ctx)=>ctx.getViewer() === did
625
- }
626
- };
627
- for (const [id, model] of Object.entries(this._record)){
628
- if (!this._includedModelIDs.has(id)) continue;
629
- switch(model.behavior){
630
- case 'interface':
631
- continue;
632
- case 'default':
633
- {
634
- const name = this._aliases[id];
635
- config[`own${name}Connection`] = {
636
- type: this._types[`${id}-connection`],
637
- args: {
638
- ...connectionArgs,
639
- filter: {
640
- type: this._inputObjects[`${id}-filter`]
641
- },
642
- orderBy: {
643
- type: toList(this._inputObjects[`${id}-order`])
644
- }
645
- },
646
- resolve: async (owner, args, ctx)=>{
647
- return await ctx.resolveConnection(this.getModelIDs(id), {
648
- ...args,
649
- owner
650
- });
651
- }
652
- };
653
- break;
654
- }
655
- case 'unique':
656
- {
657
- const name = this._aliases[id];
658
- config[`own${name}`] = {
659
- type: this._types[id],
660
- args: model.uniqueFields.length ? {
661
- with: {
662
- type: new GraphQLNonNull(this._buildSetInputObjectType(id, model.uniqueFields))
663
- }
664
- } : {},
665
- resolve: async (owner, args, ctx)=>{
666
- const unique = getUniqueDocValue(model.uniqueFields, args.with);
667
- const docID = DocumentID.create(id, owner, unique);
668
- return await ctx.loadDocument(docID.toString());
669
- }
670
- };
671
- }
672
- }
673
- }
674
- return config;
675
- }
676
- });
677
- const attachmentObject = new GraphQLObjectType({
678
- name: 'AttachmentNode',
679
- interfaces: [
680
- nodeDefs.nodeInterface
681
- ],
682
- fields: {
683
- id: {
684
- type: new GraphQLNonNull(GraphQLID),
685
- resolve: (aid)=>aid.toString()
686
- },
687
- contentLength: {
688
- type: new GraphQLNonNull(GraphQLInt)
689
- },
690
- mimeType: {
691
- type: new GraphQLNonNull(GraphQLString)
692
- }
693
- }
694
- });
695
- // Create AccessRule type for read/write permissions
696
- const accessRuleObject = new GraphQLObjectType({
697
- name: 'AccessRule',
698
- fields: {
699
- level: {
700
- type: new GraphQLNonNull(GraphQLString)
701
- },
702
- allowedDIDs: {
703
- type: new GraphQLList(new GraphQLNonNull(GraphQLString))
704
- }
705
- }
706
- });
707
- // Create AccessPermissions type
708
- const accessPermissionsObject = new GraphQLObjectType({
709
- name: 'AccessPermissions',
710
- fields: {
711
- read: {
712
- type: accessRuleObject
713
- },
714
- write: {
715
- type: accessRuleObject
716
- }
717
- }
718
- });
719
- const documentNodeFields = {
720
- // Node interface
721
- id: {
722
- type: new GraphQLNonNull(GraphQLID)
723
- },
724
- // Metadata
725
- model: {
726
- type: new GraphQLNonNull(GraphQLString)
727
- },
728
- owner: {
729
- type: new GraphQLNonNull(accountObject)
730
- },
731
- createdAt: {
732
- type: new GraphQLNonNull(GraphQLDateTimeISO)
733
- },
734
- updatedAt: {
735
- type: GraphQLDateTimeISO
736
- },
737
- // Access control
738
- accessPermissions: {
739
- type: accessPermissionsObject,
740
- resolve: (doc)=>doc.data?.accessPermissions ?? null
741
- }
742
- };
743
- const documentInterface = new GraphQLInterfaceType({
744
- name: 'DocumentNode',
745
- interfaces: [
746
- nodeDefs.nodeInterface
747
- ],
748
- fields: documentNodeFields
749
- });
750
- const queryFields = {
751
- node: nodeDefs.nodeField,
752
- viewer: {
753
- type: accountObject,
754
- description: 'Account issuing the request, if authenticated',
755
- resolve: (_self, _args, ctx)=>ctx.getViewer()
756
- }
757
- };
758
- return {
759
- ...nodeDefs,
760
- accountObject,
761
- attachmentObject,
762
- documentInterface,
763
- documentNodeFields,
764
- queryFields
765
- };
766
- }
767
- _buildDocument(id) {
768
- if (this._types[id] != null) {
769
- return this._types[id];
770
- }
771
- const model = this._record[id];
772
- if (model == null) {
773
- throw new Error(`Could not find model id: ${id}`);
774
- }
775
- const definitions = this._getDefinitions();
776
- this._aliases[id] ??= pascalCase(model.name);
777
- const name = this._aliases[id];
778
- const config = {
779
- name,
780
- interfaces: ()=>{
781
- return [
782
- definitions.documentInterface,
783
- definitions.nodeInterface
784
- ].concat(model.interfaces.map((interfaceID)=>{
785
- return this._types[getGlobalID(interfaceID, id).toString()];
786
- }).filter((type)=>type != null));
787
- },
788
- fields: ()=>this._buildDocumentFields(id, model, name)
789
- };
790
- const isInterface = model.behavior === 'interface';
791
- const nodeType = isInterface ? new GraphQLInterfaceType({
792
- ...config,
793
- resolveType: (doc)=>this._aliases[doc.model]
794
- }) : new GraphQLObjectType(config);
795
- const { connectionType, edgeType } = connectionDefinitions({
796
- nodeType
797
- });
798
- this._types[id] = nodeType;
799
- this._types[`${id}-connection`] = connectionType;
800
- this._types[`${id}-edge`] = edgeType;
801
- this._buildDocumentDataObject(id, model, name);
802
- this._buildDocumentInput(id, model);
803
- this._buildObjectFilterInput(id, model.schema, name, true);
804
- this._buildObjectOrderByInput(id, model.schema, name, true);
805
- // TODO: move to dedicated method
806
- if (!isInterface && this._includeMutations) {
807
- if (model.behavior === 'default') {
808
- this._mutations[`create${name}`] = mutationWithClientMutationId({
809
- name: `Create${name}`,
810
- inputFields: ()=>({
811
- data: {
812
- type: new GraphQLNonNull(this._inputObjects[id])
813
- }
814
- }),
815
- outputFields: ()=>({
816
- ...definitions.queryFields,
817
- document: {
818
- type: this._types[id]
819
- }
820
- }),
821
- mutateAndGetPayload: async (input, ctx, info)=>{
822
- const document = await ctx.executeCreateMutation(id, input.data, info);
823
- return {
824
- document
825
- };
826
- }
827
- });
828
- } else if (model.behavior === 'unique') {
829
- this._mutations[`set${name}`] = mutationWithClientMutationId({
830
- name: `Set${name}`,
831
- inputFields: ()=>({
832
- data: {
833
- type: new GraphQLNonNull(this._inputObjects[id])
834
- }
835
- }),
836
- outputFields: ()=>({
837
- ...definitions.queryFields,
838
- document: {
839
- type: this._types[id]
840
- }
841
- }),
842
- mutateAndGetPayload: async (input, ctx, info)=>{
843
- const unique = getUniqueDocValue(model.uniqueFields, input.data);
844
- const document = await ctx.executeSetMutation(id, unique, input.data, info);
845
- return {
846
- document
847
- };
848
- }
849
- });
850
- }
851
- this._mutations[`update${name}`] = mutationWithClientMutationId({
852
- name: `Update${name}`,
853
- inputFields: ()=>({
854
- id: {
855
- type: new GraphQLNonNull(GraphQLID)
856
- },
857
- patch: {
858
- type: new GraphQLNonNull(toList(PatchOperation))
859
- },
860
- from: {
861
- type: this._inputObjects[`${id}-update`]
862
- }
863
- }),
864
- outputFields: ()=>({
865
- ...definitions.queryFields,
866
- document: {
867
- type: this._types[id]
868
- }
869
- }),
870
- mutateAndGetPayload: async (input, ctx, info)=>{
871
- const document = await ctx.executeUpdateMutation(input, info);
872
- return {
873
- document
874
- };
875
- }
876
- });
877
- this._mutations[`remove${name}`] = mutationWithClientMutationId({
878
- name: `Remove${name}`,
879
- inputFields: ()=>({
880
- id: {
881
- type: new GraphQLNonNull(GraphQLID)
882
- }
883
- }),
884
- outputFields: ()=>({
885
- ...definitions.queryFields,
886
- // Return id so it can be used with Relay's @deleteEdge directive
887
- id: {
888
- type: new GraphQLNonNull(GraphQLID)
889
- }
890
- }),
891
- mutateAndGetPayload: async (input, ctx, info)=>{
892
- await ctx.executeRemoveMutation(input.id, info);
893
- return {
894
- id: input.id
895
- };
896
- }
897
- });
898
- }
899
- return nodeType;
900
- }
901
- _buildDocumentFields(id, model, name) {
902
- const dataObject = this._buildDocumentDataObject(id, model, name);
903
- const definitions = this._getDefinitions();
904
- const fields = {
905
- ...definitions.documentNodeFields,
906
- data: {
907
- type: new GraphQLNonNull(dataObject)
908
- }
909
- };
910
- for (const [key, field] of Object.entries(dataObject.getFields())){
911
- const meta = model.fieldsMeta[key];
912
- if (meta == null) {
913
- continue;
914
- }
915
- switch(meta.type){
916
- case 'account':
917
- {
918
- if (isList(field.type)) {
919
- fields[`${key}Accounts`] = {
920
- type: new GraphQLNonNull(toOuputList(definitions.accountObject)),
921
- resolve: (doc)=>doc.data?.[key] ?? []
922
- };
923
- } else {
924
- fields[`${key}Account`] = {
925
- type: definitions.accountObject,
926
- resolve: (doc)=>doc.data?.[key] ?? null
927
- };
928
- }
929
- break;
930
- }
931
- case 'attachment':
932
- {
933
- if (isList(field.type)) {
934
- fields[`${key}Attachments`] = {
935
- type: new GraphQLNonNull(toOuputList(definitions.attachmentObject)),
936
- resolve: (doc)=>{
937
- const ids = doc.data?.[key] ?? [];
938
- return ids.map(AttachmentID.fromString);
939
- }
940
- };
941
- } else {
942
- fields[`${key}Attachment`] = {
943
- type: definitions.attachmentObject,
944
- resolve: (doc)=>{
945
- const id = doc.data?.[key];
946
- return id ? AttachmentID.fromString(id) : null;
947
- }
948
- };
949
- }
950
- break;
951
- }
952
- case 'document':
953
- {
954
- const relationModel = meta.model;
955
- if (typeof relationModel === 'undefined') {
956
- continue;
957
- }
958
- let relationModelID;
959
- let relationType;
960
- if (relationModel === null) {
961
- relationModelID = null;
962
- relationType = definitions.documentInterface;
963
- } else {
964
- relationModelID = getGlobalID(relationModel, id).toString();
965
- // Check if the related model is included in the schema
966
- if (this._includedModelIDs.has(relationModelID)) {
967
- relationType = this._buildDocument(relationModelID);
968
- } else {
969
- // Fall back to generic DocumentNode interface
970
- relationType = definitions.documentInterface;
971
- relationModelID = null; // Treat as unspecified model
972
- }
973
- }
974
- if (isList(field.type)) {
975
- // TODO: add suport for filters?
976
- fields[`${key}Documents`] = {
977
- type: new GraphQLNonNull(toOuputList(relationType)),
978
- resolve: async (doc, _args, ctx)=>{
979
- const relationIDs = doc.data?.[key] ?? [];
980
- return relationIDs.length ? await ctx.resolveList(this.getModelIDs(relationModelID), relationIDs) : [];
981
- }
982
- };
983
- } else {
984
- fields[`${key}Document`] = {
985
- type: relationType,
986
- resolve: async (doc, _args, ctx)=>{
987
- const relationID = doc.data?.[key];
988
- return relationID ? await ctx.loadDocument(relationID) : null;
989
- }
990
- };
991
- }
992
- break;
993
- }
994
- }
995
- }
996
- for (const iid of model.interfaces ?? []){
997
- const interfaceID = getGlobalID(iid, id).toString();
998
- for (const [fieldName, relations] of Object.entries(this._relationsTo[interfaceID] ?? {})){
999
- for (const relation of relations){
1000
- if (!this._includedModelIDs.has(relation.model)) continue;
1001
- this._buildRelationFields(fields, fieldName, relation);
1002
- }
1003
- }
1004
- }
1005
- for (const [fieldName, relations] of Object.entries(this._relationsTo[id] ?? {})){
1006
- for (const relation of relations){
1007
- if (!this._includedModelIDs.has(relation.model)) continue;
1008
- this._buildRelationFields(fields, fieldName, relation);
1009
- }
1010
- }
1011
- return fields;
1012
- }
1013
- _buildDocumentDataObject(id, model, name) {
1014
- const typeID = `${id}-data`;
1015
- if (this._types[typeID] != null) {
1016
- return this._types[typeID];
1017
- }
1018
- const required = model.schema.required ?? [];
1019
- const fields = {};
1020
- for (const [key, value] of Object.entries(model.schema.properties ?? {})){
1021
- const fieldRef = value.$ref;
1022
- if (fieldRef == null) {
1023
- throw new Error(`Missing ref for field ${key} of object ${id}`);
1024
- }
1025
- const type = this._getReference(fieldRef);
1026
- fields[key] = {
1027
- type: required.includes(key) ? new GraphQLNonNull(type) : type
1028
- };
1029
- }
1030
- const config = {
1031
- name: `${name}Data`,
1032
- interfaces: ()=>{
1033
- return model.interfaces.map((interfaceID)=>{
1034
- const typeID = `${getGlobalID(interfaceID, id).toString()}-data`;
1035
- return this._types[typeID];
1036
- }).filter((type)=>type != null);
1037
- },
1038
- fields
1039
- };
1040
- const object = model.behavior === 'interface' ? new GraphQLInterfaceType(config) : new GraphQLObjectType(config);
1041
- this._types[typeID] = object;
1042
- return object;
1043
- }
1044
- _buildDocumentInput(id, model) {
1045
- if (model.behavior === 'interface') {
1046
- // No mutations for interfaces
1047
- return;
1048
- }
1049
- const setFields = {};
1050
- const updateFields = {};
1051
- for (const [key, value] of Object.entries(model.schema.properties)){
1052
- const path = value.$ref;
1053
- const setType = this._getReferenceInput(path, false);
1054
- setFields[key] = {
1055
- type: model.schema.required.includes(key) ? new GraphQLNonNull(setType) : setType
1056
- };
1057
- const id = getReferenceID(path);
1058
- const schema = this._getReferenceSchema(id);
1059
- if (schema.const == null) {
1060
- updateFields[key] = {
1061
- type: this._getReferenceInput(path, true)
1062
- };
1063
- }
1064
- }
1065
- const name = this._aliases[id];
1066
- this._inputObjects[id] = new GraphQLInputObjectType({
1067
- name: `${name}Input`,
1068
- fields: setFields
1069
- });
1070
- this._inputObjects[`${id}-update`] = new GraphQLInputObjectType({
1071
- name: `Partial${name}Input`,
1072
- fields: updateFields
1073
- });
1074
- }
1075
- _buildReferenceFilterInput(path, parentName = '') {
1076
- const id = getReferenceID(path);
1077
- const schema = this._getReferenceSchema(id);
1078
- let refParentName = parentName;
1079
- if (schema.type === 'object') {
1080
- return schema.additionalProperties ? null : this._buildObjectFilterInput(id, schema, parentName);
1081
- }
1082
- if (schema.type === 'array') {
1083
- const name = this._aliases[id] ?? pascalCase(parentName + schema.title);
1084
- refParentName = name;
1085
- // const itemType = this._buildReferenceFilterInput(schema.items.$ref, schema.title)
1086
- return new GraphQLInputObjectType({
1087
- name: `${name}Filter`,
1088
- isOneOf: true,
1089
- fields: {
1090
- // DB does not support filtering on array values
1091
- // contains: { type: toList(itemType) },
1092
- // isEmpty: { type: GraphQLBoolean },
1093
- isNull: {
1094
- type: GraphQLBoolean
1095
- }
1096
- }
1097
- });
1098
- }
1099
- const scalar = this._buildScalar(id, schema, refParentName);
1100
- const knownFilter = VALUE_FILTER_INPUT_TYPES[scalar.name];
1101
- if (knownFilter != null) {
1102
- return this._inputObjects[knownFilter];
1103
- }
1104
- const filter = isEnumType(scalar) ? createEnumValueFilterInput(scalar) : createScalarValueFilterInput(scalar);
1105
- if (this._inputObjects[filter.name] == null) {
1106
- this._inputObjects[filter.name] = filter;
1107
- }
1108
- return this._inputObjects[filter.name];
1109
- }
1110
- _buildReferenceOrderByInput(path, parentName = '') {
1111
- const id = getReferenceID(path);
1112
- const schema = this._getReferenceSchema(id);
1113
- switch(schema.type){
1114
- case 'array':
1115
- return null;
1116
- case 'object':
1117
- return schema.additionalProperties ? null : this._buildObjectOrderByInput(id, schema, parentName);
1118
- default:
1119
- return OrderByDirection;
1120
- }
1121
- }
1122
- _buildObjectFilterInput(id, schema, parentName = '', isDocument = false) {
1123
- const inputID = `${id}-filter`;
1124
- const existing = this._inputObjects[inputID];
1125
- if (existing != null) {
1126
- return existing;
1127
- }
1128
- const name = this._aliases[id] ?? pascalCase(parentName + (schema.title ?? ''));
1129
- const fields = isDocument ? {
1130
- _owner: {
1131
- type: this._inputObjects.AccountValueFilter
1132
- }
1133
- } : {};
1134
- for (const [key, value] of Object.entries(schema.properties)){
1135
- const type = this._buildReferenceFilterInput(value.$ref, name);
1136
- if (type !== null) {
1137
- fields[key] = {
1138
- type
1139
- };
1140
- }
1141
- }
1142
- const objectInput = new GraphQLInputObjectType({
1143
- name: `${name}ObjectFilter`,
1144
- fields
1145
- });
1146
- const input = new GraphQLInputObjectType({
1147
- name: `${name}Filter`,
1148
- isOneOf: true,
1149
- fields: ()=>({
1150
- where: {
1151
- type: objectInput
1152
- },
1153
- and: {
1154
- type: toList(this._inputObjects[inputID])
1155
- },
1156
- or: {
1157
- type: toList(this._inputObjects[inputID])
1158
- },
1159
- not: {
1160
- type: this._inputObjects[inputID]
1161
- }
1162
- })
1163
- });
1164
- this._inputObjects[inputID] = input;
1165
- return input;
1166
- }
1167
- _buildObjectOrderByInput(id, schema, parentName = '', isDocument = false) {
1168
- const inputID = `${id}-order`;
1169
- const existing = this._inputObjects[inputID];
1170
- if (existing != null) {
1171
- return existing;
1172
- }
1173
- const name = this._aliases[id] ?? pascalCase(parentName + (schema.title ?? ''));
1174
- const fields = isDocument ? {
1175
- _docOwner: {
1176
- type: OrderByDirection
1177
- },
1178
- _createdAt: {
1179
- type: OrderByDirection
1180
- }
1181
- } : {};
1182
- for (const [key, value] of Object.entries(schema.properties)){
1183
- const type = this._buildReferenceOrderByInput(value.$ref, name);
1184
- if (type != null) {
1185
- fields[key] = {
1186
- type
1187
- };
1188
- }
1189
- }
1190
- const input = new GraphQLInputObjectType({
1191
- name: `${name}OrderBy`,
1192
- isOneOf: true,
1193
- fields
1194
- });
1195
- this._inputObjects[inputID] = input;
1196
- return input;
1197
- }
1198
- _buildSetInputObjectType(id, withFields, relationField) {
1199
- const relationFieldName = relationField ? pascalCase(relationField) : '';
1200
- const inputID = `${id}-with-${relationFieldName}`;
1201
- const existing = this._inputObjects[inputID];
1202
- if (existing != null) {
1203
- return existing;
1204
- }
1205
- const model = this._record[id];
1206
- const name = this._aliases[id];
1207
- this._inputObjects[inputID] = new GraphQLInputObjectType({
1208
- name: `With${relationFieldName}${name}Input`,
1209
- fields: ()=>{
1210
- const fields = {};
1211
- for (const fieldName of withFields){
1212
- const field = model.schema.properties[fieldName];
1213
- if (field == null) {
1214
- throw new Error(`Field ${fieldName} not found on model ${name}`);
1215
- }
1216
- const type = this._getReferenceInput(field.$ref, false);
1217
- fields[fieldName] = {
1218
- type: new GraphQLNonNull(type)
1219
- };
1220
- }
1221
- return fields;
1222
- }
1223
- });
1224
- return this._inputObjects[inputID];
1225
- }
1226
- _buildReference(id, schema, parentName = '') {
1227
- if (this._types[id] != null) {
1228
- return this._types[id];
1229
- }
1230
- switch(schema.type){
1231
- case 'array':
1232
- this._types[id] = this._buildArray(schema, parentName);
1233
- return this._types[id];
1234
- case 'object':
1235
- this._types[id] = schema.additionalProperties ? GraphQLJSONObject : this._buildObject(id, schema, parentName);
1236
- return this._types[id];
1237
- default:
1238
- return this._buildScalar(id, schema, parentName);
1239
- }
1240
- }
1241
- _buildScalar(id, schema, parentName = '') {
1242
- if (this._types[id] != null) {
1243
- return this._types[id];
1244
- }
1245
- switch(schema.type){
1246
- case 'boolean':
1247
- this._types[id] = GraphQLBoolean;
1248
- break;
1249
- case 'integer':
1250
- this._types[id] = GraphQLInt;
1251
- break;
1252
- case 'number':
1253
- this._types[id] = GraphQLFloat;
1254
- break;
1255
- case 'string':
1256
- this._types[id] = this._buildString(schema, parentName);
1257
- break;
1258
- }
1259
- return this._types[id];
1260
- }
1261
- _buildArray(schema, parentName = '') {
1262
- const itemRef = schema.items.$ref;
1263
- return new GraphQLList(new GraphQLNonNull(this._getReference(itemRef, pascalCase(parentName + schema.title))));
1264
- }
1265
- _buildObject(id, schema, parentName = '') {
1266
- const name = pascalCase(parentName + schema.title);
1267
- const fields = this._buildObjectFields(id, schema, name);
1268
- return new GraphQLObjectType({
1269
- name,
1270
- fields
1271
- });
1272
- }
1273
- _buildObjectFields(id, schema, parentName = '') {
1274
- const required = schema.required ?? [];
1275
- const fields = {};
1276
- for (const [key, value] of Object.entries(schema.properties ?? {})){
1277
- const fieldRef = value.$ref;
1278
- if (fieldRef == null) {
1279
- throw new Error(`Missing ref for field ${key} of object ${id}`);
1280
- }
1281
- const type = this._getReference(fieldRef, parentName);
1282
- fields[key] = {
1283
- type: required.includes(key) ? new GraphQLNonNull(type) : type
1284
- };
1285
- }
1286
- return fields;
1287
- }
1288
- _buildRelationFields(fields, fieldName, relation) {
1289
- if (relation.isList) {
1290
- // DB does not support matches for array containing values
1291
- return;
1292
- }
1293
- const relationID = relation.model;
1294
- const relationName = `${pascalCase(fieldName)}Of${this._aliases[relationID]}`;
1295
- fields[`in${relationName}`] = {
1296
- type: new GraphQLNonNull(this._types[`${relationID}-connection`]),
1297
- args: {
1298
- ...connectionArgs,
1299
- filter: {
1300
- type: this._inputObjects[`${relationID}-filter`]
1301
- },
1302
- orderBy: {
1303
- type: toList(this._inputObjects[`${relationID}-order`])
1304
- }
1305
- },
1306
- resolve: (doc, args, ctx)=>{
1307
- const docFilter = {
1308
- where: {
1309
- [fieldName]: {
1310
- equalTo: doc.id
1311
- }
1312
- }
1313
- };
1314
- const filter = args.filter ? {
1315
- and: [
1316
- args.filter,
1317
- docFilter
1318
- ]
1319
- } : docFilter;
1320
- return ctx.resolveConnection(this.getModelIDs(relationID), {
1321
- ...args,
1322
- filter
1323
- });
1324
- }
1325
- };
1326
- // TODO: add support for count relation
1327
- // fields[`_${fieldName}_of_${relationName}_count`] = {
1328
- // type: new GraphQLNonNull(GraphQLInt),
1329
- // }
1330
- }
1331
- _buildRelationSubscriptions(fields, modelID, fieldName, relation) {
1332
- if (relation.isList) {
1333
- // Inverse relations to list fields are not supported in connections so we skip them for subscriptions as well
1334
- return;
1335
- }
1336
- const targetFieldName = pascalCase(fieldName);
1337
- fields[`edge${this._aliases[relation.model]}AddedAs${targetFieldName}To${this._aliases[modelID]}`] = {
1338
- type: new GraphQLNonNull(this._types[`${relation.model}-edge`]),
1339
- args: {
1340
- id: {
1341
- type: new GraphQLNonNull(GraphQLID)
1342
- }
1343
- },
1344
- resolve: (edge)=>edge,
1345
- subscribe: (_src, args, ctx)=>{
1346
- return ctx.subscribeToDocumentEdgeAdded(this.getModelIDs(relation.model), (data)=>data[fieldName] === args.id);
1347
- }
1348
- };
1349
- fields[`edge${this._aliases[relation.model]}RemovedAs${targetFieldName}From${this._aliases[modelID]}`] = {
1350
- type: new GraphQLNonNull(GraphQLID),
1351
- args: {
1352
- id: {
1353
- type: new GraphQLNonNull(GraphQLID)
1354
- }
1355
- },
1356
- resolve: (id)=>id,
1357
- subscribe: (_src, args, ctx)=>{
1358
- return ctx.subscribeToDocumentEdgeRemoved(this.getModelIDs(relation.model), (data)=>data[fieldName] === args.id);
1359
- }
1360
- };
1361
- }
1362
- _buildString(schema, parentName) {
1363
- if (schema.const != null) {
1364
- return GraphQLString;
1365
- }
1366
- if (schema.enum != null) {
1367
- return this._buildEnumString(schema, parentName);
1368
- }
1369
- if (schema.format != null) {
1370
- return this._buildFormatString(schema);
1371
- }
1372
- return this._buildCustomString(schema);
1373
- }
1374
- _buildCustomString(schema) {
1375
- if (schema.title == null) {
1376
- // Plain string type
1377
- return GraphQLString;
1378
- }
1379
- const title = schema.title.toLowerCase();
1380
- return STRING_TITLES[title] ?? GraphQLString;
1381
- }
1382
- _buildEnumString(schema, parentName = '') {
1383
- const name = pascalCase(parentName + schema.title);
1384
- const typeName = `${name}-enum`;
1385
- if (this._types[typeName] == null) {
1386
- const values = {};
1387
- for (const value of schema.enum){
1388
- values[value] = {
1389
- value
1390
- };
1391
- }
1392
- this._types[typeName] = new GraphQLEnumType({
1393
- name,
1394
- values
1395
- });
1396
- }
1397
- return this._types[typeName];
1398
- }
1399
- _buildFormatString(schema) {
1400
- const scalar = STRING_FORMATS[schema.format];
1401
- if (scalar == null) {
1402
- throw new Error(`Unsupported string format: ${schema.format}`);
1403
- }
1404
- return scalar;
1405
- }
1406
- }
1407
- export function createSchema(params) {
1408
- return new SchemaBuilder(params).build();
1409
- }
1
+ import{AttachmentID as e,DocumentID as t,DocumentModelID as i,decodeID as n}from"@kubun/id";import{REFERENCE_PREFIX as s}from"@kubun/protocol";import{pascalCase as l}from"change-case";import{GraphQLBoolean as r,GraphQLEnumType as o,GraphQLFloat as a,GraphQLID as u,GraphQLInputObjectType as d,GraphQLInt as c,GraphQLInterfaceType as p,GraphQLList as h,GraphQLNonNull as f,GraphQLObjectType as m,GraphQLSchema as y,GraphQLString as _,isEnumType as b,isListType as w,isNonNullType as g}from"graphql";import{connectionArgs as D,connectionDefinitions as I,mutationWithClientMutationId as O,nodeDefinitions as $}from"graphql-relay";import{GraphQLDateTimeISO as j,GraphQLDID as v,GraphQLDuration as S,GraphQLEmailAddress as M,GraphQLJSON as F,GraphQLJSONObject as T,GraphQLURL as R}from"graphql-scalars";import{GraphQLAttachmentID as A,GraphQLDocID as L}from"./identifiers.js";import{getUniqueDocValue as q}from"./utils.js";let P={date:_,time:_,"date-time":_,duration:S,email:M,uri:R},E={attachmentid:A,did:v,docid:L};function G(e){return e.slice(s.length)}function C(e){return w(e)||g(e)&&w(e.ofType)}export function toDocumentModelID(e){return e instanceof i?e:i.fromString(e)}export function getGlobalID(e,t){let i=toDocumentModelID(e);return i.isLocal?i.toGlobal(toDocumentModelID(t)):i}export function toList(e){return new h(new f(e))}export function toOuputList(e){return new h(new f(e))}function V(e){return new d({name:`${e.name}ValueFilter`,isOneOf:!0,fields:{isNull:{type:r},equalTo:{type:e},notEqualTo:{type:e},in:{type:toList(e)},notIn:{type:toList(e)},lessThan:{type:e},lessThanOrEqualTo:{type:e},greaterThan:{type:e},greaterThanOrEqualTo:{type:e}}})}let x={AccountValueFilter:new d({name:"AccountValueFilter",isOneOf:!0,fields:{equalTo:{type:v},notEqualTo:{type:v},in:{type:toList(v)},notIn:{type:toList(v)}}}),BooleanValueFilter:new d({name:"BooleanValueFilter",isOneOf:!0,fields:{isNull:{type:r},equalTo:{type:r}}}),FloatValueFilter:V(a),IntValueFilter:V(c),StringValueFilter:V(_)},B={GraphQLBoolean:"BooleanValueFilter",GraphQLDID:"AccountValueFilter",GraphQLFloat:"FloatValueFilter",GraphQLInt:"IntValueFilter",GraphQLString:"StringValueFilter"},k=new d({name:"PatchSetOperation",fields:{path:{type:new f(_)},value:{type:F}}}),N=new d({name:"PatchRemoveOperation",fields:{path:{type:new f(_)}}}),Q=new d({name:"PatchDestinationOperation",fields:{from:{type:new f(_)},path:{type:new f(_)}}});export const PatchOperation=new d({name:"PatchOperation",isOneOf:!0,fields:{add:{type:k},set:{type:k},remove:{type:N},replace:{type:k},copy:{type:Q},move:{type:Q}}});let U=new o({name:"OrderByDirection",values:{ASC:{value:"asc"},DESC:{value:"desc"}}}),W=new m({name:"AccessRule",fields:{level:{type:new f(_)},allowedDIDs:{type:new h(new f(_))}}}),z=new m({name:"AccessPermissions",fields:{read:{type:W},write:{type:W}}}),H=new m({name:"ModelAccessDefaults",fields:()=>({ownerDID:{type:new f(_)},modelId:{type:new f(u)},permissions:{type:new f(z)}})});export class SchemaBuilder{_aliases;_aliasToModelID;_definitions;_implemented={};_includedModelIDs;_includeInterfaceDependencies;_includeMutations;_includeRelationDependencies;_includeSubscriptions;_inputObjects={...x,PatchOperation};_mutations={};_nodeModelIDs;_onlyModels;_record;_references={};_relationsTo={};_subscriptions={};_types={OrderByDirection:U};constructor(e){for(let[t,i]of(this._record=e.record,this._onlyModels=e.onlyModels??null,this._includeInterfaceDependencies=e.includeInterfaceDependencies??!0,this._includeRelationDependencies=e.includeRelationDependencies??!0,this._includeMutations=e.includeMutations??!0,this._includeSubscriptions=e.includeSubscriptions??!0,this._aliasToModelID=e.aliases??{},this._aliases={},Object.entries(this._aliasToModelID)))this._aliases[i]=t;this._includedModelIDs=this._resolveModelIDsToInclude();let t={};for(let[n,s]of Object.entries(e.record)){let e=i.fromString(n);for(let i of(Object.assign(this._references,s.schema.$defs??{}),s.interfaces)){let s=getGlobalID(i,e).toString();t[s]??=new Set,t[s].add(n)}for(let[t,i]of Object.entries(s.fieldsMeta))if("document"===i.type&&null!=i.model){let l=G(s.schema.properties[t]?.$ref),r="array"===this._getReferenceSchema(l).type,o=getGlobalID(i.model,e).toString();this._relationsTo[o]??={},this._relationsTo[o][t]??=[],this._relationsTo[o][t].push({model:n,isList:r})}}let n=new Set;for(let e of Object.keys(t)){let i=new Set;for(let s of(!function e(t,i,n){let s=i[n];if(null==s)throw Error(`Interface ${n} not found in record`);for(let n of s)null!=i[n]?e(t,i,n):t.add(n)}(i,t,e),0!==i.size&&(this._implemented[e]=Array.from(i)),i))n.add(s)}this._nodeModelIDs=Array.from(n)}_resolveModelIdentifier(e){if(null!=this._record[e])return e;let t=this._aliasToModelID[e];if(null!=t&&null!=this._record[t])return t;throw Error(`Model identifier "${e}" not found. Must be a valid model ID or alias.`)}_resolveModelIDsToInclude(){if(null==this._onlyModels||0===this._onlyModels.length)return new Set(Object.keys(this._record));let e=new Set;for(let t of this._onlyModels){let i=this._resolveModelIdentifier(t);e.add(i)}if(!this._includeInterfaceDependencies&&!this._includeRelationDependencies)return e;let t=Array.from(e),i=new Set;for(;t.length>0;){let n=t.pop();if(null==n||i.has(n))continue;i.add(n);let s=this._record[n];if(null!=s){if(this._includeInterfaceDependencies)for(let i of s.interfaces){let s=getGlobalID(i,n).toString();e.has(s)||(e.add(s),t.push(s))}if(this._includeRelationDependencies){for(let i of Object.values(s.fieldsMeta))if("document"===i.type&&null!=i.model&&null!==i.model){let s=getGlobalID(i.model,n).toString();e.has(s)||(e.add(s),t.push(s))}}}}return e}getModelIDs(e){return null===e?this._nodeModelIDs:this._implemented[e]??[e]}build(){let e=this._buildSharedDefinitions();this._definitions=e;let t={nodes:e.nodesField,...e.queryFields};for(let e of this._includedModelIDs){this._buildDocument(e);let i=this._aliases[e],n={type:this._inputObjects[`${e}-filter`]};t[`all${i}`]={type:this._types[`${e}-connection`],args:{...D,filter:n,orderBy:{type:toList(this._inputObjects[`${e}-order`])},search:{type:_}},resolve:async(t,i,n)=>await n.resolveConnection(this.getModelIDs(e),i)}}let i=[...this._includedModelIDs][0];if(null!=i){let n=this._types[`${i}-connection`].getFields().pageInfo.type,s=new m({name:"SearchResultEdge",fields:()=>({cursor:{type:new f(_)},node:{type:new f(e.nodeInterface)},rank:{type:new f(a)}})}),l=new m({name:"SearchResultConnection",fields:()=>({edges:{type:new f(new h(new f(s)))},pageInfo:{type:n}})});t.search={type:new f(l),args:{query:{type:new f(_)},models:{type:new h(new f(_))},first:{type:c},after:{type:_}},resolve:async(e,t,i)=>{let{query:n,models:s,first:l}=t;if(""===n.trim())throw Error("Search query must not be empty");let r=s?s.map(e=>this._aliasToModelID[e]??e):[...this._includedModelIDs],o=await i.searchDocuments(n,r,l??20),a=o.map(e=>e.documentID),u=[...new Set(o.map(e=>e.modelID))],d=new Map((a.length>0?await i.resolveList(u,a):[]).map(e=>[e.id,e])),c=[];for(let e of o){let t=d.get(e.documentID);null!=t&&c.push({cursor:String(c.length),node:t,rank:Math.abs(e.rank)})}return{edges:c,pageInfo:{hasNextPage:!1,hasPreviousPage:!1,startCursor:c.length>0?c[0].cursor:null,endCursor:c.length>0?c[c.length-1].cursor:null}}}}}this._includeMutations&&(this._mutations.setModelAccessDefaults={type:H,args:{modelId:{type:new f(u)},permissionType:{type:new f(_)},accessLevel:{type:new f(_)},allowedDIDs:{type:toList(_)}},resolve:async(e,t,i)=>await i.executeSetModelAccessDefaults(t.modelId,t.permissionType,t.accessLevel,t.allowedDIDs??null)},this._mutations.removeModelAccessDefaults={type:r,args:{modelId:{type:new f(u)},permissionTypes:{type:new f(toList(_))}},resolve:async(e,t,i)=>(await i.executeRemoveModelAccessDefaults(t.modelId,t.permissionTypes),!0)},this._mutations.setDocumentAccessOverride={type:e.documentInterface,args:{documentId:{type:new f(u)},permissionType:{type:new f(_)},accessLevel:{type:new f(_)},allowedDIDs:{type:toList(_)}},resolve:async(e,t,i)=>await i.executeSetDocumentAccessOverride(t.documentId,t.permissionType,t.accessLevel,t.allowedDIDs??null)},this._mutations.removeDocumentAccessOverride={type:r,args:{documentId:{type:new f(u)},permissionTypes:{type:new f(toList(_))}},resolve:async(e,t,i)=>(await i.executeRemoveDocumentAccessOverride(t.documentId,t.permissionTypes),!0)});let n={query:new m({name:"Query",fields:t})};return this._includeMutations&&(n.mutation=new m({name:"Mutation",fields:this._mutations})),this._includeSubscriptions&&(n.subscription=new m({name:"Subscription",fields:()=>{let t={documentChanged:{type:new f(e.documentInterface),args:{id:{type:new f(u)}},resolve:e=>e,subscribe:(e,t,i)=>i.subscribeToDocumentChanged(t.id)},documentRemoved:{type:new f(u),resolve:e=>e,subscribe:(e,t,i)=>i.subscribeToDocumentRemoved()}};for(let[e,i]of Object.entries(this._record)){if(!this._includedModelIDs.has(e))continue;let n=this._aliases[e];for(let s of(t[`new${n}Created`]={type:new f(this._types[e]),resolve:e=>e,subscribe:(t,i,n)=>n.subscribeToDocumentCreated(this.getModelIDs(e))},i.interfaces??[])){let i=getGlobalID(s,e).toString();for(let[e,n]of Object.entries(this._relationsTo[i]??{}))for(let s of n)this._buildRelationSubscriptions(t,i,e,s)}for(let[i,n]of Object.entries(this._relationsTo[e]??{}))for(let s of n)this._includedModelIDs.has(s.model)&&this._buildRelationSubscriptions(t,e,i,s)}return t}})),new y(n)}_getDefinitions(){if(null==this._definitions)throw Error("Definitions must be initialized");return this._definitions}_getReference(e,t){let i=G(e),n=this._types[i];if(null!=n)return n;let s=this._getReferenceSchema(i);return this._buildReference(i,s,t)}_getReferenceSchema(e){let t=this._references[e];if(null==t)throw Error(`Could not find reference: ${e}`);return t}_getReferenceInput(e,t,i=""){let n=G(e),s=this._getReferenceSchema(n);if("array"===s.type)return new h(new f(this._getReferenceInput(s.items.$ref,!1,s.title)));if("object"===s.type){if(s.additionalProperties)return T;let e=t?`${n}-partial`:n,r=this._inputObjects[e];if(null!=r)return r;let o={};for(let[e,i]of Object.entries(s.properties)){let n=this._getReferenceInput(i.$ref,t,s.title);o[e]={type:t||!s.required.includes(e)?n:new f(n)}}let a=this._aliases[n]??l(i+s.title),u=new d({name:t?`Partial${a}Input`:`${a}Input`,fields:o});return this._inputObjects[e]=u,u}return this._buildScalar(n,s,i)}_buildSharedDefinitions(){let i=$(async(i,s)=>{if(i.startsWith("did:"))return i;let l=n(i);if(l instanceof e)return l;if(l instanceof t)return await s.loadDocument(i);throw Error(`Unsupported node ID: ${i}`)},t=>"string"==typeof t?"AccountNode":t instanceof e?"AttachmentNode":this._aliases[t.model]),s=new m({name:"AccountNode",interfaces:[i.nodeInterface],fields:()=>{let e={id:{type:new f(u),description:"Globally unique identifier of the account (DID string)",resolve:e=>e},isViewer:{type:new f(r),description:"Whether the authenticated request issuer is this account or not",resolve:(e,t,i)=>i.getViewer()===e}};for(let[i,n]of Object.entries(this._record))if(this._includedModelIDs.has(i))switch(n.behavior){case"interface":continue;case"default":{let t=this._aliases[i];e[`own${t}Connection`]={type:this._types[`${i}-connection`],args:{...D,filter:{type:this._inputObjects[`${i}-filter`]},orderBy:{type:toList(this._inputObjects[`${i}-order`])}},resolve:async(e,t,n)=>await n.resolveConnection(this.getModelIDs(i),{...t,owner:e})};break}case"unique":{let s=this._aliases[i];e[`own${s}`]={type:this._types[i],args:n.uniqueFields.length?{with:{type:new f(this._buildSetInputObjectType(i,n.uniqueFields))}}:{},resolve:async(e,s,l)=>{let r=q(n.uniqueFields,s.with),o=t.create(i,e,r);return await l.loadDocument(o.toString())}}}}return e}}),l=new m({name:"AttachmentNode",interfaces:[i.nodeInterface],fields:{id:{type:new f(u),resolve:e=>e.toString()},contentLength:{type:new f(c)},mimeType:{type:new f(_)}}}),o={id:{type:new f(u)},model:{type:new f(_)},owner:{type:new f(s)},createdAt:{type:new f(j)},updatedAt:{type:j},accessPermissions:{type:z,resolve:e=>e.data?.accessPermissions??null}},a=new p({name:"DocumentNode",interfaces:[i.nodeInterface],fields:o,resolveType:e=>this._aliases[e.model]}),d={node:i.nodeField,viewer:{type:s,description:"Account issuing the request, if authenticated",resolve:(e,t,i)=>i.getViewer()}};return{...i,accountObject:s,attachmentObject:l,documentInterface:a,documentNodeFields:o,queryFields:d}}_buildDocument(e){if(null!=this._types[e])return this._types[e];let t=this._record[e];if(null==t)throw Error(`Could not find model id: ${e}`);let i=this._getDefinitions();this._aliases[e]??=l(t.name);let n=this._aliases[e],s={name:n,interfaces:()=>[i.documentInterface,i.nodeInterface].concat(t.interfaces.map(t=>this._types[getGlobalID(t,e).toString()]).filter(e=>null!=e)),fields:()=>this._buildDocumentFields(e,t,n)},r="interface"===t.behavior,o=r?new p({...s,resolveType:e=>this._aliases[e.model]}):new m(s),{connectionType:a,edgeType:d}=I({nodeType:o});return this._types[e]=o,this._types[`${e}-connection`]=a,this._types[`${e}-edge`]=d,this._buildDocumentDataObject(e,t,n),this._buildDocumentInput(e,t),this._buildObjectFilterInput(e,t.schema,n,!0),this._buildObjectOrderByInput(e,t.schema,n,!0),!r&&this._includeMutations&&("default"===t.behavior?this._mutations[`create${n}`]=O({name:`Create${n}`,inputFields:()=>({data:{type:new f(this._inputObjects[e])}}),outputFields:()=>({...i.queryFields,document:{type:this._types[e]}}),mutateAndGetPayload:async(t,i,n)=>({document:await i.executeCreateMutation(e,t.data,n)})}):"unique"===t.behavior&&(this._mutations[`set${n}`]=O({name:`Set${n}`,inputFields:()=>({data:{type:new f(this._inputObjects[e])}}),outputFields:()=>({...i.queryFields,document:{type:this._types[e]}}),mutateAndGetPayload:async(i,n,s)=>{let l=q(t.uniqueFields,i.data);return{document:await n.executeSetMutation(e,l,i.data,s)}}})),this._mutations[`update${n}`]=O({name:`Update${n}`,inputFields:()=>({id:{type:new f(u)},patch:{type:new f(toList(PatchOperation))},from:{type:this._inputObjects[`${e}-update`]}}),outputFields:()=>({...i.queryFields,document:{type:this._types[e]}}),mutateAndGetPayload:async(e,t,i)=>({document:await t.executeUpdateMutation(e,i)})}),this._mutations[`remove${n}`]=O({name:`Remove${n}`,inputFields:()=>({id:{type:new f(u)}}),outputFields:()=>({...i.queryFields,id:{type:new f(u)}}),mutateAndGetPayload:async(e,t,i)=>(await t.executeRemoveMutation(e.id,i),{id:e.id})})),o}_buildDocumentFields(t,i,n){let s=this._buildDocumentDataObject(t,i,n),l=this._getDefinitions(),r={...l.documentNodeFields,data:{type:new f(s)}};for(let[n,o]of Object.entries(s.getFields())){let s=i.fieldsMeta[n];if(null!=s)switch(s.type){case"account":C(o.type)?r[`${n}Accounts`]={type:new f(toOuputList(l.accountObject)),resolve:e=>e.data?.[n]??[]}:r[`${n}Account`]={type:l.accountObject,resolve:e=>e.data?.[n]??null};break;case"attachment":C(o.type)?r[`${n}Attachments`]={type:new f(toOuputList(l.attachmentObject)),resolve:t=>(t.data?.[n]??[]).map(e.fromString)}:r[`${n}Attachment`]={type:l.attachmentObject,resolve:t=>{let i=t.data?.[n];return i?e.fromString(i):null}};break;case"document":{let e,i,a=s.model;if(void 0===a)continue;null===a?(e=null,i=l.documentInterface):(e=getGlobalID(a,t).toString(),this._includedModelIDs.has(e)?i=this._buildDocument(e):(i=l.documentInterface,e=null)),C(o.type)?r[`${n}Documents`]={type:new f(toOuputList(i)),resolve:async(t,i,s)=>{let l=t.data?.[n]??[];return l.length?await s.resolveList(this.getModelIDs(e),l):[]}}:r[`${n}Document`]={type:i,resolve:async(e,t,i)=>{let s=e.data?.[n];return s?await i.loadDocument(s):null}}}}}for(let e of i.interfaces??[]){let i=getGlobalID(e,t).toString();for(let[e,t]of Object.entries(this._relationsTo[i]??{}))for(let i of t)this._includedModelIDs.has(i.model)&&this._buildRelationFields(r,e,i)}for(let[e,i]of Object.entries(this._relationsTo[t]??{}))for(let t of i)this._includedModelIDs.has(t.model)&&this._buildRelationFields(r,e,t);return r}_buildDocumentDataObject(e,t,i){let n=`${e}-data`;if(null!=this._types[n])return this._types[n];let s=t.schema.required??[],l={};for(let[i,n]of Object.entries(t.schema.properties??{})){let t=n.$ref;if(null==t)throw Error(`Missing ref for field ${i} of object ${e}`);let r=this._getReference(t);l[i]={type:s.includes(i)?new f(r):r}}let r={name:`${i}Data`,interfaces:()=>t.interfaces.map(t=>{let i=`${getGlobalID(t,e).toString()}-data`;return this._types[i]}).filter(e=>null!=e),fields:l},o="interface"===t.behavior?new p(r):new m(r);return this._types[n]=o,o}_buildDocumentInput(e,t){if("interface"===t.behavior)return;let i={},n={};for(let[e,s]of Object.entries(t.schema.properties)){let l=s.$ref,r=this._getReferenceInput(l,!1);i[e]={type:t.schema.required.includes(e)?new f(r):r};let o=G(l);null==this._getReferenceSchema(o).const&&(n[e]={type:this._getReferenceInput(l,!0)})}let s=this._aliases[e];this._inputObjects[e]=new d({name:`${s}Input`,fields:i}),this._inputObjects[`${e}-update`]=new d({name:`Partial${s}Input`,fields:n})}_buildReferenceFilterInput(e,t=""){let i=G(e),n=this._getReferenceSchema(i),s=t;if("object"===n.type)return n.additionalProperties?null:this._buildObjectFilterInput(i,n,t);if("array"===n.type){let e=this._aliases[i]??l(t+n.title);return s=e,new d({name:`${e}Filter`,isOneOf:!0,fields:{isNull:{type:r}}})}let o=this._buildScalar(i,n,s),a=B[o.name];if(null!=a)return this._inputObjects[a];let u=b(o)?new d({name:`${o.name}ValueFilter`,isOneOf:!0,fields:{isNull:{type:r},equalTo:{type:o},notEqualTo:{type:o},in:{type:toList(o)},notIn:{type:toList(o)}}}):V(o);return null==this._inputObjects[u.name]&&(this._inputObjects[u.name]=u),this._inputObjects[u.name]}_buildReferenceOrderByInput(e,t=""){let i=G(e),n=this._getReferenceSchema(i);switch(n.type){case"array":return null;case"object":return n.additionalProperties?null:this._buildObjectOrderByInput(i,n,t);default:return U}}_buildObjectFilterInput(e,t,i="",n=!1){let s=`${e}-filter`,r=this._inputObjects[s];if(null!=r)return r;let o=this._aliases[e]??l(i+(t.title??"")),a=n?{_owner:{type:this._inputObjects.AccountValueFilter}}:{};for(let[e,i]of Object.entries(t.properties)){let t=this._buildReferenceFilterInput(i.$ref,o);null!==t&&(a[e]={type:t})}let u=new d({name:`${o}ObjectFilter`,fields:a}),c=new d({name:`${o}Filter`,isOneOf:!0,fields:()=>({where:{type:u},and:{type:toList(this._inputObjects[s])},or:{type:toList(this._inputObjects[s])},not:{type:this._inputObjects[s]}})});return this._inputObjects[s]=c,c}_buildObjectOrderByInput(e,t,i="",n=!1){let s=`${e}-order`,r=this._inputObjects[s];if(null!=r)return r;let o=this._aliases[e]??l(i+(t.title??"")),a=n?{_docOwner:{type:U},_createdAt:{type:U}}:{};for(let[e,i]of Object.entries(t.properties)){let t=this._buildReferenceOrderByInput(i.$ref,o);null!=t&&(a[e]={type:t})}let u=new d({name:`${o}OrderBy`,isOneOf:!0,fields:a});return this._inputObjects[s]=u,u}_buildSetInputObjectType(e,t,i){let n=i?l(i):"",s=`${e}-with-${n}`,r=this._inputObjects[s];if(null!=r)return r;let o=this._record[e],a=this._aliases[e];return this._inputObjects[s]=new d({name:`With${n}${a}Input`,fields:()=>{let e={};for(let i of t){let t=o.schema.properties[i];if(null==t)throw Error(`Field ${i} not found on model ${a}`);let n=this._getReferenceInput(t.$ref,!1);e[i]={type:new f(n)}}return e}}),this._inputObjects[s]}_buildReference(e,t,i=""){if(null!=this._types[e])return this._types[e];switch(t.type){case"array":return this._types[e]=this._buildArray(t,i),this._types[e];case"object":return this._types[e]=t.additionalProperties?T:this._buildObject(e,t,i),this._types[e];default:return this._buildScalar(e,t,i)}}_buildScalar(e,t,i=""){if(null!=this._types[e])return this._types[e];switch(t.type){case"boolean":this._types[e]=r;break;case"integer":this._types[e]=c;break;case"number":this._types[e]=a;break;case"string":this._types[e]=this._buildString(t,i)}return this._types[e]}_buildArray(e,t=""){let i=e.items.$ref;return new h(new f(this._getReference(i,l(t+e.title))))}_buildObject(e,t,i=""){let n=l(i+t.title),s=this._buildObjectFields(e,t,n);return new m({name:n,fields:s})}_buildObjectFields(e,t,i=""){let n=t.required??[],s={};for(let[l,r]of Object.entries(t.properties??{})){let t=r.$ref;if(null==t)throw Error(`Missing ref for field ${l} of object ${e}`);let o=this._getReference(t,i);s[l]={type:n.includes(l)?new f(o):o}}return s}_buildRelationFields(e,t,i){if(i.isList)return;let n=i.model,s=`${l(t)}Of${this._aliases[n]}`;e[`in${s}`]={type:new f(this._types[`${n}-connection`]),args:{...D,filter:{type:this._inputObjects[`${n}-filter`]},orderBy:{type:toList(this._inputObjects[`${n}-order`])}},resolve:(e,i,s)=>{let l={where:{[t]:{equalTo:e.id}}},r=i.filter?{and:[i.filter,l]}:l;return s.resolveConnection(this.getModelIDs(n),{...i,filter:r})}}}_buildRelationSubscriptions(e,t,i,n){if(n.isList)return;let s=l(i);e[`edge${this._aliases[n.model]}AddedAs${s}To${this._aliases[t]}`]={type:new f(this._types[`${n.model}-edge`]),args:{id:{type:new f(u)}},resolve:e=>e,subscribe:(e,t,s)=>s.subscribeToDocumentEdgeAdded(this.getModelIDs(n.model),e=>e[i]===t.id)},e[`edge${this._aliases[n.model]}RemovedAs${s}From${this._aliases[t]}`]={type:new f(u),args:{id:{type:new f(u)}},resolve:e=>e,subscribe:(e,t,s)=>s.subscribeToDocumentEdgeRemoved(this.getModelIDs(n.model),e=>e[i]===t.id)}}_buildString(e,t){return null!=e.const?_:null!=e.enum?this._buildEnumString(e,t):null!=e.format?this._buildFormatString(e):this._buildCustomString(e)}_buildCustomString(e){return null==e.title?_:E[e.title.toLowerCase()]??_}_buildEnumString(e,t=""){let i=l(t+e.title),n=`${i}-enum`;if(null==this._types[n]){let t={};for(let i of e.enum)t[i]={value:i};this._types[n]=new o({name:i,values:t})}return this._types[n]}_buildFormatString(e){let t=P[e.format];if(null==t)throw Error(`Unsupported string format: ${e.format}`);return t}}export function createSchema(e){return new SchemaBuilder(e).build()}