@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,2 @@
1
+ export { modelPlugin, isModel, ModelPlugin, type ModelPluginOptions, ModelOperation, ModelDirective, } from "./ModelPlugin.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -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
+ }