@powerhousedao/academy 4.1.0-dev.9 → 5.0.0-staging.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/CHANGELOG.md +294 -0
- package/docs/academy/02-MasteryTrack/04-WorkWithData/03-UsingSubgraphs.md +72 -419
- package/docs/academy/02-MasteryTrack/04-WorkWithData/05-RelationalDbProcessor.md +36 -33
- package/docs/academy/04-APIReferences/00-PowerhouseCLI.md +41 -23
- package/docs/academy/04-APIReferences/01-ReactHooks.md +612 -152
- package/package.json +1 -1
|
@@ -29,41 +29,40 @@ A subgraph in Powerhouse is a **GraphQL-based modular data component** that exte
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const admins = await operationalStore.get("admins");
|
|
36
|
-
return admins.includes(session.user);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
```
|
|
32
|
+
## Example: Implement a search subgraph based on data from the reactor
|
|
33
|
+
|
|
34
|
+
In this example we implement a subgraph which allows to search through todo-list documents in a specific document drive.
|
|
40
35
|
|
|
41
|
-
|
|
36
|
+
First we will generate the subgraph with the help of the ph cli, then we will define the GraphQL schema and implement the resolvers and finally we will start the reactor and execute a query through the GraphQL Gateway.
|
|
37
|
+
|
|
38
|
+
### 1. Generate the subgraph
|
|
42
39
|
|
|
43
40
|
Let's start by generating a new subgraph. For our tutorial we will create a new subgraph within our To-do List project.
|
|
44
41
|
Open your project and start your terminal.
|
|
45
42
|
The Powerhouse toolkit provides a command-line utility to create new subgraphs easily.
|
|
46
43
|
|
|
47
44
|
```bash title="Run the following command to generate a new subgraph"
|
|
48
|
-
ph generate --subgraph
|
|
45
|
+
ph generate --subgraph search-todos
|
|
49
46
|
```
|
|
50
47
|
|
|
51
48
|
```bash title="Expected Output"
|
|
52
|
-
Loaded templates:
|
|
53
|
-
FORCED: ./subgraphs/
|
|
49
|
+
Loaded templates: /projects/powerhouse/powerhouse/packages/codegen/dist/src/codegen/.hygen/templates
|
|
50
|
+
FORCED: ./subgraphs/search-todos/index.ts
|
|
54
51
|
skipped: ./subgraphs/index.ts
|
|
55
52
|
inject: ./subgraphs/index.ts
|
|
53
|
+
|
|
54
|
+
Loaded templates: /projects/powerhouse/powerhouse/packages/codegen/dist/src/codegen/.hygen/templates
|
|
55
|
+
FORCED: ./subgraphs/search-todos/resolvers.ts
|
|
56
|
+
FORCED: ./subgraphs/search-todos/schema.ts
|
|
56
57
|
```
|
|
57
58
|
|
|
58
59
|
### What happened?
|
|
59
|
-
1. A new subgraph was created in `./subgraphs/
|
|
60
|
+
1. A new subgraph was created in `./subgraphs/search-todos/`
|
|
60
61
|
2. The subgraph was automatically registered in your project's registry
|
|
61
62
|
3. Basic boilerplate code was generated with an example query
|
|
62
63
|
|
|
63
64
|
If we now run `ph reactor` we will see the new subgraph being registered during the startup of the Reactor.
|
|
64
|
-
> Registered /
|
|
65
|
-
|
|
66
|
-
Alternatively, when you are running a local reactor with `ph reactor`, a series of subgraphs will automatically get registered, among those, one for the available document models in your Powerhouse project.
|
|
65
|
+
> Registered /graphql/search-todos subgraph.
|
|
67
66
|
|
|
68
67
|
```
|
|
69
68
|
Initializing Subgraph Manager...
|
|
@@ -73,345 +72,69 @@ Initializing Subgraph Manager...
|
|
|
73
72
|
> Registered /d/:drive subgraph.
|
|
74
73
|
> Updating router
|
|
75
74
|
> Registered /graphql supergraph
|
|
76
|
-
> Registered /graphql/
|
|
75
|
+
> Registered /graphql/search-todos subgraph.
|
|
77
76
|
> Updating router
|
|
78
77
|
> Registered /graphql supergraph
|
|
79
78
|
➜ Reactor: http://localhost:4001/d/powerhouse
|
|
80
79
|
```
|
|
81
80
|
|
|
82
|
-
## 2. Building a
|
|
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
|
|
81
|
+
## 2. Building a search subgraph
|
|
115
82
|
|
|
116
|
-
Now
|
|
83
|
+
Now that we've generated our subgraph its tome to define the GraphQL schema and implement the resolvers.
|
|
117
84
|
|
|
118
|
-
**Step 1: Define the schema in `subgraphs/
|
|
85
|
+
**Step 1: Define the schema in `subgraphs/search-todos/schema.ts` by creating the file:**
|
|
119
86
|
|
|
120
87
|
```typescript
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
# Dashboard-style summary query - returns high-level metrics
|
|
124
|
-
# Similar to ToDoListStats from document model but optimized for quick queries
|
|
125
|
-
todoList: TodoListSummary
|
|
126
|
-
|
|
127
|
-
# Filtered list query - lets you get items by completion status
|
|
128
|
-
# More flexible than the basic document model - can filter checked/unchecked
|
|
129
|
-
todoItems(checked: Boolean): [TodoItem!]!
|
|
130
|
-
|
|
131
|
-
# Count-only query - when you just need numbers, not full data
|
|
132
|
-
# Faster than getting full list when you only need totals for dashboards
|
|
133
|
-
todoItemsCount(checked: Boolean): Int!
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
# This mirrors ToDoListStats from the document model
|
|
137
|
-
# But it's a "view" optimized for summary reports and dashboards
|
|
138
|
-
type TodoListSummary {
|
|
139
|
-
total: Int! # Total number of items
|
|
140
|
-
checked: Int! # Number of completed items
|
|
141
|
-
unchecked: Int! # Number of pending items
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
# This matches the ToDoItem from the document model
|
|
145
|
-
# Same data structure, but accessed through subgraph queries for filtering
|
|
146
|
-
type TodoItem {
|
|
147
|
-
id: ID! # Unique identifier
|
|
148
|
-
text: String! # The task description
|
|
149
|
-
checked: Boolean! # Completion status
|
|
150
|
-
}
|
|
151
|
-
}`
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
<details>
|
|
156
|
-
<summary> #### Understanding resolvers </summary>
|
|
88
|
+
import { gql } from "graphql-tag";
|
|
89
|
+
import type { DocumentNode } from "graphql";
|
|
157
90
|
|
|
158
|
-
|
|
159
|
-
|
|
91
|
+
export const schema: DocumentNode = gql`
|
|
92
|
+
"""
|
|
93
|
+
Subgraph definition
|
|
94
|
+
"""
|
|
160
95
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
1. **Understands the request** - "The user wants unchecked items"
|
|
164
|
-
2. **Knows where to get the data** - "I need to check the todo_items database table"
|
|
165
|
-
3. **Applies the right filters** - "Only get items where checked = false"
|
|
166
|
-
4. **Returns the answer** - "Here are the 5 unchecked items"
|
|
167
|
-
|
|
168
|
-
**The three resolvers serve different business needs:**
|
|
169
|
-
|
|
170
|
-
- **`todoList` Resolver - The Dashboard**
|
|
171
|
-
- **Business value**: Perfect for executive dashboards or KPI displays
|
|
172
|
-
- **Use case**: "We have 150 total tasks, 89 completed, 61 pending"
|
|
173
|
-
- **Users**: Executives, managers, anyone needing high-level metrics
|
|
174
|
-
|
|
175
|
-
- **`todoItems` Resolver - The Detailed List**
|
|
176
|
-
- **Business value**: Great for operational views where people need to see actual tasks
|
|
177
|
-
- **Use case**: "Show me all pending tasks" or "Show me everything"
|
|
178
|
-
- **Users**: Workers, operators, anyone who needs to act on specific items
|
|
179
|
-
|
|
180
|
-
- **`todoItemsCount` Resolver - The Counter**
|
|
181
|
-
- **Business value**: Super fast for analytics or when you only need numbers
|
|
182
|
-
- **Use case**: "How many completed tasks do we have?" → "47"
|
|
183
|
-
- **Users**: Analysts, automated systems, performance dashboards
|
|
184
|
-
|
|
185
|
-
**Why this architecture matters:**
|
|
186
|
-
- **Performance**: Count queries are much faster than getting full lists when you only need numbers
|
|
187
|
-
- **User Experience**: Different resolvers serve different user needs efficiently
|
|
188
|
-
- **Flexibility**: Users can ask for exactly what they need, nothing more, nothing less
|
|
189
|
-
|
|
190
|
-
</details>
|
|
191
|
-
|
|
192
|
-
**Step 2: Create resolvers in `subgraphs/to-do-list/resolvers.ts`:**
|
|
193
|
-
|
|
194
|
-
```typescript
|
|
195
|
-
// subgraphs/to-do-list/resolvers.ts
|
|
196
|
-
interface SubgraphInstance {
|
|
197
|
-
operationalStore: any;
|
|
96
|
+
type Query {
|
|
97
|
+
searchTodos(driveId: String!, searchTerm: String!): [String!]!
|
|
198
98
|
}
|
|
199
99
|
|
|
200
|
-
|
|
201
|
-
Query: {
|
|
202
|
-
todoList: async () => {
|
|
203
|
-
const items = await subgraphInstance.operationalStore.getAll("todo_items");
|
|
204
|
-
const total = items.length;
|
|
205
|
-
const checked = items.filter((item: any) => item.checked).length;
|
|
206
|
-
const unchecked = total - checked;
|
|
207
|
-
|
|
208
|
-
return {
|
|
209
|
-
total,
|
|
210
|
-
checked,
|
|
211
|
-
unchecked
|
|
212
|
-
};
|
|
213
|
-
},
|
|
214
|
-
|
|
215
|
-
todoItems: async (parent: any, { checked }: any) => {
|
|
216
|
-
let query = subgraphInstance.operationalStore.select("*").from("todo_items");
|
|
217
|
-
|
|
218
|
-
if (checked !== undefined) {
|
|
219
|
-
query = query.where("checked", checked);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const items = await query.orderBy("created_at", "asc");
|
|
223
|
-
return items;
|
|
224
|
-
},
|
|
225
|
-
|
|
226
|
-
todoItemsCount: async (parent: any, { checked }: any) => {
|
|
227
|
-
let query = subgraphInstance.operationalStore.count("* as count").from("todo_items");
|
|
228
|
-
|
|
229
|
-
if (checked !== undefined) {
|
|
230
|
-
query = query.where("checked", checked);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const result = await query.first();
|
|
234
|
-
return result?.count || 0;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
**Step 3: Implement the main class in `subgraphs/to-do-list/index.ts`:**
|
|
100
|
+
`;
|
|
241
101
|
|
|
242
|
-
```typescript
|
|
243
|
-
// subgraphs/to-do-list/index.ts
|
|
244
|
-
import { typeDefs } from './schema.js';
|
|
245
|
-
import { createResolvers } from './resolvers.js';
|
|
246
|
-
|
|
247
|
-
export default class ToDoListSubgraph {
|
|
248
|
-
// Define the API endpoint where this subgraph will be accessible
|
|
249
|
-
// Users can query this at: http://localhost:4001/graphql/to-do-list
|
|
250
|
-
path = '/to-do-list';
|
|
251
|
-
|
|
252
|
-
// GraphQL schema definition (what queries are available)
|
|
253
|
-
typeDefs = typeDefs;
|
|
254
|
-
|
|
255
|
-
// Query handlers (how to fetch the data)
|
|
256
|
-
resolvers: any;
|
|
257
|
-
|
|
258
|
-
// Database interface (injected by Powerhouse framework)
|
|
259
|
-
operationalStore: any;
|
|
260
|
-
|
|
261
|
-
constructor() {
|
|
262
|
-
// Connect the resolvers to this subgraph instance
|
|
263
|
-
// This gives resolvers access to the database through this.operationalStore
|
|
264
|
-
this.resolvers = createResolvers(this);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Called once when the subgraph starts up
|
|
268
|
-
async onSetup() {
|
|
269
|
-
await this.createOperationalTables();
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Create the database tables we need for storing todo items
|
|
273
|
-
async createOperationalTables() {
|
|
274
|
-
await this.operationalStore.schema.createTableIfNotExists(
|
|
275
|
-
"todo_items", // Table name
|
|
276
|
-
(table: any) => {
|
|
277
|
-
table.string("id").primary(); // Unique identifier for each todo item
|
|
278
|
-
table.string("text").notNullable(); // The actual todo task text
|
|
279
|
-
table.boolean("checked").defaultTo(false); // Completion status (unchecked by default)
|
|
280
|
-
table.timestamp("created_at").defaultTo(this.operationalStore.fn.now()); // When item was created
|
|
281
|
-
table.timestamp("updated_at").defaultTo(this.operationalStore.fn.now()); // When item was last modified
|
|
282
|
-
}
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Event processor: Keeps subgraph data synchronized with document model changes
|
|
287
|
-
// When users add/update/delete todos in Connect, this method handles the updates
|
|
288
|
-
async process(event: any) {
|
|
289
|
-
// Handle new todo item creation
|
|
290
|
-
if (event.type === "ADD_TODO_ITEM") {
|
|
291
|
-
await this.operationalStore.insert("todo_items", {
|
|
292
|
-
id: event.input.id,
|
|
293
|
-
text: event.input.text,
|
|
294
|
-
checked: false,
|
|
295
|
-
created_at: new Date(),
|
|
296
|
-
updated_at: new Date()
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
console.log(`Added todo item: ${event.input.text}`);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Handle todo item updates (text changes, checking/unchecking)
|
|
303
|
-
if (event.type === "UPDATE_TODO_ITEM") {
|
|
304
|
-
const updateData: any = {
|
|
305
|
-
updated_at: new Date() // Always update the timestamp
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
// Only update fields that were actually changed
|
|
309
|
-
if (event.input.text !== undefined) {
|
|
310
|
-
updateData.text = event.input.text;
|
|
311
|
-
}
|
|
312
|
-
if (event.input.checked !== undefined) {
|
|
313
|
-
updateData.checked = event.input.checked;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
await this.operationalStore.update("todo_items")
|
|
317
|
-
.where("id", event.input.id)
|
|
318
|
-
.update(updateData);
|
|
319
|
-
|
|
320
|
-
console.log(`Updated todo item: ${event.input.id}`);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Handle todo item deletion
|
|
324
|
-
if (event.type === "DELETE_TODO_ITEM") {
|
|
325
|
-
await this.operationalStore.delete("todo_items")
|
|
326
|
-
.where("id", event.input.id);
|
|
327
|
-
|
|
328
|
-
console.log(`Deleted todo item: ${event.input.id}`);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
102
|
```
|
|
333
103
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
**What this multi-file approach provides:**
|
|
337
|
-
|
|
338
|
-
1. **Schema separation** (`schema.ts`): Clean GraphQL type definitions
|
|
339
|
-
2. **Resolver isolation** (`resolvers.ts`): Business logic separated from structure
|
|
340
|
-
3. **Main orchestration** (`index.ts`): Combines everything and handles lifecycle methods
|
|
341
|
-
|
|
342
|
-
**Key features implemented:**
|
|
343
|
-
- A `todo_items` operational table to store individual to-do items
|
|
344
|
-
- Fields that match our document model structure
|
|
345
|
-
- Timestamps for tracking when items were created and updated
|
|
346
|
-
- Resolvers that fetch and filter todo items from the operational store
|
|
347
|
-
- Event processing to keep the subgraph data synchronized with document model changes
|
|
348
|
-
|
|
349
|
-
### 2.4 Understanding the document model event integration
|
|
350
|
-
|
|
351
|
-
Notice that our `index.ts` file already includes a `process` method - this is the **processor integration** that keeps our subgraph synchronized with To-do List document model events. When users interact with To-do List documents through Connect, this method automatically handles the updates.
|
|
352
|
-
|
|
353
|
-
**How the existing processor integration works:**
|
|
104
|
+
**Step 2: Create resolvers in `subgraphs/search-todos/resolvers.ts`:**
|
|
354
105
|
|
|
355
|
-
The `process` method in our `index.ts` file handles three types of document model events:
|
|
356
|
-
|
|
357
|
-
**1. Adding new todo items:**
|
|
358
|
-
```typescript
|
|
359
|
-
if (event.type === "ADD_TODO_ITEM") {
|
|
360
|
-
await this.operationalStore.insert("todo_items", {
|
|
361
|
-
id: event.input.id,
|
|
362
|
-
text: event.input.text,
|
|
363
|
-
checked: false,
|
|
364
|
-
created_at: new Date(),
|
|
365
|
-
updated_at: new Date()
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
**2. Updating existing items:**
|
|
371
106
|
```typescript
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}
|
|
382
|
-
|
|
107
|
+
// subgraphs/search-todos/resolvers.ts
|
|
108
|
+
import { type Subgraph } from "@powerhousedao/reactor-api";
|
|
109
|
+
import { type ToDoListDocument } from "document-models/to-do-list/index.js";
|
|
110
|
+
|
|
111
|
+
export const getResolvers = (subgraph: Subgraph) => {
|
|
112
|
+
const reactor = subgraph.reactor;
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
Query: {
|
|
116
|
+
searchTodos: async (parent: unknown, args: { driveId: string, searchTerm: string }) => {
|
|
117
|
+
const documents = await reactor.getDocuments(args.driveId);
|
|
118
|
+
const todoItems: string[] = [];
|
|
119
|
+
for (const docId of documents) {
|
|
120
|
+
const doc: ToDoListDocument = await reactor.getDocument(docId);
|
|
121
|
+
if (doc.header.documentType !== "powerhouse/todolist") {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const amountEntries = doc.state.global.items.filter(e => e.text.includes(args.searchTerm)).length;
|
|
126
|
+
if (amountEntries > 0) {
|
|
127
|
+
todoItems.push(docId);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return todoItems;
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
};
|
|
383
135
|
|
|
384
|
-
**3. Deleting items:**
|
|
385
|
-
```typescript
|
|
386
|
-
if (event.type === "DELETE_TODO_ITEM") {
|
|
387
|
-
await this.operationalStore.delete("todo_items")
|
|
388
|
-
.where("id", event.input.id);
|
|
389
|
-
}
|
|
390
136
|
```
|
|
391
137
|
|
|
392
|
-
**The integration happens automatically:**
|
|
393
|
-
1. **User action**: Someone adds a todo item in Connect
|
|
394
|
-
2. **Document model**: Processes the `ADD_TODO_ITEM` operation
|
|
395
|
-
3. **Framework routing**: Powerhouse automatically calls your subgraph's `process` method
|
|
396
|
-
4. **Subgraph response**: Your `process` method updates the operational store
|
|
397
|
-
5. **Query availability**: Users can now query the updated data via GraphQL
|
|
398
|
-
|
|
399
|
-
### 2.5 Summary of what we've built
|
|
400
|
-
|
|
401
|
-
Our complete To-do List subgraph includes:
|
|
402
|
-
|
|
403
|
-
- **GraphQL schema** (`schema.ts`): Defines `todoList`, `todoItems`, and `todoItemsCount` queries
|
|
404
|
-
- **Resolvers** (`resolvers.ts`): Handle data fetching and filtering from the operational store
|
|
405
|
-
- **Main subgraph class** (`index.ts`): Coordinates everything and includes:
|
|
406
|
-
- **Operational table creation**: Sets up the `todo_items` table with proper schema
|
|
407
|
-
- **Event processing**: The `process` method keeps subgraph data synchronized with document model changes
|
|
408
|
-
- **Real-time updates**: Automatically handles `ADD_TODO_ITEM`, `UPDATE_TODO_ITEM`, and `DELETE_TODO_ITEM` events
|
|
409
|
-
|
|
410
|
-
**Key features:**
|
|
411
|
-
- **Filtering capability**: The `todoItems` query accepts an optional `checked` parameter
|
|
412
|
-
- **Performance optimization**: The `todoItemsCount` query returns just numbers when you don't need full data
|
|
413
|
-
- **Real-time synchronization**: Changes in Connect immediately appear in subgraph queries
|
|
414
|
-
- **Complete statistics**: The `todoList` query returns total, checked, and unchecked counts
|
|
415
138
|
|
|
416
139
|
## 3. Testing the to-do list subgraph
|
|
417
140
|
|
|
@@ -421,21 +144,21 @@ To activate the subgraph, run:
|
|
|
421
144
|
```bash
|
|
422
145
|
ph reactor
|
|
423
146
|
```
|
|
424
|
-
Or, for full system startup:
|
|
425
|
-
|
|
426
|
-
```bash title="Start the Reactor & Connect in Studio or Locally"
|
|
427
|
-
ph dev
|
|
428
|
-
```
|
|
429
147
|
|
|
430
148
|
You should see the subgraph being registered in the console output:
|
|
431
149
|
```
|
|
432
|
-
> Registered /graphql/
|
|
150
|
+
> Registered /graphql/search-todos subgraph.
|
|
433
151
|
```
|
|
434
152
|
|
|
435
153
|
### 3.2. Create some test data
|
|
436
154
|
Before testing queries, let's create some To-do List documents with test data:
|
|
437
155
|
|
|
438
|
-
1.
|
|
156
|
+
1. Start Connect
|
|
157
|
+
```bash
|
|
158
|
+
ph connect
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
1. Open Connect at `http://localhost:3000` in the browser
|
|
439
162
|
2. Add the 'remote' drive that is running locally via the (+) 'Add Drive' button. Add 'http://localhost:4001/d/powerhouse'
|
|
440
163
|
3. Create a new To-do List document
|
|
441
164
|
4. Add some test items:
|
|
@@ -447,92 +170,21 @@ Before testing queries, let's create some To-do List documents with test data:
|
|
|
447
170
|
Open your browser and go to:
|
|
448
171
|
|
|
449
172
|
```bash
|
|
450
|
-
http://localhost:4001/graphql
|
|
173
|
+
http://localhost:4001/graphql
|
|
451
174
|
```
|
|
452
175
|
|
|
453
176
|
### 3.4. Test the queries
|
|
454
177
|
|
|
455
|
-
**Query 1:
|
|
178
|
+
**Query 1: Search for Todos **
|
|
456
179
|
```graphql
|
|
457
180
|
query {
|
|
458
|
-
|
|
459
|
-
total
|
|
460
|
-
checked
|
|
461
|
-
unchecked
|
|
462
|
-
}
|
|
181
|
+
searchTodos(driveId: "powerhouse", searchTerm: "Test")
|
|
463
182
|
}
|
|
464
183
|
```
|
|
465
184
|
|
|
466
|
-
|
|
467
|
-
```graphql
|
|
468
|
-
query {
|
|
469
|
-
todoItems {
|
|
470
|
-
id
|
|
471
|
-
text
|
|
472
|
-
checked
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
```
|
|
185
|
+
You should get a list of the document Ids which contain the search term "Test".
|
|
476
186
|
|
|
477
|
-
|
|
478
|
-
```graphql
|
|
479
|
-
query {
|
|
480
|
-
todoItems(checked: false) {
|
|
481
|
-
id
|
|
482
|
-
text
|
|
483
|
-
checked
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
**Query 4: Get count of completed items**
|
|
489
|
-
```graphql
|
|
490
|
-
query {
|
|
491
|
-
todoItemsCount(checked: true)
|
|
492
|
-
}
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
### 3.5. Expected responses
|
|
496
|
-
|
|
497
|
-
**For the statistics query:**
|
|
498
|
-
```json
|
|
499
|
-
{
|
|
500
|
-
"data": {
|
|
501
|
-
"todoList": {
|
|
502
|
-
"total": 3,
|
|
503
|
-
"checked": 1,
|
|
504
|
-
"unchecked": 2
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
```
|
|
509
|
-
|
|
510
|
-
**For the items query:**
|
|
511
|
-
```json
|
|
512
|
-
{
|
|
513
|
-
"data": {
|
|
514
|
-
"todoItems": [
|
|
515
|
-
{
|
|
516
|
-
"id": "item-1",
|
|
517
|
-
"text": "Learn about subgraphs",
|
|
518
|
-
"checked": false
|
|
519
|
-
},
|
|
520
|
-
{
|
|
521
|
-
"id": "item-2",
|
|
522
|
-
"text": "Build a To-do List subgraph",
|
|
523
|
-
"checked": true
|
|
524
|
-
},
|
|
525
|
-
{
|
|
526
|
-
"id": "item-3",
|
|
527
|
-
"text": "Test the subgraph",
|
|
528
|
-
"checked": false
|
|
529
|
-
}
|
|
530
|
-
]
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
```
|
|
534
|
-
|
|
535
|
-
### 3.6. Test real-time updates
|
|
187
|
+
### 3.5. Test real-time updates
|
|
536
188
|
|
|
537
189
|
To verify that your subgraph stays synchronized with document changes:
|
|
538
190
|
|
|
@@ -544,9 +196,10 @@ To verify that your subgraph stays synchronized with document changes:
|
|
|
544
196
|
|
|
545
197
|
This demonstrates the real-time synchronization between the document model and the subgraph through event processing.
|
|
546
198
|
|
|
547
|
-
## 4. Working with the supergraph or gateway
|
|
548
199
|
|
|
549
|
-
|
|
200
|
+
## 4. Working with the GraphQL Gateway
|
|
201
|
+
|
|
202
|
+
The GraphQL Gateway 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.
|
|
550
203
|
|
|
551
204
|
### 4.1 Key concepts
|
|
552
205
|
|
|
@@ -181,10 +181,10 @@ Strands represent a batch of operations that happened to documents. Each strand
|
|
|
181
181
|
import { type IRelationalDb } from "document-drive/processors/types";
|
|
182
182
|
import { RelationalDbProcessor } from "document-drive/processors/relational";
|
|
183
183
|
import { type InternalTransmitterUpdate } from "document-drive/server/listener/transmitter/internal";
|
|
184
|
-
import type { ToDoListDocument } from "
|
|
184
|
+
import type { ToDoListDocument } from "../document-models/to-do-list/index.js";
|
|
185
185
|
|
|
186
|
-
import { up } from "./migrations.js";
|
|
187
|
-
import { type DB } from "./schema.js";
|
|
186
|
+
import { up } from "./todo-indexer/migrations.js";
|
|
187
|
+
import { type DB } from "./todo-indexer/schema.js";
|
|
188
188
|
|
|
189
189
|
// Define the document type this processor handles
|
|
190
190
|
type DocumentType = ToDoListDocument;
|
|
@@ -268,19 +268,41 @@ A subgraph is a GraphQL schema that exposes your processed data to clients. It:
|
|
|
268
268
|
|
|
269
269
|
### Configure the Subgraph
|
|
270
270
|
|
|
271
|
-
Open `./subgraphs/todo/
|
|
271
|
+
Open `./subgraphs/todo/schema.ts` and configure the schema:
|
|
272
272
|
|
|
273
273
|
```ts
|
|
274
|
-
import { Subgraph } from "@powerhousedao/reactor-api";
|
|
275
274
|
import { gql } from "graphql-tag";
|
|
275
|
+
import type { DocumentNode } from "graphql";
|
|
276
|
+
|
|
277
|
+
export const schema: DocumentNode = gql`
|
|
278
|
+
|
|
279
|
+
# Define the structure of a todo item as returned by GraphQL
|
|
280
|
+
type ToDoListEntry {
|
|
281
|
+
task: String! # The task description (! means required/non-null)
|
|
282
|
+
status: Boolean! # The completion status (true = done, false = pending)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
# Define available queries
|
|
286
|
+
type Query {
|
|
287
|
+
todos(driveId: ID!): [ToDoListEntry] # Get array of todos for a specific drive
|
|
288
|
+
}
|
|
289
|
+
`;
|
|
290
|
+
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Open `./subgraphs/todo/resolvers.ts` and configure the resolvers:
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
// subgraphs/search-todos/resolvers.ts
|
|
297
|
+
import { type Subgraph } from "@powerhousedao/reactor-api";
|
|
298
|
+
import { type ToDoListDocument } from "document-models/to-do-list/index.js";
|
|
276
299
|
import { TodoIndexerProcessor } from "../../processors/todo-indexer/index.js";
|
|
277
300
|
|
|
278
|
-
export
|
|
279
|
-
|
|
280
|
-
|
|
301
|
+
export const getResolvers = (subgraph: Subgraph) => {
|
|
302
|
+
const reactor = subgraph.reactor;
|
|
303
|
+
const relationalDb = subgraph.relationalDb;
|
|
281
304
|
|
|
282
|
-
|
|
283
|
-
resolvers = {
|
|
305
|
+
return {
|
|
284
306
|
Query: {
|
|
285
307
|
todos: {
|
|
286
308
|
// Resolver function for the "todos" query
|
|
@@ -288,7 +310,7 @@ export class TodoSubgraph extends Subgraph {
|
|
|
288
310
|
resolve: async (_: any, args: {driveId: string}) => {
|
|
289
311
|
// Query the database using the processor's static query method
|
|
290
312
|
// This gives us access to the namespaced database for the specific drive
|
|
291
|
-
const todos = await TodoIndexerProcessor.query(args.driveId,
|
|
313
|
+
const todos = await TodoIndexerProcessor.query(args.driveId, relationalDb)
|
|
292
314
|
.selectFrom("todo") // Select from the "todo" table
|
|
293
315
|
.selectAll() // Get all columns
|
|
294
316
|
.execute(); // Execute the query
|
|
@@ -302,29 +324,10 @@ export class TodoSubgraph extends Subgraph {
|
|
|
302
324
|
},
|
|
303
325
|
},
|
|
304
326
|
};
|
|
305
|
-
|
|
306
|
-
// GraphQL schema definition using GraphQL Schema Definition Language (SDL)
|
|
307
|
-
typeDefs = gql`
|
|
308
|
-
|
|
309
|
-
# Define the structure of a todo item as returned by GraphQL
|
|
310
|
-
type ToDoListEntry {
|
|
311
|
-
task: String! # The task description (! means required/non-null)
|
|
312
|
-
status: Boolean! # The completion status (true = done, false = pending)
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
# Define available queries
|
|
316
|
-
type Query {
|
|
317
|
-
todos(driveId: ID!): [ToDoListEntry] # Get array of todos for a specific drive
|
|
318
|
-
}
|
|
319
|
-
`;
|
|
320
|
-
|
|
321
|
-
// Cleanup method called when the subgraph disconnects
|
|
322
|
-
async onDisconnect() {
|
|
323
|
-
// Add any cleanup logic here if needed
|
|
324
|
-
}
|
|
325
|
-
}
|
|
327
|
+
};
|
|
326
328
|
```
|
|
327
329
|
|
|
330
|
+
|
|
328
331
|
## Now query the data via the supergraph.
|
|
329
332
|
|
|
330
333
|
**Understanding the Supergraph**
|
|
@@ -344,7 +347,7 @@ The Powerhouse supergraph for any given remote drive or reactor can be found und
|
|
|
344
347
|
ph reactor
|
|
345
348
|
```
|
|
346
349
|
|
|
347
|
-
-
|
|
350
|
+
- This will return an endpoint, but you'll need to change the url of the endpoint to the following URL:
|
|
348
351
|
|
|
349
352
|
```
|
|
350
353
|
http://localhost:4001/graphql
|