fraiseql 2.1.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.
- package/README.md +784 -0
- package/dist/index.d.mts +1688 -0
- package/dist/index.d.ts +1688 -0
- package/dist/index.js +1345 -0
- package/dist/index.mjs +1265 -0
- package/package.json +82 -0
package/README.md
ADDED
|
@@ -0,0 +1,784 @@
|
|
|
1
|
+
# FraiseQL v2 - TypeScript Schema Authoring
|
|
2
|
+
|
|
3
|
+
> Compiled GraphQL execution engine - Schema authoring in TypeScript
|
|
4
|
+
|
|
5
|
+
FraiseQL v2 is a high-performance GraphQL engine that compiles schemas at build-time for zero-cost query execution. This package provides **schema authoring in TypeScript** that generates JSON schemas consumed by the Rust compiler.
|
|
6
|
+
|
|
7
|
+
**Key Principle**: TypeScript is for **authoring only** - no runtime FFI, no language bindings. Just pure JSON generation.
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
TypeScript Code (decorators)
|
|
13
|
+
↓
|
|
14
|
+
schema.json
|
|
15
|
+
↓
|
|
16
|
+
fraiseql-cli compile
|
|
17
|
+
↓
|
|
18
|
+
schema.compiled.json
|
|
19
|
+
↓
|
|
20
|
+
Rust Runtime (fraiseql-server)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install fraiseql
|
|
27
|
+
# or
|
|
28
|
+
yarn add fraiseql
|
|
29
|
+
# or
|
|
30
|
+
pnpm add fraiseql
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Requirements**: Node.js 18+
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
### 1. Define Types
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import * as fraiseql from "fraiseql";
|
|
41
|
+
|
|
42
|
+
@fraiseql.type()
|
|
43
|
+
class User {
|
|
44
|
+
id!: number;
|
|
45
|
+
name!: string;
|
|
46
|
+
email!: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Register fields (TypeScript doesn't preserve type info at runtime)
|
|
50
|
+
fraiseql.registerTypeFields("User", [
|
|
51
|
+
{ name: "id", type: "Int", nullable: false },
|
|
52
|
+
{ name: "name", type: "String", nullable: false },
|
|
53
|
+
{ name: "email", type: "String", nullable: false },
|
|
54
|
+
]);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 2. Define Queries
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
@fraiseql.query({ sqlSource: "v_user" })
|
|
61
|
+
function users(limit: number = 10, offset: number = 0): User[] {
|
|
62
|
+
throw new Error("Not executed");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fraiseql.registerQuery(
|
|
66
|
+
"users",
|
|
67
|
+
"User",
|
|
68
|
+
true, // returns list
|
|
69
|
+
false, // not nullable
|
|
70
|
+
[
|
|
71
|
+
{ name: "limit", type: "Int", nullable: false, default: 10 },
|
|
72
|
+
{ name: "offset", type: "Int", nullable: false, default: 0 },
|
|
73
|
+
],
|
|
74
|
+
"Get all users",
|
|
75
|
+
{ sql_source: "v_user" }
|
|
76
|
+
);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 3. Define Mutations
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
@fraiseql.mutation({ sqlSource: "fn_create_user", operation: "CREATE" })
|
|
83
|
+
function createUser(name: string, email: string): User {
|
|
84
|
+
throw new Error("Not executed");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
fraiseql.registerMutation(
|
|
88
|
+
"createUser",
|
|
89
|
+
"User",
|
|
90
|
+
false, // single item
|
|
91
|
+
false, // not nullable
|
|
92
|
+
[
|
|
93
|
+
{ name: "name", type: "String", nullable: false },
|
|
94
|
+
{ name: "email", type: "String", nullable: false },
|
|
95
|
+
],
|
|
96
|
+
"Create a new user",
|
|
97
|
+
{ sql_source: "fn_create_user", operation: "CREATE" }
|
|
98
|
+
);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 4. Export Schema
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
// At end of file
|
|
105
|
+
if (require.main === module) {
|
|
106
|
+
fraiseql.exportSchema("schema.json");
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 5. Compile
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Generate compiled schema
|
|
114
|
+
fraiseql-cli compile schema.json
|
|
115
|
+
|
|
116
|
+
# Start server
|
|
117
|
+
fraiseql-server --schema schema.compiled.json --port 3000
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## API Reference
|
|
121
|
+
|
|
122
|
+
### Decorators
|
|
123
|
+
|
|
124
|
+
#### `@Type(config?)`
|
|
125
|
+
|
|
126
|
+
Mark a class as a GraphQL type.
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
@fraiseql.type()
|
|
130
|
+
class User {
|
|
131
|
+
id!: number;
|
|
132
|
+
name!: string;
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Note**: Decorators alone don't capture field types. Use `registerTypeFields()` to provide field metadata.
|
|
137
|
+
|
|
138
|
+
#### `@Query(config)`
|
|
139
|
+
|
|
140
|
+
Mark a function as a GraphQL query.
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
@fraiseql.query({ sqlSource: "v_user" })
|
|
144
|
+
function users(limit: number = 10): User[] {
|
|
145
|
+
throw new Error("Not executed");
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Config Options**:
|
|
150
|
+
|
|
151
|
+
- `sqlSource`: SQL view/table name (required for data operations)
|
|
152
|
+
- `autoParams`: Auto-parameter configuration
|
|
153
|
+
- Other custom configuration
|
|
154
|
+
|
|
155
|
+
#### `@Mutation(config)`
|
|
156
|
+
|
|
157
|
+
Mark a function as a GraphQL mutation.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
@fraiseql.mutation({ sqlSource: "fn_create_user", operation: "CREATE" })
|
|
161
|
+
function createUser(name: string): User {
|
|
162
|
+
throw new Error("Not executed");
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Config Options**:
|
|
167
|
+
|
|
168
|
+
- `sqlSource`: SQL function name (required)
|
|
169
|
+
- `operation`: "CREATE" | "UPDATE" | "DELETE" | "CUSTOM"
|
|
170
|
+
- Other custom configuration
|
|
171
|
+
|
|
172
|
+
#### `@FactTable(config)`
|
|
173
|
+
|
|
174
|
+
Mark a class as a fact table for analytics.
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
@fraiseql.FactTable({
|
|
178
|
+
tableName: "tf_sales",
|
|
179
|
+
measures: ["revenue", "quantity"],
|
|
180
|
+
dimensionPaths: [
|
|
181
|
+
{
|
|
182
|
+
name: "category",
|
|
183
|
+
json_path: "data->>'category'",
|
|
184
|
+
data_type: "text",
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
})
|
|
188
|
+
@fraiseql.type()
|
|
189
|
+
class Sale {
|
|
190
|
+
id!: number;
|
|
191
|
+
revenue!: number;
|
|
192
|
+
quantity!: number;
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### `@AggregateQuery(config)`
|
|
197
|
+
|
|
198
|
+
Mark a function as an aggregate query on a fact table.
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
@fraiseql.AggregateQuery({
|
|
202
|
+
factTable: "tf_sales",
|
|
203
|
+
autoGroupBy: true,
|
|
204
|
+
autoAggregates: true,
|
|
205
|
+
})
|
|
206
|
+
@fraiseql.query()
|
|
207
|
+
function salesAggregate(): Record<string, unknown>[] {
|
|
208
|
+
throw new Error("Not executed");
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
#### `@Subscription(config?)`
|
|
213
|
+
|
|
214
|
+
Mark a function as a GraphQL subscription for real-time events.
|
|
215
|
+
|
|
216
|
+
Subscriptions in FraiseQL are **compiled database event projections** sourced from LISTEN/NOTIFY or CDC, not resolver-based.
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
@fraiseql.Subscription({ topic: "order_events" })
|
|
220
|
+
function orderCreated(userId?: string): Order {
|
|
221
|
+
pass;
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Subscription Configuration
|
|
226
|
+
|
|
227
|
+
**SubscriptionConfig Options**:
|
|
228
|
+
|
|
229
|
+
- `entityType`: Entity type being subscribed to (defaults to return type)
|
|
230
|
+
- `topic`: Optional topic/channel name for filtering events
|
|
231
|
+
- `operation`: Single event type filter - "CREATE" | "UPDATE" | "DELETE"
|
|
232
|
+
- `operations`: Multiple event type filters - ["CREATE", "UPDATE", "DELETE"]
|
|
233
|
+
|
|
234
|
+
**Manual Registration**:
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
fraiseql.registerSubscription(
|
|
238
|
+
"orderCreated", // name
|
|
239
|
+
"Order", // entityType
|
|
240
|
+
false, // nullable
|
|
241
|
+
[
|
|
242
|
+
{ name: "userId", type: "String", nullable: true }
|
|
243
|
+
], // filter arguments
|
|
244
|
+
"Subscribe to new orders",
|
|
245
|
+
{ topic: "order_events", operation: "CREATE" }
|
|
246
|
+
);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**Subscription Patterns**:
|
|
250
|
+
|
|
251
|
+
1. **Event Type Filtering** - Subscribe to specific operations
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
fraiseql.registerSubscription(
|
|
255
|
+
"userCreated",
|
|
256
|
+
"User",
|
|
257
|
+
false,
|
|
258
|
+
[],
|
|
259
|
+
"New user registrations",
|
|
260
|
+
{ operation: "CREATE" } // Only CREATE events
|
|
261
|
+
);
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
1. **Topic-Based Subscriptions** - Route to different channels
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
fraiseql.registerSubscription(
|
|
268
|
+
"criticalOrders",
|
|
269
|
+
"Order",
|
|
270
|
+
false,
|
|
271
|
+
[],
|
|
272
|
+
"High-priority orders",
|
|
273
|
+
{ topic: "orders.critical", operation: "CREATE" }
|
|
274
|
+
);
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
1. **Filtered Subscriptions** - Target specific records
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
fraiseql.registerSubscription(
|
|
281
|
+
"customerOrders",
|
|
282
|
+
"Order",
|
|
283
|
+
false,
|
|
284
|
+
[{ name: "customerId", type: "ID", nullable: false }], // Filter by customer
|
|
285
|
+
"Orders for specific customer"
|
|
286
|
+
);
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
1. **Change Data Capture (CDC)** - Capture all changes
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
fraiseql.registerSubscription(
|
|
293
|
+
"userCDC",
|
|
294
|
+
"User",
|
|
295
|
+
false,
|
|
296
|
+
[],
|
|
297
|
+
"All user changes",
|
|
298
|
+
{ operations: ["CREATE", "UPDATE", "DELETE"] }
|
|
299
|
+
);
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
1. **Alerts and Notifications** - Complex filtering
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
fraiseql.registerSubscription(
|
|
306
|
+
"unusualOrders",
|
|
307
|
+
"Order",
|
|
308
|
+
false,
|
|
309
|
+
[
|
|
310
|
+
{ name: "minAmount", type: "Decimal", nullable: false },
|
|
311
|
+
{ name: "timeWindowMinutes", type: "Int", nullable: true }
|
|
312
|
+
],
|
|
313
|
+
"Alert on high-value orders",
|
|
314
|
+
{ operation: "CREATE" }
|
|
315
|
+
);
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Type System Decorators
|
|
319
|
+
|
|
320
|
+
#### `enum_(name, values, config?)`
|
|
321
|
+
|
|
322
|
+
Define a GraphQL enum type.
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
const OrderStatus = fraiseql.enum_("OrderStatus", {
|
|
326
|
+
PENDING: "pending",
|
|
327
|
+
SHIPPED: "shipped",
|
|
328
|
+
DELIVERED: "delivered",
|
|
329
|
+
}, {
|
|
330
|
+
description: "Status of an order"
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Then use in types:
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
fraiseql.registerTypeFields("Order", [
|
|
338
|
+
{ name: "id", type: "ID", nullable: false },
|
|
339
|
+
{ name: "status", type: "OrderStatus", nullable: false },
|
|
340
|
+
]);
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
#### `interface_(name, fields, config?)`
|
|
344
|
+
|
|
345
|
+
Define a GraphQL interface - shared fields for multiple types.
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
const Node = fraiseql.interface_("Node", [
|
|
349
|
+
{ name: "id", type: "ID", nullable: false },
|
|
350
|
+
{ name: "createdAt", type: "DateTime", nullable: false },
|
|
351
|
+
], {
|
|
352
|
+
description: "An object with a globally unique ID"
|
|
353
|
+
});
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Types can implement interfaces:
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
fraiseql.registerTypeFields("User", [
|
|
360
|
+
{ name: "id", type: "ID", nullable: false },
|
|
361
|
+
{ name: "createdAt", type: "DateTime", nullable: false },
|
|
362
|
+
{ name: "name", type: "String", nullable: false },
|
|
363
|
+
]);
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
#### `union(name, memberTypes, config?)`
|
|
367
|
+
|
|
368
|
+
Define a GraphQL union - polymorphic return type.
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
const SearchResult = fraiseql.union("SearchResult",
|
|
372
|
+
["User", "Post", "Comment"],
|
|
373
|
+
{ description: "Result of a search query" }
|
|
374
|
+
);
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Then use in queries:
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
fraiseql.registerQuery(
|
|
381
|
+
"search",
|
|
382
|
+
"SearchResult", // Returns union
|
|
383
|
+
true, // returns list
|
|
384
|
+
false, // not nullable
|
|
385
|
+
[{ name: "query", type: "String", nullable: false }],
|
|
386
|
+
"Search across content"
|
|
387
|
+
);
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
#### `input(name, fields, config?)`
|
|
391
|
+
|
|
392
|
+
Define a GraphQL input type - structured parameters.
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
const CreateUserInput = fraiseql.input("CreateUserInput", [
|
|
396
|
+
{ name: "email", type: "Email", nullable: false },
|
|
397
|
+
{ name: "name", type: "String", nullable: false },
|
|
398
|
+
{ name: "role", type: "String", nullable: false, default: "user" },
|
|
399
|
+
], {
|
|
400
|
+
description: "Input for creating a new user"
|
|
401
|
+
});
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
Use in mutations:
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
fraiseql.registerMutation(
|
|
408
|
+
"createUser",
|
|
409
|
+
"User",
|
|
410
|
+
false,
|
|
411
|
+
false,
|
|
412
|
+
[{ name: "input", type: "CreateUserInput", nullable: false }],
|
|
413
|
+
"Create a new user"
|
|
414
|
+
);
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Field-Level Metadata
|
|
418
|
+
|
|
419
|
+
Add access control, deprecation markers, and documentation to individual fields:
|
|
420
|
+
|
|
421
|
+
#### `field(options)`
|
|
422
|
+
|
|
423
|
+
Create field metadata for use with `registerTypeFields()`:
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
fraiseql.registerTypeFields("User", [
|
|
427
|
+
{ name: "id", type: "ID", nullable: false },
|
|
428
|
+
{
|
|
429
|
+
name: "salary",
|
|
430
|
+
type: "Decimal",
|
|
431
|
+
nullable: false,
|
|
432
|
+
requiresScope: "read:User.salary",
|
|
433
|
+
description: "Annual salary (requires HR scope)"
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
name: "oldEmail",
|
|
437
|
+
type: "String",
|
|
438
|
+
nullable: true,
|
|
439
|
+
deprecated: "Use email instead",
|
|
440
|
+
description: "Legacy email field (deprecated)"
|
|
441
|
+
}
|
|
442
|
+
]);
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
**Field Metadata Options**:
|
|
446
|
+
|
|
447
|
+
- `requiresScope: string | string[]` - JWT scope(s) required to access this field (field-level access control)
|
|
448
|
+
- `deprecated: boolean | string` - Mark field as deprecated. Pass a string with migration guidance.
|
|
449
|
+
- `description: string` - Field documentation (appears in GraphQL schema)
|
|
450
|
+
|
|
451
|
+
**Use Cases**:
|
|
452
|
+
|
|
453
|
+
1. **PII Protection**: Require specific scopes for sensitive fields
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
{
|
|
457
|
+
name: "ssn",
|
|
458
|
+
type: "String",
|
|
459
|
+
nullable: false,
|
|
460
|
+
requiresScope: "pii:read" // Only users with pii:read scope can query this
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
1. **API Versioning**: Deprecate fields with migration guidance
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
{
|
|
468
|
+
name: "oldPrice",
|
|
469
|
+
type: "Decimal",
|
|
470
|
+
nullable: true,
|
|
471
|
+
deprecated: "Use pricing.current instead - structure moved to pricing object"
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
1. **Schema Documentation**: Add rich field descriptions
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
{
|
|
479
|
+
name: "discount",
|
|
480
|
+
type: "Decimal",
|
|
481
|
+
nullable: false,
|
|
482
|
+
description: "Discount percentage. Access requires orders:view_discounts scope.",
|
|
483
|
+
requiresScope: "orders:view_discounts"
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### Manual Registration Functions
|
|
488
|
+
|
|
489
|
+
When decorators alone don't provide enough type information:
|
|
490
|
+
|
|
491
|
+
#### `registerTypeFields(typeName, fields, description?)`
|
|
492
|
+
|
|
493
|
+
Register type field definitions.
|
|
494
|
+
|
|
495
|
+
```typescript
|
|
496
|
+
fraiseql.registerTypeFields("User", [
|
|
497
|
+
{ name: "id", type: "Int", nullable: false },
|
|
498
|
+
{ name: "name", type: "String", nullable: false },
|
|
499
|
+
{ name: "email", type: "String", nullable: true },
|
|
500
|
+
]);
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
#### `registerQuery(name, returnType, returnsList, nullable, args, description?, config?)`
|
|
504
|
+
|
|
505
|
+
Register a query with full metadata.
|
|
506
|
+
|
|
507
|
+
```typescript
|
|
508
|
+
fraiseql.registerQuery(
|
|
509
|
+
"users",
|
|
510
|
+
"User",
|
|
511
|
+
true, // returns list
|
|
512
|
+
false, // not nullable
|
|
513
|
+
[
|
|
514
|
+
{ name: "limit", type: "Int", nullable: false, default: 10 },
|
|
515
|
+
],
|
|
516
|
+
"Get all users",
|
|
517
|
+
{ sql_source: "v_user" }
|
|
518
|
+
);
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
#### `registerMutation(name, returnType, returnsList, nullable, args, description?, config?)`
|
|
522
|
+
|
|
523
|
+
Register a mutation with full metadata.
|
|
524
|
+
|
|
525
|
+
```typescript
|
|
526
|
+
fraiseql.registerMutation(
|
|
527
|
+
"createUser",
|
|
528
|
+
"User",
|
|
529
|
+
false, // single item
|
|
530
|
+
false, // not nullable
|
|
531
|
+
[
|
|
532
|
+
{ name: "name", type: "String", nullable: false },
|
|
533
|
+
],
|
|
534
|
+
"Create a new user",
|
|
535
|
+
{ sql_source: "fn_create_user", operation: "CREATE" }
|
|
536
|
+
);
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### Schema Export
|
|
540
|
+
|
|
541
|
+
#### `exportSchema(outputPath, options?)`
|
|
542
|
+
|
|
543
|
+
Export the schema to a JSON file.
|
|
544
|
+
|
|
545
|
+
```typescript
|
|
546
|
+
fraiseql.exportSchema("schema.json", { pretty: true });
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
#### `getSchemaDict()`
|
|
550
|
+
|
|
551
|
+
Get the schema as a JavaScript object.
|
|
552
|
+
|
|
553
|
+
```typescript
|
|
554
|
+
const schema = fraiseql.getSchemaDict();
|
|
555
|
+
console.log(schema.types);
|
|
556
|
+
console.log(schema.queries);
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
#### `exportSchemaToString(options?)`
|
|
560
|
+
|
|
561
|
+
Export schema to a JSON string.
|
|
562
|
+
|
|
563
|
+
```typescript
|
|
564
|
+
const json = fraiseql.exportSchemaToString({ pretty: true });
|
|
565
|
+
console.log(json);
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
## Supported GraphQL Types
|
|
569
|
+
|
|
570
|
+
### Scalars
|
|
571
|
+
|
|
572
|
+
- `Int` - 32-bit integer
|
|
573
|
+
- `Float` - Floating point number
|
|
574
|
+
- `String` - Text string
|
|
575
|
+
- `Boolean` - True/False
|
|
576
|
+
- `ID` - Unique identifier
|
|
577
|
+
|
|
578
|
+
### Modifiers
|
|
579
|
+
|
|
580
|
+
- `T[]` - List type (maps to `[T!]` in GraphQL)
|
|
581
|
+
- `T | null` - Nullable type
|
|
582
|
+
- `T | undefined` - Optional parameter
|
|
583
|
+
|
|
584
|
+
## Type Mapping
|
|
585
|
+
|
|
586
|
+
TypeScript types are converted to GraphQL types:
|
|
587
|
+
|
|
588
|
+
```typescript
|
|
589
|
+
// TypeScript → GraphQL
|
|
590
|
+
number → Float
|
|
591
|
+
string → String
|
|
592
|
+
boolean → Boolean
|
|
593
|
+
SomeClass → SomeClass (custom type)
|
|
594
|
+
T[] → [T!] (list)
|
|
595
|
+
T | null → T (nullable)
|
|
596
|
+
T | undefined → T (optional param)
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
## Analytics Features
|
|
600
|
+
|
|
601
|
+
### Fact Tables
|
|
602
|
+
|
|
603
|
+
Fact tables are special analytics tables with:
|
|
604
|
+
|
|
605
|
+
- **Measures**: Numeric columns for aggregation (SUM, AVG, COUNT)
|
|
606
|
+
- **Dimensions**: JSONB column for flexible GROUP BY
|
|
607
|
+
- **Denormalized Filters**: Indexed columns for fast WHERE clauses
|
|
608
|
+
|
|
609
|
+
```typescript
|
|
610
|
+
@fraiseql.FactTable({
|
|
611
|
+
tableName: "tf_sales", // Must start with "tf_"
|
|
612
|
+
measures: ["revenue", "cost"], // Numeric columns
|
|
613
|
+
dimensionPaths: [
|
|
614
|
+
{
|
|
615
|
+
name: "category",
|
|
616
|
+
json_path: "data->>'category'",
|
|
617
|
+
data_type: "text",
|
|
618
|
+
},
|
|
619
|
+
],
|
|
620
|
+
})
|
|
621
|
+
@fraiseql.type()
|
|
622
|
+
class Sale {
|
|
623
|
+
id!: number;
|
|
624
|
+
revenue!: number;
|
|
625
|
+
cost!: number;
|
|
626
|
+
customerId!: string;
|
|
627
|
+
}
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
### Aggregate Queries
|
|
631
|
+
|
|
632
|
+
Queries that perform GROUP BY aggregations on fact tables:
|
|
633
|
+
|
|
634
|
+
```typescript
|
|
635
|
+
@fraiseql.AggregateQuery({
|
|
636
|
+
factTable: "tf_sales",
|
|
637
|
+
autoGroupBy: true, // Auto-generate groupBy fields
|
|
638
|
+
autoAggregates: true, // Auto-generate aggregate functions
|
|
639
|
+
})
|
|
640
|
+
@fraiseql.query()
|
|
641
|
+
function salesSummary(): Record<string, unknown>[] {
|
|
642
|
+
throw new Error("Not executed");
|
|
643
|
+
}
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
These queries support:
|
|
647
|
+
|
|
648
|
+
- `groupBy`: Dimensions and temporal buckets
|
|
649
|
+
- `aggregates`: COUNT, SUM, AVG, MIN, MAX
|
|
650
|
+
- `where`: Pre-aggregation filters
|
|
651
|
+
- `having`: Post-aggregation filters
|
|
652
|
+
- `orderBy`: Sort results
|
|
653
|
+
- Pagination: `limit`, `offset`
|
|
654
|
+
|
|
655
|
+
## Examples
|
|
656
|
+
|
|
657
|
+
See the `examples/` directory:
|
|
658
|
+
|
|
659
|
+
- **basic_schema.ts** - Simple CRUD queries and mutations
|
|
660
|
+
- **analytics_schema.ts** - Fact tables and aggregate queries
|
|
661
|
+
- **enums-example.ts** - Enum definitions and usage
|
|
662
|
+
- **types-advanced.ts** - Comprehensive type system example (enums, interfaces, unions, input types)
|
|
663
|
+
- **unions-interfaces-example.ts** - Interfaces, unions, and polymorphic queries
|
|
664
|
+
- **field-metadata.ts** - Field-level access control, deprecation, and documentation
|
|
665
|
+
- **subscriptions.ts** - Real-time subscriptions: event filtering, topics, CDC, alerts
|
|
666
|
+
- **comprehensive-example.ts** - Full-featured schema with all FraiseQL capabilities
|
|
667
|
+
|
|
668
|
+
Run examples:
|
|
669
|
+
|
|
670
|
+
```bash
|
|
671
|
+
npm run example:basic # Generate basic schema
|
|
672
|
+
npm run example:analytics # Generate analytics schema
|
|
673
|
+
npm run example:enums # Generate enum example
|
|
674
|
+
npm run example:advanced # Generate advanced types example
|
|
675
|
+
npm run example:metadata # Generate field metadata example
|
|
676
|
+
npm run example:subscriptions # Generate subscriptions example
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
## Development
|
|
680
|
+
|
|
681
|
+
```bash
|
|
682
|
+
# Install dependencies
|
|
683
|
+
npm install
|
|
684
|
+
|
|
685
|
+
# Build
|
|
686
|
+
npm run build
|
|
687
|
+
|
|
688
|
+
# Run tests
|
|
689
|
+
npm test
|
|
690
|
+
|
|
691
|
+
# Watch mode
|
|
692
|
+
npm run test:watch
|
|
693
|
+
|
|
694
|
+
# Lint
|
|
695
|
+
npm run lint
|
|
696
|
+
|
|
697
|
+
# Format code
|
|
698
|
+
npm run format
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
## Testing
|
|
702
|
+
|
|
703
|
+
Tests verify:
|
|
704
|
+
|
|
705
|
+
- Type introspection and conversion
|
|
706
|
+
- Schema registration and retrieval
|
|
707
|
+
- Decorator functionality
|
|
708
|
+
- Schema JSON generation
|
|
709
|
+
- Analytics fact tables and aggregate queries
|
|
710
|
+
|
|
711
|
+
```bash
|
|
712
|
+
npm test
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
## Troubleshooting
|
|
716
|
+
|
|
717
|
+
### Issue: "Field type information not available"
|
|
718
|
+
|
|
719
|
+
**Cause**: TypeScript doesn't preserve type information at runtime by default.
|
|
720
|
+
|
|
721
|
+
**Solution**: Use `registerTypeFields()` or `registerQuery()`/`registerMutation()` with explicit type metadata.
|
|
722
|
+
|
|
723
|
+
```typescript
|
|
724
|
+
// Instead of relying on decorators alone:
|
|
725
|
+
fraiseql.registerTypeFields("User", [
|
|
726
|
+
{ name: "id", type: "Int", nullable: false },
|
|
727
|
+
// ... other fields
|
|
728
|
+
]);
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
### Issue: "Factory not started: fraiseql-cli not found"
|
|
732
|
+
|
|
733
|
+
**Solution**: Install the CLI tool:
|
|
734
|
+
|
|
735
|
+
```bash
|
|
736
|
+
# Global installation
|
|
737
|
+
npm install -g fraiseql-cli
|
|
738
|
+
|
|
739
|
+
# Or use local version
|
|
740
|
+
npx fraiseql-cli compile schema.json
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
## Performance
|
|
744
|
+
|
|
745
|
+
- **Compile-time**: Negligible (< 100ms for typical schemas)
|
|
746
|
+
- **Runtime**: Zero overhead - SQL is compiled, not interpreted
|
|
747
|
+
- **Schema generation**: Fast JSON serialization
|
|
748
|
+
|
|
749
|
+
## Architecture Notes
|
|
750
|
+
|
|
751
|
+
### No Runtime FFI
|
|
752
|
+
|
|
753
|
+
This package generates **JSON only**. There's no FFI, no native bindings, no runtime dependencies on the Rust engine.
|
|
754
|
+
|
|
755
|
+
The workflow is:
|
|
756
|
+
|
|
757
|
+
1. Write TypeScript with decorators
|
|
758
|
+
2. Run `exportSchema()` to generate `schema.json`
|
|
759
|
+
3. Compile with `fraiseql-cli` to get `schema.compiled.json`
|
|
760
|
+
4. Deploy compiled schema to Rust runtime
|
|
761
|
+
|
|
762
|
+
### Why Manual Field Registration?
|
|
763
|
+
|
|
764
|
+
TypeScript's decorator system doesn't preserve generic type parameters at runtime. To provide full type information, we require explicit field registration. This is a limitation of the language, not the framework.
|
|
765
|
+
|
|
766
|
+
Future versions may use TypeScript 5.2+ metadata if decorators mature in the standard.
|
|
767
|
+
|
|
768
|
+
## License
|
|
769
|
+
|
|
770
|
+
MIT
|
|
771
|
+
|
|
772
|
+
## Support
|
|
773
|
+
|
|
774
|
+
- **Documentation**: <https://docs.fraiseql.io>
|
|
775
|
+
- **Issues**: <https://github.com/fraiseql/fraiseql/issues>
|
|
776
|
+
- **Examples**: See `examples/` directory
|
|
777
|
+
|
|
778
|
+
## Contributing
|
|
779
|
+
|
|
780
|
+
Contributions welcome! Please follow the contribution guidelines in the main repository.
|
|
781
|
+
|
|
782
|
+
---
|
|
783
|
+
|
|
784
|
+
**Remember**: FraiseQL TypeScript is for **authoring only**. Runtime execution happens in the Rust engine.
|