@powerhousedao/academy 3.3.0-dev.10 → 3.3.0-dev.12
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 +14 -0
- package/docs/academy/02-MasteryTrack/04-WorkWithData/07-OperationalDbProcessorTutorial/01-TodoList-example.md +203 -0
- package/package.json +1 -1
- package/docs/academy/02-MasteryTrack/04-WorkWithData/07-OperationalDbProcessorTutorial/03-GenerateAnAnalyticsProcessor.md +0 -169
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## 3.3.0-dev.12 (2025-07-17)
|
|
2
|
+
|
|
3
|
+
### 🩹 Fixes
|
|
4
|
+
|
|
5
|
+
- **document-drive:** use lowercase letters when hashing relational processor namespace ([87c7944d3](https://github.com/powerhouse-inc/powerhouse/commit/87c7944d3))
|
|
6
|
+
|
|
7
|
+
### ❤️ Thank You
|
|
8
|
+
|
|
9
|
+
- acaldas
|
|
10
|
+
|
|
11
|
+
## 3.3.0-dev.11 (2025-07-16)
|
|
12
|
+
|
|
13
|
+
This was a version bump only for @powerhousedao/academy to align it with other projects, there were no code changes.
|
|
14
|
+
|
|
1
15
|
## 3.3.0-dev.10 (2025-07-15)
|
|
2
16
|
|
|
3
17
|
### 🩹 Fixes
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Build a Todo-List processor
|
|
2
|
+
|
|
3
|
+
1. Generate the processor
|
|
4
|
+
2. Define your database schema
|
|
5
|
+
3. Customize the processor to your needs
|
|
6
|
+
4. Test your processor
|
|
7
|
+
5. Use the operational store in Frontend and Subgraph
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Generate the Processor
|
|
11
|
+
|
|
12
|
+
In order to generate the processor you need to run the following command:
|
|
13
|
+
```bash
|
|
14
|
+
ph generate --processor todo-processor --processor-type relational-db --document-types powerhouse/todolist
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
With that command you create a processor named todo-processor which is of type relational db and listens on changes from documents of type powerhouse/todolist.
|
|
18
|
+
|
|
19
|
+
## Define your database schema
|
|
20
|
+
|
|
21
|
+
As next step we need to define the db schema in the `processors/todo-processor/migration.ts` file.
|
|
22
|
+
|
|
23
|
+
The migration file has a up and a down function which gets called when either the processor was added or when the processor was removed.
|
|
24
|
+
|
|
25
|
+
Below you can find the example of a todo table.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { type IOperationalStore } from "document-drive/processors/types"
|
|
29
|
+
|
|
30
|
+
export async function up(db: IOperationalStore): Promise<void> {
|
|
31
|
+
// Create table
|
|
32
|
+
await db.schema
|
|
33
|
+
.createTable("todo")
|
|
34
|
+
.addColumn("name", "varchar(255)")
|
|
35
|
+
.addColumn("completed", "boolean")
|
|
36
|
+
.addPrimaryKeyConstraint("todo_pkey", ["name"])
|
|
37
|
+
.ifNotExists()
|
|
38
|
+
.execute();
|
|
39
|
+
|
|
40
|
+
const tables = await db.introspection.getTables();
|
|
41
|
+
console.log(tables);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function down(db: IOperationalStore): Promise<void> {
|
|
45
|
+
// drop table
|
|
46
|
+
await db.schema.dropTable("todo").execute();
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Generate Types
|
|
51
|
+
|
|
52
|
+
After defining your db schema its important to generate the types for typescript. This allows to create type safety queries and make use of code completion in your IDE when writing database queries.
|
|
53
|
+
|
|
54
|
+
Simply execute the following command.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
ph generate --migration-file processors/todo-indexer/migrations.js --schema-file processors/todo-indexer/schema.ts
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Afterwards check your `processors/todo-processor/schema.ts` file.
|
|
61
|
+
It will contain the types of your database.
|
|
62
|
+
|
|
63
|
+
## Define the Filter
|
|
64
|
+
|
|
65
|
+
Checkout the `processors/todo-processor/factory.ts`.
|
|
66
|
+
|
|
67
|
+
Here you can define how the processor is being instantiated. In thise case it listens on powerhouse/todo-list document changes in the main branch and the global scope.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
export const todoProcessorProcessorFactory =
|
|
71
|
+
(module: IProcessorHostModule) =>
|
|
72
|
+
async (driveId: string): Promise<ProcessorRecord[]> => {
|
|
73
|
+
// Create a namespace for the processor and the provided drive id
|
|
74
|
+
const namespace = TodoProcessorProcessor.getNamespace(driveId);
|
|
75
|
+
|
|
76
|
+
// Create a filter for the processor
|
|
77
|
+
const filter: OperationalProcessorFilter = {
|
|
78
|
+
branch: ["main"],
|
|
79
|
+
documentId: ["*"],
|
|
80
|
+
documentType: ["powerhouse/todo-list"],
|
|
81
|
+
scope: ["global"],
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Create a namespaced store for the processor
|
|
85
|
+
const store = await createNamespacedDb<TodoProcessorProcessor>(
|
|
86
|
+
namespace,
|
|
87
|
+
module.operationalStore,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// Create the processor
|
|
91
|
+
const processor = new TodoProcessorProcessor(namespace, filter, store);
|
|
92
|
+
return [
|
|
93
|
+
{
|
|
94
|
+
processor,
|
|
95
|
+
filter,
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Customize the logic of the processor
|
|
103
|
+
|
|
104
|
+
When you defined your db schema and the filter when your processor should receive processed operations its time to implement the actual logic.
|
|
105
|
+
|
|
106
|
+
In the following you'll find an example where we store all the created and udpated todos in a table.
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
type DocumentType = ToDoListDocument;
|
|
110
|
+
|
|
111
|
+
export class TodoIndexerProcessor extends OperationalProcessor<DB> {
|
|
112
|
+
|
|
113
|
+
static override getNamespace(driveId: string): string {
|
|
114
|
+
// Default namespace: `${this.name}_${driveId.replaceAll("-", "_")}`
|
|
115
|
+
return super.getNamespace(driveId);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
override async initAndUpgrade(): Promise<void> {
|
|
119
|
+
await up(this.operationalStore as IOperationalStore);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
override async onStrands(
|
|
123
|
+
strands: InternalTransmitterUpdate<DocumentType>[],
|
|
124
|
+
): Promise<void> {
|
|
125
|
+
if (strands.length === 0) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for (const strand of strands) {
|
|
130
|
+
if (strand.operations.length === 0) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const operation of strand.operations) {
|
|
135
|
+
await this.operationalStore
|
|
136
|
+
.insertInto("todo")
|
|
137
|
+
.values({
|
|
138
|
+
task: strand.documentId,
|
|
139
|
+
status: true,
|
|
140
|
+
})
|
|
141
|
+
.execute();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async onDisconnect() {}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Fetch Data through a Subgraph
|
|
152
|
+
|
|
153
|
+
### Generate Subgraph
|
|
154
|
+
|
|
155
|
+
Simply generate a new subgraph with:
|
|
156
|
+
```bash
|
|
157
|
+
ph generate --subgraph <subgraph-name>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Fetch Data from Processor
|
|
161
|
+
|
|
162
|
+
open ```./subgraphs/<subgraph-name>/index.ts```
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
define the following:
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
resolvers = {
|
|
171
|
+
Query: {
|
|
172
|
+
todoList: {
|
|
173
|
+
resolve: async (parent, args, context, info) => {
|
|
174
|
+
const todoList = await TodoProcessor.query(
|
|
175
|
+
args.driveId ?? "powerhouse",
|
|
176
|
+
this.operationalStore
|
|
177
|
+
)
|
|
178
|
+
.selectFrom("todo")
|
|
179
|
+
.selectAll()
|
|
180
|
+
.execute();
|
|
181
|
+
return todoList
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
typeDefs = gql`
|
|
188
|
+
type Query {
|
|
189
|
+
type Todo {
|
|
190
|
+
name: String!
|
|
191
|
+
completed: Boolean!
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
todoList(driveId: String): [Todo!]!
|
|
195
|
+
}
|
|
196
|
+
`;
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
### useOperationalStore Hook
|
|
201
|
+
|
|
202
|
+
.....
|
|
203
|
+
|
package/package.json
CHANGED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
# Build a Todo-List processor
|
|
2
|
-
|
|
3
|
-
1. Generate the processor
|
|
4
|
-
2. Define your database schema
|
|
5
|
-
3. Customize the processor to your needs
|
|
6
|
-
4. Test your processor
|
|
7
|
-
5. Use the operational store in Frontend and Subgraph
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
## Generate the Processor
|
|
11
|
-
|
|
12
|
-
In order to generate the processor you need to run the following command:
|
|
13
|
-
```bash
|
|
14
|
-
ph generate --processor todo-processor --processor-type operational --document-types powerhouse/todolist
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## Define your database schema
|
|
18
|
-
|
|
19
|
-
in the migrations.ts file in your processor directory you can find the up and down methods which are being executed when the processor gets installed or removed.
|
|
20
|
-
|
|
21
|
-
```ts
|
|
22
|
-
import { type IOperationalStore } from "document-drive/processors/types"
|
|
23
|
-
|
|
24
|
-
export async function up(db: IOperationalStore): Promise<void> {
|
|
25
|
-
// Create table
|
|
26
|
-
await db.schema
|
|
27
|
-
.createTable("todo")
|
|
28
|
-
.addColumn("name", "varchar(255)")
|
|
29
|
-
.addColumn("completed", "boolean")
|
|
30
|
-
.addPrimaryKeyConstraint("todo_pkey", ["name"])
|
|
31
|
-
.ifNotExists()
|
|
32
|
-
.execute();
|
|
33
|
-
|
|
34
|
-
const tables = await db.introspection.getTables();
|
|
35
|
-
console.log(tables);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export async function down(db: IOperationalStore): Promise<void> {
|
|
39
|
-
// drop table
|
|
40
|
-
await db.schema.dropTable("todo").execute();
|
|
41
|
-
}
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
when you finished defining your database model you can generate the types for typescript from it with the following command:
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
ph generate --migration-file processors/todo-indexer/migrations.js --schema-file processors/todo-indexer/schema.ts
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
## Customize the processor
|
|
52
|
-
|
|
53
|
-
the index file contains the processor itsself with the default template:
|
|
54
|
-
|
|
55
|
-
```ts
|
|
56
|
-
import {
|
|
57
|
-
OperationalProcessor,
|
|
58
|
-
type OperationalProcessorFilter,
|
|
59
|
-
} from "document-drive/processors/operational-processor";
|
|
60
|
-
import { type InternalTransmitterUpdate } from "document-drive/server/listener/transmitter/internal";
|
|
61
|
-
import { up } from "./migrations.js";
|
|
62
|
-
import { type DB } from "./schema.js";
|
|
63
|
-
import type { ToDoListDocument } from "../../document-models/to-do-list/index.js";
|
|
64
|
-
|
|
65
|
-
type DocumentType = ToDoListDocument;
|
|
66
|
-
|
|
67
|
-
export class TodoIndexerProcessor extends OperationalProcessor<DB> {
|
|
68
|
-
get filter(): OperationalProcessorFilter {
|
|
69
|
-
return {
|
|
70
|
-
branch: ["main"],
|
|
71
|
-
documentId: ["*"],
|
|
72
|
-
documentType: ["powerhouse/todolist"],
|
|
73
|
-
scope: ["global"],
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async initAndUpgrade(): Promise<void> {
|
|
78
|
-
await up(this.operationalStore);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async onStrands(
|
|
82
|
-
strands: InternalTransmitterUpdate<DocumentType>[],
|
|
83
|
-
): Promise<void> {
|
|
84
|
-
if (strands.length === 0) {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
for (const strand of strands) {
|
|
89
|
-
if (strand.operations.length === 0) {
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
for (const operation of strand.operations) {
|
|
94
|
-
console.log(">>> ", operation.type);
|
|
95
|
-
await this.operationalStore
|
|
96
|
-
.insertInto("todo")
|
|
97
|
-
.values({
|
|
98
|
-
task: strand.documentId,
|
|
99
|
-
status: true,
|
|
100
|
-
})
|
|
101
|
-
.execute();
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async onDisconnect() {}
|
|
107
|
-
}
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
As you can see you can define with the filter options when the processor is executed.
|
|
111
|
-
In this case the processor is called when an operation on a powerhouse/todolist document was processed.
|
|
112
|
-
The operations and the corresponding states will be passed to the onStrands method.
|
|
113
|
-
|
|
114
|
-
Furthermore the Processor contains an initAndUpgrade function which is called when the processor is being activated. Next to the init there is also an onDisconnect function which is called when the processor is beging removed. The template of the operational database processor contains the up method of the migrations.ts file.
|
|
115
|
-
|
|
116
|
-
Finally there is the onStrands method. Here you can update your operational database based upon your needs.
|
|
117
|
-
|
|
118
|
-
## test the processor
|
|
119
|
-
|
|
120
|
-
.... todo
|
|
121
|
-
|
|
122
|
-
## use the operational database
|
|
123
|
-
|
|
124
|
-
### subgraph
|
|
125
|
-
|
|
126
|
-
1. generate subgraph with
|
|
127
|
-
|
|
128
|
-
```bash
|
|
129
|
-
ph generate --subgraph <subgraph-name>
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
open ```./subgraphs/<subgraph-name>/index.ts```
|
|
133
|
-
|
|
134
|
-
define the following:
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
```
|
|
138
|
-
resolvers = {
|
|
139
|
-
Query: {
|
|
140
|
-
todoList: {
|
|
141
|
-
resolve: async (parent, args, context, info) => {
|
|
142
|
-
const todoList = await this.operationalStore.selectFrom("todo").selectAll().execute();
|
|
143
|
-
return todoList
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
},
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
typeDefs = gql`
|
|
150
|
-
type Query {
|
|
151
|
-
type Todo {
|
|
152
|
-
name: String!
|
|
153
|
-
completed: Boolean!
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
todoList: [Todo!]!
|
|
157
|
-
}
|
|
158
|
-
`;
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
you can simply do sql requests with the provided operationalstore in the class for example
|
|
162
|
-
```ts
|
|
163
|
-
await this.operationalStore.selectFrom("todo").selectAll().execute();
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
### useOperationalStore Hook
|
|
167
|
-
|
|
168
|
-
.....
|
|
169
|
-
|