@powerhousedao/academy 3.3.0-dev.1 → 3.3.0-dev.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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
+