@powerhousedao/academy 3.3.0-dev.1 → 3.3.0-dev.10
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/CHANGELOG.md +84 -0
- package/docs/academy/01-GetStarted/home.mdx +1 -1
- package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/02-ConfiguringDrives.md +2 -2
- package/docs/academy/02-MasteryTrack/04-WorkWithData/{02-GraphQLAtPowerhouse.md → 01-GraphQLAtPowerhouse.md} +132 -4
- package/docs/academy/02-MasteryTrack/04-WorkWithData/{01-ReadingAndWritingThroughTheAPI.mdx → 02-UsingTheAPI.mdx} +11 -7
- package/docs/academy/02-MasteryTrack/04-WorkWithData/03-UsingSubgraphs.md +649 -0
- package/docs/academy/02-MasteryTrack/04-WorkWithData/07-OperationalDbProcessorTutorial/03-GenerateAnAnalyticsProcessor.md +169 -0
- package/docs/academy/04-APIReferences/00-PowerhouseCLI.md +5 -0
- package/docs/academy/04-APIReferences/04-OperationalDatabase.md +798 -0
- package/docs/academy/04-APIReferences/05-PHDocumentMigrationGuide.md +338 -0
- package/package.json +1 -1
- package/sidebars.ts +82 -64
- package/src/components/HomepageFeatures/index.tsx +2 -2
- package/docs/academy/02-MasteryTrack/04-WorkWithData/03-WorkingWithSubgraphs/02-GraphQLAndSubgraphs.mdx +0 -119
- package/docs/academy/02-MasteryTrack/04-WorkWithData/03-WorkingWithSubgraphs/03-WorkingWithSubgraphs.md +0 -312
- package/docs/academy/02-MasteryTrack/04-WorkWithData/03-WorkingWithSubgraphs/_category_.json +0 -8
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
# Using Subgraphs
|
|
2
|
+
|
|
3
|
+
This tutorial will demonstrate how to create and customize a subgraph using our To-do List project as an example.
|
|
4
|
+
Let's start with the basics and gradually add more complex features and functionality.
|
|
5
|
+
|
|
6
|
+
## What is a subgraph?
|
|
7
|
+
|
|
8
|
+
A subgraph in Powerhouse is a **GraphQL-based modular data component** that extends the functionality of your document models. While document models handle the core state and operations, subgraphs can:
|
|
9
|
+
1. Connect to external APIs or databases
|
|
10
|
+
2. Add custom queries and mutations
|
|
11
|
+
3. Automate interactions between different document models
|
|
12
|
+
4. Provide additional backend functionality
|
|
13
|
+
|
|
14
|
+
### Subgraphs can retrieve data from
|
|
15
|
+
|
|
16
|
+
- **The Reactor** – The core Powerhouse data system or network node.
|
|
17
|
+
- **Operational Data Stores** – Structured data storage for operational processes, offering real-time updates, for querying structured data.
|
|
18
|
+
- **Analytics Stores** – Aggregated historical data, useful for insights, reporting and business intelligence.
|
|
19
|
+
|
|
20
|
+
### Subgraphs consist of
|
|
21
|
+
|
|
22
|
+
- **A schema** – Which defines the GraphQL Queries and Mutations.
|
|
23
|
+
- **Resolvers** – Which handle data fetching and logic.
|
|
24
|
+
- **Context Fields** – Additional metadata that helps in resolving data efficiently.
|
|
25
|
+
|
|
26
|
+
#### Additionally, context fields allow resolvers to access extra information, such as:
|
|
27
|
+
- **User authentication** (e.g., checking if a user is an admin).
|
|
28
|
+
- **External data sources** (e.g., analytics).
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
```typescript title="Example of a context field"
|
|
33
|
+
context: {
|
|
34
|
+
admin: async (session) => {
|
|
35
|
+
const admins = await operationalStore.get("admins");
|
|
36
|
+
return admins.includes(session.user);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 1. How to generate a subgraph
|
|
42
|
+
|
|
43
|
+
Lets start by generating a new subgraph. For our tutorial we will create a new subgraph within our To-do List project.
|
|
44
|
+
Open your project and start your terminal.
|
|
45
|
+
The Powerhouse toolkit provides a command-line utility to create new subgraphs easily.
|
|
46
|
+
|
|
47
|
+
```bash title="Run the following command to generate a new subgraph"
|
|
48
|
+
ph generate --subgraph <to-do-list-subgraph>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```bash title="Expected Output"
|
|
52
|
+
Loaded templates: node_modules/@powerhousedao/codegen/dist/codegen/.hygen/templates
|
|
53
|
+
FORCED: ./subgraphs/to-do-list-subgraph/index.ts
|
|
54
|
+
skipped: ./subgraphs/index.ts
|
|
55
|
+
inject: ./subgraphs/index.ts
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### What happened?
|
|
59
|
+
1. A new subgraph was created in `./subgraphs/to-do-list-subgraph/`
|
|
60
|
+
2. The subgraph was automatically registered in your project's registry
|
|
61
|
+
3. Basic boilerplate code was generated with an example query
|
|
62
|
+
|
|
63
|
+
If we now run `ph reactor` we will see the new subgraph being registered during the startup of the Reactor.
|
|
64
|
+
> Registered /todolist subgraph.
|
|
65
|
+
|
|
66
|
+
Alternatively, when you are running a local reactor with `ph reactor` a series of subgraphs will automatically get registered, amongst those one for the available document models in your Powerhouse project.
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
Initializing Subgraph Manager...
|
|
70
|
+
> Registered /graphql/auth subgraph.
|
|
71
|
+
> Registered /graphql/system subgraph.
|
|
72
|
+
> Registered /graphql/analytics subgraph.
|
|
73
|
+
> Registered /d/:drive subgraph.
|
|
74
|
+
> Updating router
|
|
75
|
+
> Registered /graphql supergraph
|
|
76
|
+
> Registered /graphql/to-do-list subgraph.
|
|
77
|
+
> Updating router
|
|
78
|
+
> Registered /graphql supergraph
|
|
79
|
+
➜ Reactor: http://localhost:4001/d/powerhouse
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 2. Building a To-do List Subgraph
|
|
83
|
+
|
|
84
|
+
Now that we've generated our subgraph, let's build a complete To-do List subgraph that extends the functionality of our To-do List document model. This subgraph will provide additional querying capabilities and demonstrate how subgraphs work with document models.
|
|
85
|
+
|
|
86
|
+
### 2.1 Understanding the To-do List Document Model
|
|
87
|
+
|
|
88
|
+
Before building our subgraph, let's recall the structure of our To-do List document model from the [DocumentModelCreation tutorial](/academy/MasteryTrack/DocumentModelCreation/SpecifyTheStateSchema):
|
|
89
|
+
|
|
90
|
+
```graphql
|
|
91
|
+
type ToDoListState {
|
|
92
|
+
items: [ToDoItem!]!
|
|
93
|
+
stats: ToDoListStats!
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
type ToDoItem {
|
|
97
|
+
id: ID!
|
|
98
|
+
text: String!
|
|
99
|
+
checked: Boolean!
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
type ToDoListStats {
|
|
103
|
+
total: Int!
|
|
104
|
+
checked: Int!
|
|
105
|
+
unchecked: Int
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The document model has these operations:
|
|
110
|
+
- `ADD_TODO_ITEM`: Adds a new to-do item
|
|
111
|
+
- `UPDATE_TODO_ITEM`: Updates an existing to-do item
|
|
112
|
+
- `DELETE_TODO_ITEM`: Deletes a to-do item
|
|
113
|
+
|
|
114
|
+
### 2.2 Define the Subgraph Schema
|
|
115
|
+
|
|
116
|
+
Now let's create a subgraph that provides enhanced querying capabilities for our To-do List documents.
|
|
117
|
+
|
|
118
|
+
**Step 1: Define the schema in `subgraphs/to-do-list/schema.ts`:**
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
export const typeDefs = `
|
|
122
|
+
type Query {
|
|
123
|
+
todoList: TodoListSummary
|
|
124
|
+
todoItems(checked: Boolean): [TodoItem!]!
|
|
125
|
+
todoItemsCount(checked: Boolean): Int!
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
type TodoListSummary {
|
|
129
|
+
total: Int!
|
|
130
|
+
checked: Int!
|
|
131
|
+
unchecked: Int!
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
type TodoItem {
|
|
135
|
+
id: ID!
|
|
136
|
+
text: String!
|
|
137
|
+
checked: Boolean!
|
|
138
|
+
}
|
|
139
|
+
`;
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Step 2: Create resolvers in `subgraphs/to-do-list/resolvers.ts`:**
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// subgraphs/to-do-list/resolvers.ts
|
|
146
|
+
// subgraphs/to-do-list/resolvers.ts
|
|
147
|
+
interface SubgraphInstance {
|
|
148
|
+
operationalStore: any;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export const createResolvers = (subgraphInstance: SubgraphInstance) => ({
|
|
152
|
+
Query: {
|
|
153
|
+
todoList: async () => {
|
|
154
|
+
const items = await subgraphInstance.operationalStore.getAll("todo_items");
|
|
155
|
+
const total = items.length;
|
|
156
|
+
const checked = items.filter((item: any) => item.checked).length;
|
|
157
|
+
const unchecked = total - checked;
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
total,
|
|
161
|
+
checked,
|
|
162
|
+
unchecked
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
todoItems: async (parent: any, { checked }: any) => {
|
|
167
|
+
let query = subgraphInstance.operationalStore.select("*").from("todo_items");
|
|
168
|
+
|
|
169
|
+
if (checked !== undefined) {
|
|
170
|
+
query = query.where("checked", checked);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const items = await query.orderBy("created_at", "asc");
|
|
174
|
+
return items;
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
todoItemsCount: async (parent: any, { checked }: any) => {
|
|
178
|
+
let query = subgraphInstance.operationalStore.count("* as count").from("todo_items");
|
|
179
|
+
|
|
180
|
+
if (checked !== undefined) {
|
|
181
|
+
query = query.where("checked", checked);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const result = await query.first();
|
|
185
|
+
return result?.count || 0;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Step 3: Implement the main class in `subgraphs/to-do-list/index.ts`:**
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
// subgraphs/to-do-list/index.ts
|
|
195
|
+
import { typeDefs } from './schema.js';
|
|
196
|
+
import { createResolvers } from './resolvers.js';
|
|
197
|
+
|
|
198
|
+
export default class ToDoListSubgraph {
|
|
199
|
+
path = '/to-do-list';
|
|
200
|
+
|
|
201
|
+
typeDefs = typeDefs;
|
|
202
|
+
resolvers: any;
|
|
203
|
+
operationalStore: any;
|
|
204
|
+
|
|
205
|
+
constructor() {
|
|
206
|
+
this.resolvers = createResolvers(this);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async onSetup() {
|
|
210
|
+
await this.createOperationalTables();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async createOperationalTables() {
|
|
214
|
+
await this.operationalStore.schema.createTableIfNotExists(
|
|
215
|
+
"todo_items",
|
|
216
|
+
(table: any) => {
|
|
217
|
+
table.string("id").primary();
|
|
218
|
+
table.string("text").notNullable();
|
|
219
|
+
table.boolean("checked").defaultTo(false);
|
|
220
|
+
table.timestamp("created_at").defaultTo(this.operationalStore.fn.now());
|
|
221
|
+
table.timestamp("updated_at").defaultTo(this.operationalStore.fn.now());
|
|
222
|
+
}
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async process(event: any) {
|
|
227
|
+
// Handle To-do List document operations
|
|
228
|
+
if (event.type === "ADD_TODO_ITEM") {
|
|
229
|
+
await this.operationalStore.insert("todo_items", {
|
|
230
|
+
id: event.input.id,
|
|
231
|
+
text: event.input.text,
|
|
232
|
+
checked: false,
|
|
233
|
+
created_at: new Date(),
|
|
234
|
+
updated_at: new Date()
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
console.log(`Added todo item: ${event.input.text}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (event.type === "UPDATE_TODO_ITEM") {
|
|
241
|
+
const updateData: any = {
|
|
242
|
+
updated_at: new Date()
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// Only update fields that were provided
|
|
246
|
+
if (event.input.text !== undefined) {
|
|
247
|
+
updateData.text = event.input.text;
|
|
248
|
+
}
|
|
249
|
+
if (event.input.checked !== undefined) {
|
|
250
|
+
updateData.checked = event.input.checked;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
await this.operationalStore.update("todo_items")
|
|
254
|
+
.where("id", event.input.id)
|
|
255
|
+
.update(updateData);
|
|
256
|
+
|
|
257
|
+
console.log(`Updated todo item: ${event.input.id}`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (event.type === "DELETE_TODO_ITEM") {
|
|
261
|
+
await this.operationalStore.delete("todo_items")
|
|
262
|
+
.where("id", event.input.id);
|
|
263
|
+
|
|
264
|
+
console.log(`Deleted todo item: ${event.input.id}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**What this schema provides:**
|
|
271
|
+
- `todoList`: Returns statistics about all to-do items (total, checked, unchecked counts)
|
|
272
|
+
- `todoItems`: Returns a list of to-do items, optionally filtered by checked status
|
|
273
|
+
- `todoItemsCount`: Returns just the count of items, optionally filtered by checked status
|
|
274
|
+
|
|
275
|
+
### 2.3 Understanding the Implementation
|
|
276
|
+
|
|
277
|
+
**What this multi-file approach provides:**
|
|
278
|
+
|
|
279
|
+
1. **Schema separation** (`schema.ts`): Clean GraphQL type definitions
|
|
280
|
+
2. **Resolver isolation** (`resolvers.ts`): Business logic separated from structure
|
|
281
|
+
3. **Main orchestration** (`index.ts`): Combines everything and handles lifecycle methods
|
|
282
|
+
|
|
283
|
+
**Key features implemented:**
|
|
284
|
+
- A `todo_items` operational table to store individual to-do items
|
|
285
|
+
- Fields that match our document model structure
|
|
286
|
+
- Timestamps for tracking when items were created and updated
|
|
287
|
+
- Resolvers that fetch and filter todo items from the operational store
|
|
288
|
+
- Event processing to keep the subgraph data synchronized with document model changes
|
|
289
|
+
|
|
290
|
+
### 2.4 Connect to Document Model Events (Processor Integration)
|
|
291
|
+
|
|
292
|
+
To make our subgraph truly useful, we need to connect it to the actual To-do List document model events. This ensures that when users interact with To-do List documents through Connect, the subgraph data stays synchronized.
|
|
293
|
+
|
|
294
|
+
Add this processor integration to your subgraph:
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
async process(event) {
|
|
298
|
+
// Handle To-do List document operations
|
|
299
|
+
if (event.type === "ADD_TODO_ITEM") {
|
|
300
|
+
await this.operationalStore.insert("todo_items", {
|
|
301
|
+
id: event.input.id,
|
|
302
|
+
text: event.input.text,
|
|
303
|
+
checked: false,
|
|
304
|
+
created_at: new Date(),
|
|
305
|
+
updated_at: new Date()
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
console.log(`Added todo item: ${event.input.text}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (event.type === "UPDATE_TODO_ITEM") {
|
|
312
|
+
const updateData = {
|
|
313
|
+
updated_at: new Date()
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
// Only update fields that were provided
|
|
317
|
+
if (event.input.text !== undefined) {
|
|
318
|
+
updateData.text = event.input.text;
|
|
319
|
+
}
|
|
320
|
+
if (event.input.checked !== undefined) {
|
|
321
|
+
updateData.checked = event.input.checked;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
await this.operationalStore.update("todo_items")
|
|
325
|
+
.where("id", event.input.id)
|
|
326
|
+
.update(updateData);
|
|
327
|
+
|
|
328
|
+
console.log(`Updated todo item: ${event.input.id}`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (event.type === "DELETE_TODO_ITEM") {
|
|
332
|
+
await this.operationalStore.delete("todo_items")
|
|
333
|
+
.where("id", event.input.id);
|
|
334
|
+
|
|
335
|
+
console.log(`Deleted todo item: ${event.input.id}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**What this processor does:**
|
|
341
|
+
- Listens for document model operations (`ADD_TODO_ITEM`, `UPDATE_TODO_ITEM`, `DELETE_TODO_ITEM`)
|
|
342
|
+
- Updates the operational store in real-time when these operations occur
|
|
343
|
+
- Provides console logging for debugging
|
|
344
|
+
- Maintains data consistency between the document model and the subgraph
|
|
345
|
+
|
|
346
|
+
### 2.5 Summary of What We've Built
|
|
347
|
+
|
|
348
|
+
- **Added two main queries**: `todoList` for statistics and `todoItems` for item lists
|
|
349
|
+
- **Created an operational table** `todo_items` to store the todo items with proper schema
|
|
350
|
+
- **Added resolvers** to fetch and filter todo items from the operational store
|
|
351
|
+
- **Implemented event processing** to keep the subgraph data synchronized with document model changes
|
|
352
|
+
- **The todoItems query accepts an optional checked parameter** to filter items by their completion status
|
|
353
|
+
- **The todoList query returns the full statistics** including total, checked, and unchecked counts
|
|
354
|
+
|
|
355
|
+
## 3. Testing the To-do List Subgraph
|
|
356
|
+
|
|
357
|
+
### 3.1. Start the reactor
|
|
358
|
+
To activate the subgraph, run:
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
ph reactor
|
|
362
|
+
```
|
|
363
|
+
Or, for full system startup:
|
|
364
|
+
|
|
365
|
+
```bash title="Start the Reactor & Connect in Studio or Locally"
|
|
366
|
+
ph dev
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
You should see the subgraph being registered in the console output:
|
|
370
|
+
```
|
|
371
|
+
> Registered /graphql/to-do-list subgraph.
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### 3.2. Create some test data
|
|
375
|
+
Before testing queries, let's create some To-do List documents with test data:
|
|
376
|
+
|
|
377
|
+
1. Open Connect at `http://localhost:3001`
|
|
378
|
+
2. Add the 'remote' drive that is running locally via the (+) 'Add Drive' button. Add 'http://localhost:4001/d/powerhouse'
|
|
379
|
+
3. Create a new To-do List document
|
|
380
|
+
4. Add some test items:
|
|
381
|
+
- "Learn about subgraphs" (leave unchecked)
|
|
382
|
+
- "Build a To-do List subgraph" (mark as checked)
|
|
383
|
+
- "Test the subgraph" (leave unchecked)
|
|
384
|
+
|
|
385
|
+
### 3.3. Access GraphQL playground
|
|
386
|
+
Open your browser and go to:
|
|
387
|
+
|
|
388
|
+
```bash
|
|
389
|
+
http://localhost:4001/graphql/to-do-list
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### 3.4. Test the queries
|
|
393
|
+
|
|
394
|
+
**Query 1: Get To-do List statistics**
|
|
395
|
+
```graphql
|
|
396
|
+
query {
|
|
397
|
+
todoList {
|
|
398
|
+
total
|
|
399
|
+
checked
|
|
400
|
+
unchecked
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
**Query 2: Get all to-do items**
|
|
406
|
+
```graphql
|
|
407
|
+
query {
|
|
408
|
+
todoItems {
|
|
409
|
+
id
|
|
410
|
+
text
|
|
411
|
+
checked
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
**Query 3: Get only unchecked items**
|
|
417
|
+
```graphql
|
|
418
|
+
query {
|
|
419
|
+
todoItems(checked: false) {
|
|
420
|
+
id
|
|
421
|
+
text
|
|
422
|
+
checked
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**Query 4: Get count of completed items**
|
|
428
|
+
```graphql
|
|
429
|
+
query {
|
|
430
|
+
todoItemsCount(checked: true)
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### 3.5. Expected responses
|
|
435
|
+
|
|
436
|
+
**For the statistics query:**
|
|
437
|
+
```json
|
|
438
|
+
{
|
|
439
|
+
"data": {
|
|
440
|
+
"todoList": {
|
|
441
|
+
"total": 3,
|
|
442
|
+
"checked": 1,
|
|
443
|
+
"unchecked": 2
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
**For the items query:**
|
|
450
|
+
```json
|
|
451
|
+
{
|
|
452
|
+
"data": {
|
|
453
|
+
"todoItems": [
|
|
454
|
+
{
|
|
455
|
+
"id": "item-1",
|
|
456
|
+
"text": "Learn about subgraphs",
|
|
457
|
+
"checked": false
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
"id": "item-2",
|
|
461
|
+
"text": "Build a To-do List subgraph",
|
|
462
|
+
"checked": true
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
"id": "item-3",
|
|
466
|
+
"text": "Test the subgraph",
|
|
467
|
+
"checked": false
|
|
468
|
+
}
|
|
469
|
+
]
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### 3.6. Test real-time updates
|
|
475
|
+
|
|
476
|
+
To verify that your subgraph stays synchronized with document changes:
|
|
477
|
+
|
|
478
|
+
1. Keep the GraphQL playground open
|
|
479
|
+
2. In another tab, open your To-do List document in Connect
|
|
480
|
+
3. Add a new item or check/uncheck an existing item
|
|
481
|
+
4. Return to the GraphQL playground and re-run your queries
|
|
482
|
+
5. You should see the updated data immediately
|
|
483
|
+
|
|
484
|
+
This demonstrates the real-time synchronization between the document model and the subgraph through event processing.
|
|
485
|
+
|
|
486
|
+
## 4. Working with the supergraph or gateway
|
|
487
|
+
|
|
488
|
+
A supergraph is a GraphQL schema that combines multiple underlying GraphQL APIs, known as subgraphs, into a single, unified graph. This architecture allows different teams to work independently on their respective services (subgraphs) while providing a single entry point for clients or users to query all available data
|
|
489
|
+
|
|
490
|
+
### 4.1 Key concepts
|
|
491
|
+
|
|
492
|
+
* **Subgraph:** An independent GraphQL service with its own schema. Each subgraph typically represents a specific domain or microservice within a larger system.
|
|
493
|
+
* **Gateway/Router:** A server that sits in front of the subgraphs. It receives client queries, consults the supergraph schema, and routes parts of the query to the relevant subgraphs. It then stitches the results back together before sending the final response to the client.
|
|
494
|
+
|
|
495
|
+
### 4.2 Benefits of using a supergraph
|
|
496
|
+
|
|
497
|
+
* **Federated Architecture:** Enables a microservices-based approach where different teams can own and operate their services independently.
|
|
498
|
+
* **Scalability:** Individual subgraphs can be scaled independently based on their specific needs.
|
|
499
|
+
* **Improved Developer Experience:** Clients interact with a single, consistent GraphQL API, simplifying data fetching and reducing the need to manage multiple endpoints.
|
|
500
|
+
* **Schema Evolution:** Subgraphs can evolve their schemas independently, and the supergraph can be updated without breaking existing clients, as long as breaking changes are managed carefully.
|
|
501
|
+
* **Clear Separation of Concerns:** Each subgraph focuses on a specific domain, leading to more maintainable and understandable codebases.
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
### 4.3 Use the Powerhouse supergraph
|
|
505
|
+
|
|
506
|
+
The Powerhouse supergraph for any given remote drive or reactor can be found under `http://localhost:4001/graphql`. The gateway / supergraph available on `/graphql` combines all the subgraphs, except for the drive subgraph (which is accessible via `/d/:driveId`). To get to the endpoint open your localhost by starting the reactor and adding `graphql` to the end of the url. The following commands explain how you can test & try the supergraph.
|
|
507
|
+
|
|
508
|
+
- Start the reactor:
|
|
509
|
+
|
|
510
|
+
```bash
|
|
511
|
+
ph reactor
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
- Open the GraphQL editor in your browser:
|
|
515
|
+
|
|
516
|
+
```
|
|
517
|
+
http://localhost:4001/graphql
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
The supergraph allows to both query & mutate data from the same endpoint.
|
|
521
|
+
|
|
522
|
+
**Example: Using the supergraph with To-do List documents**
|
|
523
|
+
|
|
524
|
+
1. Create a todo document in the `powerhouse` drive using the `ToDoList_createDocument` mutation:
|
|
525
|
+
```graphql
|
|
526
|
+
mutation {
|
|
527
|
+
ToDoList_createDocument(
|
|
528
|
+
input: {
|
|
529
|
+
documentId: "my-todo-list"
|
|
530
|
+
name: "My Test To-do List"
|
|
531
|
+
}
|
|
532
|
+
) {
|
|
533
|
+
id
|
|
534
|
+
name
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
2. Add some items to your to-do list using the `ToDoList_addTodoItem` mutation:
|
|
540
|
+
```graphql
|
|
541
|
+
mutation {
|
|
542
|
+
ToDoList_addTodoItem(
|
|
543
|
+
docId: "my-todo-list"
|
|
544
|
+
input: {
|
|
545
|
+
id: "item-1"
|
|
546
|
+
text: "Learn about supergraphs"
|
|
547
|
+
}
|
|
548
|
+
)
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
3. Query the document state using the `GetDocument` query:
|
|
553
|
+
```graphql
|
|
554
|
+
query {
|
|
555
|
+
ToDoList {
|
|
556
|
+
getDocument(docId: "my-todo-list") {
|
|
557
|
+
id
|
|
558
|
+
name
|
|
559
|
+
state {
|
|
560
|
+
items {
|
|
561
|
+
id
|
|
562
|
+
text
|
|
563
|
+
checked
|
|
564
|
+
}
|
|
565
|
+
stats {
|
|
566
|
+
total
|
|
567
|
+
checked
|
|
568
|
+
unchecked
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
4. Now query the same data through your subgraph (which should be included in the supergraph):
|
|
577
|
+
```graphql
|
|
578
|
+
query {
|
|
579
|
+
todoList {
|
|
580
|
+
total
|
|
581
|
+
checked
|
|
582
|
+
unchecked
|
|
583
|
+
}
|
|
584
|
+
todoItems {
|
|
585
|
+
id
|
|
586
|
+
text
|
|
587
|
+
checked
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
This demonstrates how the supergraph provides a unified interface to both your document models and your custom subgraphs, allowing you to query and mutate data from the same endpoint.
|
|
593
|
+
|
|
594
|
+
## 5. Summary
|
|
595
|
+
|
|
596
|
+
Congratulations! You've successfully built a complete To-do List subgraph that demonstrates the power of extending document models with custom GraphQL functionality. Let's recap what you've accomplished:
|
|
597
|
+
|
|
598
|
+
### What you built:
|
|
599
|
+
- **A custom GraphQL schema** that provides enhanced querying capabilities for To-do List documents
|
|
600
|
+
- **An operational data store** that efficiently stores and retrieves to-do items
|
|
601
|
+
- **Real-time event processing** that keeps your subgraph synchronized with document model changes
|
|
602
|
+
- **Advanced query capabilities** including filtering and counting operations
|
|
603
|
+
- **Integration with the supergraph** for unified API access
|
|
604
|
+
|
|
605
|
+
### Key concepts learned:
|
|
606
|
+
- **Subgraphs extend document models** with additional querying and data processing capabilities
|
|
607
|
+
- **Operational data stores** provide efficient storage for subgraph data
|
|
608
|
+
- **Event processing** enables real-time synchronization between document models and subgraphs
|
|
609
|
+
- **The supergraph** unifies multiple subgraphs into a single GraphQL endpoint
|
|
610
|
+
|
|
611
|
+
### Next steps:
|
|
612
|
+
- Explore adding **mutations** to your subgraph for more complex operations
|
|
613
|
+
- Implement **data aggregation** for analytics and reporting
|
|
614
|
+
- Connect to **external APIs** for enhanced functionality
|
|
615
|
+
- Build **processors** that automate workflows between different document models
|
|
616
|
+
|
|
617
|
+
This tutorial has provided you with a solid foundation for building sophisticated data processing and querying capabilities in the Powerhouse ecosystem.
|
|
618
|
+
|
|
619
|
+
## Subgraphs are particularly useful for
|
|
620
|
+
|
|
621
|
+
1. **Cross-Document Interactions**: For example, connecting a To-do List with an Invoice document model:
|
|
622
|
+
- When an invoice-related task is marked complete, update the invoice status
|
|
623
|
+
- When an invoice is paid, automatically check off related tasks
|
|
624
|
+
|
|
625
|
+
2. **External Integrations**:
|
|
626
|
+
- Sync tasks with external project management tools
|
|
627
|
+
- Connect with notification systems
|
|
628
|
+
- Integrate with analytics platforms
|
|
629
|
+
|
|
630
|
+
3. **Custom Business Logic**:
|
|
631
|
+
- Implement complex task prioritization
|
|
632
|
+
- Add automated task assignments
|
|
633
|
+
- Create custom reporting functionality
|
|
634
|
+
|
|
635
|
+
### Prebuilt subgraphs
|
|
636
|
+
|
|
637
|
+
Some subgraphs (e.g., System Subgraph, Drive Subgraph) already exist.
|
|
638
|
+
To integrate with them, register them via the Reactor API.
|
|
639
|
+
|
|
640
|
+
### Future enhancements
|
|
641
|
+
|
|
642
|
+
Bridge Processors and Subgraphs – Currently, there's a gap in how processors and subgraphs interact. Powerhouse might improve this in future updates.
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
|