@kubun/graphql 0.1.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/LICENSE.md ADDED
@@ -0,0 +1,57 @@
1
+ # The Prosperity Public License 3.0.0
2
+
3
+ Contributor: Paul Le Cam
4
+
5
+ Source Code: https://github.com/PaulLeCam/kubun
6
+
7
+ ## Purpose
8
+
9
+ This license allows you to use and share this software for noncommercial purposes for free and to try this software for commercial purposes for thirty days.
10
+
11
+ ## Agreement
12
+
13
+ In order to receive this license, you have to agree to its rules. Those rules are both obligations under that agreement and conditions to your license. Don't do anything with this software that triggers a rule you can't or won't follow.
14
+
15
+ ## Notices
16
+
17
+ Make sure everyone who gets a copy of any part of this software from you, with or without changes, also gets the text of this license and the contributor and source code lines above.
18
+
19
+ ## Commercial Trial
20
+
21
+ Limit your use of this software for commercial purposes to a thirty-day trial period. If you use this software for work, your company gets one trial period for all personnel, not one trial per person.
22
+
23
+ ## Contributions Back
24
+
25
+ Developing feedback, changes, or additions that you contribute back to the contributor on the terms of a standardized public software license such as [the Blue Oak Model License 1.0.0](https://blueoakcouncil.org/license/1.0.0), [the Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.html), [the MIT license](https://spdx.org/licenses/MIT.html), or [the two-clause BSD license](https://spdx.org/licenses/BSD-2-Clause.html) doesn't count as use for a commercial purpose.
26
+
27
+ ## Personal Uses
28
+
29
+ Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, doesn't count as use for a commercial purpose.
30
+
31
+ ## Noncommercial Organizations
32
+
33
+ Use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution doesn't count as use for a commercial purpose regardless of the source of funding or obligations resulting from the funding.
34
+
35
+ ## Defense
36
+
37
+ Don't make any legal claim against anyone accusing this software, with or without changes, alone or with other technology, of infringing any patent.
38
+
39
+ ## Copyright
40
+
41
+ The contributor licenses you to do everything with this software that would otherwise infringe their copyright in it.
42
+
43
+ ## Patent
44
+
45
+ The contributor licenses you to do everything with this software that would otherwise infringe any patents they can license or become able to license.
46
+
47
+ ## Reliability
48
+
49
+ The contributor can't revoke this license.
50
+
51
+ ## Excuse
52
+
53
+ You're excused for unknowingly breaking [Notices](#notices) if you take all practical steps to comply within thirty days of learning you broke the rule.
54
+
55
+ ## No Liability
56
+
57
+ ***As far as the law allows, this software comes as is, without any warranty or condition, and the contributor won't be liable to anyone for any damages related to this software or this license, under any kind of legal claim.***
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # Kubun GraphQL
2
+
3
+ ## Installation
4
+
5
+ ```sh
6
+ npm install @kubun/graphql
7
+ ```
8
+
9
+ ## License
10
+
11
+ [Prosperity Public License 3.0.0](LICENSE.md)
@@ -0,0 +1,9 @@
1
+ import { GraphQLScalarType } from 'graphql';
2
+ import type { JSONSchema } from 'json-schema-typed';
3
+ export declare const GraphQLCID: GraphQLScalarType<string, string>;
4
+ export declare const cidSchema: JSONSchema.String;
5
+ export declare const GraphQLAttachmentID: GraphQLScalarType<string, string>;
6
+ export declare const attachmentIDSchema: JSONSchema.String;
7
+ export declare const GraphQLDocID: GraphQLScalarType<string, string>;
8
+ export declare const docIDSchema: JSONSchema.String;
9
+ //# sourceMappingURL=identifiers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identifiers.d.ts","sourceRoot":"","sources":["../src/identifiers.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,iBAAiB,EAAQ,MAAM,SAAS,CAAA;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAWnD,eAAO,MAAM,UAAU,mCAYrB,CAAA;AAEF,eAAO,MAAM,SAAS,EAAE,UAAU,CAAC,MAIlC,CAAA;AAWD,eAAO,MAAM,mBAAmB,mCAW9B,CAAA;AAEF,eAAO,MAAM,kBAAkB,EAAE,UAAU,CAAC,MAI3C,CAAA;AAWD,eAAO,MAAM,YAAY,mCAWvB,CAAA;AAEF,eAAO,MAAM,WAAW,EAAE,UAAU,CAAC,MAIpC,CAAA"}
@@ -0,0 +1,78 @@
1
+ import { AttachmentID, DocumentID } from '@kubun/id';
2
+ import { GraphQLError, GraphQLScalarType, Kind } from 'graphql';
3
+ import { CID } from 'multiformats/cid';
4
+ function validateCID(input) {
5
+ const cid = typeof input === 'string' ? CID.parse(input) : CID.asCID(input);
6
+ if (cid == null) {
7
+ throw new GraphQLError(`Could not parse input as CID: ${input}`);
8
+ }
9
+ return cid.toV1().toString();
10
+ }
11
+ export const GraphQLCID = new GraphQLScalarType({
12
+ name: 'ContentID',
13
+ description: 'A IPLD Content identifier (CID)',
14
+ specifiedByURL: 'https://github.com/multiformats/cid',
15
+ serialize: validateCID,
16
+ parseValue: validateCID,
17
+ parseLiteral (ast) {
18
+ if (ast.kind !== Kind.STRING) {
19
+ throw new GraphQLError(`Can only validate strings as ContentID but got a: ${ast.kind}`);
20
+ }
21
+ return validateCID(ast.value);
22
+ }
23
+ });
24
+ export const cidSchema = {
25
+ type: 'string',
26
+ title: 'cid',
27
+ pattern: '^b[2-7a-z]{99}$'
28
+ };
29
+ function validateAttachmentID(input) {
30
+ if (typeof input !== 'string') {
31
+ throw new GraphQLError(`Invalid AttachmentID input: ${input}`);
32
+ }
33
+ // Throws if input is not a valid AttachmentID string
34
+ AttachmentID.fromString(input);
35
+ return input;
36
+ }
37
+ export const GraphQLAttachmentID = new GraphQLScalarType({
38
+ name: 'AttachmentID',
39
+ description: 'A Kubun Attachment identifier',
40
+ serialize: validateAttachmentID,
41
+ parseValue: validateAttachmentID,
42
+ parseLiteral (ast) {
43
+ if (ast.kind !== Kind.STRING) {
44
+ throw new GraphQLError(`Can only validate strings as AttachmentID but got a: ${ast.kind}`);
45
+ }
46
+ return validateAttachmentID(ast.value);
47
+ }
48
+ });
49
+ export const attachmentIDSchema = {
50
+ type: 'string',
51
+ title: 'attachmentid',
52
+ pattern: '^k[0-9a-z]{99}$'
53
+ };
54
+ function validateDocID(input) {
55
+ if (typeof input !== 'string') {
56
+ throw new GraphQLError(`Invalid DocumentID input: ${input}`);
57
+ }
58
+ // Throws if input is not a valid DocumentID string
59
+ DocumentID.fromString(input);
60
+ return input;
61
+ }
62
+ export const GraphQLDocID = new GraphQLScalarType({
63
+ name: 'DocumentID',
64
+ description: 'A Kubun Document identifier',
65
+ serialize: validateDocID,
66
+ parseValue: validateDocID,
67
+ parseLiteral (ast) {
68
+ if (ast.kind !== Kind.STRING) {
69
+ throw new GraphQLError(`Can only validate strings as DocumentID but got a: ${ast.kind}`);
70
+ }
71
+ return validateDocID(ast.value);
72
+ }
73
+ });
74
+ export const docIDSchema = {
75
+ type: 'string',
76
+ title: 'docid',
77
+ pattern: '^k[0-9a-z]{99}$'
78
+ };
package/lib/index.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { GraphQLCID, GraphQLDocID } from './identifiers.js';
2
+ export * as scalars from './scalars.js';
3
+ export { type DocumentType, SchemaBuilder, type SchemaBuilderParams, type SharedDefinitions, } from './schema.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC3D,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AACvC,OAAO,EACL,KAAK,YAAY,EACjB,aAAa,EACb,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,GACvB,MAAM,aAAa,CAAA"}
package/lib/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { GraphQLCID, GraphQLDocID } from './identifiers.js';
2
+ export * as scalars from './scalars.js';
3
+ export { SchemaBuilder } from './schema.js';
@@ -0,0 +1,17 @@
1
+ import type { JSONSchema } from 'json-schema-typed';
2
+ export { attachmentIDSchema, cidSchema, docIDSchema } from './identifiers.js';
3
+ export declare const bigIntSchema: JSONSchema.String;
4
+ export declare const didSchema: JSONSchema.String;
5
+ export declare const dateTimeSchema: JSONSchema.String;
6
+ export declare const durationSchema: JSONSchema.String;
7
+ export declare const jsonObjectSchema: JSONSchema.String;
8
+ export declare const latitudeSchema: JSONSchema.Number;
9
+ export declare const localDateSchema: JSONSchema.String;
10
+ export declare const localDateTimeSchema: JSONSchema.String;
11
+ export declare const localTimeSchema: JSONSchema.String;
12
+ export declare const localeSchema: JSONSchema.String;
13
+ export declare const longitudeSchema: JSONSchema.Number;
14
+ export declare const timeZoneSchema: JSONSchema.String;
15
+ export declare const urlSchema: JSONSchema.String;
16
+ export declare const utcOffsetSchema: JSONSchema.String;
17
+ //# sourceMappingURL=scalars.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scalars.d.ts","sourceRoot":"","sources":["../src/scalars.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAEnD,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAE7E,eAAO,MAAM,YAAY,EAA0C,UAAU,CAAC,MAAM,CAAA;AACpF,eAAO,MAAM,SAAS,EAAuC,UAAU,CAAC,MAAM,CAAA;AAC9E,eAAO,MAAM,cAAc,EAA4C,UAAU,CAAC,MAAM,CAAA;AACxF,eAAO,MAAM,cAAc,EAA4C,UAAU,CAAC,MAAM,CAAA;AACxF,eAAO,MAAM,gBAAgB,EAA8C,UAAU,CAAC,MAAM,CAAA;AAC5F,eAAO,MAAM,cAAc,EAA4C,UAAU,CAAC,MAAM,CAAA;AACxF,eAAO,MAAM,eAAe,EAA6C,UAAU,CAAC,MAAM,CAAA;AAC1F,eAAO,MAAM,mBAAmB,EAAiD,UAAU,CAAC,MAAM,CAAA;AAClG,eAAO,MAAM,eAAe,EAA6C,UAAU,CAAC,MAAM,CAAA;AAC1F,eAAO,MAAM,YAAY,EAA0C,UAAU,CAAC,MAAM,CAAA;AACpF,eAAO,MAAM,eAAe,EAA6C,UAAU,CAAC,MAAM,CAAA;AAC1F,eAAO,MAAM,cAAc,EAA4C,UAAU,CAAC,MAAM,CAAA;AACxF,eAAO,MAAM,SAAS,EAAuC,UAAU,CAAC,MAAM,CAAA;AAC9E,eAAO,MAAM,eAAe,EAA6C,UAAU,CAAC,MAAM,CAAA"}
package/lib/scalars.js ADDED
@@ -0,0 +1,16 @@
1
+ import { GraphQLBigInt, GraphQLDID, GraphQLDateTime, GraphQLDuration, GraphQLJSONObject, GraphQLLatitude, GraphQLLocalDate, GraphQLLocalDateTime, GraphQLLocalTime, GraphQLLocale, GraphQLLongitude, GraphQLTimeZone, GraphQLURL, GraphQLUtcOffset } from 'graphql-scalars';
2
+ export { attachmentIDSchema, cidSchema, docIDSchema } from './identifiers.js';
3
+ export const bigIntSchema = GraphQLBigInt.extensions.jsonSchema;
4
+ export const didSchema = GraphQLDID.extensions.jsonSchema;
5
+ export const dateTimeSchema = GraphQLDateTime.extensions.jsonSchema;
6
+ export const durationSchema = GraphQLDuration.extensions.jsonSchema;
7
+ export const jsonObjectSchema = GraphQLJSONObject.extensions.jsonSchema;
8
+ export const latitudeSchema = GraphQLLatitude.extensions.jsonSchema;
9
+ export const localDateSchema = GraphQLLocalDate.extensions.jsonSchema;
10
+ export const localDateTimeSchema = GraphQLLocalDateTime.extensions.jsonSchema;
11
+ export const localTimeSchema = GraphQLLocalTime.extensions.jsonSchema;
12
+ export const localeSchema = GraphQLLocale.extensions.jsonSchema;
13
+ export const longitudeSchema = GraphQLLongitude.extensions.jsonSchema;
14
+ export const timeZoneSchema = GraphQLTimeZone.extensions.jsonSchema;
15
+ export const urlSchema = GraphQLURL.extensions.jsonSchema;
16
+ export const utcOffsetSchema = GraphQLUtcOffset.extensions.jsonSchema;
@@ -0,0 +1,62 @@
1
+ import { AttachmentID } from '@kubun/id';
2
+ import { type ArrayModel, type DocumentData, type DocumentModel, type DocumentModelSchema, type DocumentModelsRecord, type ObjectModel, type ReferencesRecordModel, type ScalarModel, type StringCustomModel, type StringEnumModel, type StringFormatModel, type StringModel, type TypeModel } from '@kubun/protocol';
3
+ import { GraphQLEnumType, type GraphQLFieldConfig, type GraphQLFieldConfigMap, GraphQLInputObjectType, type GraphQLInputType, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, type GraphQLOutputType, type GraphQLScalarType, GraphQLSchema } from 'graphql';
4
+ import { type Connection, type ConnectionArguments } from 'graphql-relay';
5
+ export type DocumentType = {
6
+ id: string;
7
+ model: string;
8
+ owner: string;
9
+ data: DocumentData | null;
10
+ };
11
+ export type ContextType = Record<string, unknown>;
12
+ export type GraphQLNodeDefinitions<Context extends ContextType = ContextType> = {
13
+ nodeInterface: GraphQLInterfaceType;
14
+ nodeField: GraphQLFieldConfig<unknown, Context>;
15
+ nodesField: GraphQLFieldConfig<unknown, Context>;
16
+ };
17
+ export type SharedDefinitions<Document extends DocumentType = DocumentType, Context extends ContextType = ContextType> = GraphQLNodeDefinitions<Context> & {
18
+ accountObject: GraphQLObjectType<string, Context>;
19
+ attachmentObject: GraphQLObjectType<AttachmentID, Context>;
20
+ documentInterface: GraphQLInterfaceType;
21
+ documentMetadata: GraphQLObjectType<Document, Context>;
22
+ queryFields: GraphQLFieldConfigMap<unknown, Context>;
23
+ };
24
+ export type SchemaBuilderParams = {
25
+ aliases?: Record<string, string>;
26
+ record: DocumentModelsRecord;
27
+ };
28
+ export declare class SchemaBuilder<Document extends DocumentType = DocumentType, Context extends ContextType = ContextType, Definitions extends SharedDefinitions<Document, Context> = SharedDefinitions<Document, Context>> {
29
+ _aliases: Record<string, string>;
30
+ _record: DocumentModelsRecord;
31
+ _references: ReferencesRecordModel;
32
+ _relationsTo: Record<string, Record<string, string>>;
33
+ _types: Record<string, GraphQLOutputType>;
34
+ _inputObjects: Record<string, GraphQLInputObjectType>;
35
+ _mutations: Record<string, GraphQLFieldConfig<unknown, Context>>;
36
+ constructor(params: SchemaBuilderParams);
37
+ getViewer(ctx: Context): string | null;
38
+ loadDocument(id: string, ctx: Context): Promise<Document | null>;
39
+ resolveConnection(model: string | null, args: ConnectionArguments, ctx: Context): Promise<Connection<Document>>;
40
+ resolveList(model: string | null, ids: Array<string>, ctx: Context): Promise<Array<Document>>;
41
+ buildMutations(id: string, definitions: Definitions): void;
42
+ build(): GraphQLSchema;
43
+ _getReference(path: string, definitions: Definitions, parentName?: string): GraphQLOutputType;
44
+ _getReferenceInput(path: string, definitions: Definitions, isPartial: boolean, parentName?: string): GraphQLInputType;
45
+ _buildSharedDefinitions(): Definitions;
46
+ _buildDocument(id: string, definitions: Definitions): GraphQLObjectType<Document, Context>;
47
+ _buildDocumentInput(id: string, model: DocumentModel, definitions: Definitions): void;
48
+ _buildReferenceFilterInput(path: string, definitions: Definitions, parentName?: string): GraphQLInputType;
49
+ _buildReferenceOrderByInput(path: string, definitions: Definitions, parentName?: string): GraphQLInputType | null;
50
+ _buildObjectFilterInput(id: string, schema: DocumentModelSchema | ObjectModel, definitions: Definitions, parentName?: string, isDocument?: boolean): GraphQLInputObjectType;
51
+ _buildObjectOrderByInput(id: string, schema: DocumentModelSchema | ObjectModel, definitions: Definitions, parentName?: string, isDocument?: boolean): GraphQLInputObjectType;
52
+ _buildReference(id: string, schema: TypeModel, definitions: Definitions, parentName?: string): GraphQLOutputType;
53
+ _buildScalar(id: string, schema: ScalarModel, definitions: Definitions, asInput?: boolean): GraphQLScalarType;
54
+ _buildArray(schema: ArrayModel, definitions: Definitions, parentName?: string): GraphQLList<GraphQLNonNull<GraphQLOutputType>>;
55
+ _buildObject(id: string, schema: ObjectModel, definitions: Definitions, parentName?: string): GraphQLObjectType;
56
+ _buildObjectFields(id: string, schema: DocumentModelSchema | ObjectModel, definitions: Definitions, model?: DocumentModel): Record<string, GraphQLFieldConfig<Document, Context>>;
57
+ _buildString(schema: StringModel, definitions: Definitions, asInput: boolean): GraphQLEnumType | GraphQLObjectType | GraphQLScalarType;
58
+ _buildCustomString(schema: StringCustomModel, definitions: Definitions, asInput: boolean): GraphQLScalarType<unknown, unknown> | GraphQLObjectType<string, Context> | GraphQLObjectType<AttachmentID, Context>;
59
+ _buildEnumString(schema: StringEnumModel): GraphQLEnumType;
60
+ _buildFormatString(schema: StringFormatModel): GraphQLScalarType<unknown, unknown>;
61
+ }
62
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
package/lib/schema.js ADDED
@@ -0,0 +1,718 @@
1
+ import { AttachmentID, DocumentID, GraphModelID, 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, nodeDefinitions } from 'graphql-relay';
6
+ import { GraphQLDID, GraphQLDuration, GraphQLURL } from 'graphql-scalars';
7
+ import { GraphQLAttachmentID, GraphQLCID, GraphQLDocID } from './identifiers.js';
8
+ const STRING_FORMATS = {
9
+ date: GraphQLString,
10
+ time: GraphQLString,
11
+ 'date-time': GraphQLString,
12
+ duration: GraphQLDuration,
13
+ uri: GraphQLURL
14
+ };
15
+ const STRING_TITLES = {
16
+ attachmentid: GraphQLAttachmentID,
17
+ cid: GraphQLCID,
18
+ did: GraphQLDID,
19
+ docid: GraphQLDocID
20
+ };
21
+ function isList(type) {
22
+ return isListType(type) || isNonNullType(type) && isListType(type.ofType);
23
+ }
24
+ function toList(type) {
25
+ return new GraphQLList(new GraphQLNonNull(type));
26
+ }
27
+ function toOuputList(type) {
28
+ return new GraphQLList(new GraphQLNonNull(type));
29
+ }
30
+ function createEnumValueFilterInput(type) {
31
+ return new GraphQLInputObjectType({
32
+ name: `${type.name}ValueFilter`,
33
+ isOneOf: true,
34
+ fields: {
35
+ isNull: {
36
+ type: GraphQLBoolean
37
+ },
38
+ equalTo: {
39
+ type
40
+ },
41
+ notEqualTo: {
42
+ type
43
+ },
44
+ in: {
45
+ type: toList(type)
46
+ },
47
+ notIn: {
48
+ type: toList(type)
49
+ }
50
+ }
51
+ });
52
+ }
53
+ function createScalarValueFilterInput(type) {
54
+ return new GraphQLInputObjectType({
55
+ name: `${type.name}ValueFilter`,
56
+ isOneOf: true,
57
+ fields: {
58
+ isNull: {
59
+ type: GraphQLBoolean
60
+ },
61
+ equalTo: {
62
+ type
63
+ },
64
+ notEqualTo: {
65
+ type
66
+ },
67
+ in: {
68
+ type: toList(type)
69
+ },
70
+ notIn: {
71
+ type: toList(type)
72
+ },
73
+ lessThan: {
74
+ type
75
+ },
76
+ lessThanOrEqualTo: {
77
+ type
78
+ },
79
+ greaterThan: {
80
+ type
81
+ },
82
+ greaterThanOrEqualTo: {
83
+ type
84
+ }
85
+ }
86
+ });
87
+ }
88
+ const VALUE_FILTER_INPUTS = {
89
+ AccountValueFilter: new GraphQLInputObjectType({
90
+ name: 'AccountValueFilter',
91
+ isOneOf: true,
92
+ fields: {
93
+ equalTo: {
94
+ type: GraphQLDID
95
+ },
96
+ notEqualTo: {
97
+ type: GraphQLDID
98
+ },
99
+ in: {
100
+ type: toList(GraphQLDID)
101
+ },
102
+ notIn: {
103
+ type: toList(GraphQLDID)
104
+ }
105
+ }
106
+ }),
107
+ BooleanValueFilter: new GraphQLInputObjectType({
108
+ name: 'BooleanValueFilter',
109
+ isOneOf: true,
110
+ fields: {
111
+ isNull: {
112
+ type: GraphQLBoolean
113
+ },
114
+ equalTo: {
115
+ type: GraphQLBoolean
116
+ }
117
+ }
118
+ }),
119
+ FloatValueFilter: createScalarValueFilterInput(GraphQLFloat),
120
+ IntValueFilter: createScalarValueFilterInput(GraphQLInt),
121
+ StringValueFilter: createScalarValueFilterInput(GraphQLString)
122
+ };
123
+ const VALUE_FILTER_INPUT_TYPES = {
124
+ GraphQLBoolean: 'BooleanValueFilter',
125
+ GraphQLDID: 'AccountValueFilter',
126
+ GraphQLFloat: 'FloatValueFilter',
127
+ GraphQLInt: 'IntValueFilter',
128
+ GraphQLString: 'StringValueFilter'
129
+ };
130
+ const OrderByDirection = new GraphQLEnumType({
131
+ name: 'OrderByDirection',
132
+ values: {
133
+ ASC: {
134
+ value: 'asc'
135
+ },
136
+ DESC: {
137
+ value: 'desc'
138
+ }
139
+ }
140
+ });
141
+ export class SchemaBuilder {
142
+ // Mapping of model or references IDs to their GraphQL object names
143
+ _aliases;
144
+ _record;
145
+ _references = {};
146
+ _relationsTo = {};
147
+ _types = {
148
+ OrderByDirection
149
+ };
150
+ _inputObjects = {
151
+ ...VALUE_FILTER_INPUTS
152
+ };
153
+ _mutations = {};
154
+ constructor(params){
155
+ this._aliases = params.aliases ?? {};
156
+ this._record = params.record;
157
+ for (const [id, model] of Object.entries(params.record)){
158
+ const modelID = GraphModelID.fromString(id);
159
+ Object.assign(this._references, model.schema.$defs ?? {});
160
+ // Extract inverse relations
161
+ for (const [fieldName, meta] of Object.entries(model.fieldsMeta)){
162
+ if (meta.relationModel != null) {
163
+ const relationModelID = GraphModelID.fromString(meta.relationModel);
164
+ const globalID = relationModelID.isLocal ? relationModelID.toGlobal(modelID) : relationModelID;
165
+ const relationID = globalID.toString();
166
+ this._relationsTo[relationID] ??= {};
167
+ this._relationsTo[relationID][fieldName] = id;
168
+ }
169
+ }
170
+ }
171
+ }
172
+ getViewer(ctx) {
173
+ throw new Error('Must be implemented');
174
+ }
175
+ async loadDocument(id, ctx) {
176
+ throw new Error('Must be implemented');
177
+ }
178
+ async resolveConnection(model, args, ctx) {
179
+ throw new Error('Must be implemented');
180
+ }
181
+ async resolveList(model, ids, ctx) {
182
+ throw new Error('Must be implemented');
183
+ }
184
+ buildMutations(id, definitions) {
185
+ throw new Error('Must be implemented');
186
+ }
187
+ build() {
188
+ const definitions = this._buildSharedDefinitions();
189
+ const queryFields = {
190
+ nodes: definitions.nodesField,
191
+ ...definitions.queryFields
192
+ };
193
+ // TODO: is this needed?
194
+ // for (const [id, schema] of Object.entries(this._references)) {
195
+ // this._buildReference(id, schema, definitions)
196
+ // }
197
+ for (const id of Object.keys(this._record)){
198
+ this._buildDocument(id, definitions);
199
+ this.buildMutations(id, definitions);
200
+ const name = this._aliases[id];
201
+ queryFields[`list${name}`] = {
202
+ type: this._types[`${id}-connection`],
203
+ args: {
204
+ ...connectionArgs,
205
+ filter: {
206
+ type: this._inputObjects[`${id}-filter`]
207
+ },
208
+ orderBy: {
209
+ type: toList(this._inputObjects[`${id}-order`])
210
+ }
211
+ },
212
+ resolve: async (_, args, ctx)=>{
213
+ return await this.resolveConnection(id, args, ctx);
214
+ }
215
+ };
216
+ }
217
+ const schemaFields = {
218
+ query: new GraphQLObjectType({
219
+ name: 'Query',
220
+ fields: queryFields
221
+ }),
222
+ mutation: new GraphQLObjectType({
223
+ name: 'Mutation',
224
+ fields: this._mutations
225
+ })
226
+ };
227
+ return new GraphQLSchema(schemaFields);
228
+ }
229
+ _getReference(path, definitions, parentName) {
230
+ const id = path.slice(REFERENCE_PREFIX.length);
231
+ const type = this._types[id];
232
+ if (type != null) {
233
+ return type;
234
+ }
235
+ const schema = this._references[id];
236
+ if (schema == null) {
237
+ throw new Error(`Could not find reference: ${id}`);
238
+ }
239
+ return this._buildReference(id, schema, definitions, parentName);
240
+ }
241
+ _getReferenceInput(path, definitions, isPartial, parentName = '') {
242
+ const id = path.slice(REFERENCE_PREFIX.length);
243
+ const schema = this._references[id];
244
+ if (schema == null) {
245
+ throw new Error(`Could not find reference: ${id}`);
246
+ }
247
+ if (schema.type === 'array') {
248
+ const itemType = this._getReferenceInput(schema.items.$ref, definitions, false, schema.title);
249
+ return new GraphQLList(new GraphQLNonNull(itemType));
250
+ }
251
+ if (schema.type === 'object') {
252
+ const inputID = isPartial ? `${id}-partial` : id;
253
+ const existing = this._inputObjects[inputID];
254
+ if (existing != null) {
255
+ return existing;
256
+ }
257
+ const fields = {};
258
+ for (const [key, value] of Object.entries(schema.properties)){
259
+ const type = this._getReferenceInput(value.$ref, definitions, isPartial, schema.title);
260
+ fields[key] = {
261
+ type: isPartial || !schema.required.includes(key) ? type : new GraphQLNonNull(type)
262
+ };
263
+ }
264
+ const name = this._aliases[id] ?? pascalCase(parentName + schema.title);
265
+ const input = new GraphQLInputObjectType({
266
+ name: isPartial ? `Partial${name}Input` : `${name}Input`,
267
+ fields
268
+ });
269
+ this._inputObjects[inputID] = input;
270
+ return input;
271
+ }
272
+ return this._buildScalar(id, schema, definitions, true);
273
+ }
274
+ _buildSharedDefinitions() {
275
+ const nodeDefs = nodeDefinitions(async (id, ctx)=>{
276
+ if (id.startsWith('did:')) {
277
+ return id;
278
+ }
279
+ const decoded = decodeID(id);
280
+ if (decoded instanceof AttachmentID) {
281
+ return decoded;
282
+ }
283
+ if (decoded instanceof DocumentID) {
284
+ return await this.loadDocument(id, ctx);
285
+ }
286
+ throw new Error(`Unsupported node ID: ${id}`);
287
+ }, (node)=>{
288
+ return typeof node === 'string' ? 'AccountNode' : node instanceof AttachmentID ? 'AttachmentNode' : this._aliases[node.model];
289
+ });
290
+ const accountObject = new GraphQLObjectType({
291
+ name: 'AccountNode',
292
+ interfaces: [
293
+ nodeDefs.nodeInterface
294
+ ],
295
+ fields: ()=>{
296
+ const config = {
297
+ id: {
298
+ type: new GraphQLNonNull(GraphQLID),
299
+ description: 'Globally unique identifier of the account (DID string)',
300
+ resolve: (did)=>did
301
+ },
302
+ isViewer: {
303
+ type: new GraphQLNonNull(GraphQLBoolean),
304
+ description: 'Whether the Ceramic instance is currently authenticated with this account or not',
305
+ resolve: (did, _, ctx)=>this.getViewer(ctx) === did
306
+ }
307
+ };
308
+ // TODO: add entry-points to documents created with this account
309
+ return config;
310
+ }
311
+ });
312
+ const attachmentObject = new GraphQLObjectType({
313
+ name: 'AttachmentNode',
314
+ interfaces: [
315
+ nodeDefs.nodeInterface
316
+ ],
317
+ fields: {
318
+ id: {
319
+ type: new GraphQLNonNull(GraphQLID),
320
+ resolve: (aid)=>aid.toString()
321
+ },
322
+ cid: {
323
+ type: new GraphQLNonNull(GraphQLCID)
324
+ },
325
+ contentLength: {
326
+ type: new GraphQLNonNull(GraphQLInt)
327
+ },
328
+ mimeType: {
329
+ type: new GraphQLNonNull(GraphQLString)
330
+ }
331
+ }
332
+ });
333
+ const documentMetadata = new GraphQLObjectType({
334
+ name: 'DocumentMetadata',
335
+ fields: {
336
+ model: {
337
+ type: new GraphQLNonNull(GraphQLString),
338
+ resolve: (doc)=>doc.model
339
+ },
340
+ owner: {
341
+ type: new GraphQLNonNull(accountObject),
342
+ resolve: (doc)=>doc.owner
343
+ }
344
+ }
345
+ });
346
+ const documentInterface = new GraphQLInterfaceType({
347
+ name: 'DocumentNode',
348
+ interfaces: [
349
+ nodeDefs.nodeInterface
350
+ ],
351
+ fields: {
352
+ // Node interface
353
+ id: {
354
+ type: new GraphQLNonNull(GraphQLID)
355
+ },
356
+ _doc_metadata: {
357
+ type: new GraphQLNonNull(documentMetadata)
358
+ }
359
+ }
360
+ });
361
+ const queryFields = {
362
+ node: nodeDefs.nodeField,
363
+ viewer: {
364
+ type: accountObject,
365
+ description: 'Account performing the request, if set',
366
+ resolve: (_self, _args, ctx)=>this.getViewer(ctx)
367
+ }
368
+ };
369
+ return {
370
+ ...nodeDefs,
371
+ accountObject,
372
+ attachmentObject,
373
+ documentInterface,
374
+ documentMetadata,
375
+ queryFields
376
+ };
377
+ }
378
+ _buildDocument(id, definitions) {
379
+ if (this._types[id] != null) {
380
+ return this._types[id];
381
+ }
382
+ const model = this._record[id];
383
+ if (model == null) {
384
+ throw new Error(`Could not find model id: ${id}`);
385
+ }
386
+ this._aliases[id] ??= pascalCase(model.name);
387
+ const nodeType = new GraphQLObjectType({
388
+ name: this._aliases[id],
389
+ interfaces: [
390
+ definitions.documentInterface,
391
+ definitions.nodeInterface
392
+ ],
393
+ fields: ()=>{
394
+ return this._buildObjectFields(id, model.schema, definitions, model);
395
+ }
396
+ });
397
+ const { connectionType, edgeType } = connectionDefinitions({
398
+ nodeType
399
+ });
400
+ this._types[id] = nodeType;
401
+ this._types[`${id}-connection`] = connectionType;
402
+ this._types[`${id}-edge`] = edgeType;
403
+ this._buildDocumentInput(id, model, definitions);
404
+ this._buildObjectFilterInput(id, model.schema, definitions, model.name, true);
405
+ this._buildObjectOrderByInput(id, model.schema, definitions, model.name, true);
406
+ return nodeType;
407
+ }
408
+ _buildDocumentInput(id, model, definitions) {
409
+ if (model.behavior === 'interface') {
410
+ // No mutations for interfaces
411
+ return;
412
+ }
413
+ const setFields = {};
414
+ const updateFields = {};
415
+ for (const [key, value] of Object.entries(model.schema.properties)){
416
+ const setType = this._getReferenceInput(value.$ref, definitions, false);
417
+ setFields[key] = {
418
+ type: model.schema.required.includes(key) ? new GraphQLNonNull(setType) : setType
419
+ };
420
+ updateFields[key] = {
421
+ type: this._getReferenceInput(value.$ref, definitions, true)
422
+ };
423
+ }
424
+ const name = this._aliases[id];
425
+ this._inputObjects[id] = new GraphQLInputObjectType({
426
+ name: model.behavior === 'list' ? `Create${name}` : `Set${name}`,
427
+ fields: setFields
428
+ });
429
+ this._inputObjects[`${id}-update`] = new GraphQLInputObjectType({
430
+ name: `Update${name}`,
431
+ fields: updateFields
432
+ });
433
+ }
434
+ _buildReferenceFilterInput(path, definitions, parentName = '') {
435
+ const id = path.slice(REFERENCE_PREFIX.length);
436
+ const schema = this._references[id];
437
+ if (schema == null) {
438
+ throw new Error(`Could not find reference: ${id}`);
439
+ }
440
+ if (schema.type === 'object') {
441
+ return this._buildObjectFilterInput(id, schema, definitions, parentName);
442
+ }
443
+ if (schema.type === 'array') {
444
+ const name = this._aliases[id] ?? pascalCase(parentName + schema.title);
445
+ const itemType = this._buildReferenceFilterInput(schema.items.$ref, definitions, schema.title);
446
+ return new GraphQLInputObjectType({
447
+ name: `${name}Filter`,
448
+ isOneOf: true,
449
+ fields: {
450
+ contains: {
451
+ type: toList(itemType)
452
+ },
453
+ isEmpty: {
454
+ type: GraphQLBoolean
455
+ },
456
+ isNull: {
457
+ type: GraphQLBoolean
458
+ }
459
+ }
460
+ });
461
+ }
462
+ const scalar = this._buildScalar(id, schema, definitions, true);
463
+ const knownFilter = VALUE_FILTER_INPUT_TYPES[scalar.name];
464
+ if (knownFilter != null) {
465
+ return this._inputObjects[knownFilter];
466
+ }
467
+ const filter = isEnumType(scalar) ? createEnumValueFilterInput(scalar) : createScalarValueFilterInput(scalar);
468
+ if (this._inputObjects[filter.name] == null) {
469
+ this._inputObjects[filter.name] = filter;
470
+ }
471
+ return this._inputObjects[filter.name];
472
+ }
473
+ _buildReferenceOrderByInput(path, definitions, parentName = '') {
474
+ const id = path.slice(REFERENCE_PREFIX.length);
475
+ const schema = this._references[id];
476
+ if (schema == null) {
477
+ throw new Error(`Could not find reference: ${id}`);
478
+ }
479
+ switch(schema.type){
480
+ case 'array':
481
+ return null;
482
+ case 'object':
483
+ return this._buildObjectOrderByInput(id, schema, definitions, parentName);
484
+ default:
485
+ return OrderByDirection;
486
+ }
487
+ }
488
+ _buildObjectFilterInput(id, schema, definitions, parentName = '', isDocument = false) {
489
+ const inputID = `${id}-filter`;
490
+ const existing = this._inputObjects[inputID];
491
+ if (existing != null) {
492
+ return existing;
493
+ }
494
+ const name = this._aliases[id] ?? pascalCase(parentName + (schema.title ?? ''));
495
+ const fields = isDocument ? {
496
+ _doc_owner: {
497
+ type: this._inputObjects.AccountValueFilter
498
+ }
499
+ } : {};
500
+ for (const [key, value] of Object.entries(schema.properties)){
501
+ const type = this._buildReferenceFilterInput(value.$ref, definitions, name);
502
+ fields[key] = {
503
+ type
504
+ };
505
+ }
506
+ const objectInput = new GraphQLInputObjectType({
507
+ name: `${name}ObjectFilter`,
508
+ fields
509
+ });
510
+ const input = new GraphQLInputObjectType({
511
+ name: `${name}Filter`,
512
+ isOneOf: true,
513
+ fields: ()=>({
514
+ where: {
515
+ type: objectInput
516
+ },
517
+ and: {
518
+ type: toList(this._inputObjects[inputID])
519
+ },
520
+ or: {
521
+ type: toList(this._inputObjects[inputID])
522
+ },
523
+ not: {
524
+ type: this._inputObjects[inputID]
525
+ }
526
+ })
527
+ });
528
+ this._inputObjects[inputID] = input;
529
+ return input;
530
+ }
531
+ _buildObjectOrderByInput(id, schema, definitions, parentName = '', isDocument = false) {
532
+ const inputID = `${id}-order`;
533
+ const existing = this._inputObjects[inputID];
534
+ if (existing != null) {
535
+ return existing;
536
+ }
537
+ const name = this._aliases[id] ?? pascalCase(parentName + (schema.title ?? ''));
538
+ const fields = isDocument ? {
539
+ _doc_owner: {
540
+ type: OrderByDirection
541
+ },
542
+ _doc_created_at: {
543
+ type: OrderByDirection
544
+ }
545
+ } : {};
546
+ for (const [key, value] of Object.entries(schema.properties)){
547
+ const type = this._buildReferenceOrderByInput(value.$ref, definitions, name);
548
+ if (type != null) {
549
+ fields[key] = {
550
+ type
551
+ };
552
+ }
553
+ }
554
+ const input = new GraphQLInputObjectType({
555
+ name: `${name}OrderBy`,
556
+ isOneOf: true,
557
+ fields
558
+ });
559
+ this._inputObjects[inputID] = input;
560
+ return input;
561
+ }
562
+ _buildReference(id, schema, definitions, parentName = '') {
563
+ if (this._types[id] != null) {
564
+ return this._types[id];
565
+ }
566
+ switch(schema.type){
567
+ case 'array':
568
+ this._types[id] = this._buildArray(schema, definitions, parentName);
569
+ return this._types[id];
570
+ case 'object':
571
+ this._types[id] = this._buildObject(id, schema, definitions, parentName);
572
+ return this._types[id];
573
+ default:
574
+ return this._buildScalar(id, schema, definitions);
575
+ }
576
+ }
577
+ _buildScalar(id, schema, definitions, asInput = false) {
578
+ switch(schema.type){
579
+ case 'boolean':
580
+ this._types[id] = GraphQLBoolean;
581
+ break;
582
+ case 'integer':
583
+ this._types[id] = GraphQLInt;
584
+ break;
585
+ case 'number':
586
+ this._types[id] = GraphQLFloat;
587
+ break;
588
+ case 'string':
589
+ this._types[id] = this._buildString(schema, definitions, asInput);
590
+ break;
591
+ }
592
+ return this._types[id];
593
+ }
594
+ _buildArray(schema, definitions, parentName = '') {
595
+ const itemRef = schema.items.$ref;
596
+ return new GraphQLList(new GraphQLNonNull(this._getReference(itemRef, definitions, pascalCase(parentName + schema.title))));
597
+ }
598
+ _buildObject(id, schema, definitions, parentName = '') {
599
+ const fields = this._buildObjectFields(id, schema, definitions);
600
+ return new GraphQLObjectType({
601
+ name: pascalCase(parentName + schema.title),
602
+ fields
603
+ });
604
+ }
605
+ _buildObjectFields(id, schema, definitions, model) {
606
+ const required = schema.required ?? [];
607
+ const fields = model ? {
608
+ id: {
609
+ type: new GraphQLNonNull(GraphQLID),
610
+ resolve: (doc)=>doc.id
611
+ },
612
+ _doc_metadata: {
613
+ type: new GraphQLNonNull(definitions.documentMetadata),
614
+ resolve: (doc)=>doc
615
+ }
616
+ } : {};
617
+ for (const [key, value] of Object.entries(schema.properties ?? {})){
618
+ const fieldRef = value.$ref;
619
+ if (fieldRef == null) {
620
+ throw new Error(`Missing ref for field ${key} of object ${id}`);
621
+ }
622
+ const type = this._getReference(fieldRef, definitions, schema.title ?? '');
623
+ fields[key] = {
624
+ type: required.includes(key) ? new GraphQLNonNull(type) : type
625
+ };
626
+ if (model != null) {
627
+ fields[key].resolve = (doc)=>doc.data?.[key];
628
+ const relationModel = model.fieldsMeta[key]?.relationModel;
629
+ if (typeof relationModel !== 'undefined') {
630
+ let relationModelID;
631
+ let relationType;
632
+ if (relationModel === null) {
633
+ relationModelID = null;
634
+ relationType = definitions.documentInterface;
635
+ } else {
636
+ const relationID = GraphModelID.fromString(relationModel);
637
+ relationModelID = (relationID.isLocal ? relationID.toGlobal(GraphModelID.fromString(id)) : relationID).toString();
638
+ relationType = this._buildDocument(relationModelID, definitions);
639
+ }
640
+ if (isList(type)) {
641
+ // TODO: add suport for filters?
642
+ fields[`_${key}_documents`] = {
643
+ type: new GraphQLNonNull(toOuputList(relationType)),
644
+ resolve: async (doc, _args, ctx)=>{
645
+ const relationIDs = doc.data?.[key] ?? [];
646
+ return relationIDs.length ? await this.resolveList(relationModelID, relationIDs, ctx) : [];
647
+ }
648
+ };
649
+ } else {
650
+ fields[`_${key}_document`] = {
651
+ type: relationType,
652
+ resolve: async (doc, _args, ctx)=>{
653
+ const relationID = doc.data?.[key];
654
+ return relationID ? await this.loadDocument(relationID, ctx) : null;
655
+ }
656
+ };
657
+ }
658
+ }
659
+ for (const [fieldName, relationID] of Object.entries(this._relationsTo[id] ?? {})){
660
+ const relationName = this._aliases[relationID];
661
+ fields[`_${fieldName}_of_${relationName}_list`] = {
662
+ type: new GraphQLNonNull(this._types[`${relationID}-connection`])
663
+ };
664
+ fields[`_${fieldName}_of_${relationName}_count`] = {
665
+ type: new GraphQLNonNull(GraphQLInt)
666
+ };
667
+ }
668
+ }
669
+ }
670
+ return fields;
671
+ }
672
+ _buildString(schema, definitions, asInput) {
673
+ if (schema.enum != null) {
674
+ return this._buildEnumString(schema);
675
+ }
676
+ if (schema.format != null) {
677
+ return this._buildFormatString(schema);
678
+ }
679
+ return this._buildCustomString(schema, definitions, asInput);
680
+ }
681
+ _buildCustomString(schema, definitions, asInput) {
682
+ if (schema.title == null) {
683
+ // Plain string type
684
+ return GraphQLString;
685
+ }
686
+ const title = schema.title.toLowerCase();
687
+ if (title === 'attachmentid') {
688
+ return asInput ? GraphQLID : definitions.attachmentObject;
689
+ }
690
+ if (title === 'did') {
691
+ return asInput ? GraphQLID : definitions.accountObject;
692
+ }
693
+ const scalar = STRING_TITLES[title];
694
+ if (scalar == null) {
695
+ throw new Error(`Unsupported string type: ${schema.title}`);
696
+ }
697
+ return scalar;
698
+ }
699
+ _buildEnumString(schema) {
700
+ const values = {};
701
+ for (const value of schema.enum){
702
+ values[value] = {
703
+ value
704
+ };
705
+ }
706
+ return new GraphQLEnumType({
707
+ name: schema.title,
708
+ values
709
+ });
710
+ }
711
+ _buildFormatString(schema) {
712
+ const scalar = STRING_FORMATS[schema.format];
713
+ if (scalar == null) {
714
+ throw new Error(`Unsupported string format: ${schema.format}`);
715
+ }
716
+ return scalar;
717
+ }
718
+ }
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@kubun/graphql",
3
+ "version": "0.1.0",
4
+ "license": "see LICENSE.md",
5
+ "keywords": [],
6
+ "type": "module",
7
+ "main": "lib/index.js",
8
+ "types": "lib/index.d.ts",
9
+ "exports": {
10
+ ".": "./lib/index.js"
11
+ },
12
+ "files": [
13
+ "lib/*",
14
+ "LICENSE.md"
15
+ ],
16
+ "sideEffects": false,
17
+ "dependencies": {
18
+ "change-case": "^5.4.4",
19
+ "graphql": "^16.9.0",
20
+ "graphql-relay": "^0.10.2",
21
+ "graphql-scalars": "^1.23.0",
22
+ "multiformats": "^13.3.0",
23
+ "@kubun/id": "^0.1.0",
24
+ "@kubun/protocol": "^0.1.0"
25
+ },
26
+ "devDependencies": {
27
+ "json-schema-typed": "^8.0.1"
28
+ },
29
+ "scripts": {
30
+ "build:clean": "del lib",
31
+ "build:js": "swc src -d ./lib --config-file ../../swc.json --strip-leading-paths",
32
+ "build:types": "tsc --emitDeclarationOnly --skipLibCheck",
33
+ "build:types:ci": "tsc --emitDeclarationOnly --declarationMap false",
34
+ "build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
35
+ "test:types": "tsc --noEmit",
36
+ "test:unit": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js",
37
+ "test": "pnpm run test:types && pnpm run test:unit"
38
+ }
39
+ }