@powerhousedao/academy 5.0.0-staging.1 → 5.0.0-staging.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/.vscode/settings.json +1 -1
  2. package/CHANGELOG.md +64 -0
  3. package/README.md +3 -3
  4. package/babel.config.js +1 -1
  5. package/blog/BeyondCommunication-ABlueprintForDevelopment.md +25 -24
  6. package/blog/TheChallengeOfChange.md +21 -21
  7. package/docs/academy/01-GetStarted/00-ExploreDemoPackage.mdx +61 -24
  8. package/docs/academy/01-GetStarted/01-CreateNewPowerhouseProject.md +21 -12
  9. package/docs/academy/01-GetStarted/02-DefineToDoListDocumentModel.md +24 -19
  10. package/docs/academy/01-GetStarted/03-ImplementOperationReducers.md +44 -41
  11. package/docs/academy/01-GetStarted/04-BuildToDoListEditor.md +10 -10
  12. package/docs/academy/01-GetStarted/05-SpecDrivenAI.md +143 -0
  13. package/docs/academy/01-GetStarted/home.mdx +185 -90
  14. package/docs/academy/01-GetStarted/styles.module.css +5 -5
  15. package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/01-Prerequisites.md +46 -18
  16. package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/02-StandardDocumentModelWorkflow.md +118 -68
  17. package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/03-BuilderTools.md +75 -33
  18. package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/_category_.json +6 -6
  19. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/01-WhatIsADocumentModel.md +30 -21
  20. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/02-SpecifyTheStateSchema.md +41 -37
  21. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/03-SpecifyDocumentOperations.md +29 -25
  22. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/04-UseTheDocumentModelGenerator.md +36 -37
  23. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/05-ImplementDocumentReducers.md +128 -109
  24. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/06-ImplementDocumentModelTests.md +95 -86
  25. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/07-ExampleToDoListRepository.md +7 -9
  26. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/_category_.json +6 -6
  27. package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/01-BuildingDocumentEditors.md +65 -47
  28. package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/02-ConfiguringDrives.md +77 -62
  29. package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/03-BuildingADriveExplorer.md +360 -349
  30. package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/00-DocumentToolbar.mdx +16 -10
  31. package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/01-OperationHistory.md +10 -7
  32. package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/02-RevisionHistoryTimeline.md +26 -11
  33. package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/_category_.json +6 -6
  34. package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/07-Authorization/01-RenownAuthenticationFlow.md +14 -7
  35. package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/07-Authorization/02-Authorization.md +0 -1
  36. package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/07-Authorization/_category_.json +5 -5
  37. package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/_category_.json +1 -1
  38. package/docs/academy/02-MasteryTrack/04-WorkWithData/01-GraphQLAtPowerhouse.md +45 -33
  39. package/docs/academy/02-MasteryTrack/04-WorkWithData/02-UsingTheAPI.mdx +61 -18
  40. package/docs/academy/02-MasteryTrack/04-WorkWithData/03-UsingSubgraphs.md +50 -54
  41. package/docs/academy/02-MasteryTrack/04-WorkWithData/04-analytics-processor.md +126 -110
  42. package/docs/academy/02-MasteryTrack/04-WorkWithData/05-RelationalDbProcessor.md +75 -45
  43. package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/GraphQL References/QueryingADocumentWithGraphQL.md +23 -21
  44. package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/best-practices.md +9 -9
  45. package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/graphql/index.md +11 -23
  46. package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/graphql/integration.md +25 -9
  47. package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/intro.md +10 -10
  48. package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/typescript/benchmarks.md +1 -1
  49. package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/typescript/index.md +16 -11
  50. package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/typescript/memory.md +6 -5
  51. package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/typescript/schema.md +2 -2
  52. package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/typescript/utilities.md +7 -5
  53. package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/use-cases/maker.md +32 -58
  54. package/docs/academy/02-MasteryTrack/04-WorkWithData/06-Analytics Engine/use-cases/processors.md +1 -1
  55. package/docs/academy/02-MasteryTrack/04-WorkWithData/07-drive-analytics.md +105 -71
  56. package/docs/academy/02-MasteryTrack/04-WorkWithData/_ARCHIVE-AnalyticsProcessorTutorial/_01-SetupBuilderEnvironment.md +22 -0
  57. package/docs/academy/02-MasteryTrack/04-WorkWithData/_ARCHIVE-AnalyticsProcessorTutorial/_02-CreateNewPowerhouseProject.md +9 -8
  58. package/docs/academy/02-MasteryTrack/04-WorkWithData/_ARCHIVE-AnalyticsProcessorTutorial/_03-GenerateAnAnalyticsProcessor.md +28 -32
  59. package/docs/academy/02-MasteryTrack/04-WorkWithData/_ARCHIVE-AnalyticsProcessorTutorial/_04-UpdateAnalyticsProcessor.md +25 -26
  60. package/docs/academy/02-MasteryTrack/04-WorkWithData/_ARCHIVE-AnalyticsProcessorTutorial/_category_.json +1 -1
  61. package/docs/academy/02-MasteryTrack/04-WorkWithData/_category_.json +7 -7
  62. package/docs/academy/02-MasteryTrack/05-Launch/01-IntroductionToPackages.md +3 -4
  63. package/docs/academy/02-MasteryTrack/05-Launch/02-PublishYourProject.md +69 -45
  64. package/docs/academy/02-MasteryTrack/05-Launch/03-SetupEnvironment.md +70 -40
  65. package/docs/academy/02-MasteryTrack/05-Launch/04-ConfigureEnvironment.md +1 -0
  66. package/docs/academy/02-MasteryTrack/05-Launch/_category_.json +7 -7
  67. package/docs/academy/02-MasteryTrack/_category_.json +6 -6
  68. package/docs/academy/03-ExampleUsecases/Chatroom/02-CreateNewPowerhouseProject.md +5 -3
  69. package/docs/academy/03-ExampleUsecases/Chatroom/03-DefineChatroomDocumentModel.md +38 -37
  70. package/docs/academy/03-ExampleUsecases/Chatroom/04-ImplementOperationReducers.md +45 -41
  71. package/docs/academy/03-ExampleUsecases/Chatroom/05-ImplementChatroomEditor.md +14 -14
  72. package/docs/academy/03-ExampleUsecases/Chatroom/06-LaunchALocalReactor.md +6 -6
  73. package/docs/academy/03-ExampleUsecases/Chatroom/_category_.json +1 -1
  74. package/docs/academy/04-APIReferences/00-PowerhouseCLI.md +9 -7
  75. package/docs/academy/04-APIReferences/01-ReactHooks.md +177 -129
  76. package/docs/academy/04-APIReferences/04-RelationalDatabase.md +121 -113
  77. package/docs/academy/04-APIReferences/05-PHDocumentMigrationGuide.md +48 -41
  78. package/docs/academy/04-APIReferences/_category_.json +6 -6
  79. package/docs/academy/05-Architecture/00-PowerhouseArchitecture.md +1 -2
  80. package/docs/academy/05-Architecture/01-WorkingWithTheReactor.md +11 -8
  81. package/docs/academy/05-Architecture/05-DocumentModelTheory/_category_.json +1 -1
  82. package/docs/academy/05-Architecture/_category_.json +6 -6
  83. package/docs/academy/06-ComponentLibrary/00-DocumentEngineering.md +25 -23
  84. package/docs/academy/06-ComponentLibrary/02-CreateCustomScalars.md +105 -93
  85. package/docs/academy/06-ComponentLibrary/03-IntegrateIntoAReactComponent.md +1 -0
  86. package/docs/academy/06-ComponentLibrary/_category_.json +7 -7
  87. package/docs/academy/07-Cookbook.md +267 -34
  88. package/docs/academy/08-Glossary.md +7 -1
  89. package/docs/bookofpowerhouse/01-Overview.md +2 -2
  90. package/docs/bookofpowerhouse/02-GeneralFrameworkAndPhilosophy.md +1 -7
  91. package/docs/bookofpowerhouse/03-PowerhouseSoftwareArchitecture.md +10 -7
  92. package/docs/bookofpowerhouse/04-DevelopmentApproaches.md +10 -4
  93. package/docs/bookofpowerhouse/05-SNOsandANewModelForOSSandPublicGoods.md +23 -30
  94. package/docs/bookofpowerhouse/06-SNOsInActionAndPlatformEconomies.md +0 -7
  95. package/docusaurus.config.ts +64 -66
  96. package/package.json +1 -1
  97. package/scripts/generate-combined-cli-docs.ts +43 -13
  98. package/sidebars.ts +1 -0
  99. package/src/components/HomepageFeatures/index.tsx +171 -78
  100. package/src/components/HomepageFeatures/styles.module.css +1 -2
  101. package/src/css/custom.css +89 -89
  102. package/src/pages/_archive-homepage.tsx +17 -16
  103. package/src/theme/DocCardList/index.tsx +9 -8
  104. package/static.json +6 -6
@@ -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 *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.
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
- * **`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.
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
- * **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.
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
- * **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.
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 '../../gen/to-do-list/operations.js'; // Generated type for operations
51
- import { ToDoListState } from '../../gen/types.js'; // Generated type for state
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
- * `actions.addTodoItem({ id: 'some-id', text: 'New Task' })`
102
- * `actions.updateTodoItem({ id: 'item-id', text: 'Updated Task Text', checked: true })`
103
- * `actions.deleteTodoItem({ id: 'item-id' })`
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
- * We use the spread operator (`...state`) to copy top-level properties from the old state into the new state object.
126
- * For the `items` array, we create a *new* array by spreading the existing `state.items` and then appending the `newItem`.
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
- * We use the `map` array method, which always returns a *new* array.
157
- * 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`.
158
- * 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.
159
- * If an item doesn't match the ID, it's returned as is.
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
- * We use the `filter` array method, which returns a *new* array containing only the elements for which the callback function returns `true`.
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 '../../gen/types.js';
204
- import { ToDoListToDoListOperations } from '../../gen/to-do-list/operations.js'; // Generated operations type
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(state: ToDoListState, action: { input: AddTodoItemInput /* plus type property */ }, dispatch) {
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
- * **Compile-time safety**: Catch errors related to incorrect property names or data types before runtime.
232
- * **Autocompletion and IntelliSense**: Improved developer experience in your IDE.
233
- * **Clearer code**: Types serve as documentation for the expected data structures.
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 '../../gen/to-do-list/operations.js';
250
- import { ToDoListState } from '../../gen/types.js'; // Assuming this now includes the 'stats' object
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
- // REMARKS: The addTodoItemOperation adds a new item and updates our tracking statistics.
256
- // - state: The current document state. Powerhouse uses a library like Immer.js,
257
- // so you can write code that looks like it's mutating the state directly.
258
- // Behind the scenes, Powerhouse ensures this results in an immutable update.
259
- // - action: Contains the operation's 'type' and 'input' data from the client.
260
- // - dispatch: A function to trigger subsequent operations (advanced, not used here).
261
- addTodoItemOperation(state, action, dispatch) {
262
- // REMARKS: We update our statistics for total and unchecked items.
263
- state.stats.total += 1;
264
- state.stats.unchecked += 1;
265
-
266
- // REMARKS: We push the new to-do item into the items array.
267
- // The data for the new item comes from the operation's input.
268
- state.items.push({
269
- id: action.input.id,
270
- text: action.input.text,
271
- checked: false, // New items always start as unchecked.
272
- });
273
- },
274
-
275
- // REMARKS: The updateTodoItemOperation modifies an existing to-do item.
276
- // It handles partial updates for text and checked status.
277
- updateTodoItemOperation(state, action, dispatch) {
278
- // REMARKS: First, we find the specific item we want to update using its ID.
279
- const item = state.items.find(item => item.id === action.input.id);
280
-
281
- // REMARKS: It's good practice to handle cases where the item might not be found.
282
- if (!item) {
283
- throw new Error(`Item with id ${action.input.id} not found`);
284
- }
285
-
286
- // REMARKS: We only update the text if it was provided in the input.
287
- // This allows for partial updates (e.g., just checking an item without changing its text).
288
- if (action.input.text) {
289
- item.text = action.input.text;
290
- }
291
-
292
- // REMARKS: When the checked status changes, we also update our statistics.
293
- // We check for `true` and `false` explicitly.
294
- if (action.input.checked) { // This is true only if action.input.checked is true
295
- // Note: This assumes the item was previously unchecked. For a more robust implementation,
296
- // you could check `if (item.checked === false)` before updating stats to prevent inconsistencies.
297
- state.stats.unchecked -= 1;
298
- state.stats.checked += 1;
299
- item.checked = action.input.checked;
300
- }
301
- if (action.input.checked === false) {
302
- // Note: This assumes the item was previously checked.
303
- state.stats.unchecked += 1;
304
- state.stats.checked -= 1;
305
- item.checked = action.input.checked;
306
- }
307
- },
308
-
309
- // REMARKS: The deleteTodoItemOperation removes an item from the list.
310
- deleteTodoItemOperation(state, action, dispatch) {
311
- // REMARKS: Before removing the item, we find it to determine its checked status.
312
- // This is necessary to correctly decrement our statistics.
313
- const item = state.items.find(item => item.id === action.input.id);
314
-
315
- // REMARKS: We always decrement the total count.
316
- state.stats.total -= 1;
317
-
318
- // REMARKS: We then decrement the 'checked' or 'unchecked' count based on the item's status.
319
- if (item?.checked) { // This is shorthand for item?.checked === true
320
- state.stats.checked -= 1;
321
- }
322
- if (item?.checked === false) {
323
- state.stats.unchecked -= 1;
324
- }
325
-
326
- // REMARKS: Finally, we create a new 'items' array that excludes the deleted item.
327
- // Assigning to 'state.items' is handled by Powerhouse to produce a new immutable state.
328
- state.items = state.items.filter(item => item.id !== action.input.id);
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
- * **Purity** ensures that replaying the same sequence of events will always yield the exact same final state.
341
- * **Immutability** ensures that each event clearly defines a discrete state transition, making it easy to audit changes and understand the document's history.
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
 
@@ -22,77 +22,85 @@ With the reducer logic in place, it's critical to test it. Navigate to the gener
22
22
  This suite tests each operation, verifying not only that the `items` array is correct, but also that our `stats` object is updated as expected and that the operation itself is recorded properly in the document's history.
23
23
 
24
24
  ```typescript
25
- import utils from '../../gen/utils.js';
26
- import { reducer } from '../../gen/reducer.js';
27
- import * as creators from '../../gen/creators.js';
28
- import { ToDoListDocument } from '../../gen/types.js';
29
-
30
- describe('Todolist Operations', () => {
31
- let document: ToDoListDocument;
32
-
33
- beforeEach(() => {
34
- // REMARKS: We start with a fresh, empty document for each test.
35
- // The `createDocument` utility initializes the state with an empty 'items' array
36
- // and a 'stats' object with all counts set to 0.
37
- document = utils.createDocument();
38
- });
39
-
40
- it('should handle addTodoItem operation', () => {
41
- const input = { id: '1', text: 'Buy milk' };
42
-
43
- // REMARKS: We apply the 'addTodoItem' operation.
44
- const updatedDocument = reducer(document, creators.addTodoItem(input));
45
-
46
- // REMARKS: We verify the operation was recorded in the document's history.
47
- // Powerhouse records every operation in an array.
48
- expect(updatedDocument.operations.global).toHaveLength(1);
49
- expect(updatedDocument.operations.global[0].type).toBe('ADD_TODO_ITEM');
50
- // REMARKS: We also check that the input data and index are recorded correctly.
51
- expect(updatedDocument.operations.global[0].input).toStrictEqual(input);
52
- expect(updatedDocument.operations.global[0].index).toEqual(0);
53
-
54
- // REMARKS: Finally, we verify the state was updated according to our reducer logic.
55
- expect(updatedDocument.state.global.items).toHaveLength(1);
56
- expect(updatedDocument.state.global.stats.total).toBe(1);
57
- expect(updatedDocument.state.global.stats.unchecked).toBe(1);
58
- });
59
-
60
- it('should handle updateTodoItem operation', () => {
61
- // REMARKS: For an update, we first need to add an item.
62
- const addInput = { id: '1', text: 'Buy milk' };
63
- const updateInput = { id: '1', checked: true }; // We'll test checking the item.
64
-
65
- // REMARKS: Operations are applied sequentially to build up document state.
66
- const createdDocument = reducer(document, creators.addTodoItem(addInput));
67
- const updatedDocument = reducer(createdDocument, creators.updateTodoItem(updateInput));
68
-
69
- // REMARKS: Now we should have 2 operations in the history.
70
- expect(updatedDocument.operations.global).toHaveLength(2);
71
- expect(updatedDocument.operations.global[1].type).toBe('UPDATE_TODO_ITEM');
72
- expect(updatedDocument.operations.global[1].input).toStrictEqual(updateInput);
73
-
74
- // REMARKS: We check that the state reflects the update, including our stats.
75
- expect(updatedDocument.state.global.items[0].checked).toBe(true);
76
- expect(updatedDocument.state.global.stats.total).toBe(1);
77
- expect(updatedDocument.state.global.stats.unchecked).toBe(0);
78
- expect(updatedDocument.state.global.stats.checked).toBe(1);
79
- });
80
-
81
- it('should handle deleteTodoItem operation', () => {
82
- const addInput = { id: '1', text: 'Buy milk' };
83
- const deleteInput = { id: '1' };
84
-
85
- const createdDocument = reducer(document, creators.addTodoItem(addInput));
86
- const updatedDocument = reducer(createdDocument, creators.deleteTodoItem(deleteInput));
87
-
88
- // REMARKS: After deletion, we still have 2 operations in history,
89
- // but the items array is now empty and the stats are back to zero.
90
- expect(updatedDocument.operations.global).toHaveLength(2);
91
- expect(updatedDocument.operations.global[1].type).toBe('DELETE_TODO_ITEM');
92
- expect(updatedDocument.state.global.items).toHaveLength(0);
93
- expect(updatedDocument.state.global.stats.total).toBe(0);
94
- expect(updatedDocument.state.global.stats.unchecked).toBe(0);
95
- });
25
+ import utils from "../../gen/utils.js";
26
+ import { reducer } from "../../gen/reducer.js";
27
+ import * as creators from "../../gen/creators.js";
28
+ import { ToDoListDocument } from "../../gen/types.js";
29
+
30
+ describe("Todolist Operations", () => {
31
+ let document: ToDoListDocument;
32
+
33
+ beforeEach(() => {
34
+ // REMARKS: We start with a fresh, empty document for each test.
35
+ // The `createDocument` utility initializes the state with an empty 'items' array
36
+ // and a 'stats' object with all counts set to 0.
37
+ document = utils.createDocument();
38
+ });
39
+
40
+ it("should handle addTodoItem operation", () => {
41
+ const input = { id: "1", text: "Buy milk" };
42
+
43
+ // REMARKS: We apply the 'addTodoItem' operation.
44
+ const updatedDocument = reducer(document, creators.addTodoItem(input));
45
+
46
+ // REMARKS: We verify the operation was recorded in the document's history.
47
+ // Powerhouse records every operation in an array.
48
+ expect(updatedDocument.operations.global).toHaveLength(1);
49
+ expect(updatedDocument.operations.global[0].type).toBe("ADD_TODO_ITEM");
50
+ // REMARKS: We also check that the input data and index are recorded correctly.
51
+ expect(updatedDocument.operations.global[0].input).toStrictEqual(input);
52
+ expect(updatedDocument.operations.global[0].index).toEqual(0);
53
+
54
+ // REMARKS: Finally, we verify the state was updated according to our reducer logic.
55
+ expect(updatedDocument.state.global.items).toHaveLength(1);
56
+ expect(updatedDocument.state.global.stats.total).toBe(1);
57
+ expect(updatedDocument.state.global.stats.unchecked).toBe(1);
58
+ });
59
+
60
+ it("should handle updateTodoItem operation", () => {
61
+ // REMARKS: For an update, we first need to add an item.
62
+ const addInput = { id: "1", text: "Buy milk" };
63
+ const updateInput = { id: "1", checked: true }; // We'll test checking the item.
64
+
65
+ // REMARKS: Operations are applied sequentially to build up document state.
66
+ const createdDocument = reducer(document, creators.addTodoItem(addInput));
67
+ const updatedDocument = reducer(
68
+ createdDocument,
69
+ creators.updateTodoItem(updateInput),
70
+ );
71
+
72
+ // REMARKS: Now we should have 2 operations in the history.
73
+ expect(updatedDocument.operations.global).toHaveLength(2);
74
+ expect(updatedDocument.operations.global[1].type).toBe("UPDATE_TODO_ITEM");
75
+ expect(updatedDocument.operations.global[1].input).toStrictEqual(
76
+ updateInput,
77
+ );
78
+
79
+ // REMARKS: We check that the state reflects the update, including our stats.
80
+ expect(updatedDocument.state.global.items[0].checked).toBe(true);
81
+ expect(updatedDocument.state.global.stats.total).toBe(1);
82
+ expect(updatedDocument.state.global.stats.unchecked).toBe(0);
83
+ expect(updatedDocument.state.global.stats.checked).toBe(1);
84
+ });
85
+
86
+ it("should handle deleteTodoItem operation", () => {
87
+ const addInput = { id: "1", text: "Buy milk" };
88
+ const deleteInput = { id: "1" };
89
+
90
+ const createdDocument = reducer(document, creators.addTodoItem(addInput));
91
+ const updatedDocument = reducer(
92
+ createdDocument,
93
+ creators.deleteTodoItem(deleteInput),
94
+ );
95
+
96
+ // REMARKS: After deletion, we still have 2 operations in history,
97
+ // but the items array is now empty and the stats are back to zero.
98
+ expect(updatedDocument.operations.global).toHaveLength(2);
99
+ expect(updatedDocument.operations.global[1].type).toBe("DELETE_TODO_ITEM");
100
+ expect(updatedDocument.state.global.items).toHaveLength(0);
101
+ expect(updatedDocument.state.global.stats.total).toBe(0);
102
+ expect(updatedDocument.state.global.stats.unchecked).toBe(0);
103
+ });
96
104
  });
97
105
  ```
98
106
 
@@ -112,28 +120,29 @@ If all tests pass, you have successfully verified the core logic of your `To-do
112
120
 
113
121
  While the tutorial provides a concrete example, keep these general best practices in mind when writing your tests:
114
122
 
115
- * **Isolate Tests**: Each `it` block should ideally test one specific aspect or scenario. `beforeEach` is crucial for resetting state between tests.
116
- * **Descriptive Names**: Name your `describe` and `it` blocks clearly so they explain what's being tested.
117
- * **AAA Pattern (Arrange, Act, Assert)**:
118
- * **Arrange**: Set up the initial state and any required test data (e.g., using `utils.createDocument()` and defining `input` objects).
119
- * **Act**: Execute the operation by calling the `reducer` with an action from a `creator`.
120
- * **Assert**: Check if the outcome is as expected using `expect()`.
121
- * **Test Immutability**: A key assertion is to ensure the state is not mutated directly. You can check that a new array or object was created: `expect(newState.items).not.toBe(oldState.items);`.
122
- * **Cover Edge Cases**: Test what happens when an operation receives invalid input (e.g., trying to update an item that doesn't exist). Your test should confirm the reducer either throws an error or returns the state unchanged, depending on your implementation.
123
- * **Run Tests Frequently**: Integrate testing into your development workflow. Run tests after making changes to ensure you haven't broken anything. The `pnpm run test` command is your friend.
123
+ - **Isolate Tests**: Each `it` block should ideally test one specific aspect or scenario. `beforeEach` is crucial for resetting state between tests.
124
+ - **Descriptive Names**: Name your `describe` and `it` blocks clearly so they explain what's being tested.
125
+ - **AAA Pattern (Arrange, Act, Assert)**:
126
+ - **Arrange**: Set up the initial state and any required test data (e.g., using `utils.createDocument()` and defining `input` objects).
127
+ - **Act**: Execute the operation by calling the `reducer` with an action from a `creator`.
128
+ - **Assert**: Check if the outcome is as expected using `expect()`.
129
+ - **Test Immutability**: A key assertion is to ensure the state is not mutated directly. You can check that a new array or object was created: `expect(newState.items).not.toBe(oldState.items);`.
130
+ - **Cover Edge Cases**: Test what happens when an operation receives invalid input (e.g., trying to update an item that doesn't exist). Your test should confirm the reducer either throws an error or returns the state unchanged, depending on your implementation.
131
+ - **Run Tests Frequently**: Integrate testing into your development workflow. Run tests after making changes to ensure you haven't broken anything. The `pnpm run test` command is your friend.
124
132
 
125
133
  ## Conclusion: The payoff of diligent testing
126
134
 
127
135
  Implementing comprehensive tests for your document model reducers is an investment that pays dividends in the long run. It leads to:
128
136
 
129
- * **Higher Quality Models**: More reliable and robust document models with fewer bugs.
130
- * **Increased Confidence**: Ability to make changes and refactor code without fear of breaking existing functionality.
131
- * **Easier Debugging**: When tests fail, they pinpoint the exact operation and scenario that's problematic.
132
- * **Better Collaboration**: Tests clarify the intended behavior of the document model for all team members.
137
+ - **Higher Quality Models**: More reliable and robust document models with fewer bugs.
138
+ - **Increased Confidence**: Ability to make changes and refactor code without fear of breaking existing functionality.
139
+ - **Easier Debugging**: When tests fail, they pinpoint the exact operation and scenario that's problematic.
140
+ - **Better Collaboration**: Tests clarify the intended behavior of the document model for all team members.
133
141
 
134
142
  By following the tutorial and applying these best practices, you can build a strong suite of tests that safeguard the integrity and functionality of your document models. This diligence is a hallmark of a "Mastery Track" developer, ensuring that the solutions you build are not just functional but also stable, maintainable, and trustworthy.
135
143
 
136
144
  ## Up next
137
- In the next chapter of the Mastery Track - Building User Experiences you will learn how to implement an [editor](/academy/MasteryTrack/BuildingUserExperiences/BuildingDocumentEditors) for your document model so you can see a simple user interface for the **To-do List** document model in action.
138
145
 
139
- For a complete, working example, you can always have a look at the [Example To-do List Repository](/academy/MasteryTrack/DocumentModelCreation/ExampleToDoListRepository) which contains the full implementation of the concepts discussed in this Mastery Track.
146
+ In the next chapter of the Mastery Track - Building User Experiences you will learn how to implement an [editor](/academy/MasteryTrack/BuildingUserExperiences/BuildingDocumentEditors) for your document model so you can see a simple user interface for the **To-do List** document model in action.
147
+
148
+ For a complete, working example, you can always have a look at the [Example To-do List Repository](/academy/MasteryTrack/DocumentModelCreation/ExampleToDoListRepository) which contains the full implementation of the concepts discussed in this Mastery Track.
@@ -16,12 +16,13 @@ Follow the steps in the "Mastery Track – Document Model Creation" chapters to
16
16
 
17
17
  ### Option 2: Clone and run the code locally
18
18
 
19
- The package includes:
20
- - The Document Model
21
- - Reducer Code
22
- - Reducer Tests
23
- - Editor Code
24
- - Drive Explorer Code
19
+ The package includes:
20
+
21
+ - The Document Model
22
+ - Reducer Code
23
+ - Reducer Tests
24
+ - Editor Code
25
+ - Drive Explorer Code
25
26
 
26
27
  You can clone the repository and run Connect Studio to see all the code in action:
27
28
 
@@ -36,6 +37,3 @@ Alternatively, you can install this package in a Powerhouse project or in your d
36
37
  ```bash
37
38
  ph install @powerhousedao/todo-demo-package
38
39
  ```
39
-
40
-
41
-
@@ -1,7 +1,7 @@
1
1
  {
2
- "label": "Document Model Creation",
3
- "link": {
4
- "type": "doc",
5
- "id": "academy/MasteryTrack/DocumentModelCreation/WhatIsADocumentModel"
6
- }
7
- }
2
+ "label": "Document Model Creation",
3
+ "link": {
4
+ "type": "doc",
5
+ "id": "academy/MasteryTrack/DocumentModelCreation/WhatIsADocumentModel"
6
+ }
7
+ }