@kubun/graphql 0.4.4 → 0.5.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,1202 +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
- _definitions;
226
- _implemented = {};
227
- _inputObjects = {
228
- ...VALUE_FILTER_INPUTS,
229
- PatchOperation
230
- };
231
- _mutations = {};
232
- _nodeModelIDs;
233
- _record;
234
- _references = {};
235
- _relationsTo = {};
236
- _subscriptions = {};
237
- _types = {
238
- OrderByDirection
239
- };
240
- constructor(params){
241
- this._aliases = params.aliases ?? {};
242
- this._record = params.record;
243
- const referencedInterfaces = {};
244
- for (const [modelIDstring, model] of Object.entries(params.record)){
245
- const modelID = DocumentModelID.fromString(modelIDstring);
246
- Object.assign(this._references, model.schema.$defs ?? {});
247
- // Extract interfaces
248
- for (const iid of model.interfaces){
249
- const interfaceID = getGlobalID(iid, modelID).toString();
250
- referencedInterfaces[interfaceID] ??= new Set();
251
- referencedInterfaces[interfaceID].add(modelIDstring);
252
- }
253
- // Extract inverse relations
254
- for (const [fieldName, meta] of Object.entries(model.fieldsMeta)){
255
- if (meta.type === 'document' && meta.model != null) {
256
- const relationRefID = getReferenceID(model.schema.properties[fieldName]?.$ref);
257
- const isList = this._getReferenceSchema(relationRefID).type === 'array';
258
- const relationID = getGlobalID(meta.model, modelID).toString();
259
- this._relationsTo[relationID] ??= {};
260
- this._relationsTo[relationID][fieldName] ??= [];
261
- this._relationsTo[relationID][fieldName].push({
262
- model: modelIDstring,
263
- isList
264
- });
265
- }
266
- }
267
- }
268
- // Map interface IDs to all the models implementing them
269
- const nodeModelIDs = new Set();
270
- for (const id of Object.keys(referencedInterfaces)){
271
- const implementedBy = new Set();
272
- collectAllModelIDs(implementedBy, referencedInterfaces, id);
273
- if (implementedBy.size !== 0) {
274
- this._implemented[id] = Array.from(implementedBy);
275
- }
276
- for (const modelID of implementedBy){
277
- nodeModelIDs.add(modelID);
278
- }
279
- }
280
- this._nodeModelIDs = Array.from(nodeModelIDs);
281
- }
282
- getModelIDs(id) {
283
- return id === null ? this._nodeModelIDs : this._implemented[id] ?? [
284
- id
285
- ];
286
- }
287
- build() {
288
- const definitions = this._buildSharedDefinitions();
289
- this._definitions = definitions;
290
- const queryFields = {
291
- nodes: definitions.nodesField,
292
- ...definitions.queryFields
293
- };
294
- for (const id of Object.keys(this._record)){
295
- this._buildDocument(id);
296
- const name = this._aliases[id];
297
- const filterArg = {
298
- type: this._inputObjects[`${id}-filter`]
299
- };
300
- queryFields[`all${name}`] = {
301
- type: this._types[`${id}-connection`],
302
- args: {
303
- ...connectionArgs,
304
- filter: filterArg,
305
- orderBy: {
306
- type: toList(this._inputObjects[`${id}-order`])
307
- }
308
- },
309
- resolve: async (_, args, ctx)=>{
310
- return await ctx.resolveConnection(this.getModelIDs(id), args);
311
- }
312
- };
313
- }
314
- return new GraphQLSchema({
315
- query: new GraphQLObjectType({
316
- name: 'Query',
317
- fields: queryFields
318
- }),
319
- mutation: new GraphQLObjectType({
320
- name: 'Mutation',
321
- fields: this._mutations
322
- }),
323
- subscription: new GraphQLObjectType({
324
- name: 'Subscription',
325
- fields: ()=>{
326
- const fields = {
327
- documentChanged: {
328
- type: new GraphQLNonNull(definitions.documentInterface),
329
- args: {
330
- id: {
331
- type: new GraphQLNonNull(GraphQLID)
332
- }
333
- },
334
- resolve: (doc)=>doc,
335
- subscribe: (_, args, ctx)=>{
336
- return ctx.subscribeToDocumentChanged(args.id);
337
- }
338
- },
339
- documentRemoved: {
340
- type: new GraphQLNonNull(GraphQLID),
341
- resolve: (id)=>id,
342
- subscribe: (_src, _args, ctx)=>{
343
- return ctx.subscribeToDocumentRemoved();
344
- }
345
- }
346
- };
347
- for (const [id, model] of Object.entries(this._record)){
348
- const name = this._aliases[id];
349
- fields[`new${name}Created`] = {
350
- type: new GraphQLNonNull(this._types[id]),
351
- resolve: (doc)=>doc,
352
- subscribe: (_src, _args, ctx)=>{
353
- return ctx.subscribeToDocumentCreated(this.getModelIDs(id));
354
- }
355
- };
356
- for (const iid of model.interfaces ?? []){
357
- const interfaceID = getGlobalID(iid, id).toString();
358
- for (const [fieldName, relations] of Object.entries(this._relationsTo[interfaceID] ?? {})){
359
- for (const relation of relations){
360
- this._buildRelationSubscriptions(fields, interfaceID, fieldName, relation);
361
- }
362
- }
363
- }
364
- for (const [fieldName, relations] of Object.entries(this._relationsTo[id] ?? {})){
365
- for (const relation of relations){
366
- this._buildRelationSubscriptions(fields, id, fieldName, relation);
367
- }
368
- }
369
- }
370
- return fields;
371
- }
372
- })
373
- });
374
- }
375
- _getDefinitions() {
376
- if (this._definitions == null) {
377
- throw new Error('Definitions must be initialized');
378
- }
379
- return this._definitions;
380
- }
381
- _getReference(path, parentName) {
382
- const id = getReferenceID(path);
383
- const type = this._types[id];
384
- if (type != null) {
385
- return type;
386
- }
387
- const schema = this._getReferenceSchema(id);
388
- return this._buildReference(id, schema, parentName);
389
- }
390
- _getReferenceSchema(id) {
391
- const schema = this._references[id];
392
- if (schema == null) {
393
- throw new Error(`Could not find reference: ${id}`);
394
- }
395
- return schema;
396
- }
397
- _getReferenceInput(path, isPartial, parentName = '') {
398
- const id = getReferenceID(path);
399
- const schema = this._getReferenceSchema(id);
400
- if (schema.type === 'array') {
401
- const itemType = this._getReferenceInput(schema.items.$ref, false, schema.title);
402
- return new GraphQLList(new GraphQLNonNull(itemType));
403
- }
404
- if (schema.type === 'object') {
405
- if (schema.additionalProperties) {
406
- return GraphQLJSONObject;
407
- }
408
- const inputID = isPartial ? `${id}-partial` : id;
409
- const existing = this._inputObjects[inputID];
410
- if (existing != null) {
411
- return existing;
412
- }
413
- const fields = {};
414
- for (const [key, value] of Object.entries(schema.properties)){
415
- const type = this._getReferenceInput(value.$ref, isPartial, schema.title);
416
- fields[key] = {
417
- type: isPartial || !schema.required.includes(key) ? type : new GraphQLNonNull(type)
418
- };
419
- }
420
- const name = this._aliases[id] ?? pascalCase(parentName + schema.title);
421
- const input = new GraphQLInputObjectType({
422
- name: isPartial ? `Partial${name}Input` : `${name}Input`,
423
- fields
424
- });
425
- this._inputObjects[inputID] = input;
426
- return input;
427
- }
428
- return this._buildScalar(id, schema, parentName);
429
- }
430
- _buildSharedDefinitions() {
431
- const nodeDefs = nodeDefinitions(async (id, ctx)=>{
432
- if (id.startsWith('did:')) {
433
- return id;
434
- }
435
- const decoded = decodeID(id);
436
- if (decoded instanceof AttachmentID) {
437
- return decoded;
438
- }
439
- if (decoded instanceof DocumentID) {
440
- return await ctx.loadDocument(id);
441
- }
442
- throw new Error(`Unsupported node ID: ${id}`);
443
- }, (node)=>{
444
- return typeof node === 'string' ? 'AccountNode' : node instanceof AttachmentID ? 'AttachmentNode' : this._aliases[node.model];
445
- });
446
- const accountObject = new GraphQLObjectType({
447
- name: 'AccountNode',
448
- interfaces: [
449
- nodeDefs.nodeInterface
450
- ],
451
- fields: ()=>{
452
- const config = {
453
- id: {
454
- type: new GraphQLNonNull(GraphQLID),
455
- description: 'Globally unique identifier of the account (DID string)',
456
- resolve: (did)=>did
457
- },
458
- isViewer: {
459
- type: new GraphQLNonNull(GraphQLBoolean),
460
- description: 'Whether the authenticated request issuer is this account or not',
461
- resolve: (did, _, ctx)=>ctx.getViewer() === did
462
- }
463
- };
464
- for (const [id, model] of Object.entries(this._record)){
465
- switch(model.behavior){
466
- case 'interface':
467
- continue;
468
- case 'default':
469
- {
470
- const name = this._aliases[id];
471
- config[`own${name}Connection`] = {
472
- type: this._types[`${id}-connection`],
473
- args: {
474
- ...connectionArgs,
475
- filter: {
476
- type: this._inputObjects[`${id}-filter`]
477
- },
478
- orderBy: {
479
- type: toList(this._inputObjects[`${id}-order`])
480
- }
481
- },
482
- resolve: async (owner, args, ctx)=>{
483
- return await ctx.resolveConnection(this.getModelIDs(id), {
484
- ...args,
485
- owner
486
- });
487
- }
488
- };
489
- break;
490
- }
491
- case 'unique':
492
- {
493
- const name = this._aliases[id];
494
- config[`own${name}`] = {
495
- type: this._types[id],
496
- args: model.uniqueFields.length ? {
497
- with: {
498
- type: new GraphQLNonNull(this._buildSetInputObjectType(id, model.uniqueFields))
499
- }
500
- } : {},
501
- resolve: async (owner, args, ctx)=>{
502
- const unique = getUniqueDocValue(model.uniqueFields, args.with);
503
- const docID = DocumentID.create(id, owner, unique);
504
- return await ctx.loadDocument(docID.toString());
505
- }
506
- };
507
- }
508
- }
509
- }
510
- return config;
511
- }
512
- });
513
- const attachmentObject = new GraphQLObjectType({
514
- name: 'AttachmentNode',
515
- interfaces: [
516
- nodeDefs.nodeInterface
517
- ],
518
- fields: {
519
- id: {
520
- type: new GraphQLNonNull(GraphQLID),
521
- resolve: (aid)=>aid.toString()
522
- },
523
- contentLength: {
524
- type: new GraphQLNonNull(GraphQLInt)
525
- },
526
- mimeType: {
527
- type: new GraphQLNonNull(GraphQLString)
528
- }
529
- }
530
- });
531
- const documentNodeFields = {
532
- // Node interface
533
- id: {
534
- type: new GraphQLNonNull(GraphQLID)
535
- },
536
- // Metadata
537
- model: {
538
- type: new GraphQLNonNull(GraphQLString)
539
- },
540
- owner: {
541
- type: new GraphQLNonNull(accountObject)
542
- },
543
- createdAt: {
544
- type: new GraphQLNonNull(GraphQLDateTimeISO)
545
- },
546
- updatedAt: {
547
- type: GraphQLDateTimeISO
548
- }
549
- };
550
- const documentInterface = new GraphQLInterfaceType({
551
- name: 'DocumentNode',
552
- interfaces: [
553
- nodeDefs.nodeInterface
554
- ],
555
- fields: documentNodeFields
556
- });
557
- const queryFields = {
558
- node: nodeDefs.nodeField,
559
- viewer: {
560
- type: accountObject,
561
- description: 'Account issuing the request, if authenticated',
562
- resolve: (_self, _args, ctx)=>ctx.getViewer()
563
- }
564
- };
565
- return {
566
- ...nodeDefs,
567
- accountObject,
568
- attachmentObject,
569
- documentInterface,
570
- documentNodeFields,
571
- queryFields
572
- };
573
- }
574
- _buildDocument(id) {
575
- if (this._types[id] != null) {
576
- return this._types[id];
577
- }
578
- const model = this._record[id];
579
- if (model == null) {
580
- throw new Error(`Could not find model id: ${id}`);
581
- }
582
- const definitions = this._getDefinitions();
583
- this._aliases[id] ??= pascalCase(model.name);
584
- const name = this._aliases[id];
585
- const config = {
586
- name,
587
- interfaces: ()=>{
588
- return [
589
- definitions.documentInterface,
590
- definitions.nodeInterface
591
- ].concat(model.interfaces.map((interfaceID)=>{
592
- return this._types[getGlobalID(interfaceID, id).toString()];
593
- }));
594
- },
595
- fields: ()=>this._buildDocumentFields(id, model, name)
596
- };
597
- const isInterface = model.behavior === 'interface';
598
- const nodeType = isInterface ? new GraphQLInterfaceType({
599
- ...config,
600
- resolveType: (doc)=>this._aliases[doc.model]
601
- }) : new GraphQLObjectType(config);
602
- const { connectionType, edgeType } = connectionDefinitions({
603
- nodeType
604
- });
605
- this._types[id] = nodeType;
606
- this._types[`${id}-connection`] = connectionType;
607
- this._types[`${id}-edge`] = edgeType;
608
- this._buildDocumentDataObject(id, model, name);
609
- this._buildDocumentInput(id, model);
610
- this._buildObjectFilterInput(id, model.schema, name, true);
611
- this._buildObjectOrderByInput(id, model.schema, name, true);
612
- // TODO: move to dedicated method
613
- if (!isInterface) {
614
- if (model.behavior === 'default') {
615
- this._mutations[`create${name}`] = mutationWithClientMutationId({
616
- name: `Create${name}`,
617
- inputFields: ()=>({
618
- data: {
619
- type: new GraphQLNonNull(this._inputObjects[id])
620
- }
621
- }),
622
- outputFields: ()=>({
623
- ...definitions.queryFields,
624
- document: {
625
- type: this._types[id]
626
- }
627
- }),
628
- mutateAndGetPayload: async (input, ctx, info)=>{
629
- const document = await ctx.executeCreateMutation(id, input.data, info);
630
- return {
631
- document
632
- };
633
- }
634
- });
635
- } else if (model.behavior === 'unique') {
636
- this._mutations[`set${name}`] = mutationWithClientMutationId({
637
- name: `Set${name}`,
638
- inputFields: ()=>({
639
- data: {
640
- type: new GraphQLNonNull(this._inputObjects[id])
641
- }
642
- }),
643
- outputFields: ()=>({
644
- ...definitions.queryFields,
645
- document: {
646
- type: this._types[id]
647
- }
648
- }),
649
- mutateAndGetPayload: async (input, ctx, info)=>{
650
- const unique = getUniqueDocValue(model.uniqueFields, input.data);
651
- const document = await ctx.executeSetMutation(id, unique, input.data, info);
652
- return {
653
- document
654
- };
655
- }
656
- });
657
- }
658
- this._mutations[`update${name}`] = mutationWithClientMutationId({
659
- name: `Update${name}`,
660
- inputFields: ()=>({
661
- id: {
662
- type: new GraphQLNonNull(GraphQLID)
663
- },
664
- patch: {
665
- type: new GraphQLNonNull(toList(PatchOperation))
666
- },
667
- from: {
668
- type: this._inputObjects[`${id}-update`]
669
- }
670
- }),
671
- outputFields: ()=>({
672
- ...definitions.queryFields,
673
- document: {
674
- type: this._types[id]
675
- }
676
- }),
677
- mutateAndGetPayload: async (input, ctx, info)=>{
678
- const document = await ctx.executeUpdateMutation(input, info);
679
- return {
680
- document
681
- };
682
- }
683
- });
684
- this._mutations[`remove${name}`] = mutationWithClientMutationId({
685
- name: `Remove${name}`,
686
- inputFields: ()=>({
687
- id: {
688
- type: new GraphQLNonNull(GraphQLID)
689
- }
690
- }),
691
- outputFields: ()=>definitions.queryFields,
692
- mutateAndGetPayload: async (input, ctx, info)=>{
693
- await ctx.executeRemoveMutation(input.id, info);
694
- return {};
695
- }
696
- });
697
- }
698
- return nodeType;
699
- }
700
- _buildDocumentFields(id, model, name) {
701
- const dataObject = this._buildDocumentDataObject(id, model, name);
702
- const definitions = this._getDefinitions();
703
- const fields = {
704
- ...definitions.documentNodeFields,
705
- data: {
706
- type: new GraphQLNonNull(dataObject)
707
- }
708
- };
709
- for (const [key, field] of Object.entries(dataObject.getFields())){
710
- const meta = model.fieldsMeta[key];
711
- if (meta == null) {
712
- continue;
713
- }
714
- switch(meta.type){
715
- case 'account':
716
- {
717
- if (isList(field.type)) {
718
- fields[`${key}Accounts`] = {
719
- type: new GraphQLNonNull(toOuputList(definitions.accountObject)),
720
- resolve: (doc)=>doc.data?.[key] ?? []
721
- };
722
- } else {
723
- fields[`${key}Account`] = {
724
- type: definitions.accountObject,
725
- resolve: (doc)=>doc.data?.[key] ?? null
726
- };
727
- }
728
- break;
729
- }
730
- case 'attachment':
731
- {
732
- if (isList(field.type)) {
733
- fields[`${key}Attachments`] = {
734
- type: new GraphQLNonNull(toOuputList(definitions.attachmentObject)),
735
- resolve: (doc)=>{
736
- const ids = doc.data?.[key] ?? [];
737
- return ids.map(AttachmentID.fromString);
738
- }
739
- };
740
- } else {
741
- fields[`${key}Attachment`] = {
742
- type: definitions.attachmentObject,
743
- resolve: (doc)=>{
744
- const id = doc.data?.[key];
745
- return id ? AttachmentID.fromString(id) : null;
746
- }
747
- };
748
- }
749
- break;
750
- }
751
- case 'document':
752
- {
753
- const relationModel = meta.model;
754
- if (typeof relationModel === 'undefined') {
755
- continue;
756
- }
757
- let relationModelID;
758
- let relationType;
759
- if (relationModel === null) {
760
- relationModelID = null;
761
- relationType = definitions.documentInterface;
762
- } else {
763
- relationModelID = getGlobalID(relationModel, id).toString();
764
- relationType = this._buildDocument(relationModelID);
765
- }
766
- if (isList(field.type)) {
767
- // TODO: add suport for filters?
768
- fields[`${key}Documents`] = {
769
- type: new GraphQLNonNull(toOuputList(relationType)),
770
- resolve: async (doc, _args, ctx)=>{
771
- const relationIDs = doc.data?.[key] ?? [];
772
- return relationIDs.length ? await ctx.resolveList(this.getModelIDs(relationModelID), relationIDs) : [];
773
- }
774
- };
775
- } else {
776
- fields[`${key}Document`] = {
777
- type: relationType,
778
- resolve: async (doc, _args, ctx)=>{
779
- const relationID = doc.data?.[key];
780
- return relationID ? await ctx.loadDocument(relationID) : null;
781
- }
782
- };
783
- }
784
- break;
785
- }
786
- }
787
- }
788
- for (const iid of model.interfaces ?? []){
789
- const interfaceID = getGlobalID(iid, id).toString();
790
- for (const [fieldName, relations] of Object.entries(this._relationsTo[interfaceID] ?? {})){
791
- for (const relation of relations){
792
- this._buildRelationFields(fields, fieldName, relation);
793
- }
794
- }
795
- }
796
- for (const [fieldName, relations] of Object.entries(this._relationsTo[id] ?? {})){
797
- for (const relation of relations){
798
- this._buildRelationFields(fields, fieldName, relation);
799
- }
800
- }
801
- return fields;
802
- }
803
- _buildDocumentDataObject(id, model, name) {
804
- const typeID = `${id}-data`;
805
- if (this._types[typeID] != null) {
806
- return this._types[typeID];
807
- }
808
- const required = model.schema.required ?? [];
809
- const fields = {};
810
- for (const [key, value] of Object.entries(model.schema.properties ?? {})){
811
- const fieldRef = value.$ref;
812
- if (fieldRef == null) {
813
- throw new Error(`Missing ref for field ${key} of object ${id}`);
814
- }
815
- const type = this._getReference(fieldRef);
816
- fields[key] = {
817
- type: required.includes(key) ? new GraphQLNonNull(type) : type
818
- };
819
- }
820
- const config = {
821
- name: `${name}Data`,
822
- interfaces: ()=>{
823
- return model.interfaces.map((interfaceID)=>{
824
- const typeID = `${getGlobalID(interfaceID, id).toString()}-data`;
825
- return this._types[typeID];
826
- });
827
- },
828
- fields
829
- };
830
- const object = model.behavior === 'interface' ? new GraphQLInterfaceType(config) : new GraphQLObjectType(config);
831
- this._types[typeID] = object;
832
- return object;
833
- }
834
- _buildDocumentInput(id, model) {
835
- if (model.behavior === 'interface') {
836
- // No mutations for interfaces
837
- return;
838
- }
839
- const setFields = {};
840
- const updateFields = {};
841
- for (const [key, value] of Object.entries(model.schema.properties)){
842
- const path = value.$ref;
843
- const setType = this._getReferenceInput(path, false);
844
- setFields[key] = {
845
- type: model.schema.required.includes(key) ? new GraphQLNonNull(setType) : setType
846
- };
847
- const id = getReferenceID(path);
848
- const schema = this._getReferenceSchema(id);
849
- if (schema.const == null) {
850
- updateFields[key] = {
851
- type: this._getReferenceInput(path, true)
852
- };
853
- }
854
- }
855
- const name = this._aliases[id];
856
- this._inputObjects[id] = new GraphQLInputObjectType({
857
- name: `${name}Input`,
858
- fields: setFields
859
- });
860
- this._inputObjects[`${id}-update`] = new GraphQLInputObjectType({
861
- name: `Partial${name}Input`,
862
- fields: updateFields
863
- });
864
- }
865
- _buildReferenceFilterInput(path, parentName = '') {
866
- const id = getReferenceID(path);
867
- const schema = this._getReferenceSchema(id);
868
- let refParentName = parentName;
869
- if (schema.type === 'object') {
870
- return schema.additionalProperties ? null : this._buildObjectFilterInput(id, schema, parentName);
871
- }
872
- if (schema.type === 'array') {
873
- const name = this._aliases[id] ?? pascalCase(parentName + schema.title);
874
- refParentName = name;
875
- // const itemType = this._buildReferenceFilterInput(schema.items.$ref, schema.title)
876
- return new GraphQLInputObjectType({
877
- name: `${name}Filter`,
878
- isOneOf: true,
879
- fields: {
880
- // DB does not support filtering on array values
881
- // contains: { type: toList(itemType) },
882
- // isEmpty: { type: GraphQLBoolean },
883
- isNull: {
884
- type: GraphQLBoolean
885
- }
886
- }
887
- });
888
- }
889
- const scalar = this._buildScalar(id, schema, refParentName);
890
- const knownFilter = VALUE_FILTER_INPUT_TYPES[scalar.name];
891
- if (knownFilter != null) {
892
- return this._inputObjects[knownFilter];
893
- }
894
- const filter = isEnumType(scalar) ? createEnumValueFilterInput(scalar) : createScalarValueFilterInput(scalar);
895
- if (this._inputObjects[filter.name] == null) {
896
- this._inputObjects[filter.name] = filter;
897
- }
898
- return this._inputObjects[filter.name];
899
- }
900
- _buildReferenceOrderByInput(path, parentName = '') {
901
- const id = getReferenceID(path);
902
- const schema = this._getReferenceSchema(id);
903
- switch(schema.type){
904
- case 'array':
905
- return null;
906
- case 'object':
907
- return schema.additionalProperties ? null : this._buildObjectOrderByInput(id, schema, parentName);
908
- default:
909
- return OrderByDirection;
910
- }
911
- }
912
- _buildObjectFilterInput(id, schema, parentName = '', isDocument = false) {
913
- const inputID = `${id}-filter`;
914
- const existing = this._inputObjects[inputID];
915
- if (existing != null) {
916
- return existing;
917
- }
918
- const name = this._aliases[id] ?? pascalCase(parentName + (schema.title ?? ''));
919
- const fields = isDocument ? {
920
- _owner: {
921
- type: this._inputObjects.AccountValueFilter
922
- }
923
- } : {};
924
- for (const [key, value] of Object.entries(schema.properties)){
925
- const type = this._buildReferenceFilterInput(value.$ref, name);
926
- if (type !== null) {
927
- fields[key] = {
928
- type
929
- };
930
- }
931
- }
932
- const objectInput = new GraphQLInputObjectType({
933
- name: `${name}ObjectFilter`,
934
- fields
935
- });
936
- const input = new GraphQLInputObjectType({
937
- name: `${name}Filter`,
938
- isOneOf: true,
939
- fields: ()=>({
940
- where: {
941
- type: objectInput
942
- },
943
- and: {
944
- type: toList(this._inputObjects[inputID])
945
- },
946
- or: {
947
- type: toList(this._inputObjects[inputID])
948
- },
949
- not: {
950
- type: this._inputObjects[inputID]
951
- }
952
- })
953
- });
954
- this._inputObjects[inputID] = input;
955
- return input;
956
- }
957
- _buildObjectOrderByInput(id, schema, parentName = '', isDocument = false) {
958
- const inputID = `${id}-order`;
959
- const existing = this._inputObjects[inputID];
960
- if (existing != null) {
961
- return existing;
962
- }
963
- const name = this._aliases[id] ?? pascalCase(parentName + (schema.title ?? ''));
964
- const fields = isDocument ? {
965
- _docOwner: {
966
- type: OrderByDirection
967
- },
968
- _createdAt: {
969
- type: OrderByDirection
970
- }
971
- } : {};
972
- for (const [key, value] of Object.entries(schema.properties)){
973
- const type = this._buildReferenceOrderByInput(value.$ref, name);
974
- if (type != null) {
975
- fields[key] = {
976
- type
977
- };
978
- }
979
- }
980
- const input = new GraphQLInputObjectType({
981
- name: `${name}OrderBy`,
982
- isOneOf: true,
983
- fields
984
- });
985
- this._inputObjects[inputID] = input;
986
- return input;
987
- }
988
- _buildSetInputObjectType(id, withFields, relationField) {
989
- const relationFieldName = relationField ? pascalCase(relationField) : '';
990
- const inputID = `${id}-with-${relationFieldName}`;
991
- const existing = this._inputObjects[inputID];
992
- if (existing != null) {
993
- return existing;
994
- }
995
- const model = this._record[id];
996
- const name = this._aliases[id];
997
- this._inputObjects[inputID] = new GraphQLInputObjectType({
998
- name: `With${relationFieldName}${name}Input`,
999
- fields: ()=>{
1000
- const fields = {};
1001
- for (const fieldName of withFields){
1002
- const field = model.schema.properties[fieldName];
1003
- if (field == null) {
1004
- throw new Error(`Field ${fieldName} not found on model ${name}`);
1005
- }
1006
- const type = this._getReferenceInput(field.$ref, false);
1007
- fields[fieldName] = {
1008
- type: new GraphQLNonNull(type)
1009
- };
1010
- }
1011
- return fields;
1012
- }
1013
- });
1014
- return this._inputObjects[inputID];
1015
- }
1016
- _buildReference(id, schema, parentName = '') {
1017
- if (this._types[id] != null) {
1018
- return this._types[id];
1019
- }
1020
- switch(schema.type){
1021
- case 'array':
1022
- this._types[id] = this._buildArray(schema, parentName);
1023
- return this._types[id];
1024
- case 'object':
1025
- this._types[id] = schema.additionalProperties ? GraphQLJSONObject : this._buildObject(id, schema, parentName);
1026
- return this._types[id];
1027
- default:
1028
- return this._buildScalar(id, schema, parentName);
1029
- }
1030
- }
1031
- _buildScalar(id, schema, parentName = '') {
1032
- if (this._types[id] != null) {
1033
- return this._types[id];
1034
- }
1035
- switch(schema.type){
1036
- case 'boolean':
1037
- this._types[id] = GraphQLBoolean;
1038
- break;
1039
- case 'integer':
1040
- this._types[id] = GraphQLInt;
1041
- break;
1042
- case 'number':
1043
- this._types[id] = GraphQLFloat;
1044
- break;
1045
- case 'string':
1046
- this._types[id] = this._buildString(schema, parentName);
1047
- break;
1048
- }
1049
- return this._types[id];
1050
- }
1051
- _buildArray(schema, parentName = '') {
1052
- const itemRef = schema.items.$ref;
1053
- return new GraphQLList(new GraphQLNonNull(this._getReference(itemRef, pascalCase(parentName + schema.title))));
1054
- }
1055
- _buildObject(id, schema, parentName = '') {
1056
- const name = pascalCase(parentName + schema.title);
1057
- const fields = this._buildObjectFields(id, schema, name);
1058
- return new GraphQLObjectType({
1059
- name,
1060
- fields
1061
- });
1062
- }
1063
- _buildObjectFields(id, schema, parentName = '') {
1064
- const required = schema.required ?? [];
1065
- const fields = {};
1066
- for (const [key, value] of Object.entries(schema.properties ?? {})){
1067
- const fieldRef = value.$ref;
1068
- if (fieldRef == null) {
1069
- throw new Error(`Missing ref for field ${key} of object ${id}`);
1070
- }
1071
- const type = this._getReference(fieldRef, parentName);
1072
- fields[key] = {
1073
- type: required.includes(key) ? new GraphQLNonNull(type) : type
1074
- };
1075
- }
1076
- return fields;
1077
- }
1078
- _buildRelationFields(fields, fieldName, relation) {
1079
- if (relation.isList) {
1080
- // DB does not support matches for array containing values
1081
- return;
1082
- }
1083
- const relationID = relation.model;
1084
- const relationName = `${pascalCase(fieldName)}Of${this._aliases[relationID]}`;
1085
- fields[`in${relationName}`] = {
1086
- type: new GraphQLNonNull(this._types[`${relationID}-connection`]),
1087
- args: {
1088
- ...connectionArgs,
1089
- filter: {
1090
- type: this._inputObjects[`${relationID}-filter`]
1091
- },
1092
- orderBy: {
1093
- type: toList(this._inputObjects[`${relationID}-order`])
1094
- }
1095
- },
1096
- resolve: (doc, args, ctx)=>{
1097
- const docFilter = {
1098
- where: {
1099
- [fieldName]: {
1100
- equalTo: doc.id
1101
- }
1102
- }
1103
- };
1104
- const filter = args.filter ? {
1105
- and: [
1106
- args.filter,
1107
- docFilter
1108
- ]
1109
- } : docFilter;
1110
- return ctx.resolveConnection(this.getModelIDs(relationID), {
1111
- ...args,
1112
- filter
1113
- });
1114
- }
1115
- };
1116
- // TODO: add support for count relation
1117
- // fields[`_${fieldName}_of_${relationName}_count`] = {
1118
- // type: new GraphQLNonNull(GraphQLInt),
1119
- // }
1120
- }
1121
- _buildRelationSubscriptions(fields, modelID, fieldName, relation) {
1122
- if (relation.isList) {
1123
- // Inverse relations to list fields are not supported in connections so we skip them for subscriptions as well
1124
- return;
1125
- }
1126
- const targetFieldName = pascalCase(fieldName);
1127
- fields[`edge${this._aliases[relation.model]}AddedAs${targetFieldName}To${this._aliases[modelID]}`] = {
1128
- type: new GraphQLNonNull(this._types[`${relation.model}-edge`]),
1129
- args: {
1130
- id: {
1131
- type: new GraphQLNonNull(GraphQLID)
1132
- }
1133
- },
1134
- resolve: (edge)=>edge,
1135
- subscribe: (_src, args, ctx)=>{
1136
- return ctx.subscribeToDocumentEdgeAdded(this.getModelIDs(relation.model), (data)=>data[fieldName] === args.id);
1137
- }
1138
- };
1139
- fields[`edge${this._aliases[relation.model]}RemovedAs${targetFieldName}From${this._aliases[modelID]}`] = {
1140
- type: new GraphQLNonNull(GraphQLID),
1141
- args: {
1142
- id: {
1143
- type: new GraphQLNonNull(GraphQLID)
1144
- }
1145
- },
1146
- resolve: (id)=>id,
1147
- subscribe: (_src, args, ctx)=>{
1148
- return ctx.subscribeToDocumentEdgeRemoved(this.getModelIDs(relation.model), (data)=>data[fieldName] === args.id);
1149
- }
1150
- };
1151
- }
1152
- _buildString(schema, parentName) {
1153
- if (schema.const != null) {
1154
- return GraphQLString;
1155
- }
1156
- if (schema.enum != null) {
1157
- return this._buildEnumString(schema, parentName);
1158
- }
1159
- if (schema.format != null) {
1160
- return this._buildFormatString(schema);
1161
- }
1162
- return this._buildCustomString(schema);
1163
- }
1164
- _buildCustomString(schema) {
1165
- if (schema.title == null) {
1166
- // Plain string type
1167
- return GraphQLString;
1168
- }
1169
- const title = schema.title.toLowerCase();
1170
- return STRING_TITLES[title] ?? GraphQLString;
1171
- }
1172
- _buildEnumString(schema, parentName = '') {
1173
- const name = pascalCase(parentName + schema.title);
1174
- const typeName = `${name}-enum`;
1175
- if (this._types[typeName] == null) {
1176
- const values = {};
1177
- for (const value of schema.enum){
1178
- values[value] = {
1179
- value
1180
- };
1181
- }
1182
- this._types[typeName] = new GraphQLEnumType({
1183
- name,
1184
- values
1185
- });
1186
- }
1187
- return this._types[typeName];
1188
- }
1189
- _buildFormatString(schema) {
1190
- const scalar = STRING_FORMATS[schema.format];
1191
- if (scalar == null) {
1192
- throw new Error(`Unsupported string format: ${schema.format}`);
1193
- }
1194
- return scalar;
1195
- }
1196
- }
1197
- export function createSchema(record, aliases) {
1198
- return new SchemaBuilder({
1199
- record,
1200
- aliases
1201
- }).build();
1202
- }
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"}}});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:e.modelAccessDefaultsObject,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=new m({name:"AccessRule",fields:{level:{type:new f(_)},allowedDIDs:{type:new h(new f(_))}}}),a=new m({name:"AccessPermissions",fields:{read:{type:o},write:{type:o}}}),d=new m({name:"ModelAccessDefaults",fields:()=>({ownerDID:{type:new f(_)},modelId:{type:new f(u)},permissions:{type:new f(a)}})}),y={id:{type:new f(u)},model:{type:new f(_)},owner:{type:new f(s)},createdAt:{type:new f(j)},updatedAt:{type:j},accessPermissions:{type:a,resolve:e=>e.data?.accessPermissions??null}},b=new p({name:"DocumentNode",interfaces:[i.nodeInterface],fields:y,resolveType:e=>this._aliases[e.model]}),w={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,modelAccessDefaultsObject:d,documentInterface:b,documentNodeFields:y,queryFields:w}}_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()}