zenstack-graphql-builder 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/README.md +246 -0
- package/package.json +34 -0
- package/src/index.js +1536 -0
package/README.md
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
ZenStack GraphQL Builder
|
|
2
|
+
========================
|
|
3
|
+
|
|
4
|
+
Automatically generate a complete GraphQL CRUD API from your [ZenStack](https://zenstack.dev/) schema. This library builds a fully typed GraphQL schema and resolver map that mirrors Prisma's CRUD operations, including support for relations, filtering, sorting, pagination, aggregations, and custom directives.
|
|
5
|
+
|
|
6
|
+
Features
|
|
7
|
+
--------
|
|
8
|
+
|
|
9
|
+
- 🔄 Full CRUD -- Generates `Query` and `Mutation` fields for all models: `findUnique`, `findFirst`, `findMany`, `create`, `update`, `delete`, `upsert`, `aggregate`, `groupBy`, `exists`, and more.
|
|
10
|
+
|
|
11
|
+
- 🔗 Relations -- Automatically resolves nested relations and provides filter/order arguments for to-many fields.
|
|
12
|
+
|
|
13
|
+
- 🎛 Rich Filtering -- Creates `WhereInput` types with field‑specific filters (`equals`, `contains`, `gt`, `in`, `between`, ...) and relation filters (`every`/`some`/`none`).
|
|
14
|
+
|
|
15
|
+
- 📊 Aggregations -- Supports `count`, `avg`, `sum`, `min`, `max` aggregates and groupBy queries.
|
|
16
|
+
|
|
17
|
+
- 🧩 Custom Scalars -- Includes `DateTime`, `Json`, `BigInt`, `Bytes`, `Decimal`, and a safe `JSONInt` scalar that prevents 53‑bit precision loss.
|
|
18
|
+
|
|
19
|
+
- 🛡 Security Limits -- Enforce maximum `take`/`limit` values and query depth to protect your server.
|
|
20
|
+
|
|
21
|
+
- 🧪 Directives -- Define and apply custom directives (e.g., `@upperCase`) to transform field values after resolution.
|
|
22
|
+
|
|
23
|
+
Installation
|
|
24
|
+
------------
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install zenstack-graphql-builder graphql
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Make sure `graphql` is installed as a peer dependency.
|
|
31
|
+
|
|
32
|
+
Quick Start
|
|
33
|
+
-----------
|
|
34
|
+
|
|
35
|
+
### 1\. Define your ZenStack schema
|
|
36
|
+
|
|
37
|
+
```zmodel
|
|
38
|
+
model User {
|
|
39
|
+
id String @id @default(cuid())
|
|
40
|
+
email String @unique
|
|
41
|
+
name String?
|
|
42
|
+
posts Post[]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
model Post {
|
|
46
|
+
id String @id @default(cuid())
|
|
47
|
+
title String
|
|
48
|
+
content String?
|
|
49
|
+
published Boolean @default(false)
|
|
50
|
+
author User @relation(fields: [authorId], references: [id])
|
|
51
|
+
authorId String
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 2\. Build the GraphQL schema and resolvers
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { ZenStackGraphQLBuilder } from 'zenstack-graphql-builder';
|
|
59
|
+
import { schema as zenSchema } from './path-to-your-zenstack-schema';
|
|
60
|
+
|
|
61
|
+
const builder = new ZenStackGraphQLBuilder({
|
|
62
|
+
schema: zenSchema,
|
|
63
|
+
options: {
|
|
64
|
+
maxTake: 100,
|
|
65
|
+
maxDepth: 10
|
|
66
|
+
},
|
|
67
|
+
directives,
|
|
68
|
+
directiveDefinitions,
|
|
69
|
+
// optionally filter which CRUD operations to generate
|
|
70
|
+
operations: ['findMany', 'create', 'update', 'delete'],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const schema = builder.getSchema();
|
|
74
|
+
const rootValue = builder.getRootResolver();
|
|
75
|
+
|
|
76
|
+
// Now you can use `schema` and `rootValue` with any GraphQL server (e.g., express-graphql, Apollo Server)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 3\. Use in your GraphQL server
|
|
80
|
+
|
|
81
|
+
Example with `express-graphql`:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import express from 'express';
|
|
85
|
+
import { createHandler } from 'graphql-http/lib/use/express';
|
|
86
|
+
import { ZenStackClient } from '@zenstackhq/orm';
|
|
87
|
+
import { SqliteDialect } from '@zenstackhq/orm/dialects/sqlite';
|
|
88
|
+
|
|
89
|
+
const db = new ZenStackClient(schema, {
|
|
90
|
+
dialect: new SqliteDialect({
|
|
91
|
+
database: new SQLite('./test.db'),
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const app = express();
|
|
96
|
+
|
|
97
|
+
app.use(
|
|
98
|
+
'/graphql',
|
|
99
|
+
createHandler({
|
|
100
|
+
schema,
|
|
101
|
+
rootValue,
|
|
102
|
+
context: {
|
|
103
|
+
client: db, // the ZenStack Client
|
|
104
|
+
options: { maxTake: 50 }, // per‑request security overrides
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
app.listen(4000);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
API Reference
|
|
113
|
+
-------------
|
|
114
|
+
|
|
115
|
+
### `ZenStackGraphQLBuilder` constructor
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
new ZenStackGraphQLBuilder({
|
|
119
|
+
schema: ZenSchemaDef;
|
|
120
|
+
options?: BuilderOptions;
|
|
121
|
+
directives?: Record<string, DirectiveHandler>;
|
|
122
|
+
directiveDefinitions?: GraphQLDirective[];
|
|
123
|
+
operations?: string[];
|
|
124
|
+
scalars?: Record<string, GraphQLScalarType>;
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
#### Parameters
|
|
128
|
+
|
|
129
|
+
| Param | Type | Description |
|
|
130
|
+
| --- | --- | --- |
|
|
131
|
+
| `schema` | `ZenSchemaDef` | The ZenStack schema definition object (usually exported from your `.zmodel` compilation). |
|
|
132
|
+
| `options` | `BuilderOptions` | Configuration for security and scalar handling (see below). |
|
|
133
|
+
| `directives` | `Record<string, DirectiveHandler>` | A map of directive names to resolver functions. Each function receives `(value, args, variableValues, fieldName)` and should return the transformed value (can be async). |
|
|
134
|
+
| `directiveDefinitions` | `GraphQLDirective[]` | Array of `GraphQLDirective` instances for schema introspection (e.g., for GraphQL playground to show the directives). |
|
|
135
|
+
| `operations` | `string[]` | List of CRUD operations to include. Defaults to all operations (see `AllCrudOperations` in the code). |
|
|
136
|
+
| `scalars` | `Record<string, GraphQLScalarType>` | Override or add custom scalar implementations. |
|
|
137
|
+
|
|
138
|
+
#### `BuilderOptions`
|
|
139
|
+
|
|
140
|
+
| Option | Type | Default | Description |
|
|
141
|
+
| --- | --- | --- | --- |
|
|
142
|
+
| `maxTake` | `number` | `100` | Maximum allowed value for `take`/`limit` arguments. |
|
|
143
|
+
| `maxDepth` | `number` | `9` | Maximum allowed depth of nested selections. |
|
|
144
|
+
| `throwOnError` | `boolean` | `false` | If `true`, throws an error when security limits are exceeded; otherwise silently caps the value. |
|
|
145
|
+
| `useJSONIntScalar` | `boolean` | `false` | If `true`, uses a custom `JSONInt` scalar for `Int` fields, which safely serialises BigInt values that exceed 53 bits as strings. |
|
|
146
|
+
|
|
147
|
+
### Methods
|
|
148
|
+
|
|
149
|
+
#### `getSchema(): GraphQLSchema`
|
|
150
|
+
|
|
151
|
+
Returns the generated GraphQL schema.
|
|
152
|
+
|
|
153
|
+
#### `getRootResolver(): Record<string, Function>`
|
|
154
|
+
|
|
155
|
+
Returns an object containing resolver functions for all generated Query and Mutation fields. Each resolver accepts `(args, context, info)` and:
|
|
156
|
+
|
|
157
|
+
- Validates arguments against security limits.
|
|
158
|
+
|
|
159
|
+
- Parses the GraphQL selection set into a Prisma `select` object and a transformation plan.
|
|
160
|
+
|
|
161
|
+
- Calls the corresponding Prisma client method (using `context.client[model][operation]`).
|
|
162
|
+
|
|
163
|
+
- Applies any directives to the result before returning.
|
|
164
|
+
|
|
165
|
+
Custom Directives
|
|
166
|
+
-----------------
|
|
167
|
+
|
|
168
|
+
To add a custom directive, you need to:
|
|
169
|
+
|
|
170
|
+
1. Define the directive in your schema (if you want it to appear in introspection) and pass it via `directiveDefinitions`.
|
|
171
|
+
|
|
172
|
+
2. Provide an implementation in the `directives` map.
|
|
173
|
+
|
|
174
|
+
Example:
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
// directive definition
|
|
178
|
+
import { GraphQLDirective, DirectiveLocation } from 'graphql';
|
|
179
|
+
|
|
180
|
+
const maskDirective = new GraphQLDirective({
|
|
181
|
+
name: 'mask',
|
|
182
|
+
locations: [DirectiveLocation.FIELD],
|
|
183
|
+
args: {
|
|
184
|
+
start: { type: GraphQLInt },
|
|
185
|
+
end: { type: GraphQLInt },
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// handler
|
|
190
|
+
const directives = {
|
|
191
|
+
mask: async (value, args) => {
|
|
192
|
+
if (typeof value !== 'string') return value;
|
|
193
|
+
const start = args.start ?? 0;
|
|
194
|
+
const end = args.end ?? value.length;
|
|
195
|
+
return '*'.repeat(start) + value.slice(start, end);
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// pass to builder
|
|
200
|
+
new ZenStackGraphQLBuilder({
|
|
201
|
+
schema,
|
|
202
|
+
directives,
|
|
203
|
+
directiveDefinitions: [maskDirective],
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
Now you can use `@mask` in your GraphQL queries:
|
|
207
|
+
|
|
208
|
+
```graphql
|
|
209
|
+
|
|
210
|
+
query {
|
|
211
|
+
user_findMany {
|
|
212
|
+
email @mask(start: 2, end: 5)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
Security Limits
|
|
217
|
+
---------------
|
|
218
|
+
|
|
219
|
+
The builder automatically enforces:
|
|
220
|
+
|
|
221
|
+
- Maximum `take`/`limit` -- Prevents clients from requesting too many records at once.
|
|
222
|
+
|
|
223
|
+
- Maximum query depth -- Protects against deeply nested queries that could overload the database.
|
|
224
|
+
|
|
225
|
+
You can configure these globally via `options` and override them per request via `context.options`.
|
|
226
|
+
|
|
227
|
+
Custom Scalars
|
|
228
|
+
--------------
|
|
229
|
+
|
|
230
|
+
The builder includes several common scalars out of the box:
|
|
231
|
+
|
|
232
|
+
| Scalar | Description |
|
|
233
|
+
| --- | --- |
|
|
234
|
+
| `DateTime` | ISO‑8601 date/time strings. |
|
|
235
|
+
| `Json` | Arbitrary JSON values. |
|
|
236
|
+
| `BigInt` | BigInt values (serialized as strings). |
|
|
237
|
+
| `Bytes` | Base64‑encoded binary data. |
|
|
238
|
+
| `Decimal` | High‑precision decimal numbers (using `decimal.js`). |
|
|
239
|
+
| `JSONInt` | A 53‑bit safe integer scalar (falls back to string for larger values). |
|
|
240
|
+
|
|
241
|
+
You can override any scalar by passing a custom `GraphQLScalarType` in the `scalars` option.
|
|
242
|
+
|
|
243
|
+
License
|
|
244
|
+
-------
|
|
245
|
+
|
|
246
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "zenstack-graphql-builder",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Automatically generate a complete GraphQL CRUD API from ZenStack schemas, including input types, filters, relation resolvers, custom directive support, security limits, and query projection parsing.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"zenstack",
|
|
7
|
+
"graphql",
|
|
8
|
+
"schema-builder",
|
|
9
|
+
"crud-generator",
|
|
10
|
+
"prisma",
|
|
11
|
+
"orm",
|
|
12
|
+
"api",
|
|
13
|
+
"graphql-schema",
|
|
14
|
+
"directive",
|
|
15
|
+
"security",
|
|
16
|
+
"projection",
|
|
17
|
+
"resolver"
|
|
18
|
+
],
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"author": "17",
|
|
21
|
+
"type": "commonjs",
|
|
22
|
+
"main": "src/index.js",
|
|
23
|
+
"scripts": {
|
|
24
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"decimal.js": "^10.6.0",
|
|
28
|
+
"graphql": "^16.12.0"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"src/index.js",
|
|
32
|
+
"README.md"
|
|
33
|
+
]
|
|
34
|
+
}
|