@powerhousedao/academy 5.0.0-staging.8 → 5.0.1-staging.2
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 +404 -0
- 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 +67 -30
- package/docs/academy/01-GetStarted/01-CreateNewPowerhouseProject.md +38 -21
- 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-VetraStudio.md +164 -0
- package/docs/academy/01-GetStarted/06-ReactorMCP.md +58 -0
- package/docs/academy/01-GetStarted/home.mdx +185 -90
- package/docs/academy/01-GetStarted/images/Modules.png +0 -0
- package/docs/academy/01-GetStarted/images/VetraStudioDrive.png +0 -0
- 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 +25 -17
- 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 +104 -43
- 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 +268 -35
- 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 +9 -7
- package/scripts/generate-combined-cli-docs.ts +43 -13
- package/sidebars.ts +2 -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
- package/tsconfig.tsbuildinfo +1 -0
package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/03-SpecifyDocumentOperations.md
CHANGED
|
@@ -8,9 +8,9 @@ In Powerhouse, document models adhere to event sourcing principles. This means t
|
|
|
8
8
|
|
|
9
9
|
For example, in our `To-do List` document model, operations might include:
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
- `ADD_TODO_ITEM`: To add a new task.
|
|
12
|
+
- `UPDATE_TODO_ITEM`: To modify an existing task (e.g., change its text or mark it as completed).
|
|
13
|
+
- `DELETE_TODO_ITEM`: To remove a task.
|
|
14
14
|
|
|
15
15
|
Each operation acts as a command that, when applied, transitions the document from one state to the next. The complete history of these operations defines the document's journey to its current state.
|
|
16
16
|
|
|
@@ -40,9 +40,9 @@ input DeleteTodoItemInput {
|
|
|
40
40
|
|
|
41
41
|
These `input` types are not just abstract definitions; they are the **specifications for our document operations**.
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
- **`AddTodoItemInput`** specifies that to execute an `ADD_TODO_ITEM` operation, we need an `id` and `text` for the new item.
|
|
44
|
+
- **`UpdateTodoItemInput`** specifies that for an `UPDATE_TODO_ITEM` operation, we need the `id` of the item to update, and optionally new `text` or a `checked` status.
|
|
45
|
+
- **`DeleteTodoItemInput`** specifies that a `DELETE_TODO_ITEM` operation requires the `id` of the item to be removed.
|
|
46
46
|
|
|
47
47
|
The Powerhouse Connect application uses these GraphQL input types when you define operations within a module (e.g., the `to_do_list` module with operations `ADD_TODO_ITEM`, `UPDATE_TODO_ITEM`, `DELETE_TODO_ITEM`).
|
|
48
48
|
|
|
@@ -51,36 +51,40 @@ The Powerhouse Connect application uses these GraphQL input types when you defin
|
|
|
51
51
|
Careful design of your document operations is crucial for a robust and maintainable document model. Here are some key considerations:
|
|
52
52
|
|
|
53
53
|
### 1. Granularity
|
|
54
|
+
|
|
54
55
|
Operations should be granular enough to represent distinct user intentions or logical changes.
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
- **Too coarse:** An operation like `MODIFY_TODOLIST` that takes a whole new list of items would be too broad. It would be hard to track specific changes and could lead to complex reducer logic.
|
|
58
|
+
- **Too fine:** While possible, having separate operations like `SET_TODO_ITEM_TEXT` and `SET_TODO_ITEM_CHECKED_STATUS` might be overly verbose if these are often updated together. `UPDATE_TODO_ITEM` with optional fields offers a good balance.
|
|
59
|
+
- **Just right:** The `ADD_TODO_ITEM`, `UPDATE_TODO_ITEM`, and `DELETE_TODO_ITEM` operations for our `ToDoList` are good examples. They represent clear, atomic changes.
|
|
59
60
|
|
|
60
61
|
### 2. Naming conventions
|
|
62
|
+
|
|
61
63
|
Clear and consistent naming makes your operations understandable. A common convention is `VERB_NOUN` or `VERB_NOUN_SUBJECT`.
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
- Examples: `ADD_ITEM`, `UPDATE_USER_PROFILE`, `ASSIGN_TASK_TO_USER`.
|
|
66
|
+
- In our case: `ADD_TODO_ITEM`, `UPDATE_TODO_ITEM`, `DELETE_TODO_ITEM`.
|
|
65
67
|
|
|
66
68
|
The name you provide in the Connect UI (e.g., `ADD_TODO_ITEM`) directly corresponds to the operation type that will be recorded and that your reducers will handle.
|
|
67
69
|
|
|
68
70
|
### 3. Input types (payloads)
|
|
71
|
+
|
|
69
72
|
The input type for an operation (its payload) should contain all the necessary information to perform that operation, and nothing more.
|
|
70
73
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
- **Completeness:** If an operation needs a user ID to authorize a change, include it in the input.
|
|
75
|
+
- **Conciseness:** Avoid including data that isn't directly used by the operation.
|
|
76
|
+
- **Clarity:** Use descriptive field names within your input types. `action.input.text` is clearer than `action.input.t`.
|
|
74
77
|
|
|
75
78
|
The GraphQL `input` types we defined earlier (`AddTodoItemInput`, `UpdateTodoItemInput`, `DeleteTodoItemInput`) serve precisely this purpose. They ensure that whoever triggers an operation provides the correct data in the correct format.
|
|
76
79
|
|
|
77
80
|
### 4. Immutability and pure functions
|
|
78
|
-
|
|
81
|
+
|
|
82
|
+
While not specified in the operation definition itself, remember that the _implementation_ of these operations (the reducers) should treat state as immutable and behave as pure functions. The operation specification (input type) provides the data for these pure functions.
|
|
79
83
|
|
|
80
84
|
## Role in event sourcing and CQRS
|
|
81
85
|
|
|
82
|
-
|
|
83
|
-
|
|
86
|
+
- **Events:** Each successfully executed operation is recorded as an event in the document's history. This history provides an audit trail and allows for replaying events to reconstruct state, which is invaluable for debugging and understanding how a document evolved.
|
|
87
|
+
- **Commands:** Document operations are essentially "commands" in a Command Query Responsibility Segregation (CQRS) pattern. They represent an intent to change the state. The processing of this command (by the reducer) leads to one or more events being stored and the state being updated.
|
|
84
88
|
|
|
85
89
|
## From specification to implementation
|
|
86
90
|
|
|
@@ -95,7 +99,7 @@ The generated code from `ph generate` (as seen in `03-ImplementOperationReducers
|
|
|
95
99
|
For example, the `ToDoListToDoListOperations` type generated by Powerhouse will expect methods corresponding to `addTodoItemOperation`, `updateTodoItemOperation`, and `deleteTodoItemOperation`.
|
|
96
100
|
|
|
97
101
|
```typescript
|
|
98
|
-
import { ToDoListToDoListOperations } from
|
|
102
|
+
import { ToDoListToDoListOperations } from "../../gen/to-do-list/operations.js";
|
|
99
103
|
|
|
100
104
|
export const reducer: ToDoListToDoListOperations = {
|
|
101
105
|
addTodoItemOperation(state, action, dispatch) {
|
|
@@ -121,12 +125,12 @@ Assuming you have already defined the state schema for the `To-do List` as cover
|
|
|
121
125
|
|
|
122
126
|
1. **Create a Module for Operations:**
|
|
123
127
|
Below the schema editor in Connect, find the input field labeled `Add module`. Modules help organize your operations.
|
|
124
|
-
|
|
128
|
+
- Type `to_do_list` into the field and press Enter.
|
|
125
129
|
|
|
126
130
|
2. **Add the `ADD_TODO_ITEM` Operation:**
|
|
127
131
|
A new field, `Add operation`, will appear under your new module.
|
|
128
|
-
|
|
129
|
-
|
|
132
|
+
- Type `ADD_TODO_ITEM` into this field and press Enter.
|
|
133
|
+
- An editor will appear for the operation's input type. You need to define the data required for this operation. Paste the following GraphQL `input` definition into the editor:
|
|
130
134
|
|
|
131
135
|
```graphql
|
|
132
136
|
# Defines a GraphQL input type for adding a new to-do item
|
|
@@ -137,8 +141,8 @@ Assuming you have already defined the state schema for the `To-do List` as cover
|
|
|
137
141
|
```
|
|
138
142
|
|
|
139
143
|
3. **Add the `UPDATE_TODO_ITEM` Operation:**
|
|
140
|
-
|
|
141
|
-
|
|
144
|
+
- In the `Add operation` field again, type `UPDATE_TODO_ITEM` and press Enter.
|
|
145
|
+
- Paste the corresponding `input` definition into its editor:
|
|
142
146
|
|
|
143
147
|
```graphql
|
|
144
148
|
# Defines a GraphQL input type for updating a to-do item
|
|
@@ -150,8 +154,8 @@ Assuming you have already defined the state schema for the `To-do List` as cover
|
|
|
150
154
|
```
|
|
151
155
|
|
|
152
156
|
4. **Add the `DELETE_TODO_ITEM` Operation:**
|
|
153
|
-
|
|
154
|
-
|
|
157
|
+
- Finally, type `DELETE_TODO_ITEM` in the `Add operation` field and press Enter.
|
|
158
|
+
- Paste its `input` definition:
|
|
155
159
|
|
|
156
160
|
```graphql
|
|
157
161
|
# Defines a GraphQL input type for deleting a to-do item
|
package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/04-UseTheDocumentModelGenerator.md
CHANGED
|
@@ -39,45 +39,43 @@ The generator creates a new directory specific to your document model, usually l
|
|
|
39
39
|
For example, using `Invoice.phdm.zip` would result in a directory structure under `document-models/invoice/`. Inside this directory, you will find:
|
|
40
40
|
|
|
41
41
|
1. **`spec.json` (or similar JSON representation):**
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
- **Purpose:** This file is a JSON representation of your document model specification, derived directly from the `.phdm.zip` file. It contains the parsed schema, operation definitions, document type, and other metadata.
|
|
43
|
+
- **Significance:** It serves as the canonical, machine-readable definition of your model within the project, which other tools or processes might reference.
|
|
44
44
|
|
|
45
45
|
2. **`schema.graphql`:**
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
- **Purpose:** This file contains the raw GraphQL Schema Definition Language (SDL) for both the state and operations of your document model, exactly as you defined it in Connect.
|
|
47
|
+
- **Significance:** Provides a human-readable reference of the schema and can be useful for quick checks or for tools that might directly consume GraphQL SDL.
|
|
48
48
|
|
|
49
49
|
3. **The `gen/` Directory (Generated Code):**
|
|
50
50
|
This directory is pivotal as it houses all the code automatically generated by the tool. **You should generally avoid manually editing files within the `gen/` directory**, as they will be overwritten if you regenerate the model.
|
|
51
51
|
Key files within `gen/` include:
|
|
52
|
+
- **`types.ts`:**
|
|
53
|
+
- **Purpose:** Contains TypeScript interfaces and type definitions derived from your GraphQL schema. This includes types for your document's state (e.g., `InvoiceState`), any complex types used within the state (e.g., `LineItem`), and types for the inputs of each defined operation (e.g., `AddLineItemInput`).
|
|
54
|
+
- **Significance:** This is the cornerstone of type safety in your document model implementation. By using these generated types, you ensure that your reducer logic and any client-side interactions adhere to the defined data structures, catching errors at compile-time rather than runtime.
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
- **`operations.ts` (or `creators.ts`, `actions.ts`):**
|
|
57
|
+
- **Purpose:** This file exports "action creator" functions for each operation defined in your schema. These functions take the input parameters for an operation and return a correctly structured action object that can be processed by the reducer system.
|
|
58
|
+
- **Significance:** Action creators simplify the process of creating and dispatching operations, reduce the likelihood of errors in action formatting, and improve code readability. For example, instead of manually constructing an action object like `{ type: "ADD_LINE_ITEM", input: { ... } }`, you'd use a function like `creators.addLineItem({ ... })`.
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
- **`utils.ts`:**
|
|
61
|
+
- **Purpose:** Often includes utility functions related to your document model. A common utility is a function to create an empty or initial instance of your document (e.g., `utils.createDocument()`). This is based on the default values and structure defined in your state schema.
|
|
62
|
+
- **Significance:** Provides convenient helpers for common tasks, particularly for initializing new documents in tests or application code.
|
|
60
63
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
* **`reducer.ts` (or similar, defining the reducer interface/skeleton):**
|
|
66
|
-
* **Purpose:** This file might contain a TypeScript interface or a skeleton object that your actual reducer implementation (in the `src/` directory) will need to conform to. It outlines the expected shape of the reducer map, ensuring that you provide an implementation for every defined operation.
|
|
67
|
-
* **Significance:** Guides the implementation of your reducers and helps maintain consistency, ensuring that all defined operations are accounted for.
|
|
64
|
+
- **`reducer.ts` (or similar, defining the reducer interface/skeleton):**
|
|
65
|
+
- **Purpose:** This file might contain a TypeScript interface or a skeleton object that your actual reducer implementation (in the `src/` directory) will need to conform to. It outlines the expected shape of the reducer map, ensuring that you provide an implementation for every defined operation.
|
|
66
|
+
- **Significance:** Guides the implementation of your reducers and helps maintain consistency, ensuring that all defined operations are accounted for.
|
|
68
67
|
|
|
69
68
|
4. **The `src/` Directory (Source Code for Your Implementation):**
|
|
70
69
|
This directory is where you, the developer, will write the custom logic for your document model. Unlike the `gen/` directory, files here are meant to be manually edited.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
* **Significance:** Helps in organizing shared logic or complex computations that might be used across multiple reducers or other parts of your model's ecosystem.
|
|
70
|
+
- **`reducers/`:**
|
|
71
|
+
- **`your-model-name.ts` (e.g., `invoice.ts`):** This is the most important file you'll work with after generation. It's where you implement the **reducer functions** for each operation. The generator usually creates a skeleton file with function stubs for each operation, which you then fill in with the actual state transition logic.
|
|
72
|
+
- **Significance:** This is the heart of your document model's behavior, defining how the state changes in response to each operation. The next step in the Mastery Track will typically focus on implementing these reducers.
|
|
73
|
+
- **`reducers/tests/`:**
|
|
74
|
+
- **`your-model-name.test.ts` (e.g., `invoice.test.ts`):** A placeholder or basic test file is often generated here, encouraging you to write unit tests for your reducer logic.
|
|
75
|
+
- **Significance:** Emphasizes the importance of testing your document model's core logic to ensure correctness and reliability.
|
|
76
|
+
- **`utils/` (optional):**
|
|
77
|
+
- **Purpose:** You can create this directory for any custom utility functions specific to your document model's implementation that don't fit directly into the reducers.
|
|
78
|
+
- **Significance:** Helps in organizing shared logic or complex computations that might be used across multiple reducers or other parts of your model's ecosystem.
|
|
81
79
|
|
|
82
80
|
## Benefits of using the document model generator
|
|
83
81
|
|
|
@@ -101,27 +99,28 @@ This tutorial assumes you have completed the previous steps in this Mastery Trac
|
|
|
101
99
|
|
|
102
100
|
### Prerequisites
|
|
103
101
|
|
|
104
|
-
|
|
102
|
+
- **`ToDoList.phdm.zip` file**: You must have the document model specification file exported from Connect. If you do not have this file, please revisit the previous sections on specifying the state schema and operations.
|
|
105
103
|
|
|
106
104
|
### Steps
|
|
107
105
|
|
|
108
106
|
1. **Place the Specification File in Your Project**:
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
- Navigate to the root directory of your Powerhouse project.
|
|
108
|
+
- Move or copy your `ToDoList.phdm.zip` file into this directory.
|
|
111
109
|
|
|
112
110
|
2. **Run the Generator Command**:
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
- Open your terminal in the root directory of your Powerhouse project.
|
|
112
|
+
- Execute the `ph generate` command, pointing to your specification file:
|
|
113
|
+
|
|
115
114
|
```bash
|
|
116
115
|
ph generate ToDoList.phdm.zip
|
|
117
116
|
```
|
|
118
117
|
|
|
119
118
|
3. **Explore the Generated Files**:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
119
|
+
- After the command completes successfully, you will find a new directory: `document-models/to-do-list/`.
|
|
120
|
+
- Take a moment to explore its contents, which will match the structure described earlier in this document:
|
|
121
|
+
- `spec.json` and `schema.graphql`: The definition of your model.
|
|
122
|
+
- `gen/`: Type-safe, generated code including `types.ts`, `operations.ts`, etc.
|
|
123
|
+
- `src/`: The skeleton for your implementation, most importantly `src/reducers/to-do-list.ts`, which will contain empty functions for `addTodoItemOperation`, `updateTodoItemOperation`, and `deleteTodoItemOperation`, ready for you to implement.
|
|
125
124
|
|
|
126
125
|
With these files generated, you have successfully scaffolded your document model. The project is now set up for you to implement the core business logic.
|
|
127
126
|
|
package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/05-ImplementDocumentReducers.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
In our journey through Powerhouse Document Model creation, we've defined the "what" – the structure of our data ([State Schema](02-SpecifyTheStateSchema.md)) and the ways it can be changed ([Document Operations](03-SpecifyDocumentOperations.md)). We've also seen how the [Document Model Generator](04-UseTheDocumentModelGenerator.md) translates these specifications into a coded scaffold. Now, we arrive at the "how": implementing **Document Reducers**.
|
|
6
6
|
|
|
7
|
-
Reducers are the core logic units of your document model. They are the functions that take the current state of your document and an operation (an "action"), and then determine the
|
|
7
|
+
Reducers are the core logic units of your document model. They are the functions that take the current state of your document and an operation (an "action"), and then determine the _new_ state of the document. They are the embodiment of your business rules and the engine that drives state transitions in a predictable, auditable, and immutable way.
|
|
8
8
|
|
|
9
9
|
## Recap: The journey to reducer implementation
|
|
10
10
|
|
|
@@ -24,22 +24,22 @@ In the context of Powerhouse and inspired by patterns like Redux, a reducer is a
|
|
|
24
24
|
|
|
25
25
|
Let's break down its components and principles:
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
- **`currentState`**: This is the complete, current state of your document model instance before the operation is applied. It's crucial to treat this as **immutable**.
|
|
28
|
+
- **`action`**: This is an object describing the operation to be performed. It typically has:
|
|
29
|
+
- A `type` property: A string identifying the operation (e.g., `'ADD_TODO_ITEM'`).
|
|
30
|
+
- An `input` property (or similar, like `payload`): An object containing the data necessary for the operation, matching the GraphQL `input` type you defined (e.g., `{ id: '1', text: 'Buy groceries' }` for `AddTodoItemInput`).
|
|
31
|
+
- **`newState`**: The reducer must return a _new_ state object representing the state after the operation has been applied. If the operation does not result in a state change, the reducer should return the `currentState` object itself.
|
|
32
32
|
|
|
33
33
|
### Key principles guiding reducer implementation:
|
|
34
34
|
|
|
35
35
|
1. **Purity**:
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
- **Deterministic**: Given the same `currentState` and `action`, a reducer must _always_ produce the same `newState`.
|
|
37
|
+
- **No Side Effects**: Reducers must not perform any side effects. This means no API calls, no direct DOM manipulation, no `Math.random()` (unless seeded deterministically for specific testing scenarios), and no modification of variables outside their own scope. Their sole job is to compute the next state.
|
|
38
38
|
|
|
39
39
|
2. **Immutability**:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
- **Never Mutate `currentState`**: You must never directly modify the `currentState` object or any of its nested properties.
|
|
41
|
+
- **Always Return a New Object for Changes**: If the state changes, you must create and return a brand new object. If the state does not change, you return the original `currentState` object.
|
|
42
|
+
- This is fundamental to Powerhouse's event sourcing architecture, enabling time travel, efficient change detection, and a clear audit trail. We'll explore techniques for immutability shortly.
|
|
43
43
|
|
|
44
44
|
3. **Single Source of Truth**: The document state managed by reducers is the single source of truth for that document instance. All UI rendering and data queries are derived from this state.
|
|
45
45
|
|
|
@@ -47,8 +47,8 @@ Let's break down its components and principles:
|
|
|
47
47
|
While you can write one large reducer that uses a `switch` statement or `if/else if` blocks based on `action.type`, Powerhouse's generated code typically encourages a more modular approach. You'll often implement a separate function for each operation, which are then combined into a main reducer object or map. The `ph generate` command usually sets up this structure for you. For example, in your `document-models/to-do-list/src/reducers/to-do-list.ts`, you'll find an object structure like this:
|
|
48
48
|
|
|
49
49
|
```typescript
|
|
50
|
-
import { ToDoListToDoListOperations } from
|
|
51
|
-
import { ToDoListState } from
|
|
50
|
+
import { ToDoListToDoListOperations } from "../../gen/to-do-list/operations.js"; // Generated type for operations
|
|
51
|
+
import { ToDoListState } from "../../gen/types.js"; // Generated type for state
|
|
52
52
|
|
|
53
53
|
export const reducer: ToDoListToDoListOperations = {
|
|
54
54
|
addTodoItemOperation(state: ToDoListState, action, dispatch) {
|
|
@@ -69,6 +69,7 @@ Let's break down its components and principles:
|
|
|
69
69
|
// ... other operations
|
|
70
70
|
};
|
|
71
71
|
```
|
|
72
|
+
|
|
72
73
|
The `ToDoListToDoListOperations` type (or similar, depending on your model name) is generated by Powerhouse and ensures your reducer object correctly implements all defined operations. The `state` and `action` parameters within these methods will also be strongly typed based on your schema.
|
|
73
74
|
|
|
74
75
|
The `dispatch` parameter is an advanced feature allowing a reducer to trigger subsequent operations. While powerful for complex workflows, it's often not needed for basic operations and can be ignored if unused.
|
|
@@ -78,6 +79,7 @@ Let's break down its components and principles:
|
|
|
78
79
|
Let's use our familiar `ToDoList` example to illustrate common patterns. For this example, we'll assume our state schema has been updated to include a `stats` object to track the number of total, checked, and unchecked items.
|
|
79
80
|
|
|
80
81
|
Our `ToDoListState` now looks like this:
|
|
82
|
+
|
|
81
83
|
```typescript
|
|
82
84
|
interface ToDoItem {
|
|
83
85
|
id: string;
|
|
@@ -98,9 +100,10 @@ interface ToDoListState {
|
|
|
98
100
|
```
|
|
99
101
|
|
|
100
102
|
And our action creators (from `../../gen/creators` or `../../gen/operations.js`) provide actions like:
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
|
|
104
|
+
- `actions.addTodoItem({ id: 'some-id', text: 'New Task' })`
|
|
105
|
+
- `actions.updateTodoItem({ id: 'item-id', text: 'Updated Task Text', checked: true })`
|
|
106
|
+
- `actions.deleteTodoItem({ id: 'item-id' })`
|
|
104
107
|
|
|
105
108
|
### 1. Adding an item (e.g., `addTodoItemOperation`)
|
|
106
109
|
|
|
@@ -121,9 +124,11 @@ addTodoItemOperation(state: ToDoListState, action: /* AddTodoItemActionType */ a
|
|
|
121
124
|
};
|
|
122
125
|
}
|
|
123
126
|
```
|
|
127
|
+
|
|
124
128
|
**Explanation**:
|
|
125
|
-
|
|
126
|
-
|
|
129
|
+
|
|
130
|
+
- We use the spread operator (`...state`) to copy top-level properties from the old state into the new state object.
|
|
131
|
+
- For the `items` array, we create a _new_ array by spreading the existing `state.items` and then appending the `newItem`.
|
|
127
132
|
|
|
128
133
|
### 2. Updating an item (e.g., `updateTodoItemOperation`)
|
|
129
134
|
|
|
@@ -152,16 +157,19 @@ updateTodoItemOperation(state: ToDoListState, action: /* UpdateTodoItemActionTyp
|
|
|
152
157
|
};
|
|
153
158
|
}
|
|
154
159
|
```
|
|
160
|
+
|
|
155
161
|
**Explanation**:
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
162
|
+
|
|
163
|
+
- We use the `map` array method, which always returns a _new_ array.
|
|
164
|
+
- For the item that matches `action.input.id`, we create a new item object using the spread operator (`...item`) and then overwrite the properties (`text`, `checked`) that are present in `action.input`.
|
|
165
|
+
- The conditional spread (`...(condition && { property: value })`) is a concise way to only include a property in the new object if its corresponding input value is provided. This elegantly handles partial updates.
|
|
166
|
+
- If an item doesn't match the ID, it's returned as is.
|
|
160
167
|
|
|
161
168
|
**Error Handling Note**: In a real application, you might want to add a check to see if an item with `action.input.id` actually exists. If not, you could throw an error or handle it according to your application's requirements:
|
|
169
|
+
|
|
162
170
|
```typescript
|
|
163
171
|
// Inside updateTodoItemOperation, before returning:
|
|
164
|
-
const itemToUpdate = state.items.find(item => item.id === action.input.id);
|
|
172
|
+
const itemToUpdate = state.items.find((item) => item.id === action.input.id);
|
|
165
173
|
if (!itemToUpdate) {
|
|
166
174
|
// Option 1: Throw an error (Powerhouse runtime might catch this)
|
|
167
175
|
throw new Error(`Item with id ${action.input.id} not found.`);
|
|
@@ -186,8 +194,10 @@ deleteTodoItemOperation(state: ToDoListState, action: /* DeleteTodoItemActionTyp
|
|
|
186
194
|
};
|
|
187
195
|
}
|
|
188
196
|
```
|
|
197
|
+
|
|
189
198
|
**Explanation**:
|
|
190
|
-
|
|
199
|
+
|
|
200
|
+
- We use the `filter` array method, which returns a _new_ array containing only the elements for which the callback function returns `true`.
|
|
191
201
|
|
|
192
202
|
## Leveraging generated types
|
|
193
203
|
|
|
@@ -200,8 +210,8 @@ import {
|
|
|
200
210
|
ToDoListState,
|
|
201
211
|
AddTodoItemInput, // Generated input type
|
|
202
212
|
// ... other types
|
|
203
|
-
} from
|
|
204
|
-
import { ToDoListToDoListOperations } from
|
|
213
|
+
} from "../../gen/types.js";
|
|
214
|
+
import { ToDoListToDoListOperations } from "../../gen/to-do-list/operations.js"; // Generated operations type
|
|
205
215
|
|
|
206
216
|
// Define the type for the action more explicitly if needed, or rely on inferred types
|
|
207
217
|
// from ToDoListToDoListOperations. For complex actions, defining specific action types can be beneficial.
|
|
@@ -212,7 +222,11 @@ import { ToDoListToDoListOperations } from '../../gen/to-do-list/operations.js';
|
|
|
212
222
|
// }
|
|
213
223
|
|
|
214
224
|
export const reducer: ToDoListToDoListOperations = {
|
|
215
|
-
addTodoItemOperation(
|
|
225
|
+
addTodoItemOperation(
|
|
226
|
+
state: ToDoListState,
|
|
227
|
+
action: { input: AddTodoItemInput /* plus type property */ },
|
|
228
|
+
dispatch,
|
|
229
|
+
) {
|
|
216
230
|
// Now 'action.input.text' and 'action.input.id' are type-checked
|
|
217
231
|
const newItem = {
|
|
218
232
|
id: action.input.id,
|
|
@@ -227,10 +241,12 @@ export const reducer: ToDoListToDoListOperations = {
|
|
|
227
241
|
// ... other reducers
|
|
228
242
|
};
|
|
229
243
|
```
|
|
244
|
+
|
|
230
245
|
Using these types provides:
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
246
|
+
|
|
247
|
+
- **Compile-time safety**: Catch errors related to incorrect property names or data types before runtime.
|
|
248
|
+
- **Autocompletion and IntelliSense**: Improved developer experience in your IDE.
|
|
249
|
+
- **Clearer code**: Types serve as documentation for the expected data structures.
|
|
234
250
|
|
|
235
251
|
## Practical implementation: Writing the `ToDoList` reducers
|
|
236
252
|
|
|
@@ -246,87 +262,89 @@ This tutorial assumes you have followed the steps in the previous chapters, espe
|
|
|
246
262
|
Navigate to `document-models/to-do-list/src/reducers/to-do-list.ts`. The generator will have created a skeleton file. Replace its contents with the following logic.
|
|
247
263
|
|
|
248
264
|
```typescript
|
|
249
|
-
import { ToDoListToDoListOperations } from
|
|
250
|
-
import { ToDoListState } from
|
|
265
|
+
import { ToDoListToDoListOperations } from "../../gen/to-do-list/operations.js";
|
|
266
|
+
import { ToDoListState } from "../../gen/types.js"; // Assuming this now includes the 'stats' object
|
|
251
267
|
|
|
252
268
|
// REMARKS: This is our main reducer object. It implements all operations defined in the schema.
|
|
253
269
|
// The ToDoListToDoListOperations type is auto-generated from our GraphQL specification and ensures type safety.
|
|
254
270
|
export const reducer: ToDoListToDoListOperations = {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
271
|
+
// REMARKS: The addTodoItemOperation adds a new item and updates our tracking statistics.
|
|
272
|
+
// - state: The current document state. Powerhouse uses a library like Immer.js,
|
|
273
|
+
// so you can write code that looks like it's mutating the state directly.
|
|
274
|
+
// Behind the scenes, Powerhouse ensures this results in an immutable update.
|
|
275
|
+
// - action: Contains the operation's 'type' and 'input' data from the client.
|
|
276
|
+
// - dispatch: A function to trigger subsequent operations (advanced, not used here).
|
|
277
|
+
addTodoItemOperation(state, action, dispatch) {
|
|
278
|
+
// REMARKS: We update our statistics for total and unchecked items.
|
|
279
|
+
state.stats.total += 1;
|
|
280
|
+
state.stats.unchecked += 1;
|
|
281
|
+
|
|
282
|
+
// REMARKS: We push the new to-do item into the items array.
|
|
283
|
+
// The data for the new item comes from the operation's input.
|
|
284
|
+
state.items.push({
|
|
285
|
+
id: action.input.id,
|
|
286
|
+
text: action.input.text,
|
|
287
|
+
checked: false, // New items always start as unchecked.
|
|
288
|
+
});
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
// REMARKS: The updateTodoItemOperation modifies an existing to-do item.
|
|
292
|
+
// It handles partial updates for text and checked status.
|
|
293
|
+
updateTodoItemOperation(state, action, dispatch) {
|
|
294
|
+
// REMARKS: First, we find the specific item we want to update using its ID.
|
|
295
|
+
const item = state.items.find((item) => item.id === action.input.id);
|
|
296
|
+
|
|
297
|
+
// REMARKS: It's good practice to handle cases where the item might not be found.
|
|
298
|
+
if (!item) {
|
|
299
|
+
throw new Error(`Item with id ${action.input.id} not found`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// REMARKS: We only update the text if it was provided in the input.
|
|
303
|
+
// This allows for partial updates (e.g., just checking an item without changing its text).
|
|
304
|
+
if (action.input.text) {
|
|
305
|
+
item.text = action.input.text;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// REMARKS: When the checked status changes, we also update our statistics.
|
|
309
|
+
// We check for `true` and `false` explicitly.
|
|
310
|
+
if (action.input.checked) {
|
|
311
|
+
// This is true only if action.input.checked is true
|
|
312
|
+
// Note: This assumes the item was previously unchecked. For a more robust implementation,
|
|
313
|
+
// you could check `if (item.checked === false)` before updating stats to prevent inconsistencies.
|
|
314
|
+
state.stats.unchecked -= 1;
|
|
315
|
+
state.stats.checked += 1;
|
|
316
|
+
item.checked = action.input.checked;
|
|
317
|
+
}
|
|
318
|
+
if (action.input.checked === false) {
|
|
319
|
+
// Note: This assumes the item was previously checked.
|
|
320
|
+
state.stats.unchecked += 1;
|
|
321
|
+
state.stats.checked -= 1;
|
|
322
|
+
item.checked = action.input.checked;
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
|
|
326
|
+
// REMARKS: The deleteTodoItemOperation removes an item from the list.
|
|
327
|
+
deleteTodoItemOperation(state, action, dispatch) {
|
|
328
|
+
// REMARKS: Before removing the item, we find it to determine its checked status.
|
|
329
|
+
// This is necessary to correctly decrement our statistics.
|
|
330
|
+
const item = state.items.find((item) => item.id === action.input.id);
|
|
331
|
+
|
|
332
|
+
// REMARKS: We always decrement the total count.
|
|
333
|
+
state.stats.total -= 1;
|
|
334
|
+
|
|
335
|
+
// REMARKS: We then decrement the 'checked' or 'unchecked' count based on the item's status.
|
|
336
|
+
if (item?.checked) {
|
|
337
|
+
// This is shorthand for item?.checked === true
|
|
338
|
+
state.stats.checked -= 1;
|
|
339
|
+
}
|
|
340
|
+
if (item?.checked === false) {
|
|
341
|
+
state.stats.unchecked -= 1;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// REMARKS: Finally, we create a new 'items' array that excludes the deleted item.
|
|
345
|
+
// Assigning to 'state.items' is handled by Powerhouse to produce a new immutable state.
|
|
346
|
+
state.items = state.items.filter((item) => item.id !== action.input.id);
|
|
347
|
+
},
|
|
330
348
|
};
|
|
331
349
|
```
|
|
332
350
|
|
|
@@ -337,8 +355,9 @@ export const reducer: ToDoListToDoListOperations = {
|
|
|
337
355
|
Every time a reducer processes an operation and returns a new state, Powerhouse records the original operation (the "event") in an append-only log associated with the document instance. The current state of the document is effectively a "fold" or "reduction" of all past events, applied sequentially by the reducers.
|
|
338
356
|
|
|
339
357
|
This is why purity and immutability are so critical:
|
|
340
|
-
|
|
341
|
-
|
|
358
|
+
|
|
359
|
+
- **Purity** ensures that replaying the same sequence of events will always yield the exact same final state.
|
|
360
|
+
- **Immutability** ensures that each event clearly defines a discrete state transition, making it easy to audit changes and understand the document's history.
|
|
342
361
|
|
|
343
362
|
## Conclusion
|
|
344
363
|
|