bunsane 0.1.0 → 0.1.2
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/.github/workflows/deploy-docs.yml +57 -0
- package/LICENSE.md +1 -1
- package/README.md +2 -28
- package/TODO.md +8 -1
- package/bun.lock +3 -0
- package/config/upload.config.ts +135 -0
- package/core/App.ts +168 -4
- package/core/ArcheType.ts +122 -0
- package/core/BatchLoader.ts +100 -0
- package/core/ComponentRegistry.ts +4 -3
- package/core/Components.ts +2 -2
- package/core/Decorators.ts +15 -8
- package/core/Entity.ts +193 -14
- package/core/EntityCache.ts +15 -0
- package/core/EntityHookManager.ts +855 -0
- package/core/EntityManager.ts +12 -2
- package/core/ErrorHandler.ts +64 -7
- package/core/FileValidator.ts +284 -0
- package/core/Query.ts +503 -85
- package/core/RequestContext.ts +24 -0
- package/core/RequestLoaders.ts +89 -0
- package/core/SchedulerManager.ts +710 -0
- package/core/UploadManager.ts +261 -0
- package/core/components/UploadComponent.ts +206 -0
- package/core/decorators/EntityHooks.ts +190 -0
- package/core/decorators/ScheduledTask.ts +83 -0
- package/core/events/EntityLifecycleEvents.ts +177 -0
- package/core/processors/ImageProcessor.ts +423 -0
- package/core/storage/LocalStorageProvider.ts +290 -0
- package/core/storage/StorageProvider.ts +112 -0
- package/database/DatabaseHelper.ts +183 -58
- package/database/index.ts +5 -5
- package/database/sqlHelpers.ts +7 -0
- package/docs/README.md +149 -0
- package/docs/_coverpage.md +36 -0
- package/docs/_sidebar.md +23 -0
- package/docs/api/core.md +568 -0
- package/docs/api/hooks.md +554 -0
- package/docs/api/index.md +222 -0
- package/docs/api/query.md +678 -0
- package/docs/api/service.md +744 -0
- package/docs/core-concepts/archetypes.md +512 -0
- package/docs/core-concepts/components.md +498 -0
- package/docs/core-concepts/entity.md +314 -0
- package/docs/core-concepts/hooks.md +683 -0
- package/docs/core-concepts/query.md +588 -0
- package/docs/core-concepts/services.md +647 -0
- package/docs/examples/code-examples.md +425 -0
- package/docs/getting-started.md +337 -0
- package/docs/index.html +97 -0
- package/gql/Generator.ts +58 -35
- package/gql/decorators/Upload.ts +176 -0
- package/gql/helpers.ts +67 -0
- package/gql/index.ts +65 -31
- package/gql/types.ts +1 -1
- package/index.ts +79 -11
- package/package.json +19 -10
- package/rest/Generator.ts +3 -0
- package/rest/index.ts +22 -0
- package/service/Service.ts +1 -1
- package/service/ServiceRegistry.ts +10 -6
- package/service/index.ts +12 -1
- package/tests/bench/insert.bench.ts +59 -0
- package/tests/bench/relations.bench.ts +269 -0
- package/tests/bench/sorting.bench.ts +415 -0
- package/tests/component-hooks.test.ts +1409 -0
- package/tests/component.test.ts +338 -0
- package/tests/errorHandling.test.ts +155 -0
- package/tests/hooks.test.ts +666 -0
- package/tests/query-sorting.test.ts +101 -0
- package/tests/relations.test.ts +169 -0
- package/tests/scheduler.test.ts +724 -0
- package/tsconfig.json +35 -34
- package/types/graphql.types.ts +87 -0
- package/types/hooks.types.ts +141 -0
- package/types/scheduler.types.ts +165 -0
- package/types/upload.types.ts +184 -0
- package/upload/index.ts +140 -0
- package/utils/UploadHelper.ts +305 -0
- package/utils/cronParser.ts +366 -0
- package/utils/errorMessages.ts +151 -0
- package/core/Events.ts +0 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
# Getting Started with BunSane
|
|
2
|
+
|
|
3
|
+
This guide will walk you through installing and setting up BunSane for your first project.
|
|
4
|
+
|
|
5
|
+
## 📋 Prerequisites
|
|
6
|
+
|
|
7
|
+
Before you begin, ensure you have the following installed:
|
|
8
|
+
|
|
9
|
+
- **Bun Runtime**: Version 1.0 or later ([Download Bun](https://bun.sh/))
|
|
10
|
+
- **PostgreSQL**: Version 12 or later ([Download PostgreSQL](https://www.postgresql.org/download/))
|
|
11
|
+
- **Node.js**: Version 18+ (for some development tools, though Bun is the primary runtime)
|
|
12
|
+
|
|
13
|
+
## 🚀 Installation
|
|
14
|
+
|
|
15
|
+
### 1. Install BunSane
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bun install bunsane
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 2. Verify Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bun run --version
|
|
25
|
+
# Should show Bun version 1.x.x
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## ⚙️ Configuration
|
|
29
|
+
|
|
30
|
+
### TypeScript Configuration
|
|
31
|
+
|
|
32
|
+
BunSane requires experimental decorators to be enabled. Update your `tsconfig.json`:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"compilerOptions": {
|
|
37
|
+
"target": "ES2022",
|
|
38
|
+
"module": "ESNext",
|
|
39
|
+
"moduleResolution": "bundler",
|
|
40
|
+
"experimentalDecorators": true,
|
|
41
|
+
"emitDecoratorMetadata": true,
|
|
42
|
+
"strict": true,
|
|
43
|
+
"esModuleInterop": true,
|
|
44
|
+
"skipLibCheck": true,
|
|
45
|
+
"forceConsistentCasingInFileNames": true,
|
|
46
|
+
"allowSyntheticDefaultImports": true,
|
|
47
|
+
"resolveJsonModule": true,
|
|
48
|
+
"isolatedModules": true,
|
|
49
|
+
"noEmit": true,
|
|
50
|
+
"types": ["bun-types"]
|
|
51
|
+
},
|
|
52
|
+
"include": ["src/**/*", "index.ts"],
|
|
53
|
+
"exclude": ["node_modules"]
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Database Setup
|
|
58
|
+
|
|
59
|
+
Create a PostgreSQL database for your application:
|
|
60
|
+
|
|
61
|
+
```sql
|
|
62
|
+
-- Create database
|
|
63
|
+
CREATE DATABASE bunsane_app;
|
|
64
|
+
|
|
65
|
+
-- Create user (optional)
|
|
66
|
+
CREATE USER bunsane_user WITH PASSWORD 'your_password';
|
|
67
|
+
|
|
68
|
+
-- Grant permissions
|
|
69
|
+
GRANT ALL PRIVILEGES ON DATABASE bunsane_app TO bunsane_user;
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Environment Configuration
|
|
73
|
+
|
|
74
|
+
Create a `.env` file in your project root:
|
|
75
|
+
|
|
76
|
+
```env
|
|
77
|
+
# Database Configuration
|
|
78
|
+
DATABASE_URL=postgresql://username:password@localhost:5432/bunsane_app
|
|
79
|
+
|
|
80
|
+
# Application Configuration
|
|
81
|
+
NODE_ENV=development
|
|
82
|
+
PORT=3000
|
|
83
|
+
|
|
84
|
+
# Optional: Logging
|
|
85
|
+
LOG_LEVEL=info
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## 🏗️ Your First BunSane Application
|
|
89
|
+
|
|
90
|
+
### Project Structure
|
|
91
|
+
|
|
92
|
+
Create the following directory structure:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
my-bunsane-app/
|
|
96
|
+
├── src/
|
|
97
|
+
│ ├── services/
|
|
98
|
+
│ └── helpers/
|
|
99
|
+
├── index.ts
|
|
100
|
+
├── package.json
|
|
101
|
+
├── tsconfig.json
|
|
102
|
+
└── .env
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 1. Create Your First Component
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
// src/services/UserService.ts
|
|
109
|
+
import { Component, CompData, BaseComponent, ArcheType, Entity, GraphQLObjectType, GraphQLOperation, GraphQLFieldTypes, GraphQLField } from 'bunsane';
|
|
110
|
+
|
|
111
|
+
@Component
|
|
112
|
+
export class UserProfile extends BaseComponent {
|
|
113
|
+
@CompData()
|
|
114
|
+
name: string = '';
|
|
115
|
+
|
|
116
|
+
@CompData()
|
|
117
|
+
email: string = '';
|
|
118
|
+
|
|
119
|
+
@CompData({ indexed: true })
|
|
120
|
+
username: string = '';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@Component
|
|
124
|
+
export class UserPreferences extends BaseComponent {
|
|
125
|
+
@CompData()
|
|
126
|
+
theme: 'light' | 'dark' = 'light';
|
|
127
|
+
|
|
128
|
+
@CompData()
|
|
129
|
+
notifications: boolean = true;
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 2. Create an ArcheType
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
// src/services/UserService.ts (continued)
|
|
137
|
+
import { ArcheType } from 'bunsane';
|
|
138
|
+
|
|
139
|
+
export const UserArcheType = new ArcheType([
|
|
140
|
+
UserProfile,
|
|
141
|
+
UserPreferences
|
|
142
|
+
]);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 3. Create a Service
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// src/services/UserService.ts (continued)
|
|
149
|
+
import { BaseService, GraphQLObjectType, GraphQLOperation, GraphQLFieldTypes, GraphQLField } from 'bunsane';
|
|
150
|
+
|
|
151
|
+
const userFields = {
|
|
152
|
+
id: GraphQLFieldTypes.ID_REQUIRED,
|
|
153
|
+
name: GraphQLFieldTypes.STRING_OPTIONAL,
|
|
154
|
+
email: GraphQLFieldTypes.STRING_REQUIRED,
|
|
155
|
+
username: GraphQLFieldTypes.STRING_OPTIONAL
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const userInputs = {
|
|
159
|
+
createUser: {
|
|
160
|
+
name: GraphQLFieldTypes.STRING_REQUIRED,
|
|
161
|
+
email: GraphQLFieldTypes.STRING_REQUIRED,
|
|
162
|
+
username: GraphQLFieldTypes.STRING_REQUIRED
|
|
163
|
+
},
|
|
164
|
+
getUser: {
|
|
165
|
+
id: GraphQLFieldTypes.ID_REQUIRED
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
@GraphQLObjectType({
|
|
170
|
+
name: "User",
|
|
171
|
+
fields: userFields
|
|
172
|
+
})
|
|
173
|
+
export default class UserService extends BaseService {
|
|
174
|
+
@GraphQLOperation({
|
|
175
|
+
type: "Mutation",
|
|
176
|
+
input: userInputs.createUser,
|
|
177
|
+
output: "User"
|
|
178
|
+
})
|
|
179
|
+
async createUser(args: { name: string; email: string; username: string }) {
|
|
180
|
+
const userEntity = UserArcheType.fill(args).createEntity();
|
|
181
|
+
await userEntity.save();
|
|
182
|
+
return await UserArcheType.Unwrap(userEntity);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
@GraphQLOperation({
|
|
186
|
+
type: "Query",
|
|
187
|
+
input: userInputs.getUser,
|
|
188
|
+
output: "User"
|
|
189
|
+
})
|
|
190
|
+
async getUser(args: { id: string }) {
|
|
191
|
+
const entity = await Entity.FindById(args.id);
|
|
192
|
+
if (!entity) return null;
|
|
193
|
+
return await UserArcheType.Unwrap(entity);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
@GraphQLField({ type: "User", field: "id" })
|
|
197
|
+
idResolver(parent: Entity) {
|
|
198
|
+
return parent.id;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
@GraphQLField({ type: "User", field: "name" })
|
|
202
|
+
async nameResolver(parent: Entity) {
|
|
203
|
+
const profile = await parent.get(UserProfile);
|
|
204
|
+
return profile?.name ?? "";
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
@GraphQLField({ type: "User", field: "email" })
|
|
208
|
+
async emailResolver(parent: Entity) {
|
|
209
|
+
const profile = await parent.get(UserProfile);
|
|
210
|
+
return profile?.email ?? "";
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
@GraphQLField({ type: "User", field: "username" })
|
|
214
|
+
async usernameResolver(parent: Entity) {
|
|
215
|
+
const profile = await parent.get(UserProfile);
|
|
216
|
+
return profile?.username ?? "";
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### 4. Set Up the Application
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
// index.ts
|
|
225
|
+
import { App } from 'bunsane';
|
|
226
|
+
import UserService from './src/services/UserService';
|
|
227
|
+
|
|
228
|
+
async function main() {
|
|
229
|
+
// Services are automatically registered when imported
|
|
230
|
+
// No manual registration needed
|
|
231
|
+
|
|
232
|
+
// Create and start the application
|
|
233
|
+
const app = new App({
|
|
234
|
+
port: 3000,
|
|
235
|
+
databaseUrl: process.env.DATABASE_URL
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
await app.start();
|
|
239
|
+
|
|
240
|
+
console.log('🚀 BunSane server running on http://localhost:3000');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
main().catch(console.error);
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### 5. Run Your Application
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
bun run index.ts
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## 🧪 Testing Your Setup
|
|
253
|
+
|
|
254
|
+
### GraphQL API Testing
|
|
255
|
+
|
|
256
|
+
Your service now exposes GraphQL endpoints. Visit `http://localhost:3000/graphql` to access the GraphQL playground.
|
|
257
|
+
|
|
258
|
+
**Create User Mutation:**
|
|
259
|
+
```graphql
|
|
260
|
+
mutation CreateUser($input: CreateUserInput!) {
|
|
261
|
+
createUser(input: $input) {
|
|
262
|
+
id
|
|
263
|
+
name
|
|
264
|
+
email
|
|
265
|
+
username
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
With variables:
|
|
271
|
+
```json
|
|
272
|
+
{
|
|
273
|
+
"input": {
|
|
274
|
+
"name": "John Doe",
|
|
275
|
+
"email": "john.doe@example.com",
|
|
276
|
+
"username": "johndoe"
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Get User Query:**
|
|
282
|
+
```graphql
|
|
283
|
+
query GetUser($id: ID!) {
|
|
284
|
+
getUser(input: { id: $id }) {
|
|
285
|
+
id
|
|
286
|
+
name
|
|
287
|
+
email
|
|
288
|
+
username
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### REST API Testing
|
|
294
|
+
|
|
295
|
+
You can also test REST endpoints using tools like curl or Postman:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
# Example curl request
|
|
299
|
+
curl -X GET http://localhost:3000/api/users
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## 🔍 What's Next?
|
|
303
|
+
|
|
304
|
+
Congratulations! You now have a working BunSane application. Here's what you can explore next:
|
|
305
|
+
|
|
306
|
+
- **[Entity System](core-concepts/entity.md)** - Deep dive into entity management
|
|
307
|
+
- **[Component Architecture](core-concepts/components.md)** - Advanced component patterns
|
|
308
|
+
- **[Query System](core-concepts/query.md)** - Efficient data retrieval
|
|
309
|
+
- **[Lifecycle Hooks](core-concepts/hooks.md)** - Business logic integration
|
|
310
|
+
- **[Real Examples](examples/)** - Complete application tutorials
|
|
311
|
+
|
|
312
|
+
## 🐛 Troubleshooting
|
|
313
|
+
|
|
314
|
+
### Common Issues
|
|
315
|
+
|
|
316
|
+
**"Component not registered" error**
|
|
317
|
+
- Ensure all components are properly decorated with `@Component`
|
|
318
|
+
- Check that components are imported before use
|
|
319
|
+
|
|
320
|
+
**Database connection failed**
|
|
321
|
+
- Verify PostgreSQL is running
|
|
322
|
+
- Check DATABASE_URL format and credentials
|
|
323
|
+
- Ensure database exists and user has permissions
|
|
324
|
+
|
|
325
|
+
**Service not found error**
|
|
326
|
+
- Verify services extend `BaseService`
|
|
327
|
+
- Check that services are registered with `ServiceRegistry`
|
|
328
|
+
|
|
329
|
+
### Getting Help
|
|
330
|
+
|
|
331
|
+
- [GitHub Issues](https://github.com/yaaruu/bunsane/issues) - Report bugs
|
|
332
|
+
- [GitHub Discussions](https://github.com/yaaruu/bunsane/discussions) - Ask questions
|
|
333
|
+
- [Documentation](https://yaaruu.github.io/bunsane/) - Complete reference
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
*Ready to build something amazing? Let's continue with the [Entity System](core-concepts/entity.md)!* 🚀
|
package/docs/index.html
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>BunSane Framework Documentation</title>
|
|
6
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
|
7
|
+
<meta name="description" content="Professional documentation for BunSane - A batteries-included TypeScript API framework for Bun">
|
|
8
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
|
|
9
|
+
<link rel="icon" href="https://raw.githubusercontent.com/yaaruu/bunsane/main/BunSane.jpg" type="image/x-icon">
|
|
10
|
+
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css">
|
|
11
|
+
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/dark.css" title="dark" disabled>
|
|
12
|
+
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/buble.css" title="buble" disabled>
|
|
13
|
+
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/pure.css" title="pure" disabled>
|
|
14
|
+
<style>
|
|
15
|
+
.sidebar-nav ul li a {
|
|
16
|
+
font-size: 14px;
|
|
17
|
+
padding: 8px 15px;
|
|
18
|
+
}
|
|
19
|
+
.sidebar-nav ul li.active > a {
|
|
20
|
+
background-color: #f0f8ff;
|
|
21
|
+
border-left: 4px solid #007acc;
|
|
22
|
+
}
|
|
23
|
+
.content {
|
|
24
|
+
max-width: 900px;
|
|
25
|
+
}
|
|
26
|
+
.markdown-section h1 {
|
|
27
|
+
border-bottom: 2px solid #007acc;
|
|
28
|
+
padding-bottom: 10px;
|
|
29
|
+
}
|
|
30
|
+
.markdown-section h2 {
|
|
31
|
+
border-bottom: 1px solid #ddd;
|
|
32
|
+
padding-bottom: 5px;
|
|
33
|
+
}
|
|
34
|
+
.markdown-section code {
|
|
35
|
+
background-color: #f6f8fa;
|
|
36
|
+
padding: 2px 6px;
|
|
37
|
+
border-radius: 3px;
|
|
38
|
+
font-size: 0.9em;
|
|
39
|
+
}
|
|
40
|
+
.markdown-section pre code {
|
|
41
|
+
background-color: transparent;
|
|
42
|
+
padding: 0;
|
|
43
|
+
}
|
|
44
|
+
.markdown-section blockquote {
|
|
45
|
+
border-left: 4px solid #007acc;
|
|
46
|
+
background-color: #f0f8ff;
|
|
47
|
+
margin: 20px 0;
|
|
48
|
+
padding: 15px 20px;
|
|
49
|
+
}
|
|
50
|
+
</style>
|
|
51
|
+
</head>
|
|
52
|
+
<body>
|
|
53
|
+
<div id="app"></div>
|
|
54
|
+
<script>
|
|
55
|
+
window.$docsify = {
|
|
56
|
+
name: 'BunSane Framework',
|
|
57
|
+
repo: 'https://github.com/yaaruu/bunsane',
|
|
58
|
+
loadSidebar: true,
|
|
59
|
+
loadNavbar: true,
|
|
60
|
+
coverpage: true,
|
|
61
|
+
themeColor: '#007acc',
|
|
62
|
+
auto2top: true,
|
|
63
|
+
search: {
|
|
64
|
+
paths: 'auto',
|
|
65
|
+
placeholder: 'Search documentation...',
|
|
66
|
+
noData: 'No results found',
|
|
67
|
+
depth: 3
|
|
68
|
+
},
|
|
69
|
+
pagination: {
|
|
70
|
+
previousText: 'Previous',
|
|
71
|
+
nextText: 'Next',
|
|
72
|
+
crossChapter: true,
|
|
73
|
+
crossChapterText: true
|
|
74
|
+
},
|
|
75
|
+
plugins: [
|
|
76
|
+
function(hook, vm) {
|
|
77
|
+
hook.beforeEach(function(html) {
|
|
78
|
+
return html + '\n\n---\n\n*Last updated: ' + new Date().toLocaleDateString() + '*';
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
</script>
|
|
84
|
+
<script src="//cdn.jsdelivr.net/npm/docsify@4/lib/docsify.min.js"></script>
|
|
85
|
+
<script src="//cdn.jsdelivr.net/npm/docsify@4/lib/plugins/search.min.js"></script>
|
|
86
|
+
<script src="//cdn.jsdelivr.net/npm/docsify@4/lib/plugins/emoji.min.js"></script>
|
|
87
|
+
<script src="//cdn.jsdelivr.net/npm/docsify@4/lib/plugins/zoom-image.min.js"></script>
|
|
88
|
+
<script src="//cdn.jsdelivr.net/npm/docsify@4/lib/plugins/external-script.min.js"></script>
|
|
89
|
+
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-typescript.min.js"></script>
|
|
90
|
+
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-bash.min.js"></script>
|
|
91
|
+
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-json.min.js"></script>
|
|
92
|
+
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-sql.min.js"></script>
|
|
93
|
+
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-graphql.min.js"></script>
|
|
94
|
+
<script src="//cdn.jsdelivr.net/npm/prismjs@1/plugins/line-numbers/prism-line-numbers.min.js"></script>
|
|
95
|
+
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/prismjs@1/plugins/line-numbers/prism-line-numbers.css">
|
|
96
|
+
</body>
|
|
97
|
+
</html>
|
package/gql/Generator.ts
CHANGED
|
@@ -1,72 +1,94 @@
|
|
|
1
1
|
import { GraphQLSchema, GraphQLError } from "graphql";
|
|
2
|
-
import {
|
|
2
|
+
import { createSchema } from "graphql-yoga";
|
|
3
3
|
import { logger as MainLogger } from "core/Logger";
|
|
4
|
+
import type { GraphQLType } from "./helpers";
|
|
4
5
|
const logger = MainLogger.child({ scope: "GraphQLGenerator" });
|
|
5
|
-
export interface
|
|
6
|
+
export interface GraphQLObjectTypeMeta {
|
|
6
7
|
name: string;
|
|
7
|
-
fields: Record<string,
|
|
8
|
+
fields: Record<string, GraphQLType>;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export interface GraphQLOperationMeta {
|
|
11
12
|
type: "Query" | "Mutation";
|
|
12
13
|
name?: string;
|
|
13
|
-
input?: Record<string,
|
|
14
|
-
output: Record<string,
|
|
14
|
+
input?: Record<string, GraphQLType>;
|
|
15
|
+
output: GraphQLType | Record<string, GraphQLType>;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export interface GraphQLFieldMeta {
|
|
18
|
-
type:
|
|
19
|
+
type: GraphQLType;
|
|
19
20
|
field: string;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
export function
|
|
23
|
+
export function GraphQLObjectType(meta: GraphQLObjectTypeMeta) {
|
|
23
24
|
return (target: any) => {
|
|
24
|
-
target.
|
|
25
|
+
if (!target.__graphqlObjectType) target.__graphqlObjectType = [];
|
|
26
|
+
target.__graphqlObjectType.push(meta);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function GraphQLScalarType(name: string) {
|
|
31
|
+
return (target: any) => {
|
|
32
|
+
if (!target.__graphqlScalarTypes) target.__graphqlScalarTypes = [];
|
|
33
|
+
target.__graphqlScalarTypes.push(name);
|
|
25
34
|
}
|
|
26
35
|
}
|
|
27
36
|
|
|
28
37
|
export function GraphQLOperation(meta: GraphQLOperationMeta) {
|
|
29
|
-
return function (target: any,
|
|
38
|
+
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
|
30
39
|
if (!target.__graphqlOperations) target.__graphqlOperations = [];
|
|
31
|
-
const operationName = meta.name ??
|
|
40
|
+
const operationName = meta.name ?? propertyKey;
|
|
32
41
|
if (!operationName) {
|
|
33
|
-
throw new Error("GraphQLOperation: Operation name is required (either meta.name or
|
|
42
|
+
throw new Error("GraphQLOperation: Operation name is required (either meta.name or propertyKey must be defined)");
|
|
34
43
|
}
|
|
35
|
-
const operationMeta = { ...meta, name: operationName, propertyKey
|
|
44
|
+
const operationMeta = { ...meta, name: operationName, propertyKey };
|
|
36
45
|
target.__graphqlOperations.push(operationMeta);
|
|
37
46
|
};
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
export function GraphQLField(meta: GraphQLFieldMeta) {
|
|
41
|
-
return function (target: any,
|
|
50
|
+
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
|
42
51
|
if (!target.__graphqlFields) target.__graphqlFields = [];
|
|
43
|
-
target.__graphqlFields.push({ ...meta, propertyKey
|
|
52
|
+
target.__graphqlFields.push({ ...meta, propertyKey });
|
|
44
53
|
};
|
|
45
54
|
}
|
|
46
55
|
|
|
47
|
-
export function generateGraphQLSchema(
|
|
48
|
-
let typeDefs =
|
|
49
|
-
|
|
56
|
+
export function generateGraphQLSchema(services: any[]): { schema: GraphQLSchema | null; resolvers: any } {
|
|
57
|
+
let typeDefs = `
|
|
58
|
+
`;
|
|
59
|
+
const scalarTypes: Set<string> = new Set();
|
|
60
|
+
const resolvers: any = {};
|
|
50
61
|
const queryFields: string[] = [];
|
|
51
62
|
const mutationFields: string[] = [];
|
|
52
63
|
|
|
53
|
-
|
|
54
|
-
logger.trace(`Processing
|
|
55
|
-
if
|
|
56
|
-
const
|
|
57
|
-
|
|
64
|
+
services.forEach(service => {
|
|
65
|
+
logger.trace(`Processing service: ${service.constructor.name}`);
|
|
66
|
+
if(service.constructor.__graphqlScalarTypes) {
|
|
67
|
+
for (const scalarName of service.constructor.__graphqlScalarTypes) {
|
|
68
|
+
if (!scalarTypes.has(scalarName)) {
|
|
69
|
+
scalarTypes.add(scalarName);
|
|
70
|
+
typeDefs += `scalar ${scalarName}\n`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (service.constructor.__graphqlObjectType) {
|
|
75
|
+
for (const meta of service.constructor.__graphqlObjectType) {
|
|
76
|
+
const { name, fields } = meta;
|
|
77
|
+
typeDefs += `type ${name} {\n${Object.entries(fields).map(([k, v]) => ` ${k}: ${v}`).join('\n')}\n}\n`;
|
|
78
|
+
}
|
|
58
79
|
}
|
|
59
|
-
if (
|
|
60
|
-
|
|
80
|
+
if (service.__graphqlOperations) {
|
|
81
|
+
service.__graphqlOperations.forEach((op: any) => {
|
|
61
82
|
const { type, name, input, output, propertyKey } = op;
|
|
83
|
+
if (!resolvers[type]) resolvers[type] = {};
|
|
62
84
|
let fieldDef = `${name}`;
|
|
63
85
|
if (input) {
|
|
64
86
|
const inputName = `${name}Input`;
|
|
65
87
|
typeDefs += `input ${inputName} {\n${Object.entries(input).map(([k, v]) => ` ${k}: ${v}`).join('\n')}\n}\n`;
|
|
66
88
|
fieldDef += `(input: ${inputName}!)`;
|
|
67
|
-
resolvers[type][name] = async (_: any, args: any, context: any) => {
|
|
89
|
+
resolvers[type][name] = async (_: any, args: any, context: any, info: any) => {
|
|
68
90
|
try {
|
|
69
|
-
return await
|
|
91
|
+
return await service[propertyKey](args.input || args, context, info);
|
|
70
92
|
} catch (error) {
|
|
71
93
|
logger.error(`Error in ${type}.${name}:`);
|
|
72
94
|
logger.error(error);
|
|
@@ -82,9 +104,9 @@ export function generateGraphQLSchema(systems: any[]): { schema: GraphQLSchema |
|
|
|
82
104
|
}
|
|
83
105
|
};
|
|
84
106
|
} else {
|
|
85
|
-
resolvers[type][name] = async (_: any, args: any, context: any) => {
|
|
107
|
+
resolvers[type][name] = async (_: any, args: any, context: any, info: any) => {
|
|
86
108
|
try {
|
|
87
|
-
return await
|
|
109
|
+
return await service[propertyKey]({}, context, info);
|
|
88
110
|
} catch (error) {
|
|
89
111
|
logger.error(`Error in ${type}.${name}:`);
|
|
90
112
|
logger.error(error);
|
|
@@ -117,14 +139,14 @@ export function generateGraphQLSchema(systems: any[]): { schema: GraphQLSchema |
|
|
|
117
139
|
});
|
|
118
140
|
|
|
119
141
|
// Process field resolvers
|
|
120
|
-
|
|
121
|
-
if (
|
|
122
|
-
|
|
142
|
+
services.forEach(service => {
|
|
143
|
+
if (service.__graphqlFields) {
|
|
144
|
+
service.__graphqlFields.forEach((fieldMeta: any) => {
|
|
123
145
|
const { type, field, propertyKey } = fieldMeta;
|
|
124
146
|
if (!resolvers[type]) resolvers[type] = {};
|
|
125
|
-
resolvers[type][field] = async (parent: any, args: any, context: any) => {
|
|
147
|
+
resolvers[type][field] = async (parent: any, args: any, context: any, info: any) => {
|
|
126
148
|
try {
|
|
127
|
-
return await
|
|
149
|
+
return await service[propertyKey](parent, args, context, info);
|
|
128
150
|
} catch (error) {
|
|
129
151
|
logger.error(`Error in ${type}.${field}:`);
|
|
130
152
|
logger.error(error);
|
|
@@ -152,8 +174,9 @@ export function generateGraphQLSchema(systems: any[]): { schema: GraphQLSchema |
|
|
|
152
174
|
|
|
153
175
|
logger.trace(`System Type Defs: ${typeDefs}`);
|
|
154
176
|
let schema : GraphQLSchema | null = null;
|
|
155
|
-
if
|
|
156
|
-
|
|
177
|
+
// Check if typeDefs contains actual schema definitions, not just whitespace
|
|
178
|
+
if(typeDefs.trim() !== "" && (queryFields.length > 0 || mutationFields.length > 0 || scalarTypes.size > 0)) {
|
|
179
|
+
schema = createSchema({ typeDefs, resolvers });
|
|
157
180
|
}
|
|
158
181
|
return { schema, resolvers };
|
|
159
182
|
}
|