@gqlbase/plugins 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ITransformerPlugin } from "@gqlbase/core";
|
|
2
|
+
import type { ITransformerContext } from "@gqlbase/core/context";
|
|
3
|
+
import { DefinitionNode, ObjectNode } from "@gqlbase/core/definition";
|
|
4
|
+
export declare const ModelDirective: {
|
|
5
|
+
readonly MODEL: "model";
|
|
6
|
+
};
|
|
7
|
+
export declare const ModelOperation: {
|
|
8
|
+
readonly GET: "get";
|
|
9
|
+
readonly LIST: "list";
|
|
10
|
+
readonly SYNC: "sync";
|
|
11
|
+
readonly SUBSCRIBE: "subscribe";
|
|
12
|
+
readonly CREATE: "create";
|
|
13
|
+
readonly UPDATE: "update";
|
|
14
|
+
readonly UPSERT: "upsert";
|
|
15
|
+
readonly DELETE: "delete";
|
|
16
|
+
readonly READ: "read";
|
|
17
|
+
readonly WRITE: "write";
|
|
18
|
+
};
|
|
19
|
+
type OperationType = (typeof ModelOperation)[keyof typeof ModelOperation];
|
|
20
|
+
export declare const DEFAULT_READ_OPERATIONS: OperationType[];
|
|
21
|
+
export declare const DEFAULT_WRITE_OPERATIONS: OperationType[];
|
|
22
|
+
export declare const isModel: (definition: DefinitionNode) => definition is ObjectNode;
|
|
23
|
+
export interface ModelPluginOptions {
|
|
24
|
+
operations: OperationType[];
|
|
25
|
+
}
|
|
26
|
+
export declare class ModelPlugin implements ITransformerPlugin {
|
|
27
|
+
readonly name = "ModelPlugin";
|
|
28
|
+
readonly context: ITransformerContext;
|
|
29
|
+
private _defaultOperations;
|
|
30
|
+
constructor(context: ITransformerContext, options?: ModelPluginOptions);
|
|
31
|
+
private _expandOperations;
|
|
32
|
+
private _getOperationNames;
|
|
33
|
+
private _createGetQuery;
|
|
34
|
+
private _createListQuery;
|
|
35
|
+
private _shouldSkipFieldFromMutationInput;
|
|
36
|
+
private _isBuildInScalarType;
|
|
37
|
+
/**
|
|
38
|
+
* For each field in the model
|
|
39
|
+
* 1. If has `@readOnly` or connection directives - skip
|
|
40
|
+
* 2. If scalar or enum - add to input;
|
|
41
|
+
* 3. If union - skip;
|
|
42
|
+
* 4. If object or interface - check definition;
|
|
43
|
+
* 4.1 If `@model` - skip;
|
|
44
|
+
* 4.2 If implements `Node` interface - skip
|
|
45
|
+
* 4.3 If fields are `@readOnly` - skip;
|
|
46
|
+
*/
|
|
47
|
+
private _createMutationInput;
|
|
48
|
+
private _createDeleteMutationField;
|
|
49
|
+
private _createMutationField;
|
|
50
|
+
private _createMutation;
|
|
51
|
+
init(): void;
|
|
52
|
+
match(definition: DefinitionNode): definition is ObjectNode;
|
|
53
|
+
execute(definition: ObjectNode): void;
|
|
54
|
+
cleanup(definition: ObjectNode): void;
|
|
55
|
+
after(): void;
|
|
56
|
+
}
|
|
57
|
+
export declare const modelPlugin: (options?: ModelPluginOptions | undefined) => import("@gqlbase/core").IPluginFactory;
|
|
58
|
+
export {};
|
|
59
|
+
//# sourceMappingURL=ModelPlugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ModelPlugin.d.ts","sourceRoot":"","sources":["../../src/base/ModelPlugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EACL,cAAc,EAUd,UAAU,EAEX,MAAM,0BAA0B,CAAC;AAKlC,eAAO,MAAM,cAAc;;CAEjB,CAAC;AAEX,eAAO,MAAM,cAAc;;;;;;;;;;;CAWjB,CAAC;AAEX,KAAK,aAAa,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,OAAO,cAAc,CAAC,CAAC;AAE1E,eAAO,MAAM,uBAAuB,EAAE,aAAa,EAAoB,CAAC;AACxE,eAAO,MAAM,wBAAwB,EAAE,aAAa,EAAmC,CAAC;AAExF,eAAO,MAAM,OAAO,GAAI,YAAY,cAAc,KAAG,UAAU,IAAI,UAElE,CAAC;AAEF,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,aAAa,EAAE,CAAC;CAC7B;AAED,qBAAa,WAAY,YAAW,kBAAkB;IACpD,SAAgB,IAAI,iBAAiB;IACrC,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IACtC,OAAO,CAAC,kBAAkB,CAAkB;gBAG1C,OAAO,EAAE,mBAAmB,EAC5B,OAAO,GAAE,kBAAsD;IASjE,OAAO,CAAC,iBAAiB;IAgBzB,OAAO,CAAC,kBAAkB;IAmB1B,OAAO,CAAC,eAAe;IAkBvB,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,iCAAiC;IAUzC,OAAO,CAAC,oBAAoB;IAI5B;;;;;;;;;OASG;IAEH,OAAO,CAAC,oBAAoB;IAkE5B,OAAO,CAAC,0BAA0B;IAYlC,OAAO,CAAC,oBAAoB;IAqB5B,OAAO,CAAC,eAAe;IAehB,IAAI;IAqBJ,KAAK,CAAC,UAAU,EAAE,cAAc;IAIhC,OAAO,CAAC,UAAU,EAAE,UAAU;IAwB9B,OAAO,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI;IAIrC,KAAK,IAAI,IAAI;CAGrB;AAED,eAAO,MAAM,WAAW,sFAAmC,CAAC"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { DirectiveDefinitionNode, DirectiveNode, EnumNode, FieldNode, InputObjectNode, InputValueNode, ListTypeNode, NamedTypeNode, NonNullTypeNode, ObjectNode, ScalarNode, } from "@gqlbase/core/definition";
|
|
2
|
+
import { createPluginFactory, InternalDirective } from "@gqlbase/core/plugins";
|
|
3
|
+
import { TransformerPluginExecutionError } from "@gqlbase/shared/errors";
|
|
4
|
+
import { camelCase, pascalCase, pluralize } from "@gqlbase/shared/format";
|
|
5
|
+
export const ModelDirective = {
|
|
6
|
+
MODEL: "model",
|
|
7
|
+
};
|
|
8
|
+
export const ModelOperation = {
|
|
9
|
+
GET: "get",
|
|
10
|
+
LIST: "list",
|
|
11
|
+
SYNC: "sync",
|
|
12
|
+
SUBSCRIBE: "subscribe",
|
|
13
|
+
CREATE: "create",
|
|
14
|
+
UPDATE: "update",
|
|
15
|
+
UPSERT: "upsert",
|
|
16
|
+
DELETE: "delete",
|
|
17
|
+
READ: "read",
|
|
18
|
+
WRITE: "write",
|
|
19
|
+
};
|
|
20
|
+
export const DEFAULT_READ_OPERATIONS = ["get", "list"];
|
|
21
|
+
export const DEFAULT_WRITE_OPERATIONS = ["create", "update", "delete"];
|
|
22
|
+
export const isModel = (definition) => {
|
|
23
|
+
return definition instanceof ObjectNode && definition.hasDirective(ModelDirective.MODEL);
|
|
24
|
+
};
|
|
25
|
+
export class ModelPlugin {
|
|
26
|
+
name = "ModelPlugin";
|
|
27
|
+
context;
|
|
28
|
+
_defaultOperations;
|
|
29
|
+
constructor(context, options = { operations: ["read", "write"] }) {
|
|
30
|
+
this.context = context;
|
|
31
|
+
this._defaultOperations = this._expandOperations(options.operations);
|
|
32
|
+
}
|
|
33
|
+
// #region Operations
|
|
34
|
+
_expandOperations(operations) {
|
|
35
|
+
const expandedOperations = new Set();
|
|
36
|
+
for (const operation of operations) {
|
|
37
|
+
if (operation === "read") {
|
|
38
|
+
DEFAULT_READ_OPERATIONS.forEach((op) => expandedOperations.add(op));
|
|
39
|
+
}
|
|
40
|
+
else if (operation === "write") {
|
|
41
|
+
DEFAULT_WRITE_OPERATIONS.forEach((op) => expandedOperations.add(op));
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
expandedOperations.add(operation);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return Array.from(expandedOperations);
|
|
48
|
+
}
|
|
49
|
+
_getOperationNames(object) {
|
|
50
|
+
const directive = object.getDirective("model");
|
|
51
|
+
if (!directive) {
|
|
52
|
+
throw new TransformerPluginExecutionError(this.name, `@model directive not found for type ${object.name}`);
|
|
53
|
+
}
|
|
54
|
+
const args = directive.getArgumentsJSON();
|
|
55
|
+
if (args.operations && args.operations.length > 0) {
|
|
56
|
+
return this._expandOperations(args.operations);
|
|
57
|
+
}
|
|
58
|
+
return this._defaultOperations;
|
|
59
|
+
}
|
|
60
|
+
_createGetQuery(model) {
|
|
61
|
+
const fieldName = camelCase("get", model.name);
|
|
62
|
+
const queryNode = this.context.document.getQueryNode();
|
|
63
|
+
// We allow users to implement custom definition for fields.
|
|
64
|
+
// So, if the field already exists, we skip creating it.
|
|
65
|
+
if (!queryNode.hasField(fieldName)) {
|
|
66
|
+
const field = FieldNode.create(fieldName, NamedTypeNode.create(model.name), [InputValueNode.create("id", NonNullTypeNode.create("ID"))], [DirectiveNode.create("hasOne")]);
|
|
67
|
+
queryNode.addField(field);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
_createListQuery(model) {
|
|
71
|
+
const fieldName = camelCase("list", pluralize(model.name));
|
|
72
|
+
const queryNode = this.context.document.getQueryNode();
|
|
73
|
+
if (!queryNode.hasField(fieldName)) {
|
|
74
|
+
const field = FieldNode.create(fieldName, NamedTypeNode.create(model.name), null, [
|
|
75
|
+
DirectiveNode.create("hasMany"),
|
|
76
|
+
]);
|
|
77
|
+
queryNode.addField(field);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
_shouldSkipFieldFromMutationInput(field) {
|
|
81
|
+
return (field.hasDirective("readOnly") ||
|
|
82
|
+
field.hasDirective("serverOnly") ||
|
|
83
|
+
field.hasDirective("clientOnly") ||
|
|
84
|
+
field.hasDirective("hasOne") ||
|
|
85
|
+
field.hasDirective("hasMany"));
|
|
86
|
+
}
|
|
87
|
+
_isBuildInScalarType(typeName) {
|
|
88
|
+
return ["ID", "String", "Int", "Float", "Boolean"].includes(typeName);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* For each field in the model
|
|
92
|
+
* 1. If has `@readOnly` or connection directives - skip
|
|
93
|
+
* 2. If scalar or enum - add to input;
|
|
94
|
+
* 3. If union - skip;
|
|
95
|
+
* 4. If object or interface - check definition;
|
|
96
|
+
* 4.1 If `@model` - skip;
|
|
97
|
+
* 4.2 If implements `Node` interface - skip
|
|
98
|
+
* 4.3 If fields are `@readOnly` - skip;
|
|
99
|
+
*/
|
|
100
|
+
_createMutationInput(model, inputName, requiredFields = []) {
|
|
101
|
+
const input = InputObjectNode.create(inputName);
|
|
102
|
+
if (inputName.startsWith("Delete")) {
|
|
103
|
+
input.addField(InputValueNode.create("id", NonNullTypeNode.create(NamedTypeNode.create("ID"))));
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
for (const field of model.fields ?? []) {
|
|
107
|
+
if (this._shouldSkipFieldFromMutationInput(field)) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const fieldTypeName = field.type.getTypeName();
|
|
111
|
+
if (requiredFields.includes(field.name)) {
|
|
112
|
+
input.addField(InputValueNode.create(field.name, NonNullTypeNode.create(fieldTypeName)));
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
// Buildin scalars
|
|
116
|
+
if (this._isBuildInScalarType(fieldTypeName)) {
|
|
117
|
+
input.addField(InputValueNode.create(field.name, NamedTypeNode.create(fieldTypeName)));
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const typeDef = this.context.document.getNode(fieldTypeName);
|
|
121
|
+
if (!typeDef) {
|
|
122
|
+
throw new TransformerPluginExecutionError(this.name, `Unknown type ${fieldTypeName}`);
|
|
123
|
+
}
|
|
124
|
+
if (typeDef instanceof ScalarNode || typeDef instanceof EnumNode) {
|
|
125
|
+
input.addField(InputValueNode.create(field.name, NamedTypeNode.create(fieldTypeName)));
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (typeDef instanceof ObjectNode) {
|
|
129
|
+
if (typeDef.hasDirective("model") ||
|
|
130
|
+
typeDef.hasDirective("readOnly") ||
|
|
131
|
+
typeDef.hasDirective("serverOnly") ||
|
|
132
|
+
typeDef.hasDirective("clientOnly") ||
|
|
133
|
+
typeDef.hasInterface("Node")) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const inputName = pascalCase(fieldTypeName, "input");
|
|
137
|
+
if (!this.context.document.hasNode(inputName)) {
|
|
138
|
+
this._createMutationInput(typeDef, inputName);
|
|
139
|
+
}
|
|
140
|
+
input.addField(InputValueNode.create(field.name, NamedTypeNode.create(inputName)));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
this.context.document.addNode(input);
|
|
145
|
+
}
|
|
146
|
+
_createDeleteMutationField(model, fieldName) {
|
|
147
|
+
const mutationNode = this.context.document.getMutationNode();
|
|
148
|
+
if (!mutationNode.hasField(fieldName)) {
|
|
149
|
+
const field = FieldNode.create(fieldName, NamedTypeNode.create(model.name), [
|
|
150
|
+
InputValueNode.create("id", NonNullTypeNode.create(NamedTypeNode.create("ID"))),
|
|
151
|
+
]);
|
|
152
|
+
mutationNode.addField(field);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
_createMutationField(model, fieldName, inputName, requiredFields) {
|
|
156
|
+
const mutationNode = this.context.document.getMutationNode();
|
|
157
|
+
if (!this.context.document.getNode(inputName)) {
|
|
158
|
+
this._createMutationInput(model, inputName, requiredFields);
|
|
159
|
+
}
|
|
160
|
+
if (!mutationNode.hasField(fieldName)) {
|
|
161
|
+
const field = FieldNode.create(fieldName, NamedTypeNode.create(model.name), [
|
|
162
|
+
InputValueNode.create("input", NonNullTypeNode.create(NamedTypeNode.create(inputName))),
|
|
163
|
+
]);
|
|
164
|
+
mutationNode.addField(field);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
_createMutation(model, verb) {
|
|
168
|
+
const fieldName = camelCase(verb, model.name);
|
|
169
|
+
const inputName = pascalCase(verb, model.name, "input");
|
|
170
|
+
if (verb === "delete") {
|
|
171
|
+
this._createDeleteMutationField(model, fieldName);
|
|
172
|
+
}
|
|
173
|
+
else if (verb === "update") {
|
|
174
|
+
this._createMutationField(model, fieldName, inputName, ["id"]);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
this._createMutationField(model, fieldName, inputName);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// #endregion Operations
|
|
181
|
+
init() {
|
|
182
|
+
this.context.base
|
|
183
|
+
.addNode(EnumNode.create("ModelOperation", Object.values(ModelOperation), [
|
|
184
|
+
DirectiveNode.create(InternalDirective.INTERNAL),
|
|
185
|
+
]))
|
|
186
|
+
.addNode(DirectiveDefinitionNode.create("model", ["OBJECT"], [
|
|
187
|
+
InputValueNode.create("operations", ListTypeNode.create(NonNullTypeNode.create("ModelOperation"))),
|
|
188
|
+
]));
|
|
189
|
+
}
|
|
190
|
+
match(definition) {
|
|
191
|
+
return isModel(definition);
|
|
192
|
+
}
|
|
193
|
+
execute(definition) {
|
|
194
|
+
// 1. Add operation fields
|
|
195
|
+
const operations = this._getOperationNames(definition);
|
|
196
|
+
for (const verb of operations) {
|
|
197
|
+
switch (verb) {
|
|
198
|
+
case "get":
|
|
199
|
+
this._createGetQuery(definition);
|
|
200
|
+
break;
|
|
201
|
+
case "list":
|
|
202
|
+
this._createListQuery(definition);
|
|
203
|
+
break;
|
|
204
|
+
case "create":
|
|
205
|
+
case "upsert":
|
|
206
|
+
case "update":
|
|
207
|
+
case "delete":
|
|
208
|
+
this._createMutation(definition, verb);
|
|
209
|
+
break;
|
|
210
|
+
default:
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
cleanup(definition) {
|
|
216
|
+
definition.removeDirective(ModelDirective.MODEL);
|
|
217
|
+
}
|
|
218
|
+
after() {
|
|
219
|
+
this.context.document.removeNode(ModelDirective.MODEL).removeNode("ModelOperation");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
export const modelPlugin = createPluginFactory(ModelPlugin);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/base/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,OAAO,EACP,WAAW,EACX,KAAK,kBAAkB,EACvB,cAAc,EACd,cAAc,GACf,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { modelPlugin, isModel, ModelPlugin, ModelOperation, ModelDirective, } from "./ModelPlugin.js";
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gqlbase/plugins",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Collection of plugins and presets for gqlbase, providing common transformations and utilities that can be used in GraphQL schema transformation processes.",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": {
|
|
9
|
+
"name": "sls.dev",
|
|
10
|
+
"email": "dev@sls.dev",
|
|
11
|
+
"url": "https://wwww.sls.dev"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://www.sls.dev",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/slsdotdev/gqlbase.git",
|
|
20
|
+
"directory": "packages/plugins"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/slsdotdev/gqlbase/issues"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md",
|
|
28
|
+
"../../LICENSE"
|
|
29
|
+
],
|
|
30
|
+
"main": "dist/index.js",
|
|
31
|
+
"types": "dist/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
"./*": {
|
|
34
|
+
"default": "./dist/*/index.js",
|
|
35
|
+
"types": "./dist/*/index.d.ts"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc",
|
|
40
|
+
"dev": "tsc --watch",
|
|
41
|
+
"lint": "eslint 'src/**/*.{ts,tsx}' --fix",
|
|
42
|
+
"test": "vitest run"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@gqlbase/core": "*",
|
|
46
|
+
"@gqlbase/shared": "*"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"graphql": ">=16"
|
|
50
|
+
}
|
|
51
|
+
}
|