@vived/core 2.0.0 → 2.0.2
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/README.md +275 -65
- package/dist/cjs/AppObject/AppObjectEntityRepo.js +16 -2
- package/dist/cjs/AppObject/AppObjectEntityRepo.js.map +1 -1
- package/dist/cjs/ExampleFeature/Entities/ExampleRepo.js +3 -4
- package/dist/cjs/ExampleFeature/Entities/ExampleRepo.js.map +1 -1
- package/dist/cjs/ExampleFeature/index.js +23 -0
- package/dist/cjs/ExampleFeature/index.js.map +1 -0
- package/dist/esm/AppObject/AppObjectEntityRepo.js +16 -2
- package/dist/esm/AppObject/AppObjectEntityRepo.js.map +1 -1
- package/dist/esm/ExampleFeature/Entities/ExampleRepo.js +3 -4
- package/dist/esm/ExampleFeature/Entities/ExampleRepo.js.map +1 -1
- package/dist/esm/ExampleFeature/index.js +7 -0
- package/dist/esm/ExampleFeature/index.js.map +1 -0
- package/dist/types/AppObject/AppObjectEntityRepo.d.ts +9 -1
- package/dist/types/AppObject/AppObjectEntityRepo.d.ts.map +1 -1
- package/dist/types/ExampleFeature/Entities/ExampleRepo.d.ts +1 -1
- package/dist/types/ExampleFeature/Entities/ExampleRepo.d.ts.map +1 -1
- package/dist/types/ExampleFeature/index.d.ts +5 -0
- package/dist/types/ExampleFeature/index.d.ts.map +1 -0
- package/package.json +3 -2
- package/src/AppObject/README.md +476 -0
- package/src/DomainFactories/README.md +154 -0
- package/src/Entities/README.md +340 -0
- package/src/ExampleFeature/README.md +804 -0
- package/src/Types/README.md +549 -0
- package/src/Utilities/README.md +478 -0
- package/src/ValueObjects/README.md +552 -0
|
@@ -72,12 +72,11 @@ export function makeExampleRepo(appObject) {
|
|
|
72
72
|
class ExampleRepoImp extends ExampleRepo {
|
|
73
73
|
/**
|
|
74
74
|
* Factory implementation for creating ExampleEntity instances
|
|
75
|
-
* @param
|
|
75
|
+
* @param appObject The AppObject for the entity
|
|
76
76
|
* @returns A newly created ExampleEntity
|
|
77
77
|
*/
|
|
78
|
-
entityFactory(
|
|
79
|
-
|
|
80
|
-
return makeExampleEntity(ao);
|
|
78
|
+
entityFactory(appObject) {
|
|
79
|
+
return makeExampleEntity(appObject);
|
|
81
80
|
}
|
|
82
81
|
/**
|
|
83
82
|
* Deletes an ExampleEntity from the repository by its AppObject ID
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExampleRepo.js","sourceRoot":"","sources":["../../../../src/ExampleFeature/Entities/ExampleRepo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAa,mBAAmB,EAAiB,MAAM,iBAAiB,CAAC;AAChF,OAAO,EAAiB,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAKnE;;;GAGG;AACH,MAAM,OAAgB,WAAY,SAAQ,mBAAkC;IAO1E;;;;OAIG;IACH,MAAM,CAAC,GAAG,CAAC,MAAiB;QAC1B,OAAO,MAAM,CAAC,YAAY,CAAc,IAAI,CAAC,IAAI,CAAC,CAAC;IACrD,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,OAAO,CACZ,EAAU,EACV,UAAyB;QAEzB,OAAO,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,YAAY,CAAc,IAAI,CAAC,IAAI,CAAC,CAAC;IAClE,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,YAAY,CAAC,SAAoB;QACtC,MAAM,QAAQ,GAAG,SAAS,CAAC,YAAY,CAAc,WAAW,CAAC,IAAI,CAAC,CAAC;QACvE,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,QAAQ,CAAC;QAClB,CAAC;aAAM,CAAC;YACN,OAAO,eAAe,CAAC,SAAS,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;;AAxCD,gDAAgD;AAChC,gBAAI,GAAG,iBAAiB,CAAC;AA0C3C;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,SAAoB;IAClD,OAAO,IAAI,cAAc,CAAC,SAAS,CAAC,CAAC;AACvC,CAAC;AAED;;;GAGG;AACH,MAAM,cAAe,SAAQ,WAAW;IACtC;;;;OAIG;IACH,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"ExampleRepo.js","sourceRoot":"","sources":["../../../../src/ExampleFeature/Entities/ExampleRepo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAa,mBAAmB,EAAiB,MAAM,iBAAiB,CAAC;AAChF,OAAO,EAAiB,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAKnE;;;GAGG;AACH,MAAM,OAAgB,WAAY,SAAQ,mBAAkC;IAO1E;;;;OAIG;IACH,MAAM,CAAC,GAAG,CAAC,MAAiB;QAC1B,OAAO,MAAM,CAAC,YAAY,CAAc,IAAI,CAAC,IAAI,CAAC,CAAC;IACrD,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,OAAO,CACZ,EAAU,EACV,UAAyB;QAEzB,OAAO,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,YAAY,CAAc,IAAI,CAAC,IAAI,CAAC,CAAC;IAClE,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,YAAY,CAAC,SAAoB;QACtC,MAAM,QAAQ,GAAG,SAAS,CAAC,YAAY,CAAc,WAAW,CAAC,IAAI,CAAC,CAAC;QACvE,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,QAAQ,CAAC;QAClB,CAAC;aAAM,CAAC;YACN,OAAO,eAAe,CAAC,SAAS,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;;AAxCD,gDAAgD;AAChC,gBAAI,GAAG,iBAAiB,CAAC;AA0C3C;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,SAAoB;IAClD,OAAO,IAAI,cAAc,CAAC,SAAS,CAAC,CAAC;AACvC,CAAC;AAED;;;GAGG;AACH,MAAM,cAAe,SAAQ,WAAW;IACtC;;;;OAIG;IACH,aAAa,CAAC,SAAoB;QAChC,OAAO,iBAAiB,CAAC,SAAS,CAAC,CAAC;IACtC,CAAC;IAED;;;OAGG;IACH,mBAAmB,CAAC,EAAU;QAC5B,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAChC,IAAI,CAAC,MAAM;YAAE,OAAO;QAEpB,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;QAC3B,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IACtB,CAAC;IAED,YAAY,SAAoB;QAC9B,KAAK,CAAC,SAAS,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC;CACF","sourcesContent":["/**\r\n * ExampleRepo.ts\r\n *\r\n * This file demonstrates how to implement a repository to manage collections of entities.\r\n * Repositories are responsible for creating, retrieving, and deleting entities.\r\n *\r\n * Key concepts:\r\n * - Repositories extend AppObjectEntityRepo<T> where T is the entity type\r\n * - They provide methods to create and delete entities\r\n * - They manage collections of entities and provide access to them\r\n * - They can use custom entity factories to create specialized entities\r\n *\r\n * Usage pattern:\r\n * 1. Get or create a repository using getById, get, or addIfMissing\r\n * 2. Use the repository to create new entities\r\n * 3. Access entities through the repository's getters\r\n * 4. Delete entities through the repository when they're no longer needed\r\n */\r\n\r\nimport { AppObject, AppObjectEntityRepo, AppObjectRepo } from \"../../AppObject\";\r\nimport { ExampleEntity, makeExampleEntity } from \"./ExampleEntity\";\r\n\r\n/** Type definition for a factory function that creates ExampleEntity instances */\r\nexport type ExampleEntityFactory = (appObject: AppObject) => ExampleEntity;\r\n\r\n/**\r\n * ExampleRepo manages a collection of ExampleEntity instances.\r\n * Abstract class provides the interface and static helper methods.\r\n */\r\nexport abstract class ExampleRepo extends AppObjectEntityRepo<ExampleEntity> {\r\n /** Unique type identifier for this component */\r\n static readonly type = \"ExampleRepoType\";\r\n\r\n /** Deletes an entity by its AppObject ID */\r\n abstract deleteExampleEntity(id: string): void;\r\n\r\n /**\r\n * Retrieves an ExampleRepo component from an AppObject\r\n * @param appObj The AppObject to get the component from\r\n * @returns The ExampleRepo component or undefined if not found\r\n */\r\n static get(appObj: AppObject): ExampleRepo | undefined {\r\n return appObj.getComponent<ExampleRepo>(this.type);\r\n }\r\n\r\n /**\r\n * Retrieves an ExampleRepo by its parent AppObject's ID\r\n * @param id The ID of the parent AppObject\r\n * @param appObjects The AppObjectRepo to search in\r\n * @returns The ExampleRepo component or undefined if not found\r\n */\r\n static getById(\r\n id: string,\r\n appObjects: AppObjectRepo\r\n ): ExampleRepo | undefined {\r\n return appObjects.get(id)?.getComponent<ExampleRepo>(this.type);\r\n }\r\n\r\n /**\r\n * Ensures an ExampleRepo exists on the AppObject, creating one if needed\r\n * @param appObject The AppObject to check/add the component to\r\n * @returns The existing or newly created ExampleRepo\r\n */\r\n static addIfMissing(appObject: AppObject): ExampleRepo {\r\n const existing = appObject.getComponent<ExampleRepo>(ExampleRepo.type);\r\n if (existing) {\r\n return existing;\r\n } else {\r\n return makeExampleRepo(appObject);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Factory function to create a new ExampleRepo\r\n * @param appObject The AppObject to attach the repo to\r\n * @returns A new ExampleRepo instance\r\n */\r\nexport function makeExampleRepo(appObject: AppObject): ExampleRepo {\r\n return new ExampleRepoImp(appObject);\r\n}\r\n\r\n/**\r\n * Concrete implementation of ExampleRepo\r\n * This private class handles the actual implementation details\r\n */\r\nclass ExampleRepoImp extends ExampleRepo {\r\n /**\r\n * Factory implementation for creating ExampleEntity instances\r\n * @param appObject The AppObject for the entity\r\n * @returns A newly created ExampleEntity\r\n */\r\n entityFactory(appObject: AppObject): ExampleEntity {\r\n return makeExampleEntity(appObject);\r\n }\r\n\r\n /**\r\n * Deletes an ExampleEntity from the repository by its AppObject ID\r\n * @param id The ID of the entity's AppObject\r\n */\r\n deleteExampleEntity(id: string): void {\r\n const entity = this.getById(id);\r\n if (!entity) return;\r\n\r\n entity.appObject.dispose();\r\n this.removeById(id);\r\n }\r\n\r\n constructor(appObject: AppObject) {\r\n super(appObject, ExampleRepo.type);\r\n }\r\n}\r\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Controllers
|
|
2
|
+
export * from "./Controllers/setExampleText";
|
|
3
|
+
export * from "./Controllers/toggleExampleBoolean";
|
|
4
|
+
// Adapters
|
|
5
|
+
export * from "./Adapters/examplePmAdapter";
|
|
6
|
+
export * from "./Adapters/exampleSingletonPmAdapter";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/ExampleFeature/index.ts"],"names":[],"mappings":"AAAA,cAAc;AACd,cAAc,8BAA8B,CAAC;AAC7C,cAAc,oCAAoC,CAAC;AAEnD,WAAW;AACX,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sCAAsC,CAAC","sourcesContent":["// Controllers\r\nexport * from \"./Controllers/setExampleText\";\r\nexport * from \"./Controllers/toggleExampleBoolean\";\r\n\r\n// Adapters\r\nexport * from \"./Adapters/examplePmAdapter\";\r\nexport * from \"./Adapters/exampleSingletonPmAdapter\";\r\n"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AppObject } from "./AppObject";
|
|
1
2
|
import { AppObjectEntity } from "./AppObjectEntity";
|
|
2
3
|
/**
|
|
3
4
|
* A repository for managing collections of AppObjectEntity instances.
|
|
@@ -106,7 +107,7 @@ export declare class AppObjectEntityRepo<T extends AppObjectEntity> extends AppO
|
|
|
106
107
|
* @returns {T} A new entity instance
|
|
107
108
|
* @throws {Error} If not overridden in derived class
|
|
108
109
|
*/
|
|
109
|
-
entityFactory(
|
|
110
|
+
entityFactory(appObject: AppObject): T;
|
|
110
111
|
/**
|
|
111
112
|
* Removes the entity associated with the specified ID.
|
|
112
113
|
*
|
|
@@ -152,5 +153,12 @@ export declare class AppObjectEntityRepo<T extends AppObjectEntity> extends AppO
|
|
|
152
153
|
* @returns {T[]} An array of all entities
|
|
153
154
|
*/
|
|
154
155
|
getAll: () => T[];
|
|
156
|
+
/**
|
|
157
|
+
* Gets an entity by ID, or creates it if it doesn't exist.
|
|
158
|
+
*
|
|
159
|
+
* @param {string} id - The ID of the entity to get or create
|
|
160
|
+
* @returns {T} The existing or newly created entity
|
|
161
|
+
*/
|
|
162
|
+
getOrCreate: (id: string) => T;
|
|
155
163
|
}
|
|
156
164
|
//# sourceMappingURL=AppObjectEntityRepo.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppObjectEntityRepo.d.ts","sourceRoot":"","sources":["../../../src/AppObject/AppObjectEntityRepo.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,qBAAa,mBAAmB,CAC9B,CAAC,SAAS,eAAe,CACzB,SAAQ,eAAe;IACvB,OAAO,CAAC,YAAY,CAAwB;IAE5C,OAAO,CAAC,sBAAsB,CAAyB;IACvD;;;;OAIG;IACH,sBAAsB,GAAI,UAAU,CAAC,WAAW,EAAE,CAAC,KAAK,IAAI,UAE1D;IAEF;;;;OAIG;IACH,yBAAyB,GAAI,UAAU,CAAC,WAAW,EAAE,CAAC,KAAK,IAAI,KAAG,IAAI,CAEpE;IAEF,OAAO,CAAC,wBAAwB,CAAyB;IACzD;;;;OAIG;IACH,wBAAwB,GAAI,UAAU,CAAC,aAAa,EAAE,CAAC,KAAK,IAAI,UAE9D;IAEF;;;;OAIG;IACH,2BAA2B,GACzB,UAAU,CAAC,aAAa,EAAE,CAAC,KAAK,IAAI,KACnC,IAAI,CAEL;IAEF;;;;;OAKG;IACH,GAAG,GAAI,IAAI,MAAM,KAAG,OAAO,CAEzB;IAEF;;;;;;OAMG;IACH,eAAe,GAAI,aAAa,MAAM,KAAG,OAAO,CAE9C;IAEF;;;;;;;OAOG;IACH,GAAG,CAAC,MAAM,EAAE,CAAC;IAYb;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC;
|
|
1
|
+
{"version":3,"file":"AppObjectEntityRepo.d.ts","sourceRoot":"","sources":["../../../src/AppObject/AppObjectEntityRepo.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,qBAAa,mBAAmB,CAC9B,CAAC,SAAS,eAAe,CACzB,SAAQ,eAAe;IACvB,OAAO,CAAC,YAAY,CAAwB;IAE5C,OAAO,CAAC,sBAAsB,CAAyB;IACvD;;;;OAIG;IACH,sBAAsB,GAAI,UAAU,CAAC,WAAW,EAAE,CAAC,KAAK,IAAI,UAE1D;IAEF;;;;OAIG;IACH,yBAAyB,GAAI,UAAU,CAAC,WAAW,EAAE,CAAC,KAAK,IAAI,KAAG,IAAI,CAEpE;IAEF,OAAO,CAAC,wBAAwB,CAAyB;IACzD;;;;OAIG;IACH,wBAAwB,GAAI,UAAU,CAAC,aAAa,EAAE,CAAC,KAAK,IAAI,UAE9D;IAEF;;;;OAIG;IACH,2BAA2B,GACzB,UAAU,CAAC,aAAa,EAAE,CAAC,KAAK,IAAI,KACnC,IAAI,CAEL;IAEF;;;;;OAKG;IACH,GAAG,GAAI,IAAI,MAAM,KAAG,OAAO,CAEzB;IAEF;;;;;;OAMG;IACH,eAAe,GAAI,aAAa,MAAM,KAAG,OAAO,CAE9C;IAEF;;;;;;;OAOG;IACH,GAAG,CAAC,MAAM,EAAE,CAAC;IAYb;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC;IAQtB;;;;;;;;;OASG;IACH,aAAa,CAAC,SAAS,EAAE,SAAS,GAAG,CAAC;IAKtC;;;;;;OAMG;IACH,UAAU,GAAI,IAAI,MAAM,UAQtB;IAEF;;;;;;;OAOG;IACH,kBAAkB,GAAI,IAAI,MAAM,UAE9B;IAEF;;;;;OAKG;IACH,SAAS,aAUP;IAEF;;;;;OAKG;IACH,OAAO,GAAI,IAAI,MAAM,KAAG,CAAC,GAAG,SAAS,CAEnC;IAEF;;;;;;OAMG;IACH,eAAe,GAAI,aAAa,MAAM,KAAG,CAAC,GAAG,SAAS,CAEpD;IAEF;;;;OAIG;IACH,MAAM,QAAO,CAAC,EAAE,CAEd;IAEF;;;;;OAKG;IACH,WAAW,GAAI,IAAI,MAAM,KAAG,CAAC,CAM3B;CACH"}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
import { AppObject, AppObjectEntityRepo, AppObjectRepo } from "../../AppObject";
|
|
20
20
|
import { ExampleEntity } from "./ExampleEntity";
|
|
21
21
|
/** Type definition for a factory function that creates ExampleEntity instances */
|
|
22
|
-
export type ExampleEntityFactory = (
|
|
22
|
+
export type ExampleEntityFactory = (appObject: AppObject) => ExampleEntity;
|
|
23
23
|
/**
|
|
24
24
|
* ExampleRepo manages a collection of ExampleEntity instances.
|
|
25
25
|
* Abstract class provides the interface and static helper methods.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExampleRepo.d.ts","sourceRoot":"","sources":["../../../../src/ExampleFeature/Entities/ExampleRepo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChF,OAAO,EAAE,aAAa,EAAqB,MAAM,iBAAiB,CAAC;AAEnE,kFAAkF;AAClF,MAAM,MAAM,oBAAoB,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"ExampleRepo.d.ts","sourceRoot":"","sources":["../../../../src/ExampleFeature/Entities/ExampleRepo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChF,OAAO,EAAE,aAAa,EAAqB,MAAM,iBAAiB,CAAC;AAEnE,kFAAkF;AAClF,MAAM,MAAM,oBAAoB,GAAG,CAAC,SAAS,EAAE,SAAS,KAAK,aAAa,CAAC;AAE3E;;;GAGG;AACH,8BAAsB,WAAY,SAAQ,mBAAmB,CAAC,aAAa,CAAC;IAC1E,gDAAgD;IAChD,MAAM,CAAC,QAAQ,CAAC,IAAI,qBAAqB;IAEzC,4CAA4C;IAC5C,QAAQ,CAAC,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAE9C;;;;OAIG;IACH,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,SAAS;IAItD;;;;;OAKG;IACH,MAAM,CAAC,OAAO,CACZ,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,aAAa,GACxB,WAAW,GAAG,SAAS;IAI1B;;;;OAIG;IACH,MAAM,CAAC,YAAY,CAAC,SAAS,EAAE,SAAS,GAAG,WAAW;CAQvD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,SAAS,GAAG,WAAW,CAEjE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ExampleFeature/index.ts"],"names":[],"mappings":"AACA,cAAc,8BAA8B,CAAC;AAC7C,cAAc,oCAAoC,CAAC;AAGnD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sCAAsC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vived/core",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "Core Components for VIVED Apps and Hosts",
|
|
5
5
|
"main": "./dist/cjs/index.js",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"uuid": "^11.1.0"
|
|
56
56
|
},
|
|
57
57
|
"files": [
|
|
58
|
-
"dist"
|
|
58
|
+
"dist",
|
|
59
|
+
"src/**/README.md"
|
|
59
60
|
]
|
|
60
61
|
}
|
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
# App Object Architecture
|
|
2
|
+
|
|
3
|
+
> This document describes the `AppObject` architecture and its component ecosystem. The low-level `AppObjectController` base class file itself is not detailed here, but controller *behavior* and flow are now documented for completeness.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
The architecture centers around the concept of an **App Object** – a composable, observable container of domain-specific components. Each `AppObject` aggregates behavior and state through **components** that follow clear separation-of-responsibility categories:
|
|
7
|
+
|
|
8
|
+
- Entity: stateful, observable model logic
|
|
9
|
+
- Presentation Manager (PM): derives and emits immutable view models
|
|
10
|
+
- Use Case (UC): encapsulates application operations / workflows
|
|
11
|
+
- View: renders or binds presentation output to a UI or rendering substrate
|
|
12
|
+
- (Other) Arbitrary/custom components categorized as `UNKNOWN`
|
|
13
|
+
|
|
14
|
+
The system encourages:
|
|
15
|
+
- Decoupling via **component lookup** and **repository-level singleton resolution**
|
|
16
|
+
- Reactive propagation via observer lists on entities, PMs, and AppObjects
|
|
17
|
+
- Composability: AppObjects can be extended at runtime by adding/replacing components
|
|
18
|
+
- Testability: Small, focused classes with explicit contracts
|
|
19
|
+
|
|
20
|
+
## Core Building Blocks
|
|
21
|
+
|
|
22
|
+
### AppObject (`AppObject.ts`)
|
|
23
|
+
An abstract observable node that:
|
|
24
|
+
- Owns a unique `id`
|
|
25
|
+
- Registers itself in an `AppObjectRepo`
|
|
26
|
+
- Manages a map of `type -> AppObjectComponent`
|
|
27
|
+
- Notifies observers when its component set changes (e.g., add/remove/replace)
|
|
28
|
+
- Handles lifecycle: `dispose()` removes itself from the repo and disposes all components
|
|
29
|
+
|
|
30
|
+
Creation is performed via `makeAppObject(id, repo)` which returns a concrete internal implementation.
|
|
31
|
+
|
|
32
|
+
### AppObjectComponent (`AppObjectComponent.ts`)
|
|
33
|
+
The base class for all components. Responsibilities:
|
|
34
|
+
- Auto–attaches to its parent `AppObject` on construction
|
|
35
|
+
- Exposes `componentType` (categorical enum) and `type` (unique string identifier)
|
|
36
|
+
- Provides cached retrieval helpers:
|
|
37
|
+
- `getCachedLocalComponent(type)` – same AppObject
|
|
38
|
+
- `getCachedSingleton(type)` – repo-level singleton component
|
|
39
|
+
- `getSingleton(type, logLevel)` – non‑cached lookup with log control
|
|
40
|
+
- Supports disposal: removing itself if still attached
|
|
41
|
+
- Provides uniform logging APIs proxied to the repository
|
|
42
|
+
|
|
43
|
+
Design Notes:
|
|
44
|
+
- Caching avoids repeated map traversal and singleton scans
|
|
45
|
+
- Logging includes composite key `AppObjectID/ComponentTypeString`
|
|
46
|
+
|
|
47
|
+
### AppObjectEntity (`AppObjectEntity.ts`)
|
|
48
|
+
Specialized component for **domain/application state**. Provides:
|
|
49
|
+
- Change observers via `addChangeObserver` / `notifyOnChange`
|
|
50
|
+
- Disposal observers via `addOnDisposeObserver`
|
|
51
|
+
- Automatic registration of the parent `AppObject`'s `notify` method as a change observer so AppObject-level observers react to entity changes
|
|
52
|
+
|
|
53
|
+
Patterns enabled:
|
|
54
|
+
- PM subscribes to one or more Entities to derive view state
|
|
55
|
+
- Repository-level aggregation via `AppObjectEntityRepo`
|
|
56
|
+
|
|
57
|
+
### AppObjectSingletonEntity (`AppObjectSingletonEntity.ts`)
|
|
58
|
+
A specialized entity that automatically registers itself as a singleton. Extends `AppObjectEntity` with:
|
|
59
|
+
- Automatic singleton registration via `appObjects.registerSingleton(this)` in constructor
|
|
60
|
+
- Automatic singleton unregistration via `appObjects.unregisterSingleton(this.type)` on disposal
|
|
61
|
+
- Ensures only one instance of this entity type exists across the entire application
|
|
62
|
+
|
|
63
|
+
Use this when you need globally unique entities (e.g., application-wide configuration, user session state).
|
|
64
|
+
|
|
65
|
+
### AppObjectEntityRepo (`AppObjectEntityRepo.ts`)
|
|
66
|
+
A stateful collection component (itself an `AppObjectEntity`) that manages many entity instances keyed by `AppObject.id`:
|
|
67
|
+
- Add/remove operations attach/detach change observers on each entity to bubble aggregate change notifications
|
|
68
|
+
- Emits addition/removal notifications through dedicated observer lists
|
|
69
|
+
- Factory pattern support:
|
|
70
|
+
- `create(id?: string)` – creates a new entity via `entityFactory` and adds it to the repository (auto-generates ID if not provided)
|
|
71
|
+
- `entityFactory(appObject: AppObject)` – abstract method that derived classes override to provide custom entity creation logic
|
|
72
|
+
- Query surface:
|
|
73
|
+
- `has(id)` – checks if an entity exists for the given ID
|
|
74
|
+
- `getById(id)` – retrieves an entity by its ID
|
|
75
|
+
- `getOrCreate(id)` – gets an existing entity by ID, or creates it if it doesn't exist
|
|
76
|
+
- `getAll()` – returns all entities in the repository
|
|
77
|
+
- Mutation operations:
|
|
78
|
+
- `add(entity)` – adds an entity to the repository
|
|
79
|
+
- `removeById(id)` – removes a single entity by its ID
|
|
80
|
+
- `deleteAll()` – removes all entities from the repository
|
|
81
|
+
- Legacy methods (deprecated):
|
|
82
|
+
- `hasForAppObject(id)` – use `has(id)` instead
|
|
83
|
+
- `getForAppObject(id)` – use `getById(id)` instead
|
|
84
|
+
- `removeForAppObject(id)` – use `removeById(id)` instead
|
|
85
|
+
|
|
86
|
+
**Factory Pattern Usage:**
|
|
87
|
+
Derived repositories should override `entityFactory` to provide entity creation logic:
|
|
88
|
+
```ts
|
|
89
|
+
class PlayerRepo extends AppObjectEntityRepo<PlayerEntity> {
|
|
90
|
+
static type = "playerRepo";
|
|
91
|
+
|
|
92
|
+
constructor(appObject: AppObject) {
|
|
93
|
+
super(appObject, PlayerRepo.type);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
entityFactory(appObject: AppObject): PlayerEntity {
|
|
97
|
+
return new PlayerEntity(appObject);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Usage
|
|
102
|
+
const repo = new PlayerRepo(repoAppObject);
|
|
103
|
+
const player1 = repo.create("player-1"); // Create with specific ID
|
|
104
|
+
const player2 = repo.create(); // Create with auto-generated ID
|
|
105
|
+
const player3 = repo.getOrCreate("player-1"); // Get existing player1 or create if missing
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
This enables higher-level coordination (e.g., multi-selection, batch processing, dashboards) with cohesive reactivity.
|
|
109
|
+
|
|
110
|
+
### AppObjectSingletonEntityRepo (`AppObjectSingletonEntityRepo.ts`)
|
|
111
|
+
Generic singleton repository for managing entity collections. Extends `AppObjectEntityRepo<T>` with:
|
|
112
|
+
- Automatic singleton registration and unregistration (same pattern as `AppObjectSingletonEntity`)
|
|
113
|
+
- Type-safe entity collection management across the entire application
|
|
114
|
+
- Use when you need a single, centralized collection (e.g., all players, all items, all tasks)
|
|
115
|
+
|
|
116
|
+
Example:
|
|
117
|
+
```ts
|
|
118
|
+
class PlayerRepo extends AppObjectSingletonEntityRepo<PlayerEntity> {
|
|
119
|
+
static type = "playerRepo";
|
|
120
|
+
constructor(appObject: AppObject) {
|
|
121
|
+
super(appObject, PlayerRepo.type);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### AppObjectPM (`AppObjectPM.ts`)
|
|
127
|
+
The Presentation Manager (Presentation Model / MVVM mediator). Responsibilities:
|
|
128
|
+
- Maintains last emitted view model (`lastVM`)
|
|
129
|
+
- Optionally provides a `defaultVM` for initial state before any view model is generated
|
|
130
|
+
- Compares new vs prior view model via abstract `vmsAreEqual(a,b)` to suppress redundant updates
|
|
131
|
+
- Manages a list of view observers (`addView` / `removeView`)
|
|
132
|
+
- Provides `doUpdateView(vm)` to push updates only when meaningfully changed
|
|
133
|
+
- Supports automatic entity observation via `observeEntity(entity)` for reactive view model updates
|
|
134
|
+
- Implements lazy evaluation: `formVM()` is only called when views are registered
|
|
135
|
+
- Provides `onViewAdded()` lifecycle hook that is called whenever a view is added
|
|
136
|
+
- Automatically cleans up entity observers on disposal
|
|
137
|
+
|
|
138
|
+
**New Reactive Pattern (Preferred):**
|
|
139
|
+
Derived PMs can now use automatic entity observation:
|
|
140
|
+
1. Call `observeEntity(entity)` in the constructor to register entities
|
|
141
|
+
2. Override `formVM()` to generate view models from entity state
|
|
142
|
+
3. When observed entities change, `formVM()` is automatically called (only if views are registered)
|
|
143
|
+
4. Optionally override `onViewAdded()` to react when views are added (e.g., start animations, log analytics)
|
|
144
|
+
5. Entity observers are automatically cleaned up on disposal
|
|
145
|
+
|
|
146
|
+
**Backward Compatibility:**
|
|
147
|
+
All existing PM implementations continue to work without modification. The new features are optional enhancements that provide:
|
|
148
|
+
- Automatic view model regeneration on entity changes
|
|
149
|
+
- Default view model support for initial state
|
|
150
|
+
- Simplified entity observation with automatic cleanup
|
|
151
|
+
- Lifecycle hooks for reacting to view additions
|
|
152
|
+
|
|
153
|
+
This isolates transformation logic and prevents UI churn.
|
|
154
|
+
|
|
155
|
+
### AppObjectSingletonPM (`AppObjectSingletonPM.ts`)
|
|
156
|
+
Abstract singleton presentation manager that extends `AppObjectPM<T>` with:
|
|
157
|
+
- Automatic singleton registration and unregistration
|
|
158
|
+
- Ensures only one PM instance of this type exists across the application
|
|
159
|
+
- Inherits all automatic entity observation and view model caching features
|
|
160
|
+
- Use for global UI state (e.g., application theme, notification center, global progress indicator)
|
|
161
|
+
|
|
162
|
+
Example:
|
|
163
|
+
```ts
|
|
164
|
+
class GlobalThemePM extends AppObjectSingletonPM<ThemeVM> {
|
|
165
|
+
static type = "globalThemePM";
|
|
166
|
+
|
|
167
|
+
constructor(appObject: AppObject) {
|
|
168
|
+
super(appObject, GlobalThemePM.type);
|
|
169
|
+
const settings = this.getCachedSingleton<SettingsEntity>(SettingsEntity.type);
|
|
170
|
+
if (settings) this.observeEntity(settings);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
vmsAreEqual(a: ThemeVM, b: ThemeVM): boolean {
|
|
174
|
+
return a.mode === b.mode && a.primaryColor === b.primaryColor;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
formVM(): void {
|
|
178
|
+
// Transform settings into theme view model
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### AppObjectUC (`AppObjectUC.ts`)
|
|
184
|
+
A semantic base for **application operations** (e.g., workflows, transactional steps). It adds categorical identity (`componentType = UC`) but intentionally stays minimal so concrete subclasses can:
|
|
185
|
+
- Orchestrate entities, PMs, and repositories
|
|
186
|
+
- Enforce validation and domain rules
|
|
187
|
+
- Trigger PM updates or entity mutations
|
|
188
|
+
|
|
189
|
+
### AppObjectSingletonUC (`AppObjectSingletonUC.ts`)
|
|
190
|
+
Singleton use case component that extends `AppObjectUC` with:
|
|
191
|
+
- Automatic singleton registration and unregistration
|
|
192
|
+
- Ensures only one UC instance of this type exists across the application
|
|
193
|
+
- Use for application-wide orchestration logic (e.g., global purchase manager, authentication coordinator)
|
|
194
|
+
|
|
195
|
+
Example:
|
|
196
|
+
```ts
|
|
197
|
+
class AuthenticationUC extends AppObjectSingletonUC {
|
|
198
|
+
static type = "authenticationUC";
|
|
199
|
+
|
|
200
|
+
constructor(appObject: AppObject) {
|
|
201
|
+
super(appObject, AuthenticationUC.type);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
login(username: string, password: string): Promise<boolean> {
|
|
205
|
+
// Application-wide authentication logic
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
logout(): void {
|
|
209
|
+
// Clean up session, notify entities, etc.
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### AppObjectView (`AppObjectView.ts`)
|
|
215
|
+
A rendering/binding endpoint:
|
|
216
|
+
- Categorized as `VIEW`
|
|
217
|
+
- Typically subscribes to one or more PMs (manually, via the PM's `addView` API)
|
|
218
|
+
- Focused purely on projecting view models to a target (DOM, WebGL scene graph, canvas, etc.)
|
|
219
|
+
|
|
220
|
+
### getSingletonComponent (`getSingletonComponent.ts`)
|
|
221
|
+
A convenience helper:
|
|
222
|
+
```ts
|
|
223
|
+
const camera = getSingletonComponent<CameraPM>(CameraPM.type, repo);
|
|
224
|
+
```
|
|
225
|
+
- Wraps `repo.getSingleton(type)` to simplify imports and generics at call sites
|
|
226
|
+
|
|
227
|
+
### printAppObjectDetails (`printAppObjectDetails.ts`)
|
|
228
|
+
Debugging utility that enumerates components attached to a given AppObject ID.
|
|
229
|
+
|
|
230
|
+
## Repository Layer
|
|
231
|
+
`AppObjectRepo`:
|
|
232
|
+
- Tracks all AppObjects (`id -> AppObject`)
|
|
233
|
+
- Aggregates reactivity: registers itself as observer of each AppObject; its own observers can react to structural changes
|
|
234
|
+
- Maintains explicit singleton registry (Map of `type -> component`)
|
|
235
|
+
- `registerSingleton(component)` – explicitly register a component as singleton
|
|
236
|
+
- `unregisterSingleton(type)` – remove a singleton registration (called automatically by singleton components on disposal)
|
|
237
|
+
- `hasSingleton(type)` – check if a singleton exists (checks registry first, then falls back to scanning for single instance)
|
|
238
|
+
- `getSingleton(type)` – retrieve a singleton component
|
|
239
|
+
- Fallback heuristic: if not explicitly registered, scan all components of that type
|
|
240
|
+
- Warns on zero or multiple matches; caches first valid resolution
|
|
241
|
+
- Query Helpers:
|
|
242
|
+
- `getAllAppObjectsWithComponent(type)`
|
|
243
|
+
- `getAllComponents(type)`
|
|
244
|
+
- `getAppObjectComponent(appObjectID, type)`
|
|
245
|
+
- Logging hub consumed by components
|
|
246
|
+
|
|
247
|
+
### Singleton Component Pattern
|
|
248
|
+
The framework provides four singleton base classes that automatically handle registration/unregistration:
|
|
249
|
+
- `AppObjectSingletonEntity` – singleton entities
|
|
250
|
+
- `AppObjectSingletonEntityRepo<T>` – singleton entity repositories
|
|
251
|
+
- `AppObjectSingletonPM<T>` – singleton presentation managers
|
|
252
|
+
- `AppObjectSingletonUC` – singleton use cases
|
|
253
|
+
|
|
254
|
+
All singleton components:
|
|
255
|
+
1. Call `this.appObjects.registerSingleton(this)` in their constructor
|
|
256
|
+
2. Call `this.appObjects.unregisterSingleton(this.type)` in their `dispose()` method
|
|
257
|
+
3. Can be retrieved via `repo.getSingleton(type)` or `component.getCachedSingleton(type)`
|
|
258
|
+
|
|
259
|
+
This pattern ensures singleton lifecycle management is handled consistently across all component types.
|
|
260
|
+
|
|
261
|
+
## Typical User → UI → Domain Flow
|
|
262
|
+
|
|
263
|
+
1. **Controller Trigger**
|
|
264
|
+
A user action (click, input, gesture, hotkey, network event) calls a small *controller function*. Controllers are intentionally plain functions (see `ExampleFeature/Controllers/*.ts`). They:
|
|
265
|
+
- Accept raw UI parameters (strings, numbers, ids)
|
|
266
|
+
- Locate the appropriate Use Case (by `id` for per-object UCs or via static singleton accessors for global UCs)
|
|
267
|
+
- Guard against missing UCs and submit warnings through the repo
|
|
268
|
+
- Invoke a single semantic method on the UC
|
|
269
|
+
|
|
270
|
+
2. **Use Case Mutation**
|
|
271
|
+
The Use Case applies business rules and mutates one or more **Entities** (and occasionally invokes other UCs). Entities are considered part of the inner domain and remain decoupled from presentation concerns.
|
|
272
|
+
|
|
273
|
+
3. **Entity Notification**
|
|
274
|
+
Entities call `notifyOnChange()` after state mutation. Their change observers (PMs, the parent `AppObject`, repositories, etc.) are invoked.
|
|
275
|
+
|
|
276
|
+
4. **Presentation Derivation**
|
|
277
|
+
PMs receiving the change recompute an immutable **View Model**. If `vmsAreEqual(last, next)` is false, `doUpdateView(nextVM)` broadcasts the new model.
|
|
278
|
+
|
|
279
|
+
5. **View Update**
|
|
280
|
+
Views (or framework-side adapters) receive the new View Model and update rendering / UI bindings. React hooks, canvas redraws, WebGL scene updates, etc., occur here.
|
|
281
|
+
|
|
282
|
+
Controller Functions remain deliberately slim: *resolve UC → call UC → handle missing cases*. This keeps UI code declarative and reduces duplication of lookup logic.
|
|
283
|
+
|
|
284
|
+
### Adapters (Domain Boundary Helpers)
|
|
285
|
+
Adapters (see `ExampleFeature/Adapters/*.ts`) standardize subscription mechanics between UI frameworks (React hooks, etc.) and PMs:
|
|
286
|
+
- Provide a `defaultVM` for initial render
|
|
287
|
+
- Encapsulate `subscribe` / `unsubscribe` logic
|
|
288
|
+
- Support both per-object (`PmAdapter`) and singleton (`SingletonPmAdapter`) patterns
|
|
289
|
+
|
|
290
|
+
## Dependency Direction (Clean Architecture Constraints)
|
|
291
|
+
|
|
292
|
+
Strict layering reduces coupling and prevents presentation concerns from leaking inward:
|
|
293
|
+
|
|
294
|
+
| Layer | May Depend On | Must NOT Depend On | Notes |
|
|
295
|
+
|-------|----------------|--------------------|-------|
|
|
296
|
+
| Entities (incl. Repos & Value Objects) | Other Entities, Value Objects | UCs, PMs, Controllers, Adapters | Repositories are treated as Entities (state holders) |
|
|
297
|
+
| Use Cases (UCs) | Entities, other UCs | PMs | They orchestrate but never shape view models directly |
|
|
298
|
+
| PMs | Entities, UCs | Other PMs (allowed but avoided), Controllers | No current need for PM→PM dependency encountered |
|
|
299
|
+
| Controllers | UCs, (optionally) Repos for lookup | Entities, PM internals | Pure boundary functions; translate UI intent to UC invocation |
|
|
300
|
+
| Adapters | PMs (subscription), Repos (to resolve PM) | UCs, Entities (direct mutation) | Provide view-model streaming into UI layer |
|
|
301
|
+
| Views | PMs (via adapters or direct subscription) | UCs, Entities | Rendering only |
|
|
302
|
+
|
|
303
|
+
Additional Rules:
|
|
304
|
+
- Repos are considered data/state layer; treat them like Entities for dependency purposes.
|
|
305
|
+
- Singletons do not relax dependency direction—acquire them only where the layer already allows the dependency.
|
|
306
|
+
- Logging via repo methods is allowed anywhere because it does not introduce upward coupling.
|
|
307
|
+
|
|
308
|
+
Violation Signals:
|
|
309
|
+
- An Entity importing a UC or PM
|
|
310
|
+
- A UC importing a PM
|
|
311
|
+
- A PM invoking controller logic
|
|
312
|
+
- Adapters mutating Entity state directly
|
|
313
|
+
|
|
314
|
+
Refactor Strategy on Violation:
|
|
315
|
+
1. Push mutation inward (Controller → UC → Entity)
|
|
316
|
+
2. Introduce a new UC if orchestration spans multiple existing UCs
|
|
317
|
+
3. Decompose a PM if it begins coordinating workflow rather than projecting state
|
|
318
|
+
|
|
319
|
+
## Data & Reactive Flow
|
|
320
|
+
1. Entity mutation occurs (e.g., property set in a subclass) → calls `notifyOnChange()`
|
|
321
|
+
2. Entity notifies its observers (including its parent AppObject and any PMs)
|
|
322
|
+
3. AppObject notifies its observers (repository + any external listeners)
|
|
323
|
+
4. PM recalculates view model; if changed (`vmsAreEqual` is false), it calls `doUpdateView(vm)`
|
|
324
|
+
5. Views previously registered with the PM receive the new view model and re-render
|
|
325
|
+
|
|
326
|
+
```
|
|
327
|
+
Entity --(notifyOnChange)--> PM --(doUpdateView)--> View
|
|
328
|
+
| ^ |
|
|
329
|
+
+----> AppObject --(notify)----+ |
|
|
330
|
+
| |
|
|
331
|
+
v |
|
|
332
|
+
AppObjectRepo (optional higher-level observers)
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## Lifecycle Summary
|
|
336
|
+
- Construct `AppObject` via `makeAppObject(id, repo)` → auto-added to repo
|
|
337
|
+
- Construct components with `(appObject, type)` → auto-attached
|
|
338
|
+
- Replace component: adding another of same `type` disposes previous instance
|
|
339
|
+
- Dispose entity / component: removes self from AppObject, clears observers
|
|
340
|
+
- Dispose AppObject: disposes all components, removes itself from repo
|
|
341
|
+
|
|
342
|
+
## Extension Guidelines
|
|
343
|
+
When adding a new component type:
|
|
344
|
+
1. Define a static string identifier (e.g., `export const MyFeatureEntityType = "MyFeatureEntity";`)
|
|
345
|
+
2. Choose the appropriate base class:
|
|
346
|
+
- For **regular components**: `AppObjectEntity`, `AppObjectPM`, `AppObjectUC`, or `AppObjectView`
|
|
347
|
+
- For **singleton components**: `AppObjectSingletonEntity`, `AppObjectSingletonEntityRepo<T>`, `AppObjectSingletonPM<T>`, or `AppObjectSingletonUC`
|
|
348
|
+
3. Invoke `super(appObject, MyFeatureEntityType);` in constructor
|
|
349
|
+
4. For PMs: implement `vmsAreEqual` (and optionally `formVM()` with `observeEntity()` for automatic updates)
|
|
350
|
+
5. For singleton components: No additional registration needed—handled automatically by the base class
|
|
351
|
+
6. Use cached getters for performance when repeatedly accessing collaborating components
|
|
352
|
+
|
|
353
|
+
### Choosing Between Regular and Singleton Components
|
|
354
|
+
Use **singleton components** when:
|
|
355
|
+
- Only one instance should exist across the entire application
|
|
356
|
+
- The component represents global application state or behavior
|
|
357
|
+
- Examples: user session, app configuration, global theme, authentication manager
|
|
358
|
+
|
|
359
|
+
Use **regular components** when:
|
|
360
|
+
- Multiple instances may exist (one per AppObject)
|
|
361
|
+
- The component represents per-object state or behavior
|
|
362
|
+
- Examples: player state, item properties, entity-specific UI
|
|
363
|
+
|
|
364
|
+
### Choosing Component Categories
|
|
365
|
+
- Put durable, observable state in an Entity
|
|
366
|
+
- Put pure transformation / derivation logic in a PM
|
|
367
|
+
- Put orchestration / cross-entity logic in a UC
|
|
368
|
+
- Put rendering / binding logic in a View
|
|
369
|
+
- Avoid mixing responsibilities—compose instead
|
|
370
|
+
|
|
371
|
+
## Example Composition (Pseudo-Code)
|
|
372
|
+
```ts
|
|
373
|
+
// Create repo & object
|
|
374
|
+
const repo = makeAppObjectRepo();
|
|
375
|
+
const playerAO = makeAppObject("player-1", repo);
|
|
376
|
+
|
|
377
|
+
// Entity
|
|
378
|
+
class PlayerState extends AppObjectEntity {
|
|
379
|
+
static type = "PlayerState";
|
|
380
|
+
health = 100;
|
|
381
|
+
constructor(ao: AppObject) { super(ao, PlayerState.type); }
|
|
382
|
+
damage(amount: number) { this.health = Math.max(0, this.health - amount); this.notifyOnChange(); }
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// PM using new automatic entity observation (v1.7+)
|
|
386
|
+
class PlayerHUDPM extends AppObjectPM<{ healthPercent: number }> {
|
|
387
|
+
static type = "PlayerHUDPM";
|
|
388
|
+
readonly defaultVM = { healthPercent: 1.0 }; // Initial full health
|
|
389
|
+
|
|
390
|
+
constructor(ao: AppObject) {
|
|
391
|
+
super(ao, PlayerHUDPM.type);
|
|
392
|
+
const state = ao.getComponent<PlayerState>(PlayerState.type);
|
|
393
|
+
if (state) {
|
|
394
|
+
this.observeEntity(state); // Automatic observation
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
vmsAreEqual(a, b) { return a.healthPercent === b.healthPercent; }
|
|
399
|
+
|
|
400
|
+
// Automatically called when observed entities change (if views are registered)
|
|
401
|
+
formVM(): void {
|
|
402
|
+
const state = this.getCachedLocalComponent<PlayerState>(PlayerState.type);
|
|
403
|
+
if (state) {
|
|
404
|
+
this.doUpdateView({ healthPercent: state.health / 100 });
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// View (simplified)
|
|
410
|
+
class ConsoleHUDView extends AppObjectView {
|
|
411
|
+
static type = "ConsoleHUDView";
|
|
412
|
+
constructor(ao: AppObject, hudPM: PlayerHUDPM) {
|
|
413
|
+
super(ao, ConsoleHUDView.type);
|
|
414
|
+
hudPM.addView(vm => console.log("HP:", vm.healthPercent));
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
new PlayerState(playerAO);
|
|
419
|
+
const hudPM = new PlayerHUDPM(playerAO);
|
|
420
|
+
new ConsoleHUDView(playerAO, hudPM);
|
|
421
|
+
|
|
422
|
+
// Trigger - PM automatically updates views when entity changes
|
|
423
|
+
const ps = playerAO.getComponent<PlayerState>(PlayerState.type);
|
|
424
|
+
ps?.damage(10); // PM automatically calls formVM() and updates views
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**Legacy Pattern (Pre-v1.7, still supported):**
|
|
428
|
+
```ts
|
|
429
|
+
class PlayerHUDPM extends AppObjectPM<{ healthPercent: number }> {
|
|
430
|
+
static type = "PlayerHUDPM";
|
|
431
|
+
constructor(ao: AppObject) { super(ao, PlayerHUDPM.type); }
|
|
432
|
+
vmsAreEqual(a, b) { return a.healthPercent === b.healthPercent; }
|
|
433
|
+
|
|
434
|
+
// Manual update method
|
|
435
|
+
update() {
|
|
436
|
+
const state = this.getCachedLocalComponent<PlayerState>(PlayerState.type);
|
|
437
|
+
if (!state) return;
|
|
438
|
+
this.doUpdateView({ healthPercent: state.health / 100 });
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Manual trigger required
|
|
443
|
+
ps?.damage(10);
|
|
444
|
+
hudPM.update(); // Must manually call update
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
## Logging & Diagnostics
|
|
448
|
+
- Components call `log|warn|error` → forwarded via repo (current implementation prints to console)
|
|
449
|
+
- `printAppObjectDetails(id, repo)` lists attached component types
|
|
450
|
+
- Replacing a component logs a warning
|
|
451
|
+
|
|
452
|
+
## Performance Considerations
|
|
453
|
+
- Component caches avoid redundant map lookups and singleton scans
|
|
454
|
+
- PM equality check prevents spurious view updates
|
|
455
|
+
- Repository singleton cache resolves dynamic discovery only once per type
|
|
456
|
+
|
|
457
|
+
## Error Handling & Warnings
|
|
458
|
+
- Missing singleton lookup emits a warning (not fatal)
|
|
459
|
+
- Multiple candidates for a supposed singleton: first is used + warning
|
|
460
|
+
- Replacing a component of same type logs a warning and disposes old instance
|
|
461
|
+
|
|
462
|
+
## Glossary
|
|
463
|
+
- AppObject: A composable, observable unit of application composition
|
|
464
|
+
- Component: A behavior/state module attached to an AppObject
|
|
465
|
+
- Entity: Stateful, observable component storing domain data
|
|
466
|
+
- PM (Presentation Manager): Transforms Entities into view models for Views
|
|
467
|
+
- UC (Use Case): Encapsulates business workflow logic
|
|
468
|
+
- View: Renders or binds presentation logic to UI / output medium
|
|
469
|
+
- Singleton Component: A component intended to exist once across the repo, retrievable via `getSingleton`
|
|
470
|
+
|
|
471
|
+
## Future Enhancements (Ideas)
|
|
472
|
+
- Stronger typing for component `type` identifiers (string literal unions)
|
|
473
|
+
- Async disposal hooks for resources (e.g., WebGL buffers)
|
|
474
|
+
- Built-in metrics / instrumentation hooks
|
|
475
|
+
- Dev tooling: Graph generation of AppObjects and dependencies
|
|
476
|
+
|