@spearwolf/shadow-objects 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +157 -111
- package/bundle.js +2 -2
- package/package.json +2 -2
- package/src/elements/ShaeWorkerElement.d.ts.map +1 -1
- package/src/elements/ShaeWorkerElement.js +2 -1
- package/src/elements/ShaeWorkerElement.js.map +1 -1
- package/src/in-the-dark/Entity.d.ts.map +1 -1
- package/src/in-the-dark/Entity.js +0 -3
- package/src/in-the-dark/Entity.js.map +1 -1
- package/src/in-the-dark/Kernel.d.ts.map +1 -1
- package/src/in-the-dark/Kernel.js +52 -11
- package/src/in-the-dark/Kernel.js.map +1 -1
- package/src/in-the-dark/events.d.ts.map +1 -1
- package/src/in-the-dark/events.js.map +1 -1
- package/src/types.d.ts +7 -5
- package/src/types.d.ts.map +1 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# CHANGELOG
|
|
2
|
+
|
|
3
|
+
All notable changes to [@spearwolf/shadow-objects](https://github.com/spearwolf/shadow-objects/tree/main/packages/shadow-objects) will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.24.0] - 2025-11-27
|
|
9
|
+
|
|
10
|
+
- renamed interface `ShadowObjectParams` to `ShadowObjectCreationAPI` for clarity and consistency with the concept of the _Shadow Object Creation API_
|
|
11
|
+
- renamed `useResource()` to `createResource()` in `ShadowObjectCreationAPI` interface
|
|
12
|
+
|
|
13
|
+
## [0.23.0] - 2025-11-26
|
|
14
|
+
|
|
15
|
+
- enhance the shadow-objects creation api _aka_ `ShadowObjectParams`
|
|
16
|
+
- added the `useProperties()` function
|
|
17
|
+
- added the `useResource()` function
|
|
18
|
+
- added lots of new tests and improved code coverage
|
|
19
|
+
|
package/README.md
CHANGED
|
@@ -1,154 +1,200 @@
|
|
|
1
|
-
|
|
2
|
-
# shadow-objects 🧛
|
|
1
|
+
# Shadow Objects Framework 🧛
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
The **Shadow Objects Framework** is a reactive library designed to decouple business logic and state management from the UI rendering layer. It runs application logic "in the dark" (e.g., in a web worker), mirroring the view hierarchy of your application.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
> [!WARNING]
|
|
6
|
+
> 🚀 This is a highly experimental framework that is slowly maturing. Use at your own risk. 🔥
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
## Core Concepts
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
### 1. Entities
|
|
11
|
+
An **Entity** is the fundamental unit in the framework. It represents a node in the hierarchy, mirroring a view component (e.g., a Web Component or a DOM element).
|
|
12
|
+
- **Hierarchy**: Entities have parents and children, forming a tree structure.
|
|
13
|
+
- **Properties**: Entities hold reactive properties that sync with the view.
|
|
14
|
+
- **Context**: Entities participate in a hierarchical context system (dependency injection).
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
### 2. Shadow Objects
|
|
17
|
+
A **Shadow Object** is a functional unit of logic attached to an Entity.
|
|
18
|
+
- **Logic Containers**: They contain the state, effects, and business logic for a specific feature.
|
|
19
|
+
- **Lifecycle**: They are automatically created and destroyed by the **Kernel** based on the Entity's **Token**.
|
|
20
|
+
- **Reactivity**: They use **Signals** and **Effects** (via `@spearwolf/signalize`) to react to changes in properties or context.
|
|
13
21
|
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
### 3. The Kernel
|
|
23
|
+
The **Kernel** is the brain of the framework.
|
|
24
|
+
- **Manages Entities**: Handles creation, destruction, and hierarchy updates of Entities.
|
|
25
|
+
- **Orchestrates Shadow Objects**: Instantiates the correct Shadow Objects for each Entity based on its Token and the Registry.
|
|
26
|
+
- **Message Dispatch**: Handles communication between the View (UI) and the Shadow World.
|
|
16
27
|
|
|
17
|
-
|
|
28
|
+
### 4. The Registry
|
|
29
|
+
The **Registry** maps **Tokens** to **Shadow Object Constructors**.
|
|
30
|
+
- **Tokens**: Strings that identify what logic an Entity should have (e.g., `"my-component"`).
|
|
31
|
+
- **Routes**: Defines rules for composing multiple Shadow Objects. For example, a token can "route" to other tokens, causing multiple Shadow Objects to be instantiated for a single Entity.
|
|
32
|
+
- **Conditional Routing**: Routes can be triggered based on the presence of specific "truthy" properties on the Entity (e.g., `@myProp` routes only if `myProp` is set).
|
|
18
33
|
|
|
19
|
-
|
|
20
|
-
<shae-worker-env local? src="./my-personal-shadow-objects.js" />
|
|
34
|
+
---
|
|
21
35
|
|
|
22
|
-
|
|
23
|
-
<shae-ent ns? token="bar">
|
|
24
|
-
<shae-prop name="myNumbers" value="1 2 3" type="integer[]" />
|
|
25
|
-
</shae-ent>
|
|
26
|
-
</shae-ent>
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
In the shadows, the _shadow objects_ come and go with the entities. An entity can cover many shadows. But all are united by the entity _token_. That token, which is specified by the _view components_.
|
|
30
|
-
|
|
31
|
-
The _shadow object module_ stores which shadow objects are hidden behind a token. Either a _function_ or a _class_.
|
|
32
|
-
|
|
33
|
-
```js
|
|
34
|
-
shadowObjects.define('token', function() {
|
|
35
|
-
console.log('The simplest shadow object ever.');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
@ShadowObject('token')
|
|
39
|
-
class Foo {
|
|
40
|
-
constructor({
|
|
41
|
-
entity,
|
|
42
|
-
provideContext,
|
|
43
|
-
useContext,
|
|
44
|
-
useProperty,
|
|
45
|
-
onDestroy,
|
|
46
|
-
}: ShadowObjectParams) {
|
|
47
|
-
console.log('hello');
|
|
48
|
-
|
|
49
|
-
onDestroy(() => {
|
|
50
|
-
console.log('bye');
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
```
|
|
36
|
+
## Developer Guide
|
|
55
37
|
|
|
56
|
-
|
|
57
|
-
> Sorry, at this point there should be a precise and crisp introduction to the concepts of the framework, but unfortunately this is not currently available.
|
|
58
|
-
> Instead of that, a few insights into the implementation will follow
|
|
38
|
+
### 1. Defining Shadow Objects
|
|
59
39
|
|
|
60
|
-
|
|
40
|
+
You can define a Shadow Object as a **Function** or a **Class**. Both receive a `ShadowObjectCreationAPI` object containing the API methods.
|
|
61
41
|
|
|
62
|
-
|
|
42
|
+
#### Function-based (Recommended)
|
|
63
43
|
|
|
64
|
-
|
|
44
|
+
```typescript
|
|
45
|
+
import { ShadowObjectCreationAPI } from "@spearwolf/shadow-objects";
|
|
65
46
|
|
|
66
|
-
|
|
67
|
-
|
|
47
|
+
export function MyShadowObject({
|
|
48
|
+
useProperty,
|
|
49
|
+
useContext,
|
|
50
|
+
createEffect,
|
|
51
|
+
on,
|
|
52
|
+
onDestroy
|
|
53
|
+
}: ShadowObjectCreationAPI) {
|
|
68
54
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// ... PUT YOUR IMPLEMENTATION HERE ...
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
// Return an object. This step is optional.
|
|
75
|
-
return {
|
|
76
|
-
// All methods here are reactive and correspond to entity events of the same name!
|
|
55
|
+
// 1. Read Properties
|
|
56
|
+
const title = useProperty("title");
|
|
77
57
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
58
|
+
// 2. React to changes
|
|
59
|
+
createEffect(() => {
|
|
60
|
+
console.log("Title is now:", title());
|
|
61
|
+
});
|
|
81
62
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
63
|
+
// 3. Handle Lifecycle
|
|
64
|
+
onDestroy(() => {
|
|
65
|
+
console.log("Shadow Object destroyed");
|
|
66
|
+
});
|
|
85
67
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
68
|
+
// 4. Return public methods (optional)
|
|
69
|
+
return {
|
|
70
|
+
someMethod() { /* ... */ }
|
|
89
71
|
};
|
|
90
72
|
}
|
|
91
73
|
```
|
|
92
74
|
|
|
93
|
-
|
|
75
|
+
#### Class-based
|
|
94
76
|
|
|
95
|
-
|
|
77
|
+
```typescript
|
|
78
|
+
import { ShadowObjectCreationAPI } from "@spearwolf/shadow-objects";
|
|
96
79
|
|
|
97
|
-
|
|
98
|
-
|
|
80
|
+
export class MyShadowObject {
|
|
81
|
+
constructor({ useProperty, createEffect, onDestroy }: ShadowObjectCreationAPI) {
|
|
82
|
+
const title = useProperty("title");
|
|
99
83
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
// ... INITIALIZE SHADOW OBJECT ...
|
|
104
|
-
//
|
|
105
|
-
}
|
|
84
|
+
createEffect(() => {
|
|
85
|
+
console.log("Title is now:", title());
|
|
86
|
+
});
|
|
106
87
|
|
|
107
|
-
|
|
108
|
-
// ...
|
|
88
|
+
onDestroy(() => this.cleanup());
|
|
109
89
|
}
|
|
110
90
|
|
|
111
|
-
|
|
112
|
-
|
|
91
|
+
cleanup() {
|
|
92
|
+
console.log("Shadow Object destroyed");
|
|
113
93
|
}
|
|
114
94
|
}
|
|
115
95
|
```
|
|
116
96
|
|
|
117
|
-
### Shadow Object
|
|
97
|
+
### 2. The Shadow Object Creation API
|
|
98
|
+
|
|
99
|
+
The `ShadowObjectCreationAPI` object provides all necessary tools to interact with the Entity, the View, and the Context system.
|
|
100
|
+
|
|
101
|
+
| Method | Description |
|
|
102
|
+
| :--- | :--- |
|
|
103
|
+
| **`useProperty(name)`** | Returns a signal reader for a specific property on the Entity. Updates when the view property changes. |
|
|
104
|
+
| **`useProperties(map)`** | Returns an object of signal readers for multiple properties. |
|
|
105
|
+
| **`useContext(name)`** | Consumes a context value provided by a parent Entity. |
|
|
106
|
+
| **`useParentContext(name)`** | Skips the current Entity and consumes context directly from the parent. |
|
|
107
|
+
| **`provideContext(name, value)`** | Provides a context value (or signal) to descendant Entities. |
|
|
108
|
+
| **`provideGlobalContext(name, value)`** | Provides a context value globally to all Entities. |
|
|
109
|
+
| **`createResource(factory, cleanup)`** | Manages an external resource (e.g., a Three.js object) with automatic cleanup when dependencies change. |
|
|
110
|
+
| **`createEffect(callback)`** | Runs a side effect whenever accessed signals change. |
|
|
111
|
+
| **`createSignal(initialValue)`** | Creates a local reactive state signal. |
|
|
112
|
+
| **`createMemo(factory)`** | Creates a derived signal that updates only when dependencies change. |
|
|
113
|
+
| **`on(target, event, callback)`** | Listens for events on the Entity or other event targets. |
|
|
114
|
+
| **`once(target, event, callback)`** | Listens for an event exactly once. |
|
|
115
|
+
| **`onDestroy(callback)`** | Registers a callback to be executed when the Shadow Object is destroyed. |
|
|
116
|
+
|
|
117
|
+
### 3. Registering Shadow Objects
|
|
118
|
+
|
|
119
|
+
Shadow Objects are organized in **Modules**. A module defines which Tokens map to which Shadow Objects.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// my-module.ts
|
|
123
|
+
import { MyShadowObject } from "./MyShadowObject";
|
|
124
|
+
|
|
125
|
+
export default {
|
|
126
|
+
// Map tokens to constructors
|
|
127
|
+
define: {
|
|
128
|
+
"my-component": MyShadowObject,
|
|
129
|
+
},
|
|
130
|
+
// Define routing rules
|
|
131
|
+
routes: {
|
|
132
|
+
"my-component": ["mixin-logger", "mixin-analytics"], // Composition
|
|
133
|
+
"@debug": ["debug-overlay"], // Conditional routing based on 'debug' property
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
```
|
|
118
137
|
|
|
119
|
-
|
|
138
|
+
### 4. View Integration
|
|
120
139
|
|
|
121
|
-
|
|
140
|
+
In your HTML or View layer, you use the provided Web Components to create the Entity hierarchy.
|
|
141
|
+
|
|
142
|
+
```html
|
|
143
|
+
<!-- 1. Initialize the Environment -->
|
|
144
|
+
<shae-worker-env src="./my-module.js"></shae-worker-env>
|
|
122
145
|
|
|
123
|
-
|
|
146
|
+
<!-- 2. Create Entities -->
|
|
147
|
+
<shae-ent token="my-component">
|
|
148
|
+
<!-- Properties -->
|
|
149
|
+
<shae-prop name="title" value="Hello World"></shae-prop>
|
|
150
|
+
|
|
151
|
+
<!-- Nested Entities -->
|
|
152
|
+
<shae-ent token="child-component"></shae-ent>
|
|
153
|
+
</shae-ent>
|
|
154
|
+
```
|
|
124
155
|
|
|
125
|
-
|
|
156
|
+
---
|
|
126
157
|
|
|
127
|
-
|
|
158
|
+
## Architecture & Internals
|
|
128
159
|
|
|
129
|
-
|
|
130
|
-
|------|----------------|-------------|
|
|
131
|
-
| __useProperty__ | `useProperty(name, isEqual?): SignalReader` | Read access to the property value from the view |
|
|
132
|
-
| __useContext__ | `useContext(name, isEqual?): SignalReader` | Get the value for a named context. The context is derived from the shadow entity hierarchy and the shadow object's position within it. |
|
|
133
|
-
| __useParentContext__ | `useParentContext(name, isEqual?): SignalReader` | Unlike the `useContext` method, this method skips the context of the _current_ shadow entity and directly requests the context of the parent entity. This is useful when you want to provide a custom context that depends on the parent context. |
|
|
134
|
-
| __provideContext__ | `provideContext(name, initialValue?, isEqual?): Signal` | Specify a context. This context overrides (if set) the context of the same name from the entity's parent hierarchy. The context applies to the current entity and all child entities. |
|
|
135
|
-
| __provideGlobalContext__ | `provideGlobalContext(name, initialValue?, isEqual?): Signal` | Define a context that applies to _all_ entities, regardless of hierarchy. |
|
|
136
|
-
| __createEffect__ | `createEffect(...): Effect` | see [@spearwolf/signalize#createEffect()](https://github.com/spearwolf/signalize) |
|
|
137
|
-
| __createSignal__ | `createSignal(...): Signal` | see [@spearwolf/signalize#createSignal()](https://github.com/spearwolf/signalize) |
|
|
138
|
-
| __createMemo__ | `createMemo(...): SignalReader` | see [@spearwolf/signalize#createMemo()](https://github.com/spearwolf/signalize) |
|
|
139
|
-
| __on__ | `on(...): UnsubscribeCallback` | see [@spearwolf/eventize#on()](https://github.com/spearwolf/eventize) |
|
|
140
|
-
| __once__ | `once(...): UnsubscribeCallback` | see [@spearwolf/eventize#once()](https://github.com/spearwolf/eventize) |
|
|
141
|
-
| __onDestroy__ | `onDestroy(callback)` | Called when the shadow object is destroyed. |
|
|
160
|
+
### Lifecycle
|
|
142
161
|
|
|
162
|
+
1. **Creation**: When an Entity is created (e.g., `<shae-ent>` connects), the Kernel looks up its Token in the Registry.
|
|
163
|
+
2. **Instantiation**: The Kernel instantiates all Shadow Objects associated with that Token (and its routes).
|
|
164
|
+
3. **Execution**: The Shadow Object function runs, setting up signals, effects, and context providers.
|
|
165
|
+
4. **Updates**:
|
|
166
|
+
* **Properties**: When view properties change, the Entity's signals update, triggering any dependent effects in the Shadow Object.
|
|
167
|
+
* **Context**: If a parent Entity changes a provided context, child Shadow Objects consuming that context automatically update.
|
|
168
|
+
5. **Destruction**: When an Entity is removed or its Token changes, the Kernel destroys the associated Shadow Objects, cleaning up all signals and effects.
|
|
143
169
|
|
|
144
|
-
|
|
170
|
+
### Architecture Diagram
|
|
171
|
+
|
|
172
|
+
```mermaid
|
|
173
|
+
graph TD
|
|
174
|
+
View[View / DOM] -->|Messages| Kernel
|
|
175
|
+
Kernel -->|Updates| EntityTree[Entity Tree]
|
|
176
|
+
|
|
177
|
+
subgraph "Shadow World"
|
|
178
|
+
EntityTree
|
|
179
|
+
Entity[Entity]
|
|
180
|
+
SO[Shadow Object]
|
|
181
|
+
|
|
182
|
+
EntityTree --> Entity
|
|
183
|
+
Entity -->|Has| SO
|
|
184
|
+
|
|
185
|
+
SO -->|Reads| Props[Properties]
|
|
186
|
+
SO -->|Reads/Writes| Context
|
|
187
|
+
SO -->|Runs| Logic[Business Logic]
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
Logic -->|Updates| Signals
|
|
191
|
+
Signals -->|Triggers| Effects
|
|
192
|
+
```
|
|
145
193
|
|
|
146
|
-
|
|
194
|
+
### Further Reading
|
|
147
195
|
|
|
148
|
-
|
|
149
|
-

|
|
196
|
+
For deep dives into specific subsystems:
|
|
150
197
|
|
|
151
|
-
|
|
152
|
-
-
|
|
153
|
-
-
|
|
154
|
-
- [ViewComponent](https://github.com/spearwolf/shadow-objects/blob/main/packages/shadow-objects/src/view/ViewComponent.md)
|
|
198
|
+
- [**ShadowEnv**](src/view/README.md): The environment wrapper.
|
|
199
|
+
- [**ComponentContext**](src/view/ComponentContext.md): Context implementation details.
|
|
200
|
+
- [**ViewComponent**](src/view/ViewComponent.md): Base class for view components.
|