Skip to content

Commit 0ac384e

Browse files
committed
📝 Add CLAUDE.md
1 parent 8de6507 commit 0ac384e

1 file changed

Lines changed: 330 additions & 0 deletions

File tree

CLAUDE.md

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
# Protoblast Development Guide
2+
3+
## Overview
4+
5+
Protoblast is a native object expansion library that adds utility methods to JavaScript built-in types. It provides the foundation for the entire Alchemy ecosystem, including the class inheritance system used by AlchemyMVC, Hawkejs, and other projects.
6+
7+
## Commands
8+
- Run tests: `npm test`
9+
- Run with coverage: `npm run coverage`
10+
11+
## Usage Modes
12+
13+
### Modify Native Prototypes (Default)
14+
```javascript
15+
// Adds methods directly to native objects
16+
require('protoblast')();
17+
18+
'hello world'.after('hello'); // ' world'
19+
```
20+
21+
### Bound Functions Mode
22+
```javascript
23+
// Does not modify native objects - safer for libraries
24+
const Blast = require('protoblast')(false);
25+
26+
Blast.Bound.String.after('hello world', 'hello'); // ' world'
27+
```
28+
29+
## Core Concepts
30+
31+
### Global Variables (when loaded)
32+
- `Blast` - The main Protoblast instance
33+
- `Classes` - All registered classes (e.g., `Classes.Informer`, `Classes.Pledge`)
34+
- `Fn` - Alias for `Blast.Collection.Function`
35+
- `Obj` - Alias for `Blast.Bound.Object`
36+
- `Bound` - Contains bound versions of all methods
37+
38+
These variables are actually NOT globals, they're just made available via the custom module wrapper used by Protoblast.
39+
40+
### Environment Detection
41+
```javascript
42+
Blast.isBrowser // Running in browser
43+
Blast.isNode // Running in Node.js
44+
Blast.isServer // Node.js or Bun
45+
Blast.isBun // Running in Bun
46+
```
47+
48+
## Adding Methods to Native Types
49+
50+
Each type file (e.g., `lib/string.js`) uses definers created at the top:
51+
52+
```javascript
53+
const defStat = Blast.createStaticDefiner('String'), // For String.method()
54+
defProto = Blast.createProtoDefiner('String'); // For 'str'.method()
55+
56+
// Add a static method: String.isLetter(char)
57+
defStat(function isLetter(character) {
58+
return character.toUpperCase() !== character.toLowerCase();
59+
});
60+
61+
// Add a prototype method: 'hello'.after('he')
62+
defProto(function after(needle) {
63+
// implementation
64+
});
65+
```
66+
67+
For shimming existing methods only when they don't exist:
68+
```javascript
69+
defProto(function trim() { ... }, true); // true = shim only
70+
```
71+
72+
## Browser Builds
73+
74+
Code between `// PROTOBLAST START CUT` and `// PROTOBLAST END CUT` is stripped from browser builds. Use this for Node.js-only code:
75+
76+
```javascript
77+
// PROTOBLAST START CUT
78+
const fs = require('fs');
79+
// Node-only implementation
80+
// PROTOBLAST END CUT
81+
```
82+
83+
The `//_PROTOBLAST_ENV_//` marker is replaced with environment configuration during builds.
84+
85+
## Class Inheritance System
86+
87+
Protoblast provides `Function.inherits()` which is the foundation for all Alchemy projects:
88+
89+
```javascript
90+
// Create a new class
91+
const Animal = Function.inherits(function Animal(name) {
92+
this.name = name;
93+
});
94+
95+
// Add instance method - function name becomes method name
96+
Animal.setMethod(function speak() {
97+
return this.name + ' makes a sound';
98+
});
99+
100+
// Add static method
101+
Animal.setStatic(function create(name) {
102+
return new this(name);
103+
});
104+
105+
// Add getter/setter property
106+
Animal.setProperty(function upperName() {
107+
return this.name.toUpperCase();
108+
});
109+
110+
// Lazy-initialized property (computed once on first access)
111+
Animal.prepareProperty('cache', function() {
112+
return new Map();
113+
});
114+
115+
// Inherit from Animal with namespace
116+
const Dog = Function.inherits('Animal', 'MyApp', function Dog(name) {
117+
Dog.super.call(this, name);
118+
});
119+
120+
// Deferred setup (runs after class hierarchy is ready)
121+
Dog.constitute(function setup() {
122+
// Configure class after all parents are ready
123+
});
124+
125+
// Mark class as abstract
126+
Animal.makeAbstractClass();
127+
128+
// Get all child classes
129+
Animal.getDescendants(); // Returns array of child classes
130+
Animal.getDescendantsDict(); // Returns {type_name: ChildClass} map
131+
Animal.getDescendant('dog'); // Get specific child by type_name
132+
```
133+
134+
### Class Path Format
135+
136+
```javascript
137+
// Function.inherits(parent, namespace, constructor)
138+
Function.inherits('Alchemy.Base', 'MyApp', function MyClass() {});
139+
// -> Classes.MyApp.MyClass
140+
141+
// Without namespace: class goes in parent's namespace
142+
Function.inherits('Alchemy.Base', function MyClass() {});
143+
// -> Classes.Alchemy.MyClass
144+
```
145+
146+
**Namespace vs class as parent:**
147+
148+
If the parent path points to a namespace, it inherits from `Namespace.Namespace`:
149+
```javascript
150+
// Alchemy.Widget is a namespace containing Alchemy.Widget.Widget
151+
Function.inherits('Alchemy.Widget', function MyWidget() {});
152+
// Inherits from: Alchemy.Widget.Widget
153+
// Result: Classes.Alchemy.Widget.MyWidget
154+
```
155+
156+
If the parent path points to a class directly, child goes in parent's namespace:
157+
```javascript
158+
// Alchemy.Base is a class at Classes.Alchemy.Base (not a namespace)
159+
Function.inherits('Alchemy.Base', function MyClass() {});
160+
// Result: Classes.Alchemy.MyClass (NOT Classes.Alchemy.Base.MyClass)
161+
```
162+
163+
## Key Classes
164+
165+
### Informer
166+
Event emitter with queryable filters:
167+
```javascript
168+
const emitter = new Blast.Classes.Informer();
169+
170+
emitter.on('event', callback); // Simple event
171+
emitter.on({type: 'user', action: 'login'}, callback); // Filter-based
172+
emitter.once('ready', callback); // Fire once
173+
emitter.after('ready', callback); // Fire after event (or immediately if seen)
174+
emitter.emit('event', data);
175+
emitter.hasBeenSeen('ready');
176+
emitter.unsee('ready');
177+
```
178+
179+
### Pledge
180+
Promise implementation with extra features:
181+
```javascript
182+
const Pledge = Blast.Classes.Pledge;
183+
184+
const pledge = new Pledge((resolve, reject) => resolve('value'));
185+
186+
pledge.state; // 0=pending, 1=resolved, 2=rejected
187+
pledge.isPending();
188+
pledge.done((err, result) => {}); // Node-style callback
189+
190+
// Progress tracking
191+
pledge.addProgressPart(10);
192+
pledge.reportProgressPart(1);
193+
194+
// Variants
195+
new Pledge.Lazy(executor); // Starts on first .then()
196+
new Pledge.Timeout(executor, 5000); // Auto-rejects after timeout
197+
new Pledge.Swift(executor); // Synchronous when possible
198+
```
199+
200+
### Swift (High-Performance Pledge)
201+
For performance-critical code where synchronous execution is preferred:
202+
```javascript
203+
const Swift = Blast.Classes.Pledge.Swift;
204+
205+
Swift.execute(valueOrPromise); // Returns value directly if resolved
206+
Swift.waterfall(task1, task2, task3);
207+
Swift.parallel([task1, task2]);
208+
Swift.done(value, (err, result) => {});
209+
```
210+
211+
## Async Flow Control
212+
213+
```javascript
214+
// Sequential execution
215+
Function.series([task1, task2, task3], callback);
216+
await Function.series([task1, task2]);
217+
218+
// Parallel execution (optionally limited)
219+
Function.parallel([task1, task2, task3], callback);
220+
Function.parallel(2, [task1, task2, task3], callback); // Max 2 concurrent
221+
222+
// Pass results through chain
223+
Function.waterfall(
224+
(next) => next(null, 'value1'),
225+
(prev, next) => next(null, prev + 'value2'),
226+
callback
227+
);
228+
229+
// Loop constructs
230+
Function.while(testFn, taskFn, callback);
231+
Function.forEach(data, (value, key, next) => {}, callback);
232+
```
233+
234+
## Lifecycle Hooks
235+
236+
```javascript
237+
Blast.ready(() => { /* All Blast classes available */ });
238+
Blast.loaded(() => { /* All scripts loaded */ });
239+
Blast.queueTick(fn);
240+
Blast.queueImmediate(fn);
241+
```
242+
243+
## Directory Structure
244+
245+
```
246+
lib/
247+
├── init.js # Entry point, core initialization
248+
├── blast.js # Blast instance methods
249+
├── function_inheritance.js # Function.inherits() and class system
250+
├── function_flow.js # series, parallel, waterfall, etc.
251+
├── informer.js # Event emitter class
252+
├── pledge.js # Promise implementation
253+
├── string.js # String prototype extensions
254+
├── array.js # Array prototype extensions
255+
├── object.js # Object utilities
256+
├── date.js # Date extensions
257+
├── json.js # JSON-Dry integration
258+
└── ... # See lib/ for full list
259+
```
260+
261+
## JSON-Dry Integration
262+
263+
Protoblast integrates [json-dry](https://github.com/11ways/json-dry) for serialization and cloning.
264+
265+
```javascript
266+
// Serialize to JSON string (preserves references, class instances)
267+
let dried = JSON.dry(obj);
268+
let undried = JSON.undry(dried);
269+
270+
// Clone (returns live object, not string)
271+
let cloned = JSON.clone(obj);
272+
let prepared = JSON.clone(obj, 'toHawkejs'); // With custom method
273+
```
274+
275+
**Custom method cloning** (`JSON.clone(obj, 'methodName')`):
276+
- For each object, if `obj.methodName` exists, it's called with `(weakmap, ...extra_args)`
277+
- Return value replaces the original in the clone
278+
- This is how Hawkejs transforms server objects to client-safe versions
279+
280+
### Implementing Serialization
281+
282+
```javascript
283+
// Instance method - what data to serialize
284+
MyClass.prototype.toDry = function() {
285+
return { value: { name: this.name } };
286+
};
287+
288+
// Static method - reconstruct from serialized data
289+
MyClass.unDry = function(value) {
290+
return new MyClass(value);
291+
};
292+
293+
JSON.registerClass(MyClass); // Required for unDry
294+
```
295+
296+
## Gotchas
297+
298+
1. **`Function.inherits()` signature:** First arg can be parent class name (string), parent class (function), or array (multiple inheritance)
299+
300+
2. **`constitute()` is queued, not overridden:** Multiple `constitute()` calls are all queued and run in order. No `super` call needed - parent constitutes run automatically before child constitutes.
301+
302+
3. **`postInherit()` vs `constitute()`:** `postInherit()` runs immediately after inheritance, `constitute()` runs after Blast.loaded()
303+
304+
4. **Pledge vs Promise:** Pledges have extra methods like `.done()`, progress tracking, and can be synchronous with Swift
305+
306+
5. **Bound vs Prototype:** When not modifying prototypes, use `Blast.Bound.String.method(str, args)` instead of `str.method(args)`
307+
308+
6. **Series/Parallel tasks:** Tasks receive a `next` callback - call it with `(err, result)` pattern
309+
310+
7. **Class type_name:** Automatically derived from class name (e.g., `MyClass` becomes `my_class`) - used in `getDescendantsDict()`
311+
312+
8. **Namespace functions:** A namespace like `Classes.MyApp` can be called as a function - it instantiates `Classes.MyApp.MyApp`
313+
314+
9. **Namespace as parent:** If parent path is a namespace (not a class), it inherits from `Namespace.Namespace` (e.g., `'Alchemy.Widget'` -> `Alchemy.Widget.Widget`)
315+
316+
10. **Namespace argument creates path:** `Function.inherits('Parent', 'A.B.C', fn)` registers class at `Classes.A.B.C.ClassName`
317+
318+
11. **Class groups are inherited:** `startNewGroup()` creates a group attached to that class. Child classes inherit group membership unless they call `startNewGroup()` again
319+
320+
12. **`JSON.clone` vs `JSON.dry`:** `clone()` returns a live object; `dry()` returns a JSON string. Clone with custom method (`clone(obj, 'toHawkejs')`) is used for object transformation, not serialization
321+
322+
13. **Custom clone method signature:** Methods like `toHawkejs` receive `(wm, ...extra_args)` where `wm` is a WeakMap - pass it when cloning nested objects to preserve reference identity
323+
324+
14. **`FixedDecimal.ensure()` uses the instance's scale:**
325+
```javascript
326+
let rate = new FixedDecimal('0.05', 2); // scale=2
327+
let divisor = rate.ensure(0.001); // Returns 0.00 (rounded to scale 2!)
328+
// Use Decimal.ensure() to preserve precision of small values
329+
let divisor = Decimal.ensure(0.001); // Keeps full precision
330+
```

0 commit comments

Comments
 (0)