@powerhousedao/academy 4.1.0-staging.1 → 5.0.0-staging.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/.vscode/settings.json +1 -1
- package/CHANGELOG.md +127 -1
- package/README.md +3 -3
- package/babel.config.js +1 -1
- package/blog/BeyondCommunication-ABlueprintForDevelopment.md +25 -24
- package/blog/TheChallengeOfChange.md +21 -21
- package/docs/academy/01-GetStarted/00-ExploreDemoPackage.mdx +61 -24
- package/docs/academy/01-GetStarted/01-CreateNewPowerhouseProject.md +21 -12
- package/docs/academy/01-GetStarted/02-DefineToDoListDocumentModel.md +24 -19
- package/docs/academy/01-GetStarted/03-ImplementOperationReducers.md +44 -41
- package/docs/academy/01-GetStarted/04-BuildToDoListEditor.md +10 -10
- package/docs/academy/01-GetStarted/05-SpecDrivenAI.md +143 -0
- package/docs/academy/01-GetStarted/home.mdx +185 -90
- package/docs/academy/01-GetStarted/styles.module.css +5 -5
- package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/01-Prerequisites.md +46 -18
- package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/02-StandardDocumentModelWorkflow.md +118 -68
- package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/03-BuilderTools.md +75 -33
- package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/_category_.json +6 -6
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/01-WhatIsADocumentModel.md +30 -21
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/02-SpecifyTheStateSchema.md +41 -37
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/03-SpecifyDocumentOperations.md +29 -25
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/04-UseTheDocumentModelGenerator.md +36 -37
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/05-ImplementDocumentReducers.md +128 -109
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/06-ImplementDocumentModelTests.md +95 -86
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/07-ExampleToDoListRepository.md +7 -9
- package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/_category_.json +6 -6
- package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/01-BuildingDocumentEditors.md +65 -47
- package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/02-ConfiguringDrives.md +77 -62
- package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/03-BuildingADriveExplorer.md +360 -349
- package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/00-DocumentToolbar.mdx +16 -10
- package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/01-OperationHistory.md +10 -7
- package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/02-RevisionHistoryTimeline.md +26 -11
- package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/_category_.json +6 -6
- package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/07-Authorization/01-RenownAuthenticationFlow.md +14 -7
- package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/07-Authorization/02-Authorization.md +0 -1
- package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/07-Authorization/_category_.json +5 -5
- package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/_category_.json +1 -1
- package/docs/academy/02-MasteryTrack/04-WorkWithData/01-GraphQLAtPowerhouse.md +45 -33
- package/docs/academy/02-MasteryTrack/04-WorkWithData/02-UsingTheAPI.mdx +61 -18
- package/docs/academy/02-MasteryTrack/04-WorkWithData/03-UsingSubgraphs.md +50 -54
- package/docs/academy/02-MasteryTrack/04-WorkWithData/04-analytics-processor.md +126 -110
- package/docs/academy/02-MasteryTrack/04-WorkWithData/05-RelationalDbProcessor.md +75 -45
- package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/GraphQL References/QueryingADocumentWithGraphQL.md +23 -21
- package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/best-practices.md +9 -9
- package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/graphql/index.md +11 -23
- package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/graphql/integration.md +25 -9
- package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/intro.md +10 -10
- package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/typescript/benchmarks.md +1 -1
- package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/typescript/index.md +16 -11
- package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/typescript/memory.md +6 -5
- package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/typescript/schema.md +2 -2
- package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/typescript/utilities.md +7 -5
- package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/use-cases/maker.md +32 -58
- package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/use-cases/processors.md +1 -1
- package/docs/academy/02-MasteryTrack/04-WorkWithData/07-drive-analytics.md +105 -71
- package/docs/academy/02-MasteryTrack/04-WorkWithData/_ARCHIVE-AnalyticsProcessorTutorial/_01-SetupBuilderEnvironment.md +22 -0
- package/docs/academy/02-MasteryTrack/04-WorkWithData/_ARCHIVE-AnalyticsProcessorTutorial/_02-CreateNewPowerhouseProject.md +9 -8
- package/docs/academy/02-MasteryTrack/04-WorkWithData/_ARCHIVE-AnalyticsProcessorTutorial/_03-GenerateAnAnalyticsProcessor.md +28 -32
- package/docs/academy/02-MasteryTrack/04-WorkWithData/_ARCHIVE-AnalyticsProcessorTutorial/_04-UpdateAnalyticsProcessor.md +25 -26
- package/docs/academy/02-MasteryTrack/04-WorkWithData/_ARCHIVE-AnalyticsProcessorTutorial/_category_.json +1 -1
- package/docs/academy/02-MasteryTrack/04-WorkWithData/_category_.json +7 -7
- package/docs/academy/02-MasteryTrack/05-Launch/01-IntroductionToPackages.md +3 -4
- package/docs/academy/02-MasteryTrack/05-Launch/02-PublishYourProject.md +69 -45
- package/docs/academy/02-MasteryTrack/05-Launch/03-SetupEnvironment.md +70 -40
- package/docs/academy/02-MasteryTrack/05-Launch/04-ConfigureEnvironment.md +1 -0
- package/docs/academy/02-MasteryTrack/05-Launch/_category_.json +7 -7
- package/docs/academy/02-MasteryTrack/_category_.json +6 -6
- package/docs/academy/03-ExampleUsecases/Chatroom/02-CreateNewPowerhouseProject.md +5 -3
- package/docs/academy/03-ExampleUsecases/Chatroom/03-DefineChatroomDocumentModel.md +38 -37
- package/docs/academy/03-ExampleUsecases/Chatroom/04-ImplementOperationReducers.md +45 -41
- package/docs/academy/03-ExampleUsecases/Chatroom/05-ImplementChatroomEditor.md +14 -14
- package/docs/academy/03-ExampleUsecases/Chatroom/06-LaunchALocalReactor.md +6 -6
- package/docs/academy/03-ExampleUsecases/Chatroom/_category_.json +1 -1
- package/docs/academy/04-APIReferences/00-PowerhouseCLI.md +9 -7
- package/docs/academy/04-APIReferences/01-ReactHooks.md +177 -129
- package/docs/academy/04-APIReferences/04-RelationalDatabase.md +121 -113
- package/docs/academy/04-APIReferences/05-PHDocumentMigrationGuide.md +48 -41
- package/docs/academy/04-APIReferences/_category_.json +6 -6
- package/docs/academy/05-Architecture/00-PowerhouseArchitecture.md +1 -2
- package/docs/academy/05-Architecture/01-WorkingWithTheReactor.md +11 -8
- package/docs/academy/05-Architecture/05-DocumentModelTheory/_category_.json +1 -1
- package/docs/academy/05-Architecture/_category_.json +6 -6
- package/docs/academy/06-ComponentLibrary/00-DocumentEngineering.md +25 -23
- package/docs/academy/06-ComponentLibrary/02-CreateCustomScalars.md +105 -93
- package/docs/academy/06-ComponentLibrary/03-IntegrateIntoAReactComponent.md +1 -0
- package/docs/academy/06-ComponentLibrary/_category_.json +7 -7
- package/docs/academy/07-Cookbook.md +267 -34
- package/docs/academy/08-Glossary.md +7 -1
- package/docs/bookofpowerhouse/01-Overview.md +2 -2
- package/docs/bookofpowerhouse/02-GeneralFrameworkAndPhilosophy.md +1 -7
- package/docs/bookofpowerhouse/03-PowerhouseSoftwareArchitecture.md +10 -7
- package/docs/bookofpowerhouse/04-DevelopmentApproaches.md +10 -4
- package/docs/bookofpowerhouse/05-SNOsandANewModelForOSSandPublicGoods.md +23 -30
- package/docs/bookofpowerhouse/06-SNOsInActionAndPlatformEconomies.md +0 -7
- package/docusaurus.config.ts +64 -66
- package/package.json +1 -1
- package/scripts/generate-combined-cli-docs.ts +43 -13
- package/sidebars.ts +1 -0
- package/src/components/HomepageFeatures/index.tsx +171 -78
- package/src/components/HomepageFeatures/styles.module.css +1 -2
- package/src/css/custom.css +89 -89
- package/src/pages/_archive-homepage.tsx +17 -16
- package/src/theme/DocCardList/index.tsx +9 -8
- package/static.json +6 -6
|
@@ -6,6 +6,7 @@ Let's start with the basics and gradually add more complex features and function
|
|
|
6
6
|
## What is a subgraph?
|
|
7
7
|
|
|
8
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
|
+
|
|
9
10
|
1. Connect to external APIs or databases
|
|
10
11
|
2. Add custom queries and mutations
|
|
11
12
|
3. Automate interactions between different document models
|
|
@@ -13,8 +14,8 @@ A subgraph in Powerhouse is a **GraphQL-based modular data component** that exte
|
|
|
13
14
|
|
|
14
15
|
### Subgraphs can retrieve data from
|
|
15
16
|
|
|
16
|
-
- **The Reactor** – The core Powerhouse data system or network node.
|
|
17
|
-
- **Relational Data Stores** – Structured data storage for operational processes, offering real-time updates, for querying structured data.
|
|
17
|
+
- **The Reactor** – The core Powerhouse data system or network node.
|
|
18
|
+
- **Relational Data Stores** – Structured data storage for operational processes, offering real-time updates, for querying structured data.
|
|
18
19
|
- **Analytics Stores** – Aggregated historical data, useful for insights, reporting and business intelligence.
|
|
19
20
|
|
|
20
21
|
### Subgraphs consist of
|
|
@@ -24,22 +25,21 @@ A subgraph in Powerhouse is a **GraphQL-based modular data component** that exte
|
|
|
24
25
|
- **Context Fields** — Additional metadata that helps in resolving data efficiently.
|
|
25
26
|
|
|
26
27
|
#### Additionally, context fields allow resolvers to access extra information, such as:
|
|
28
|
+
|
|
27
29
|
- **User authentication** (e.g., checking if a user is an admin).
|
|
28
30
|
- **External data sources** (e.g., analytics).
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
32
|
## Example: Implement a search subgraph based on data from the reactor
|
|
33
33
|
|
|
34
|
-
In this example we implement a subgraph which allows to search through todo-list documents in a specific document drive.
|
|
34
|
+
In this example we implement a subgraph which allows to search through todo-list documents in a specific document drive.
|
|
35
35
|
|
|
36
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
37
|
|
|
38
38
|
### 1. Generate the subgraph
|
|
39
39
|
|
|
40
|
-
Let's start by generating a new subgraph. For our tutorial we will create a new subgraph within our To-do List project.
|
|
40
|
+
Let's start by generating a new subgraph. For our tutorial we will create a new subgraph within our To-do List project.
|
|
41
41
|
Open your project and start your terminal.
|
|
42
|
-
The Powerhouse toolkit provides a command-line utility to create new subgraphs easily.
|
|
42
|
+
The Powerhouse toolkit provides a command-line utility to create new subgraphs easily.
|
|
43
43
|
|
|
44
44
|
```bash title="Run the following command to generate a new subgraph"
|
|
45
45
|
ph generate --subgraph search-todos
|
|
@@ -57,24 +57,26 @@ Loaded templates: /projects/powerhouse/powerhouse/packages/codegen/dist/src/code
|
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
### What happened?
|
|
60
|
+
|
|
60
61
|
1. A new subgraph was created in `./subgraphs/search-todos/`
|
|
61
62
|
2. The subgraph was automatically registered in your project's registry
|
|
62
63
|
3. Basic boilerplate code was generated with an example query
|
|
63
64
|
|
|
64
65
|
If we now run `ph reactor` we will see the new subgraph being registered during the startup of the Reactor.
|
|
65
|
-
|
|
66
|
+
|
|
67
|
+
> Registered /graphql/search-todos subgraph.
|
|
66
68
|
|
|
67
69
|
```
|
|
68
70
|
Initializing Subgraph Manager...
|
|
69
|
-
> Registered /graphql/auth subgraph.
|
|
71
|
+
> Registered /graphql/auth subgraph.
|
|
70
72
|
> Registered /graphql/system subgraph.
|
|
71
73
|
> Registered /graphql/analytics subgraph.
|
|
72
74
|
> Registered /d/:drive subgraph.
|
|
73
75
|
> Updating router
|
|
74
|
-
> Registered /graphql supergraph
|
|
75
|
-
> Registered /graphql/search-todos subgraph.
|
|
76
|
+
> Registered /graphql supergraph
|
|
77
|
+
> Registered /graphql/search-todos subgraph.
|
|
76
78
|
> Updating router
|
|
77
|
-
> Registered /graphql supergraph
|
|
79
|
+
> Registered /graphql supergraph
|
|
78
80
|
➜ Reactor: http://localhost:4001/d/powerhouse
|
|
79
81
|
```
|
|
80
82
|
|
|
@@ -89,16 +91,13 @@ import { gql } from "graphql-tag";
|
|
|
89
91
|
import type { DocumentNode } from "graphql";
|
|
90
92
|
|
|
91
93
|
export const schema: DocumentNode = gql`
|
|
92
|
-
"""
|
|
93
|
-
Subgraph definition
|
|
94
|
-
"""
|
|
95
|
-
|
|
96
|
-
type Query {
|
|
94
|
+
"""
|
|
95
|
+
Subgraph definition
|
|
96
|
+
"""
|
|
97
|
+
type Query {
|
|
97
98
|
searchTodos(driveId: String!, searchTerm: String!): [String!]!
|
|
98
|
-
}
|
|
99
|
-
|
|
99
|
+
}
|
|
100
100
|
`;
|
|
101
|
-
|
|
102
101
|
```
|
|
103
102
|
|
|
104
103
|
**Step 2: Create resolvers in `subgraphs/search-todos/resolvers.ts`:**
|
|
@@ -113,7 +112,10 @@ export const getResolvers = (subgraph: Subgraph) => {
|
|
|
113
112
|
|
|
114
113
|
return {
|
|
115
114
|
Query: {
|
|
116
|
-
searchTodos: async (
|
|
115
|
+
searchTodos: async (
|
|
116
|
+
parent: unknown,
|
|
117
|
+
args: { driveId: string; searchTerm: string },
|
|
118
|
+
) => {
|
|
117
119
|
const documents = await reactor.getDocuments(args.driveId);
|
|
118
120
|
const todoItems: string[] = [];
|
|
119
121
|
for (const docId of documents) {
|
|
@@ -122,7 +124,9 @@ export const getResolvers = (subgraph: Subgraph) => {
|
|
|
122
124
|
continue;
|
|
123
125
|
}
|
|
124
126
|
|
|
125
|
-
const amountEntries = doc.state.global.items.filter(e =>
|
|
127
|
+
const amountEntries = doc.state.global.items.filter((e) =>
|
|
128
|
+
e.text.includes(args.searchTerm),
|
|
129
|
+
).length;
|
|
126
130
|
if (amountEntries > 0) {
|
|
127
131
|
todoItems.push(docId);
|
|
128
132
|
}
|
|
@@ -132,13 +136,12 @@ export const getResolvers = (subgraph: Subgraph) => {
|
|
|
132
136
|
},
|
|
133
137
|
};
|
|
134
138
|
};
|
|
135
|
-
|
|
136
139
|
```
|
|
137
140
|
|
|
138
|
-
|
|
139
141
|
## 3. Testing the to-do list subgraph
|
|
140
142
|
|
|
141
143
|
### 3.1. Start the reactor
|
|
144
|
+
|
|
142
145
|
To activate the subgraph, run:
|
|
143
146
|
|
|
144
147
|
```bash
|
|
@@ -146,14 +149,17 @@ ph reactor
|
|
|
146
149
|
```
|
|
147
150
|
|
|
148
151
|
You should see the subgraph being registered in the console output:
|
|
152
|
+
|
|
149
153
|
```
|
|
150
154
|
> Registered /graphql/search-todos subgraph.
|
|
151
155
|
```
|
|
152
156
|
|
|
153
157
|
### 3.2. Create some test data
|
|
158
|
+
|
|
154
159
|
Before testing queries, let's create some To-do List documents with test data:
|
|
155
160
|
|
|
156
|
-
1. Start Connect
|
|
161
|
+
1. Start Connect
|
|
162
|
+
|
|
157
163
|
```bash
|
|
158
164
|
ph connect
|
|
159
165
|
```
|
|
@@ -167,6 +173,7 @@ ph connect
|
|
|
167
173
|
- "Test the subgraph" (leave unchecked)
|
|
168
174
|
|
|
169
175
|
### 3.3. Access GraphQL playground
|
|
176
|
+
|
|
170
177
|
Open your browser and go to:
|
|
171
178
|
|
|
172
179
|
```bash
|
|
@@ -176,6 +183,7 @@ http://localhost:4001/graphql
|
|
|
176
183
|
### 3.4. Test the queries
|
|
177
184
|
|
|
178
185
|
**Query 1: Search for Todos **
|
|
186
|
+
|
|
179
187
|
```graphql
|
|
180
188
|
query {
|
|
181
189
|
searchTodos(driveId: "powerhouse", searchTerm: "Test")
|
|
@@ -196,28 +204,26 @@ To verify that your subgraph stays synchronized with document changes:
|
|
|
196
204
|
|
|
197
205
|
This demonstrates the real-time synchronization between the document model and the subgraph through event processing.
|
|
198
206
|
|
|
199
|
-
|
|
200
207
|
## 4. Working with the GraphQL Gateway
|
|
201
208
|
|
|
202
209
|
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.
|
|
203
210
|
|
|
204
211
|
### 4.1 Key concepts
|
|
205
212
|
|
|
206
|
-
|
|
207
|
-
|
|
213
|
+
- **Subgraph:** An independent GraphQL service with its own schema. Each subgraph typically represents a specific domain or microservice within a larger system.
|
|
214
|
+
- **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.
|
|
208
215
|
|
|
209
216
|
### 4.2 Benefits of using a supergraph
|
|
210
217
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
218
|
+
- **Federated Architecture:** Enables a microservices-based approach where different teams can own and operate their services independently.
|
|
219
|
+
- **Scalability:** Individual subgraphs can be scaled independently based on their specific needs.
|
|
220
|
+
- **Improved Developer Experience:** Clients interact with a single, consistent GraphQL API, simplifying data fetching and reducing the need to manage multiple endpoints.
|
|
221
|
+
- **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.
|
|
222
|
+
- **Clear Separation of Concerns:** Each subgraph focuses on a specific domain, leading to more maintainable and understandable codebases.
|
|
217
223
|
|
|
218
224
|
### 4.3 Use the Powerhouse supergraph
|
|
219
225
|
|
|
220
|
-
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 access the endpoint, start the reactor and navigate to the URL with `graphql` appended. The following commands explain how you can test & try the supergraph.
|
|
226
|
+
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 access the endpoint, start the reactor and navigate to the URL with `graphql` appended. The following commands explain how you can test & try the supergraph.
|
|
221
227
|
|
|
222
228
|
- Start the reactor:
|
|
223
229
|
|
|
@@ -231,18 +237,16 @@ The Powerhouse supergraph for any given remote drive or reactor can be found und
|
|
|
231
237
|
http://localhost:4001/graphql
|
|
232
238
|
```
|
|
233
239
|
|
|
234
|
-
The supergraph allows you to both query & mutate data from the same endpoint.
|
|
240
|
+
The supergraph allows you to both query & mutate data from the same endpoint.
|
|
235
241
|
|
|
236
242
|
**Example: Using the supergraph with To-do List documents**
|
|
237
243
|
|
|
238
244
|
1. Create a todo document in the `powerhouse` drive using the `ToDoList_createDocument` mutation:
|
|
245
|
+
|
|
239
246
|
```graphql
|
|
240
247
|
mutation {
|
|
241
248
|
ToDoList_createDocument(
|
|
242
|
-
input: {
|
|
243
|
-
documentId: "my-todo-list"
|
|
244
|
-
name: "My Test To-do List"
|
|
245
|
-
}
|
|
249
|
+
input: { documentId: "my-todo-list", name: "My Test To-do List" }
|
|
246
250
|
) {
|
|
247
251
|
id
|
|
248
252
|
name
|
|
@@ -251,19 +255,18 @@ The supergraph allows you to both query & mutate data from the same endpoint.
|
|
|
251
255
|
```
|
|
252
256
|
|
|
253
257
|
2. Add some items to your to-do list using the `ToDoList_addTodoItem` mutation:
|
|
258
|
+
|
|
254
259
|
```graphql
|
|
255
260
|
mutation {
|
|
256
261
|
ToDoList_addTodoItem(
|
|
257
262
|
docId: "my-todo-list"
|
|
258
|
-
input: {
|
|
259
|
-
id: "item-1"
|
|
260
|
-
text: "Learn about supergraphs"
|
|
261
|
-
}
|
|
263
|
+
input: { id: "item-1", text: "Learn about supergraphs" }
|
|
262
264
|
)
|
|
263
265
|
}
|
|
264
266
|
```
|
|
265
267
|
|
|
266
268
|
3. Query the document state using the `GetDocument` query:
|
|
269
|
+
|
|
267
270
|
```graphql
|
|
268
271
|
query {
|
|
269
272
|
ToDoList {
|
|
@@ -303,13 +306,14 @@ The supergraph allows you to both query & mutate data from the same endpoint.
|
|
|
303
306
|
}
|
|
304
307
|
```
|
|
305
308
|
|
|
306
|
-
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.
|
|
309
|
+
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.
|
|
307
310
|
|
|
308
311
|
## 5. Summary
|
|
309
312
|
|
|
310
313
|
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:
|
|
311
314
|
|
|
312
315
|
### Key concepts learned:
|
|
316
|
+
|
|
313
317
|
- **Subgraphs extend document models** with additional querying and data processing capabilities
|
|
314
318
|
- **Operational data stores** provide efficient storage for subgraph data
|
|
315
319
|
- **Event processing** enables real-time synchronization between document models and subgraphs
|
|
@@ -323,7 +327,7 @@ This tutorial has provided you with a solid foundation for building sophisticate
|
|
|
323
327
|
- When an invoice-related task is marked complete, update the invoice status
|
|
324
328
|
- When an invoice is paid, automatically check off related tasks
|
|
325
329
|
|
|
326
|
-
2. **External Integrations**:
|
|
330
|
+
2. **External Integrations**:
|
|
327
331
|
- Sync tasks with external project management tools
|
|
328
332
|
- Connect with notification systems
|
|
329
333
|
- Integrate with analytics platforms
|
|
@@ -333,14 +337,6 @@ This tutorial has provided you with a solid foundation for building sophisticate
|
|
|
333
337
|
- Add automated task assignments
|
|
334
338
|
- Create custom reporting functionality
|
|
335
339
|
|
|
336
|
-
|
|
337
340
|
### Future enhancements
|
|
338
341
|
|
|
339
342
|
Bridge Processors and Subgraphs — Currently, there's a gap in how processors and subgraphs interact. Powerhouse might improve this in future updates.
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
@@ -152,9 +152,7 @@ const dimensions = {
|
|
|
152
152
|
asset: AnalyticsPath.fromString(
|
|
153
153
|
`sky/rwas/assets/t-bills/${fixedIncomeTransaction.assetId}`,
|
|
154
154
|
),
|
|
155
|
-
portfolio: AnalyticsPath.fromString(
|
|
156
|
-
`sky/rwas/portfolios/${documentId}`,
|
|
157
|
-
),
|
|
155
|
+
portfolio: AnalyticsPath.fromString(`sky/rwas/portfolios/${documentId}`),
|
|
158
156
|
};
|
|
159
157
|
|
|
160
158
|
// create the series values
|
|
@@ -202,9 +200,7 @@ With variables:
|
|
|
202
200
|
"granularity": "annual",
|
|
203
201
|
"start": "2024-01-01",
|
|
204
202
|
"end": "2025-01-01",
|
|
205
|
-
"metrics": [
|
|
206
|
-
"AssetBalance",
|
|
207
|
-
],
|
|
203
|
+
"metrics": ["AssetBalance"],
|
|
208
204
|
"dimensions": [
|
|
209
205
|
{
|
|
210
206
|
"name": "asset",
|
|
@@ -220,14 +216,17 @@ With variables:
|
|
|
220
216
|
|
|
221
217
|
The RWA processor example pulls information from operation _inputs_ to insert analytics data. Another use case might be to capture meta-analytics from the states themselves.
|
|
222
218
|
|
|
223
|
-
This processor listens to all documents of type `powerhouse/document-drive`, and since the `document-drive` is itself implemented on top of the document model core systems, this means that we can process all virtual file system operations.
|
|
219
|
+
This processor listens to all documents of type `powerhouse/document-drive`, and since the `document-drive` is itself implemented on top of the document model core systems, this means that we can process all virtual file system operations. This processor count basic usage metrics using document operations and states.
|
|
224
220
|
|
|
225
221
|
```typescript
|
|
226
222
|
import { IAnalyticsStore } from "@powerhousedao/reactor-api";
|
|
227
223
|
import { AnalyticsPath } from "@powerhousedao/reactor-api";
|
|
228
224
|
import { AnalyticsSeriesInput } from "@powerhousedao/reactor-api";
|
|
229
225
|
import { InternalTransmitterUpdate, IProcessor } from "document-drive";
|
|
230
|
-
import {
|
|
226
|
+
import {
|
|
227
|
+
AddFileInput,
|
|
228
|
+
DeleteNodeInput,
|
|
229
|
+
} from "document-drive/drive-document-model/gen/types";
|
|
231
230
|
import { PHDocument } from "document-model";
|
|
232
231
|
import { DateTime } from "luxon";
|
|
233
232
|
|
|
@@ -247,91 +246,103 @@ export class DriveProcessorProcessor implements IProcessor {
|
|
|
247
246
|
constructor(private readonly analyticsStore: IAnalyticsStore) {
|
|
248
247
|
//
|
|
249
248
|
}
|
|
250
|
-
|
|
249
|
+
|
|
251
250
|
async onStrands<TDocument extends PHDocument>(
|
|
252
|
-
strands: InternalTransmitterUpdate<TDocument>[]
|
|
251
|
+
strands: InternalTransmitterUpdate<TDocument>[],
|
|
253
252
|
): Promise<void> {
|
|
254
253
|
if (strands.length === 0) {
|
|
255
254
|
return;
|
|
256
255
|
}
|
|
257
256
|
|
|
258
|
-
const values:AnalyticsSeriesInput[] = [];
|
|
257
|
+
const values: AnalyticsSeriesInput[] = [];
|
|
259
258
|
|
|
260
259
|
for (const strand of strands) {
|
|
261
260
|
const operations = strand.operations;
|
|
262
|
-
await Promise.all(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
// lookup node in state
|
|
279
|
-
const input = operation.input as AddFileInput;
|
|
280
|
-
const node = findNode(strand.state, input.id);
|
|
281
|
-
if (!node) {
|
|
282
|
-
return Promise.resolve();
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
dimensions["kind"] = AnalyticsPath.fromString(`document/kind/${node.kind}`);
|
|
286
|
-
|
|
287
|
-
// increment by adding a 1
|
|
288
|
-
values.push({
|
|
289
|
-
source,
|
|
290
|
-
start,
|
|
291
|
-
value: 1,
|
|
292
|
-
metric: "Count",
|
|
293
|
-
dimensions,
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
break;
|
|
297
|
-
}
|
|
298
|
-
case "ADD_FOLDER": {
|
|
299
|
-
dimensions["kind"] = AnalyticsPath.fromString("document/kind/folder");
|
|
300
|
-
|
|
301
|
-
// increment by adding a 1
|
|
302
|
-
values.push({
|
|
303
|
-
source,
|
|
304
|
-
start,
|
|
305
|
-
value: 1,
|
|
306
|
-
metric: "Count",
|
|
307
|
-
dimensions,
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
break;
|
|
261
|
+
await Promise.all(
|
|
262
|
+
operations.map((operation) => {
|
|
263
|
+
const source = AnalyticsPath.fromString(
|
|
264
|
+
`switchboard/default/${strand.driveId}`,
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
const start = DateTime.fromISO(operation.timestamp);
|
|
268
|
+
const dimensions: any = {
|
|
269
|
+
documentType: AnalyticsPath.fromString(
|
|
270
|
+
`document/type/powerhouse/document-drive`,
|
|
271
|
+
),
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
if (operation.index === 0) {
|
|
275
|
+
this.analyticsStore.clearSeriesBySource(source);
|
|
311
276
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
277
|
+
|
|
278
|
+
switch (operation.type) {
|
|
279
|
+
case "ADD_FILE": {
|
|
280
|
+
// count documents of each type (ADD_FILE, input.documentType)
|
|
281
|
+
|
|
282
|
+
// lookup node in state
|
|
283
|
+
const input = operation.input as AddFileInput;
|
|
284
|
+
const node = findNode(strand.state, input.id);
|
|
285
|
+
if (!node) {
|
|
286
|
+
return Promise.resolve();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
dimensions["kind"] = AnalyticsPath.fromString(
|
|
290
|
+
`document/kind/${node.kind}`,
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
// increment by adding a 1
|
|
294
|
+
values.push({
|
|
295
|
+
source,
|
|
296
|
+
start,
|
|
297
|
+
value: 1,
|
|
298
|
+
metric: "Count",
|
|
299
|
+
dimensions,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
case "ADD_FOLDER": {
|
|
305
|
+
dimensions["kind"] = AnalyticsPath.fromString(
|
|
306
|
+
"document/kind/folder",
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
// increment by adding a 1
|
|
310
|
+
values.push({
|
|
311
|
+
source,
|
|
312
|
+
start,
|
|
313
|
+
value: 1,
|
|
314
|
+
metric: "Count",
|
|
315
|
+
dimensions,
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
case "DELETE_NODE": {
|
|
321
|
+
// the operation only contains the id, so lookup deleted item type in previous state
|
|
322
|
+
const input = operation.input as DeleteNodeInput;
|
|
323
|
+
const node = findNode(operation.previousState, input.id);
|
|
324
|
+
if (!node) {
|
|
325
|
+
return Promise.resolve();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
dimensions["kind"] = AnalyticsPath.fromString(
|
|
329
|
+
`document/kind/${node.kind}`,
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
// decrement by adding a -1
|
|
333
|
+
values.push({
|
|
334
|
+
source,
|
|
335
|
+
start,
|
|
336
|
+
value: -1,
|
|
337
|
+
metric: "Count",
|
|
338
|
+
dimensions,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
break;
|
|
318
342
|
}
|
|
319
|
-
|
|
320
|
-
dimensions["kind"] = AnalyticsPath.fromString(`document/kind/${node.kind}`);
|
|
321
|
-
|
|
322
|
-
// decrement by adding a -1
|
|
323
|
-
values.push({
|
|
324
|
-
source,
|
|
325
|
-
start,
|
|
326
|
-
value: -1,
|
|
327
|
-
metric: "Count",
|
|
328
|
-
dimensions,
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
break;
|
|
332
343
|
}
|
|
333
|
-
}
|
|
334
|
-
|
|
344
|
+
}),
|
|
345
|
+
);
|
|
335
346
|
}
|
|
336
347
|
|
|
337
348
|
await this.analyticsStore.addSeriesValues(values);
|
|
@@ -462,9 +473,12 @@ Now, we can define the dimensions we want to group by. We can imagine that we wi
|
|
|
462
473
|
|
|
463
474
|
```ts
|
|
464
475
|
const totalSpendOnHeadcount = useAnalyticsQuery({
|
|
465
|
-
start,
|
|
476
|
+
start,
|
|
477
|
+
end,
|
|
478
|
+
granularity,
|
|
479
|
+
metrics,
|
|
466
480
|
select: {
|
|
467
|
-
contributor: "/billing-statement/contributor"
|
|
481
|
+
contributor: "/billing-statement/contributor",
|
|
468
482
|
},
|
|
469
483
|
lod: {
|
|
470
484
|
contributor: 3,
|
|
@@ -483,7 +497,7 @@ const totalSpend = useAnalyticsQuery({
|
|
|
483
497
|
granularity: "total", // <--- this means we'll get results for the entire time period
|
|
484
498
|
metrics: ["Cash", "Powt"],
|
|
485
499
|
select: {
|
|
486
|
-
budget: "/billing-statement"
|
|
500
|
+
budget: "/billing-statement",
|
|
487
501
|
},
|
|
488
502
|
lod: {
|
|
489
503
|
budget: 0, // <--- this means we'll get all results lumped together
|
|
@@ -496,7 +510,7 @@ const monthlySpendByBudget = useAnalyticsQuery({
|
|
|
496
510
|
granularity: "monthly", // <--- this means we'll get results grouped by month
|
|
497
511
|
metrics: ["Cash", "Powt"],
|
|
498
512
|
select: {
|
|
499
|
-
budget: "/billing-statement/budget"
|
|
513
|
+
budget: "/billing-statement/budget",
|
|
500
514
|
},
|
|
501
515
|
lod: {
|
|
502
516
|
budget: 3, // <--- this means we'll get results grouped by "/billing-statement/budget/budget1", "/billing-statement/budget/budget2", etc.
|
|
@@ -509,7 +523,7 @@ const monthlySpendByCategory = useAnalyticsQuery({
|
|
|
509
523
|
granularity: "monthly", // <--- this means we'll get results grouped by month
|
|
510
524
|
metrics: ["Cash", "Powt"],
|
|
511
525
|
select: {
|
|
512
|
-
category: "/billing-statement/category"
|
|
526
|
+
category: "/billing-statement/category",
|
|
513
527
|
},
|
|
514
528
|
lod: {
|
|
515
529
|
category: 3, // <--- this means we'll get results grouped by "/billing-statement/category/category1", "/billing-statement/category/category2", etc.
|
|
@@ -522,7 +536,7 @@ const yearlySpendByBudget = useAnalyticsQuery({
|
|
|
522
536
|
granularity: "yearly", // <--- this means we'll get results grouped by year
|
|
523
537
|
metrics: ["Cash", "Powt"],
|
|
524
538
|
select: {
|
|
525
|
-
budget: "/billing-statement/budget"
|
|
539
|
+
budget: "/billing-statement/budget",
|
|
526
540
|
},
|
|
527
541
|
lod: {
|
|
528
542
|
budget: 3, // <--- this means we'll get results grouped by "/billing-statement/budget/budget1", "/billing-statement/budget/budget2", etc.
|
|
@@ -535,7 +549,7 @@ const monthlySpendByBudget = useAnalyticsQuery({
|
|
|
535
549
|
granularity: "monthly", // <--- this means we'll get results grouped by month
|
|
536
550
|
metrics: ["Cash", "Powt"],
|
|
537
551
|
select: {
|
|
538
|
-
budget: "/billing-statement/budget"
|
|
552
|
+
budget: "/billing-statement/budget",
|
|
539
553
|
},
|
|
540
554
|
lod: {
|
|
541
555
|
budget: 3, // <--- this means we'll get results grouped by "/billing-statement/budget/budget1", "/billing-statement/budget/budget2", etc.
|
|
@@ -548,7 +562,7 @@ const last30DaysSpendByBudget = useAnalyticsQuery({
|
|
|
548
562
|
granularity: "day", // <--- this means we'll get results grouped by day
|
|
549
563
|
metrics: ["Cash", "Powt"],
|
|
550
564
|
select: {
|
|
551
|
-
budget: "/billing-statement/budget"
|
|
565
|
+
budget: "/billing-statement/budget",
|
|
552
566
|
},
|
|
553
567
|
lod: {
|
|
554
568
|
budget: 3, // <--- this means we'll get results grouped by "/billing-statement/budget/budget1", "/billing-statement/budget/budget2", etc.
|
|
@@ -568,10 +582,10 @@ For instance, say we take our monthly spend by category query:
|
|
|
568
582
|
const monthlySpendByCategory = useAnalyticsQuery({
|
|
569
583
|
start,
|
|
570
584
|
end,
|
|
571
|
-
granularity: "monthly",
|
|
585
|
+
granularity: "monthly",
|
|
572
586
|
metrics: ["Cash", "Powt"],
|
|
573
587
|
select: {
|
|
574
|
-
category: "/billing-statement/category"
|
|
588
|
+
category: "/billing-statement/category",
|
|
575
589
|
},
|
|
576
590
|
lod: {
|
|
577
591
|
category: 3,
|
|
@@ -583,31 +597,36 @@ This gives us the results we're looking for but, by design, there may be many di
|
|
|
583
597
|
|
|
584
598
|
```ts
|
|
585
599
|
// this source will match all analytics updates from any document in the drive
|
|
586
|
-
const driveSource = AnalyticsPath.fromString(
|
|
600
|
+
const driveSource = AnalyticsPath.fromString(
|
|
601
|
+
`billing-statement/${drive.header.id}`,
|
|
602
|
+
);
|
|
587
603
|
|
|
588
604
|
// this source will match all analytics updates from a specific document in a drive
|
|
589
|
-
const documentSource = AnalyticsPath.fromString(
|
|
605
|
+
const documentSource = AnalyticsPath.fromString(
|
|
606
|
+
`billing-statement/${drive.header.id}/${document.header.id}`,
|
|
607
|
+
);
|
|
590
608
|
```
|
|
591
609
|
|
|
592
610
|
```ts
|
|
593
611
|
const { state, data: drive } = useSelectedDrive();
|
|
594
612
|
|
|
595
|
-
const results = useAnalyticsQuery(
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
613
|
+
const results = useAnalyticsQuery(
|
|
614
|
+
{
|
|
615
|
+
start,
|
|
616
|
+
end,
|
|
617
|
+
granularity: "monthly",
|
|
618
|
+
metrics: ["Cash", "Powt"],
|
|
619
|
+
select: {
|
|
620
|
+
category: "/billing-statement/category",
|
|
621
|
+
},
|
|
622
|
+
lod: {
|
|
623
|
+
category: 3,
|
|
624
|
+
},
|
|
601
625
|
},
|
|
602
|
-
|
|
603
|
-
|
|
626
|
+
{
|
|
627
|
+
sources: [`/billing-statement/${drive.header.id}/`],
|
|
604
628
|
},
|
|
605
|
-
|
|
606
|
-
{
|
|
607
|
-
sources: [
|
|
608
|
-
`/billing-statement/${drive.header.id}/`
|
|
609
|
-
],
|
|
610
|
-
});
|
|
629
|
+
);
|
|
611
630
|
```
|
|
612
631
|
|
|
613
632
|
### `IProcessor`
|
|
@@ -617,6 +636,3 @@ Now that we have designed out our data, we can open up `line-item-processor/inde
|
|
|
617
636
|
```ts
|
|
618
637
|
|
|
619
638
|
```
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|