Skip to content

Commit 6824670

Browse files
committed
Adding Readme
1 parent 89035b6 commit 6824670

1 file changed

Lines changed: 233 additions & 45 deletions

File tree

readme.md

Lines changed: 233 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,251 @@
11
# ClassPlus
22

3+
A Motoko library designed to reduce boilerplate when instantiating and managing class-like objects within actor classes. ClassPlus enables developers to create modular, upgrade-friendly classes that leverage stable variables for persistence across upgrades.
34

4-
A simple library to reduce boilerplate when instantiating ClassPlus Objects in Motoko.
5+
---
56

6-
# Requirements
7+
## Requirements
78

8-
At least DFX 0.24.0
9+
- **DFX Version**: Requires DFX 0.24.0 or later.
910

11+
---
1012

11-
# Usage
13+
## Installation
1214

13-
ClassPlus classes should be used from actor classes. They simplify boilerplate for constructing class objects that *virtually* survive upgrades. Upon each upgrade they are reconstituted from stable variables in the canister.
15+
`mops add class-plus`
1416

15-
ClassPlus objects need to be created with the signature:
17+
## Overview
1618

17-
`public class AClass(stored: ?State, caller: Principal, canister: Principal, _environment: ?Environment)`
19+
ClassPlus simplifies the process of defining and managing objects in actor classes by:
1820

19-
The State and Environment Objects can very and are handled with generic typing so you can define those to be what every you need. Typically you'll want to use a migration pattern for your state structure and it needs to be made up of stable compatible objects.
21+
1. **Reducing Boilerplate**: It minimizes repetitive code for constructing and maintaining objects.
22+
2. **Supporting Upgrades**: Ensures objects can be reconstituted from stable variables after an upgrade.
23+
3. **Encapsulating Complexity**: Provides a unified interface for initialization, state management, and environment configuration.
2024

21-
In your class will need to define:
25+
ClassPlus objects are instantiated with a predefined structure and integrate seamlessly into actor classes.
2226

23-
- `public type State = {}` - What is the shape of your state. These all need to be stable compatible variables.
24-
- `public let initialState = {}` - What are the defaults? You can update these in the initialization step if they are not ready.
25-
- `public type Environment = {}` - Your class can support environment style variables unique to your class instantiation.
27+
---
2628

29+
## Usage
2730

28-
Instantiating your class in your actor is accomplished as follows:
31+
### Core Concepts
2932

30-
```
33+
1. **State**: The shape of the class's state, stored in stable variables, must be composed of stable-compatible types.
34+
2. **Environment**: Optional environment variables passed to the class for contextual operations.
35+
3. **Initialization**: Initialization logic, including setup and configuration, can be provided during class creation.
36+
37+
### Class Definition
38+
39+
To define a class compatible with ClassPlus, follow this structure:
40+
41+
#### Example Class Definition
42+
43+
```motoko
44+
public class AClass(stored: ?State, caller: Principal, canister: Principal, args: ?InitArgs, _environment: ?Environment, onStateChange: (State) -> ()) {
45+
// Define the initial state.
46+
public let state = switch(stored) {
47+
case (?val) val;
48+
case (null) initialState();
49+
};
50+
51+
// Notify about state changes.
52+
onStateChange(state);
3153
32-
let initManager = ClassPlus.ClassPlusInitializationManager();
33-
34-
stable let aClass_state : AClass.State = AClass.initialState;
35-
36-
let aClass = ClassPlus.ClassPlus<system,
37-
AClass.AClass,
38-
AClass.State,
39-
AClass.Environment>(
40-
_owner, //typically the msg.caller from your canister creation
41-
actor(Principal.toText(Principal.fromActor(this))), //important - helps capture the current canister you are running on.
42-
aClass_state,
43-
AClass.AClass,
44-
initManager,
45-
// set up any environment settings here
46-
?(func() : AClass.Environment {
47-
{
48-
//you can set up any post install references here. If you need references to other Class Plus items here you can reference them here as long as they are initialized before hand. Order is important.
49-
thisActor = actor(Principal.toText(Principal.fromActor(this)));
54+
// Capture environment settings.
55+
let environment: Environment = switch(_environment) {
56+
case (?val) val;
57+
case (null) D.trap("No Environment Set");
58+
};
59+
60+
// Apply initial arguments, if provided.
61+
switch (args) {
62+
case (?val) {
63+
if (state.message == "Uninitialized") {
64+
state.message := val.messageModifier;
65+
}
5066
};
51-
}),
52-
//any initialization code
53-
?(func () : async* () {
54-
D.print("Initializing AClass");
55-
//do any work here necessary for initialization
56-
})
57-
).get;
58-
59-
public shared func getMessage() : async Text {
60-
//this is how you use your class
61-
aClass().message();
62-
}
63-
```
67+
case (null) {};
68+
};
69+
70+
// Define class methods.
71+
public func message(): Text {
72+
state.message # " from canister " # Principal.toText(canister) # " created by " # Principal.toText(caller);
73+
};
74+
75+
public func setMessage(x: Text): () {
76+
state.message := x;
77+
};
78+
}
79+
```
80+
81+
#### Required Definitions
82+
83+
1. **`State`**: Define the structure of the class's state.
84+
85+
```motoko
86+
public type State = {
87+
var message: Text;
88+
};
89+
```
90+
91+
2. **`Environment`**: Define any environment variables (optional).
92+
93+
```motoko
94+
public type Environment = {
95+
thisActor: actor {
96+
auto_init: () -> async ();
97+
};
98+
};
99+
```
100+
101+
3. **`initialState`**: Define default state values.
102+
103+
```motoko
104+
public func initialState(): State = {
105+
var message = "Uninitialized";
106+
};
107+
```
108+
109+
4. **`InitArgs`**: Define any arguments required for initialization (optional).
110+
111+
```motoko
112+
public type InitArgs = {
113+
messageModifier: Text;
114+
};
115+
```
116+
117+
### Instantiating the Class in an Actor
118+
119+
Use the `ClassPlus` library to simplify instantiation and initialization within an actor.
120+
121+
#### Example Actor Definition
122+
123+
```motoko
124+
import AClassLib "aclass";
125+
import ClassPlus "../";
126+
127+
shared ({ caller = _owner }) actor class Token () = this {
128+
type AClass = AClassLib.AClass;
129+
type State = AClassLib.State;
130+
type InitArgs = AClassLib.InitArgs;
131+
type Environment = AClassLib.Environment;
132+
133+
let initManager = ClassPlus.ClassPlusInitializationManager(_owner, Principal.fromActor(this), true);
134+
135+
stable var aClass_state: State = AClassLib.initialState();
136+
137+
let aClass = AClassLib.Init<system>({
138+
manager = initManager;
139+
initialState = aClass_state;
140+
args = ?({ messageModifier = "Hello World" });
141+
pullEnvironment = ?(func() : Environment {
142+
{
143+
thisActor = actor(Principal.toText(Principal.fromActor(this)));
144+
};
145+
});
146+
onInitialize = ?(func(newClass: AClassLib.AClass): async* () {
147+
D.print("Initializing AClass");
148+
});
149+
onStorageChange = func(new_state: State) {
150+
aClass_state := new_state;
151+
}
152+
});
153+
154+
public shared func getMessage(): async Text {
155+
aClass().message();
156+
};
157+
158+
public shared func SetMessage(x: Text): async () {
159+
aClass().setMessage(x);
160+
};
161+
162+
private shared func initStuff(): async* (){
163+
//add init logic here
164+
}
165+
166+
initManager.calls.add(initStuff);
167+
};
168+
```
169+
170+
---
171+
172+
## ClassPlus Library API
173+
174+
### **Modules and Classes**
175+
176+
#### **`ClassPlusInitializationManager`**
177+
178+
Handles initialization and tracking of ClassPlus objects.
179+
180+
- **Constructor**: `ClassPlusInitializationManager(_owner: Principal, _canister: Principal, autoTimer: Bool)`
181+
182+
- `_owner`: The principal of the actor owner.
183+
- `_canister`: The principal of the canister where the object resides.
184+
- `autoTimer`: Automatically initialize objects on a timer.
185+
186+
- **Methods**:
187+
188+
- `initialize(): async* ()`
189+
- Executes initialization logic for all registered classes.
190+
191+
- Members
192+
193+
- calls: Buffer.Buffer(() ->async\*()
194+
- queue up functions to call during initialization by adding them to the calls buffer. They will be executed in the order you add them.
195+
196+
#### **`ClassPlus`**
197+
198+
Encapsulates logic for creating and managing a class instance.
199+
200+
- **Constructor**: `ClassPlus<system, T, S, A, E>(config: {...})`
201+
202+
- `manager`: Instance of `ClassPlusInitializationManager`.
203+
- `initialState`: Initial state of the class.
204+
- `constructor`: Constructor function for the class.
205+
- `args`: Optional initialization arguments.
206+
- `pullEnvironment`: Function to retrieve environment variables.
207+
- `onInitialize`: Optional initialization logic.
208+
- `onStorageChange`: Callback for state updates.
209+
210+
- **Methods**:
211+
212+
- `get(): T`
213+
- Retrieves the class instance, creating it if necessary.
214+
- `initialize(): async* ()`
215+
- Performs any setup logic for the class.
216+
- `getState(): S`
217+
- Retrieves the current state.
218+
- `getEnvironment(): ?E`
219+
- Retrieves the environment, initializing it if necessary.
220+
221+
### **Helper Functions**
222+
223+
#### **`ClassPlusGetter`**
224+
225+
Simplifies retrieval of a class instance.
226+
227+
```motoko
228+
public func ClassPlusGetter<T, S, A, E>(x: ?ClassPlus<T, S, A, E>): () -> T;
229+
```
230+
231+
#### **`BuildInit`**
232+
233+
Constructs initialization logic for a class.
234+
235+
```motoko
236+
public func BuildInit<system, T, S, A, E>(Constructor: (...)): (...) -> ();
237+
```
238+
239+
---
240+
241+
## Advantages of ClassPlus
242+
243+
- **Reduced Boilerplate**: Eliminates repetitive code in actor classes.
244+
- **Upgrade-Safe**: Ensures class objects can be reconstituted from stable variables.
245+
- **Modular and Organized**: Provides a clear structure for defining and managing classes.
246+
- **Automatic Initialization**: Built-in timer management simplifies initialization.
247+
248+
---
249+
250+
This library is ideal for projects requiring modular, upgrade-friendly object management in Motoko. By leveraging ClassPlus, developers can focus more on functionality and less on boilerplate code.
251+

0 commit comments

Comments
 (0)