@gqloom/json 0.11.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 +9 -0
- package/README.md +45 -0
- package/dist/index.cjs +227 -0
- package/dist/index.d.cts +44 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +201 -0
- package/package.json +61 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Modevol https://www.modevol.com/
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# GQLoom
|
|
4
|
+
|
|
5
|
+
GQLoom is a **Code-First** GraphQL Schema Loom used to weave **runtime types** in the **TypeScript/JavaScript** ecosystem into GraphQL Schema, helping you build GraphQL server enjoyably and efficiently.
|
|
6
|
+
|
|
7
|
+
Runtime validation libraries such as [Zod](https://zod.dev/), [Valibot](https://valibot.dev/), and [Yup](https://github.com/jquense/yup) have been widely used in backend application development. Meanwhile, when using ORM libraries like [Prisma](https://www.prisma.io/), [MikroORM](https://mikro-orm.io/), and [Drizzle](https://orm.drizzle.team/), we also pre-define database table structures or entity models that contain runtime types.
|
|
8
|
+
The responsibility of GQLoom is to weave these runtime types into a GraphQL Schema.
|
|
9
|
+
|
|
10
|
+
When developing backend applications with GQLoom, you only need to write types using the Schema libraries you're familiar with. Modern Schema libraries will infer TypeScript types for you, and GQLoom will weave GraphQL types for you.
|
|
11
|
+
In addition, the **resolver factory** of GQLoom can create CRUD interfaces for `Prisma`, `MikroORM`, and `Drizzle`, and supports custom input and adding middleware.
|
|
12
|
+
|
|
13
|
+
# @gqloom/json
|
|
14
|
+
|
|
15
|
+
This package provides GQLoom integration with [JSON Schema](https://json-schema.org/) to weave JSON Schema to GraphQL Schema.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# use npm
|
|
21
|
+
npm i @gqloom/core @gqloom/json
|
|
22
|
+
|
|
23
|
+
# use pnpm
|
|
24
|
+
pnpm add @gqloom/core @gqloom/json
|
|
25
|
+
|
|
26
|
+
# use yarn
|
|
27
|
+
yarn add @gqloom/core @gqloom/json
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Hello World
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { query, resolver, weave } from "@gqloom/core"
|
|
34
|
+
import { jsonSilk } from "@gqloom/json"
|
|
35
|
+
|
|
36
|
+
const helloResolver = resolver({
|
|
37
|
+
hello: query(jsonSilk({ type: "string" }))
|
|
38
|
+
.input({ name: jsonSilk({ type: "string" }) })
|
|
39
|
+
.resolve(({ name }) => `Hello, ${name}!`),
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
export const schema = weave(helloResolver)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Read more at [GQLoom Document](https://gqloom.dev/docs/schema/json).
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
let __gqloom_core = require("@gqloom/core");
|
|
25
|
+
__gqloom_core = __toESM(__gqloom_core);
|
|
26
|
+
let graphql = require("graphql");
|
|
27
|
+
graphql = __toESM(graphql);
|
|
28
|
+
|
|
29
|
+
//#region src/index.ts
|
|
30
|
+
var JSONWeaver = class JSONWeaver {
|
|
31
|
+
static vendor = "json";
|
|
32
|
+
/**
|
|
33
|
+
* Create a JSON weaver config object
|
|
34
|
+
* @param config JSON weaver config options
|
|
35
|
+
* @returns a JSON weaver config object
|
|
36
|
+
*/
|
|
37
|
+
static config = function(config) {
|
|
38
|
+
return {
|
|
39
|
+
...config,
|
|
40
|
+
[__gqloom_core.SYMBOLS.WEAVER_CONFIG]: "gqloom.json"
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* get GraphQL Silk from JSON Schema
|
|
45
|
+
* @param schema JSON Schema
|
|
46
|
+
* @returns GraphQL Silk Like JSON Schema
|
|
47
|
+
*/
|
|
48
|
+
static unravel(schema) {
|
|
49
|
+
const config = __gqloom_core.weaverContext.value?.getConfig("gqloom.json");
|
|
50
|
+
if (typeof schema === "object" && !("~standard" in schema)) Object.defineProperty(schema, "~standard", {
|
|
51
|
+
value: {
|
|
52
|
+
version: 1,
|
|
53
|
+
vendor: "gqloom.json",
|
|
54
|
+
validate: (value) => ({ value })
|
|
55
|
+
},
|
|
56
|
+
enumerable: false
|
|
57
|
+
});
|
|
58
|
+
if (typeof schema === "object" && !(__gqloom_core.SYMBOLS.GET_GRAPHQL_TYPE in schema)) Object.defineProperty(schema, __gqloom_core.SYMBOLS.GET_GRAPHQL_TYPE, {
|
|
59
|
+
value: config ? function() {
|
|
60
|
+
return __gqloom_core.weaverContext.useConfig(config, () => JSONWeaver.getGraphQLTypeBySelf.call(this));
|
|
61
|
+
} : JSONWeaver.getGraphQLTypeBySelf,
|
|
62
|
+
enumerable: false
|
|
63
|
+
});
|
|
64
|
+
return schema;
|
|
65
|
+
}
|
|
66
|
+
static sourceTypeMap = /* @__PURE__ */ new Map();
|
|
67
|
+
static getGraphQLType(schema, { source } = {}) {
|
|
68
|
+
if (source) JSONWeaver.sourceTypeMap.set(schema, source);
|
|
69
|
+
return JSONWeaver.toNullableGraphQLType(schema);
|
|
70
|
+
}
|
|
71
|
+
static getGraphQLTypeBySelf() {
|
|
72
|
+
return JSONWeaver.toNullableGraphQLType(this);
|
|
73
|
+
}
|
|
74
|
+
static toNullableGraphQLType(schema) {
|
|
75
|
+
if (typeof schema === "boolean") throw new Error("Boolean JSON schemas are not supported");
|
|
76
|
+
const gqlType = JSONWeaver.toMemoriedGraphQLType(schema);
|
|
77
|
+
const type = schema.type;
|
|
78
|
+
if (typeof schema.default !== "undefined" || Array.isArray(type) && type.includes("null") || [...schema.anyOf ?? [], ...schema.oneOf ?? []].some((s) => typeof s === "object" && s && s.type === "null")) return (0, graphql.isNonNullType)(gqlType) ? gqlType.ofType : gqlType;
|
|
79
|
+
return (0, graphql.isNonNullType)(gqlType) ? gqlType : new graphql.GraphQLNonNull(gqlType);
|
|
80
|
+
}
|
|
81
|
+
static toMemoriedGraphQLType(schema) {
|
|
82
|
+
if (typeof schema === "boolean") throw new Error("Boolean JSON schemas are not supported");
|
|
83
|
+
const name = schema.title ?? JSONWeaver.getCollectedName(schema) ?? JSONWeaver.getTypeName(schema);
|
|
84
|
+
const existing = __gqloom_core.weaverContext.getGraphQLType(schema) ?? (name ? __gqloom_core.weaverContext.getNamedType(name) : void 0);
|
|
85
|
+
if (existing) return existing;
|
|
86
|
+
const gqlType = JSONWeaver.toGraphQLTypeInner(schema);
|
|
87
|
+
if (name) __gqloom_core.weaverContext.memoNamedType(gqlType);
|
|
88
|
+
return __gqloom_core.weaverContext.memoGraphQLType(schema, gqlType);
|
|
89
|
+
}
|
|
90
|
+
static toGraphQLTypeInner(schema) {
|
|
91
|
+
if (typeof schema === "boolean") throw new Error("Boolean JSON schemas are not supported");
|
|
92
|
+
const presetType = (__gqloom_core.weaverContext.value?.getConfig("gqloom.json"))?.presetGraphQLType?.(schema);
|
|
93
|
+
if (presetType) return presetType;
|
|
94
|
+
if (schema.allOf) {
|
|
95
|
+
const interfaceSchemas = schema.allOf.filter((s) => {
|
|
96
|
+
const subSchema = s;
|
|
97
|
+
return typeof subSchema === "object" && subSchema.title && subSchema.properties;
|
|
98
|
+
});
|
|
99
|
+
const mergedProperties = {};
|
|
100
|
+
const mergedRequired = [];
|
|
101
|
+
for (const subSchema of schema.allOf) {
|
|
102
|
+
const s = subSchema;
|
|
103
|
+
if (typeof s === "object" && s.properties) {
|
|
104
|
+
Object.assign(mergedProperties, s.properties);
|
|
105
|
+
if (s.required) mergedRequired.push(...s.required);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const mergedSchema = {
|
|
109
|
+
title: schema.title,
|
|
110
|
+
type: "object",
|
|
111
|
+
properties: mergedProperties,
|
|
112
|
+
required: mergedRequired,
|
|
113
|
+
description: schema.description
|
|
114
|
+
};
|
|
115
|
+
const objectType = JSONWeaver.toGraphQLTypeInner(mergedSchema);
|
|
116
|
+
if (interfaceSchemas.length > 0) {
|
|
117
|
+
const interfaceTypes = interfaceSchemas.map((interfaceSchema) => {
|
|
118
|
+
const interfaceObjectType = JSONWeaver.toMemoriedGraphQLType(interfaceSchema);
|
|
119
|
+
return (0, __gqloom_core.ensureInterfaceType)(interfaceObjectType);
|
|
120
|
+
});
|
|
121
|
+
return new graphql.GraphQLObjectType({
|
|
122
|
+
...objectType.toConfig(),
|
|
123
|
+
interfaces: interfaceTypes
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return objectType;
|
|
127
|
+
}
|
|
128
|
+
if (schema.oneOf || schema.anyOf) {
|
|
129
|
+
const schemas = (schema.oneOf ?? schema.anyOf).filter((s) => {
|
|
130
|
+
const subSchema = s;
|
|
131
|
+
if (typeof subSchema !== "object" || !subSchema) return true;
|
|
132
|
+
return subSchema.type !== "null";
|
|
133
|
+
});
|
|
134
|
+
if (schemas.length === 1) {
|
|
135
|
+
const unwrappedSchema = {
|
|
136
|
+
...schemas[0],
|
|
137
|
+
title: schema.title,
|
|
138
|
+
$id: schema.$id,
|
|
139
|
+
description: schema.description
|
|
140
|
+
};
|
|
141
|
+
return JSONWeaver.toGraphQLTypeInner(unwrappedSchema);
|
|
142
|
+
}
|
|
143
|
+
const name = schema.title ?? JSONWeaver.getCollectedName(schema);
|
|
144
|
+
if (!name) throw new Error("Union type must have a name");
|
|
145
|
+
return new graphql.GraphQLUnionType({
|
|
146
|
+
name,
|
|
147
|
+
description: schema.description,
|
|
148
|
+
types: () => schemas.map((s) => {
|
|
149
|
+
const gqlType = JSONWeaver.toMemoriedGraphQLType(s);
|
|
150
|
+
if (!(0, graphql.isObjectType)(gqlType)) throw new Error(`Union type member of ${name} must be an object type`);
|
|
151
|
+
return gqlType;
|
|
152
|
+
})
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
const type = Array.isArray(schema.type) ? schema.type.find((t) => t !== "null") : schema.type;
|
|
156
|
+
if (schema.enum) {
|
|
157
|
+
const name = schema.title ?? JSONWeaver.getCollectedName(schema);
|
|
158
|
+
if (!name) throw new Error("Enum type must have a name");
|
|
159
|
+
const values = {};
|
|
160
|
+
for (const value of schema.enum) if (typeof value === "string" || typeof value === "number") {
|
|
161
|
+
const key = String(value).replace(/[^_a-zA-Z0-9]/g, "_");
|
|
162
|
+
values[key] = { value };
|
|
163
|
+
}
|
|
164
|
+
return new graphql.GraphQLEnumType({
|
|
165
|
+
name,
|
|
166
|
+
description: schema.description,
|
|
167
|
+
values
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
switch (type) {
|
|
171
|
+
case "string": return graphql.GraphQLString;
|
|
172
|
+
case "number": return graphql.GraphQLFloat;
|
|
173
|
+
case "integer": return graphql.GraphQLInt;
|
|
174
|
+
case "boolean": return graphql.GraphQLBoolean;
|
|
175
|
+
case "array": {
|
|
176
|
+
if (!schema.items || typeof schema.items !== "object" || Array.isArray(schema.items)) throw new Error("Array schema must have a single object in 'items'");
|
|
177
|
+
const itemType = JSONWeaver.toNullableGraphQLType(schema.items);
|
|
178
|
+
return new graphql.GraphQLList(itemType);
|
|
179
|
+
}
|
|
180
|
+
case "object": {
|
|
181
|
+
const name = schema.title ?? JSONWeaver.getCollectedName(schema) ?? JSONWeaver.getTypeName(schema) ?? __gqloom_core.LoomObjectType.AUTO_ALIASING;
|
|
182
|
+
return new graphql.GraphQLObjectType({
|
|
183
|
+
name,
|
|
184
|
+
description: schema.description,
|
|
185
|
+
fields: () => (0, __gqloom_core.mapValue)(schema.properties ?? {}, (propSchema, key) => {
|
|
186
|
+
if (key.startsWith("__")) return __gqloom_core.mapValue.SKIP;
|
|
187
|
+
const fieldSchema = propSchema;
|
|
188
|
+
if (typeof fieldSchema === "boolean") throw new Error("Boolean JSON schemas are not supported in properties");
|
|
189
|
+
let fieldType = JSONWeaver.toMemoriedGraphQLType(fieldSchema);
|
|
190
|
+
if (schema.required?.includes(key)) {
|
|
191
|
+
if (!(0, graphql.isNonNullType)(fieldType)) fieldType = new graphql.GraphQLNonNull(fieldType);
|
|
192
|
+
} else if ((0, graphql.isNonNullType)(fieldType)) fieldType = fieldType.ofType;
|
|
193
|
+
return {
|
|
194
|
+
type: fieldType,
|
|
195
|
+
description: fieldSchema.description,
|
|
196
|
+
defaultValue: fieldSchema.default
|
|
197
|
+
};
|
|
198
|
+
})
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
case "null": throw new Error("Standalone 'null' type is not supported in GraphQL. It can only be used in a union type, like ['string', 'null']");
|
|
202
|
+
}
|
|
203
|
+
throw new Error(`Unsupported JSON schema type: ${String(type)}`);
|
|
204
|
+
}
|
|
205
|
+
static getTypeName(schema) {
|
|
206
|
+
if (typeof schema !== "object") return void 0;
|
|
207
|
+
if (!schema.properties?.__typename) return void 0;
|
|
208
|
+
const typenameSchema = schema.properties?.__typename;
|
|
209
|
+
if (typeof typenameSchema === "object" && typenameSchema.const && typeof typenameSchema.const === "string") return typenameSchema.const;
|
|
210
|
+
if (typeof typenameSchema === "object" && typenameSchema.enum && Array.isArray(typenameSchema.enum) && typenameSchema.enum.length === 1) return typenameSchema.enum[0];
|
|
211
|
+
}
|
|
212
|
+
static getCollectedName(schema) {
|
|
213
|
+
if (typeof schema !== "object") return void 0;
|
|
214
|
+
const name = __gqloom_core.weaverContext.names.get(schema);
|
|
215
|
+
if (name) return name;
|
|
216
|
+
const source = JSONWeaver.sourceTypeMap.get(schema);
|
|
217
|
+
if (source == null) return void 0;
|
|
218
|
+
return __gqloom_core.weaverContext.names.get(source);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
function jsonSilk(schema) {
|
|
222
|
+
return JSONWeaver.unravel(schema);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
//#endregion
|
|
226
|
+
exports.JSONWeaver = JSONWeaver;
|
|
227
|
+
exports.jsonSilk = jsonSilk;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { GraphQLSilk, SYMBOLS, WeaverConfig } from "@gqloom/core";
|
|
2
|
+
import { GraphQLOutputType } from "graphql";
|
|
3
|
+
import { FromSchema, JSONSchema, JSONSchema as JSONSchema$1 } from "json-schema-to-ts";
|
|
4
|
+
|
|
5
|
+
//#region src/types.d.ts
|
|
6
|
+
interface JSONWeaverConfigOptions {
|
|
7
|
+
presetGraphQLType?: (schema: JSONSchema$1) => GraphQLOutputType | undefined;
|
|
8
|
+
}
|
|
9
|
+
interface JSONWeaverConfig extends WeaverConfig, JSONWeaverConfigOptions {
|
|
10
|
+
[SYMBOLS.WEAVER_CONFIG]: "gqloom.json";
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/index.d.ts
|
|
14
|
+
declare class JSONWeaver {
|
|
15
|
+
static vendor: string;
|
|
16
|
+
/**
|
|
17
|
+
* Create a JSON weaver config object
|
|
18
|
+
* @param config JSON weaver config options
|
|
19
|
+
* @returns a JSON weaver config object
|
|
20
|
+
*/
|
|
21
|
+
static config: (config: JSONWeaverConfigOptions) => JSONWeaverConfig;
|
|
22
|
+
/**
|
|
23
|
+
* get GraphQL Silk from JSON Schema
|
|
24
|
+
* @param schema JSON Schema
|
|
25
|
+
* @returns GraphQL Silk Like JSON Schema
|
|
26
|
+
*/
|
|
27
|
+
static unravel<const TSchema extends JSONSchema, TData = FromSchema<TSchema>>(schema: TSchema): JSONSilk<TSchema, TData>;
|
|
28
|
+
protected static sourceTypeMap: Map<JSONSchema, any>;
|
|
29
|
+
static getGraphQLType(schema: JSONSchema, {
|
|
30
|
+
source
|
|
31
|
+
}?: {
|
|
32
|
+
source?: any;
|
|
33
|
+
}): GraphQLOutputType;
|
|
34
|
+
protected static getGraphQLTypeBySelf(this: JSONSchema): GraphQLOutputType;
|
|
35
|
+
protected static toNullableGraphQLType(schema: JSONSchema): GraphQLOutputType;
|
|
36
|
+
protected static toMemoriedGraphQLType(schema: JSONSchema): GraphQLOutputType;
|
|
37
|
+
protected static toGraphQLTypeInner(schema: JSONSchema): GraphQLOutputType;
|
|
38
|
+
protected static getTypeName(schema: JSONSchema): string | undefined;
|
|
39
|
+
protected static getCollectedName(schema: JSONSchema): string | undefined;
|
|
40
|
+
}
|
|
41
|
+
type JSONSilk<TSchema extends JSONSchema, TData = FromSchema<TSchema>> = TSchema & GraphQLSilk<TData, TData>;
|
|
42
|
+
declare function jsonSilk<const TSchema extends JSONSchema, TData = FromSchema<TSchema>>(schema: TSchema): JSONSilk<TSchema, TData>;
|
|
43
|
+
//#endregion
|
|
44
|
+
export { type FromSchema, type JSONSchema, JSONSilk, JSONWeaver, jsonSilk };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { GraphQLSilk, SYMBOLS, WeaverConfig } from "@gqloom/core";
|
|
2
|
+
import { GraphQLOutputType } from "graphql";
|
|
3
|
+
import { FromSchema, JSONSchema, JSONSchema as JSONSchema$1 } from "json-schema-to-ts";
|
|
4
|
+
|
|
5
|
+
//#region src/types.d.ts
|
|
6
|
+
interface JSONWeaverConfigOptions {
|
|
7
|
+
presetGraphQLType?: (schema: JSONSchema$1) => GraphQLOutputType | undefined;
|
|
8
|
+
}
|
|
9
|
+
interface JSONWeaverConfig extends WeaverConfig, JSONWeaverConfigOptions {
|
|
10
|
+
[SYMBOLS.WEAVER_CONFIG]: "gqloom.json";
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/index.d.ts
|
|
14
|
+
declare class JSONWeaver {
|
|
15
|
+
static vendor: string;
|
|
16
|
+
/**
|
|
17
|
+
* Create a JSON weaver config object
|
|
18
|
+
* @param config JSON weaver config options
|
|
19
|
+
* @returns a JSON weaver config object
|
|
20
|
+
*/
|
|
21
|
+
static config: (config: JSONWeaverConfigOptions) => JSONWeaverConfig;
|
|
22
|
+
/**
|
|
23
|
+
* get GraphQL Silk from JSON Schema
|
|
24
|
+
* @param schema JSON Schema
|
|
25
|
+
* @returns GraphQL Silk Like JSON Schema
|
|
26
|
+
*/
|
|
27
|
+
static unravel<const TSchema extends JSONSchema, TData = FromSchema<TSchema>>(schema: TSchema): JSONSilk<TSchema, TData>;
|
|
28
|
+
protected static sourceTypeMap: Map<JSONSchema, any>;
|
|
29
|
+
static getGraphQLType(schema: JSONSchema, {
|
|
30
|
+
source
|
|
31
|
+
}?: {
|
|
32
|
+
source?: any;
|
|
33
|
+
}): GraphQLOutputType;
|
|
34
|
+
protected static getGraphQLTypeBySelf(this: JSONSchema): GraphQLOutputType;
|
|
35
|
+
protected static toNullableGraphQLType(schema: JSONSchema): GraphQLOutputType;
|
|
36
|
+
protected static toMemoriedGraphQLType(schema: JSONSchema): GraphQLOutputType;
|
|
37
|
+
protected static toGraphQLTypeInner(schema: JSONSchema): GraphQLOutputType;
|
|
38
|
+
protected static getTypeName(schema: JSONSchema): string | undefined;
|
|
39
|
+
protected static getCollectedName(schema: JSONSchema): string | undefined;
|
|
40
|
+
}
|
|
41
|
+
type JSONSilk<TSchema extends JSONSchema, TData = FromSchema<TSchema>> = TSchema & GraphQLSilk<TData, TData>;
|
|
42
|
+
declare function jsonSilk<const TSchema extends JSONSchema, TData = FromSchema<TSchema>>(schema: TSchema): JSONSilk<TSchema, TData>;
|
|
43
|
+
//#endregion
|
|
44
|
+
export { type FromSchema, type JSONSchema, JSONSilk, JSONWeaver, jsonSilk };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { LoomObjectType, SYMBOLS, ensureInterfaceType, mapValue, weaverContext } from "@gqloom/core";
|
|
2
|
+
import { GraphQLBoolean, GraphQLEnumType, GraphQLFloat, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLString, GraphQLUnionType, isNonNullType, isObjectType } from "graphql";
|
|
3
|
+
|
|
4
|
+
//#region src/index.ts
|
|
5
|
+
var JSONWeaver = class JSONWeaver {
|
|
6
|
+
static vendor = "json";
|
|
7
|
+
/**
|
|
8
|
+
* Create a JSON weaver config object
|
|
9
|
+
* @param config JSON weaver config options
|
|
10
|
+
* @returns a JSON weaver config object
|
|
11
|
+
*/
|
|
12
|
+
static config = function(config) {
|
|
13
|
+
return {
|
|
14
|
+
...config,
|
|
15
|
+
[SYMBOLS.WEAVER_CONFIG]: "gqloom.json"
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* get GraphQL Silk from JSON Schema
|
|
20
|
+
* @param schema JSON Schema
|
|
21
|
+
* @returns GraphQL Silk Like JSON Schema
|
|
22
|
+
*/
|
|
23
|
+
static unravel(schema) {
|
|
24
|
+
const config = weaverContext.value?.getConfig("gqloom.json");
|
|
25
|
+
if (typeof schema === "object" && !("~standard" in schema)) Object.defineProperty(schema, "~standard", {
|
|
26
|
+
value: {
|
|
27
|
+
version: 1,
|
|
28
|
+
vendor: "gqloom.json",
|
|
29
|
+
validate: (value) => ({ value })
|
|
30
|
+
},
|
|
31
|
+
enumerable: false
|
|
32
|
+
});
|
|
33
|
+
if (typeof schema === "object" && !(SYMBOLS.GET_GRAPHQL_TYPE in schema)) Object.defineProperty(schema, SYMBOLS.GET_GRAPHQL_TYPE, {
|
|
34
|
+
value: config ? function() {
|
|
35
|
+
return weaverContext.useConfig(config, () => JSONWeaver.getGraphQLTypeBySelf.call(this));
|
|
36
|
+
} : JSONWeaver.getGraphQLTypeBySelf,
|
|
37
|
+
enumerable: false
|
|
38
|
+
});
|
|
39
|
+
return schema;
|
|
40
|
+
}
|
|
41
|
+
static sourceTypeMap = /* @__PURE__ */ new Map();
|
|
42
|
+
static getGraphQLType(schema, { source } = {}) {
|
|
43
|
+
if (source) JSONWeaver.sourceTypeMap.set(schema, source);
|
|
44
|
+
return JSONWeaver.toNullableGraphQLType(schema);
|
|
45
|
+
}
|
|
46
|
+
static getGraphQLTypeBySelf() {
|
|
47
|
+
return JSONWeaver.toNullableGraphQLType(this);
|
|
48
|
+
}
|
|
49
|
+
static toNullableGraphQLType(schema) {
|
|
50
|
+
if (typeof schema === "boolean") throw new Error("Boolean JSON schemas are not supported");
|
|
51
|
+
const gqlType = JSONWeaver.toMemoriedGraphQLType(schema);
|
|
52
|
+
const type = schema.type;
|
|
53
|
+
if (typeof schema.default !== "undefined" || Array.isArray(type) && type.includes("null") || [...schema.anyOf ?? [], ...schema.oneOf ?? []].some((s) => typeof s === "object" && s && s.type === "null")) return isNonNullType(gqlType) ? gqlType.ofType : gqlType;
|
|
54
|
+
return isNonNullType(gqlType) ? gqlType : new GraphQLNonNull(gqlType);
|
|
55
|
+
}
|
|
56
|
+
static toMemoriedGraphQLType(schema) {
|
|
57
|
+
if (typeof schema === "boolean") throw new Error("Boolean JSON schemas are not supported");
|
|
58
|
+
const name = schema.title ?? JSONWeaver.getCollectedName(schema) ?? JSONWeaver.getTypeName(schema);
|
|
59
|
+
const existing = weaverContext.getGraphQLType(schema) ?? (name ? weaverContext.getNamedType(name) : void 0);
|
|
60
|
+
if (existing) return existing;
|
|
61
|
+
const gqlType = JSONWeaver.toGraphQLTypeInner(schema);
|
|
62
|
+
if (name) weaverContext.memoNamedType(gqlType);
|
|
63
|
+
return weaverContext.memoGraphQLType(schema, gqlType);
|
|
64
|
+
}
|
|
65
|
+
static toGraphQLTypeInner(schema) {
|
|
66
|
+
if (typeof schema === "boolean") throw new Error("Boolean JSON schemas are not supported");
|
|
67
|
+
const presetType = (weaverContext.value?.getConfig("gqloom.json"))?.presetGraphQLType?.(schema);
|
|
68
|
+
if (presetType) return presetType;
|
|
69
|
+
if (schema.allOf) {
|
|
70
|
+
const interfaceSchemas = schema.allOf.filter((s) => {
|
|
71
|
+
const subSchema = s;
|
|
72
|
+
return typeof subSchema === "object" && subSchema.title && subSchema.properties;
|
|
73
|
+
});
|
|
74
|
+
const mergedProperties = {};
|
|
75
|
+
const mergedRequired = [];
|
|
76
|
+
for (const subSchema of schema.allOf) {
|
|
77
|
+
const s = subSchema;
|
|
78
|
+
if (typeof s === "object" && s.properties) {
|
|
79
|
+
Object.assign(mergedProperties, s.properties);
|
|
80
|
+
if (s.required) mergedRequired.push(...s.required);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const mergedSchema = {
|
|
84
|
+
title: schema.title,
|
|
85
|
+
type: "object",
|
|
86
|
+
properties: mergedProperties,
|
|
87
|
+
required: mergedRequired,
|
|
88
|
+
description: schema.description
|
|
89
|
+
};
|
|
90
|
+
const objectType = JSONWeaver.toGraphQLTypeInner(mergedSchema);
|
|
91
|
+
if (interfaceSchemas.length > 0) {
|
|
92
|
+
const interfaceTypes = interfaceSchemas.map((interfaceSchema) => {
|
|
93
|
+
const interfaceObjectType = JSONWeaver.toMemoriedGraphQLType(interfaceSchema);
|
|
94
|
+
return ensureInterfaceType(interfaceObjectType);
|
|
95
|
+
});
|
|
96
|
+
return new GraphQLObjectType({
|
|
97
|
+
...objectType.toConfig(),
|
|
98
|
+
interfaces: interfaceTypes
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return objectType;
|
|
102
|
+
}
|
|
103
|
+
if (schema.oneOf || schema.anyOf) {
|
|
104
|
+
const schemas = (schema.oneOf ?? schema.anyOf).filter((s) => {
|
|
105
|
+
const subSchema = s;
|
|
106
|
+
if (typeof subSchema !== "object" || !subSchema) return true;
|
|
107
|
+
return subSchema.type !== "null";
|
|
108
|
+
});
|
|
109
|
+
if (schemas.length === 1) {
|
|
110
|
+
const unwrappedSchema = {
|
|
111
|
+
...schemas[0],
|
|
112
|
+
title: schema.title,
|
|
113
|
+
$id: schema.$id,
|
|
114
|
+
description: schema.description
|
|
115
|
+
};
|
|
116
|
+
return JSONWeaver.toGraphQLTypeInner(unwrappedSchema);
|
|
117
|
+
}
|
|
118
|
+
const name = schema.title ?? JSONWeaver.getCollectedName(schema);
|
|
119
|
+
if (!name) throw new Error("Union type must have a name");
|
|
120
|
+
return new GraphQLUnionType({
|
|
121
|
+
name,
|
|
122
|
+
description: schema.description,
|
|
123
|
+
types: () => schemas.map((s) => {
|
|
124
|
+
const gqlType = JSONWeaver.toMemoriedGraphQLType(s);
|
|
125
|
+
if (!isObjectType(gqlType)) throw new Error(`Union type member of ${name} must be an object type`);
|
|
126
|
+
return gqlType;
|
|
127
|
+
})
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const type = Array.isArray(schema.type) ? schema.type.find((t) => t !== "null") : schema.type;
|
|
131
|
+
if (schema.enum) {
|
|
132
|
+
const name = schema.title ?? JSONWeaver.getCollectedName(schema);
|
|
133
|
+
if (!name) throw new Error("Enum type must have a name");
|
|
134
|
+
const values = {};
|
|
135
|
+
for (const value of schema.enum) if (typeof value === "string" || typeof value === "number") {
|
|
136
|
+
const key = String(value).replace(/[^_a-zA-Z0-9]/g, "_");
|
|
137
|
+
values[key] = { value };
|
|
138
|
+
}
|
|
139
|
+
return new GraphQLEnumType({
|
|
140
|
+
name,
|
|
141
|
+
description: schema.description,
|
|
142
|
+
values
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
switch (type) {
|
|
146
|
+
case "string": return GraphQLString;
|
|
147
|
+
case "number": return GraphQLFloat;
|
|
148
|
+
case "integer": return GraphQLInt;
|
|
149
|
+
case "boolean": return GraphQLBoolean;
|
|
150
|
+
case "array": {
|
|
151
|
+
if (!schema.items || typeof schema.items !== "object" || Array.isArray(schema.items)) throw new Error("Array schema must have a single object in 'items'");
|
|
152
|
+
const itemType = JSONWeaver.toNullableGraphQLType(schema.items);
|
|
153
|
+
return new GraphQLList(itemType);
|
|
154
|
+
}
|
|
155
|
+
case "object": {
|
|
156
|
+
const name = schema.title ?? JSONWeaver.getCollectedName(schema) ?? JSONWeaver.getTypeName(schema) ?? LoomObjectType.AUTO_ALIASING;
|
|
157
|
+
return new GraphQLObjectType({
|
|
158
|
+
name,
|
|
159
|
+
description: schema.description,
|
|
160
|
+
fields: () => mapValue(schema.properties ?? {}, (propSchema, key) => {
|
|
161
|
+
if (key.startsWith("__")) return mapValue.SKIP;
|
|
162
|
+
const fieldSchema = propSchema;
|
|
163
|
+
if (typeof fieldSchema === "boolean") throw new Error("Boolean JSON schemas are not supported in properties");
|
|
164
|
+
let fieldType = JSONWeaver.toMemoriedGraphQLType(fieldSchema);
|
|
165
|
+
if (schema.required?.includes(key)) {
|
|
166
|
+
if (!isNonNullType(fieldType)) fieldType = new GraphQLNonNull(fieldType);
|
|
167
|
+
} else if (isNonNullType(fieldType)) fieldType = fieldType.ofType;
|
|
168
|
+
return {
|
|
169
|
+
type: fieldType,
|
|
170
|
+
description: fieldSchema.description,
|
|
171
|
+
defaultValue: fieldSchema.default
|
|
172
|
+
};
|
|
173
|
+
})
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
case "null": throw new Error("Standalone 'null' type is not supported in GraphQL. It can only be used in a union type, like ['string', 'null']");
|
|
177
|
+
}
|
|
178
|
+
throw new Error(`Unsupported JSON schema type: ${String(type)}`);
|
|
179
|
+
}
|
|
180
|
+
static getTypeName(schema) {
|
|
181
|
+
if (typeof schema !== "object") return void 0;
|
|
182
|
+
if (!schema.properties?.__typename) return void 0;
|
|
183
|
+
const typenameSchema = schema.properties?.__typename;
|
|
184
|
+
if (typeof typenameSchema === "object" && typenameSchema.const && typeof typenameSchema.const === "string") return typenameSchema.const;
|
|
185
|
+
if (typeof typenameSchema === "object" && typenameSchema.enum && Array.isArray(typenameSchema.enum) && typenameSchema.enum.length === 1) return typenameSchema.enum[0];
|
|
186
|
+
}
|
|
187
|
+
static getCollectedName(schema) {
|
|
188
|
+
if (typeof schema !== "object") return void 0;
|
|
189
|
+
const name = weaverContext.names.get(schema);
|
|
190
|
+
if (name) return name;
|
|
191
|
+
const source = JSONWeaver.sourceTypeMap.get(schema);
|
|
192
|
+
if (source == null) return void 0;
|
|
193
|
+
return weaverContext.names.get(source);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
function jsonSilk(schema) {
|
|
197
|
+
return JSONWeaver.unravel(schema);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
//#endregion
|
|
201
|
+
export { JSONWeaver, jsonSilk };
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gqloom/json",
|
|
3
|
+
"version": "0.11.0",
|
|
4
|
+
"description": "Create GraphQL schema and resolvers easily using using JSON Schema!",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.cts",
|
|
16
|
+
"default": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"keywords": [
|
|
24
|
+
"gqloom",
|
|
25
|
+
"graphql",
|
|
26
|
+
"schema",
|
|
27
|
+
"typescript",
|
|
28
|
+
"json-schema",
|
|
29
|
+
"validation",
|
|
30
|
+
"validate"
|
|
31
|
+
],
|
|
32
|
+
"author": "xcfox",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@gqloom/core": ">= 0.11.1",
|
|
36
|
+
"graphql": ">= 16.8.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"ajv": "^8.17.1",
|
|
40
|
+
"arktype": "^2.1.22",
|
|
41
|
+
"effect": "^3.17.13",
|
|
42
|
+
"typebox": "^1.0.4",
|
|
43
|
+
"@gqloom/core": "0.11.1"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://gqloom.dev/",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "https://github.com/modevol-com/gqloom.git",
|
|
49
|
+
"directory": "packages/json"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"json-schema-to-ts": "^3.1.1"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsdown",
|
|
59
|
+
"check:type": "tsgo --noEmit"
|
|
60
|
+
}
|
|
61
|
+
}
|