@spearwolf/shadow-objects 0.21.1 → 0.23.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 ADDED
@@ -0,0 +1,14 @@
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.23.0] - 2025-11-26
9
+
10
+ - enhance the shadow-objects creation api _aka_ `ShadowObjectParams`
11
+ - added the `useProperties()` function
12
+ - added the `useResource()` function
13
+ - added lots of new tests and improved code coverage
14
+
package/README.md CHANGED
@@ -1,154 +1,200 @@
1
- _hi! welcome to .._
2
- # shadow-objects 🧛
1
+ # Shadow Objects Framework 🧛
3
2
 
4
- ## Introduction
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
- _Shadow-objects_ is a standalone, reactive entity←component framework 🔥
5
+ > [!WARNING]
6
+ > 🚀 This is a highly experimental framework that is slowly maturing. Use at your own risk. 🔥
7
7
 
8
- The original idea is visualized in this overview:
8
+ ## Core Concepts
9
9
 
10
- ![architecture overview](https://raw.githubusercontent.com/spearwolf/shadow-objects/main/packages/shadow-objects/docs/architecture%402x.png)
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
- > _ents_ is short for latin _"entitatis"_, which translates to _"shadow entities"_
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
- > [!IMPORTANT]
15
- > _Dear adventurer, be warned: everything here is highly experimental and in active development and constant flux!_
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
- The components are created in the view space. Hierarchical. In your browser. There is a JavaScript API for this. But to keep things simple, there are also ready-to-use web components.
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
- ```html
20
- <shae-worker-env local? src="./my-personal-shadow-objects.js" />
34
+ ---
21
35
 
22
- <shae-ent ns? token="foo">
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
- > [!WARNING]
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
- ## 📖 Shadow Objects CHEAT SHEET
40
+ You can define a Shadow Object as a **Function** or a **Class**. Both receive a `ShadowObjectParams` object containing the API methods.
61
41
 
62
- There are two ways to create a _shadow object component_: either as a _function_ or as a _class_:
42
+ #### Function-based (Recommended)
63
43
 
64
- ### Create Shadow Object by FUNCTION
44
+ ```typescript
45
+ import { ShadowObjectParams } from "@spearwolf/shadow-objects";
65
46
 
66
- ```js
67
- import { onDestroy, type ShadowObjectParams, type Entity } from "@spearwolf/shadow-objects/shadow-objects.js";
47
+ export function MyShadowObject({
48
+ useProperty,
49
+ useContext,
50
+ createEffect,
51
+ on,
52
+ onDestroy
53
+ }: ShadowObjectParams) {
68
54
 
69
- function MyShadowObject(params: ShadowObjectParams) {
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
- [onDestroy](entity: Entity) {
79
- // Called when the shadow object is destroyed.
80
- // This is one of the predefined events that a shadow object can receive.
58
+ // 2. React to changes
59
+ createEffect(() => {
60
+ console.log("Title is now:", title());
61
+ });
81
62
 
82
- // This can happen when the entity is destroyed or the shadow object component
83
- // is removed from the entity (e.g., by changing the entity token, view properties, and/or routing)
84
- },
63
+ // 3. Handle Lifecycle
64
+ onDestroy(() => {
65
+ console.log("Shadow Object destroyed");
66
+ });
85
67
 
86
- fooBar(plah) {
87
- /* is called when the entity receives a 'fooBar' event */
88
- },
68
+ // 4. Return public methods (optional)
69
+ return {
70
+ someMethod() { /* ... */ }
89
71
  };
90
72
  }
91
73
  ```
92
74
 
93
- ### Create Shadow Object by CLASS
75
+ #### Class-based
94
76
 
95
- Essentially the same as above, but as a `class`:
77
+ ```typescript
78
+ import { ShadowObjectParams } from "@spearwolf/shadow-objects";
96
79
 
97
- ```js
98
- import { onDestroy, type ShadowObjectParams, type Entity } from "@spearwolf/shadow-objects/shadow-objects.js";
80
+ export class MyShadowObject {
81
+ constructor({ useProperty, createEffect, onDestroy }: ShadowObjectParams) {
82
+ const title = useProperty("title");
99
83
 
100
- class MyShadowObject {
101
- constructor(params: ShadowObjectParams) {
102
- //
103
- // ... INITIALIZE SHADOW OBJECT ...
104
- //
105
- }
84
+ createEffect(() => {
85
+ console.log("Title is now:", title());
86
+ });
106
87
 
107
- [onDestroy](entity: Entity) {
108
- // ...
88
+ onDestroy(() => this.cleanup());
109
89
  }
110
90
 
111
- fooBar(plah) {
112
- // ...
91
+ cleanup() {
92
+ console.log("Shadow Object destroyed");
113
93
  }
114
94
  }
115
95
  ```
116
96
 
117
- ### Shadow Object Construction API
97
+ ### 2. The `ShadowObjectParams` API
98
+
99
+ The `ShadowObjectParams` 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
+ | **`useResource(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
- The parameters that a shadow object receives when it is created contain all the important API methods for exchanging data and events with the _view_ and also with the _shadow entity hierarchy and context_.
138
+ ### 4. View Integration
120
139
 
121
- [The interface `ShadowObjectsParams` is defined here](./src/types.ts)
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
- #### Properties
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
- __entity__: The shadow entity.
156
+ ---
126
157
 
127
- #### Methods
158
+ ## Architecture & Internals
128
159
 
129
- | Name | Call Signature | Description |
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
- ## Documentation
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
- TODO ... add documentation here ... !
194
+ ### Further Reading
147
195
 
148
- Here is the big class graph overview:
149
- ![class graph overview](https://raw.githubusercontent.com/spearwolf/shadow-objects/main/packages/shadow-objects/src/view/ClassGraphOverview.drawio.svg)
196
+ For deep dives into specific subsystems:
150
197
 
151
- More in-depth docs here:
152
- - [ShadowEnv](https://github.com/spearwolf/shadow-objects/blob/main/packages/shadow-objects/src/view/README.md)
153
- - [ComponentContext](https://github.com/spearwolf/shadow-objects/blob/main/packages/shadow-objects/src/view/ComponentContext.md)
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.