@agile-ts/core
Advanced tools
Comparing version 0.0.17 to 0.1.0
# Change Log | ||
## 0.1.0 | ||
### Minor Changes | ||
- 855a921: #### :rocket: New Feature | ||
- `core`, `multieditor`, `react`, `vue` | ||
- [#161](https://github.com/agile-ts/agile/pull/161) Subscribe to Group value or output ([@bennodev19](https://github.com/bennodev19)) | ||
- `core` | ||
- [#160](https://github.com/agile-ts/agile/pull/160) added null option to Selector ([@bennodev19](https://github.com/bennodev19)) | ||
- [#159](https://github.com/agile-ts/agile/pull/159) make compute method async compatible ([@bennodev19](https://github.com/bennodev19)) | ||
#### :bug: Bug Fix | ||
- `core` | ||
- [#152](https://github.com/agile-ts/agile/pull/152) Fix remove selected Item loop ([@bennodev19](https://github.com/bennodev19)) | ||
#### :nail_care: Polish | ||
- `core`, `multieditor`, `react`, `vue` | ||
- [#161](https://github.com/agile-ts/agile/pull/161) Subscribe to Group value or output ([@bennodev19](https://github.com/bennodev19)) | ||
- `core`, `cra-template-agile-typescript`, `cra-template-agile`, `event`, `logger`, `multieditor`, `proxytree`, `react`, `utils`, `vue` | ||
- [#153](https://github.com/agile-ts/agile/pull/153) refactor core descriptions ([@bennodev19](https://github.com/bennodev19)) | ||
- `core`, `event`, `react`, `vue` | ||
- [#154](https://github.com/agile-ts/agile/pull/154) Optimise Runtime ([@bennodev19](https://github.com/bennodev19)) | ||
- `core`, `proxytree` | ||
- [#150](https://github.com/agile-ts/agile/pull/150) Outsource log messages ([@bennodev19](https://github.com/bennodev19)) | ||
#### Committers: 1 | ||
- BennoDev ([@bennodev19](https://github.com/bennodev19)) | ||
### Patch Changes | ||
- Updated dependencies [855a921] | ||
- @agile-ts/logger@0.0.5 | ||
- @agile-ts/utils@0.0.5 | ||
## 0.0.17 | ||
@@ -4,0 +42,0 @@ |
@@ -1,2 +0,2 @@ | ||
import { Runtime, Integration, State, Storage, Collection, CollectionConfig, DefaultItem, Computed, Integrations, Observer, SubController, Storages, CreateStorageConfigInterface, RegisterConfigInterface, Logger, CreateLoggerConfigInterface, StateConfigInterface, Group } from './internal'; | ||
import { Runtime, Integration, State, Storage, Collection, CollectionConfig, DefaultItem, Computed, Integrations, SubController, Storages, CreateStorageConfigInterface, RegisterConfigInterface, Logger, CreateLoggerConfigInterface, StateConfigInterface, DependableAgileInstancesType, CreateComputedConfigInterface, ComputeFunctionType } from './internal'; | ||
export declare class Agile { | ||
@@ -12,82 +12,200 @@ config: AgileConfigInterface; | ||
/** | ||
* The Agile Class is the main Instance of AgileTs | ||
* and should be unique to your application. | ||
* | ||
* Simply put, the Agile Instance is the brain of AgileTs | ||
* and manages all [Agile Sub Instance](https://agile-ts.org/docs/introduction/#agile-sub-instance) | ||
* such as States. | ||
* | ||
* It should be noted that it doesn't store the States; | ||
* It only manages them. Each State has an Instance of the Agile Class, | ||
* for example, to ingest its changes into the Runtime. | ||
* In summary, the main tasks of the Agile Class are to: | ||
* - queue [Agile Sub Instance](https://agile-ts.org/docs/introduction/#agile-sub-instance) | ||
* changes in the Runtime to prevent race conditions | ||
* - update/rerender subscribed UI-Components through the provided Integrations | ||
* such as the [React Integration](https://agile-ts.org/docs/react) | ||
* - integrate with the persistent [Storage](https://agile-ts.org/docs/core/storage) | ||
* - provide configuration object | ||
* | ||
* Each Agile Sub Instance requires an Agile Instance to be instantiated and function properly. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/agile-instance/) | ||
* | ||
* @public | ||
* Agile - Global state and logic framework for reactive Typescript & Javascript applications | ||
* @param config - Config | ||
* @param config - Configuration object | ||
*/ | ||
constructor(config?: CreateAgileConfigInterface); | ||
/** | ||
* Returns a newly created Storage. | ||
* | ||
* A Storage Class serves as an interface to external storages, | ||
* such as the [Async Storage](https://github.com/react-native-async-storage/async-storage) or | ||
* [Local Storage](https://www.w3schools.com/html/html5_webstorage.asp). | ||
* | ||
* It creates the foundation to easily [`persist()`](https://agile-ts.org/docs/core/state/methods#persist) [Agile Sub Instances](https://agile-ts.org/docs/introduction/#agile-sub-instance) | ||
* (like States or Collections) in nearly any external storage. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createstorage) | ||
* | ||
* @public | ||
* Storage - Handy Interface for storing Items permanently | ||
* @param config - Config | ||
* @param config - Configuration object | ||
*/ | ||
createStorage(config: CreateStorageConfigInterface): Storage; | ||
/** | ||
* Returns a newly created State. | ||
* | ||
* A State manages a piece of Information | ||
* that we need to remember globally at a later point in time. | ||
* While providing a toolkit to use and mutate this piece of Information. | ||
* | ||
* You can create as many global States as you need. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createstate) | ||
* | ||
* @public | ||
* State - Class that holds one Value and causes rerender on subscribed Components | ||
* @param initialValue - Initial Value of the State | ||
* @param config - Config | ||
* @param initialValue - Initial value of the State. | ||
* @param config - Configuration object | ||
*/ | ||
createState<ValueType = any>(initialValue: ValueType, config?: StateConfigInterface): State<ValueType>; | ||
/** | ||
* Returns a newly created Collection. | ||
* | ||
* A Collection manages a reactive set of Information | ||
* that we need to remember globally at a later point in time. | ||
* While providing a toolkit to use and mutate this set of Information. | ||
* | ||
* It is designed for arrays of data objects following the same pattern. | ||
* | ||
* Each of these data object must have a unique `primaryKey` to be correctly identified later. | ||
* | ||
* You can create as many global Collections as you need. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createcollection) | ||
* | ||
* @public | ||
* Collection - Class that holds a List of Objects with key and causes rerender on subscribed Components | ||
* @param config - Config | ||
* @param config - Configuration object | ||
*/ | ||
createCollection<DataType extends object = DefaultItem>(config?: CollectionConfig<DataType>): Collection<DataType>; | ||
createCollection<DataType extends Object = DefaultItem>(config?: CollectionConfig<DataType>): Collection<DataType>; | ||
/** | ||
* Returns a newly created Computed. | ||
* | ||
* A Computed is an extension of the State Class | ||
* that computes its value based on a specified compute function. | ||
* | ||
* The computed value will be cached to avoid unnecessary recomputes | ||
* and is only recomputed when one of its direct dependencies changes. | ||
* | ||
* Direct dependencies can be States and Collections. | ||
* So when, for example, a dependent State value changes, the computed value is recomputed. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createstate) | ||
* | ||
* @public | ||
* Computed - Function that recomputes its value if a dependency changes | ||
* @param computeFunction - Function for computing value | ||
* @param config - Config | ||
* @param deps - Hard coded dependencies of Computed Function | ||
* @param computeFunction - Function to compute the computed value. | ||
* @param config - Configuration object | ||
*/ | ||
createComputed<ComputedValueType = any>(computeFunction: () => ComputedValueType, config?: StateConfigInterface, deps?: Array<Observer | State | Event | Group>): Computed<ComputedValueType>; | ||
createComputed<ComputedValueType = any>(computeFunction: ComputeFunctionType<ComputedValueType>, config?: CreateComputedConfigInterface): Computed<ComputedValueType>; | ||
/** | ||
* Returns a newly created Computed. | ||
* | ||
* A Computed is an extension of the State Class | ||
* that computes its value based on a specified compute function. | ||
* | ||
* The computed value will be cached to avoid unnecessary recomputes | ||
* and is only recomputed when one of its direct dependencies changes. | ||
* | ||
* Direct dependencies can be States and Collections. | ||
* So when, for example, a dependent State value changes, the computed value is recomputed. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createcomputed) | ||
* | ||
* @public | ||
* Computed - Function that recomputes its value if a dependency changes | ||
* @param computeFunction - Function for computing value | ||
* @param deps - Hard coded dependencies of Computed Function | ||
* @param computeFunction - Function to compute the computed value. | ||
* @param deps - Hard-coded dependencies on which the Computed Class should depend. | ||
*/ | ||
createComputed<ComputedValueType = any>(computeFunction: () => ComputedValueType, deps?: Array<Observer | State | Event>): Computed<ComputedValueType>; | ||
createComputed<ComputedValueType = any>(computeFunction: ComputeFunctionType<ComputedValueType>, deps?: Array<DependableAgileInstancesType>): Computed<ComputedValueType>; | ||
/** | ||
* Registers the specified Integration with AgileTs. | ||
* | ||
* After a successful registration, | ||
* [Agile Sub Instances](https://agile-ts.org/docs/introduction/#agile-sub-instance) such as States | ||
* can be bound to the Integration's UI-Components for reactivity. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#integrate) | ||
* | ||
* @public | ||
* Integrates framework into Agile | ||
* @param integration - Integration that gets registered/integrated | ||
* @param integration - Integration to be integrated/registered. | ||
*/ | ||
integrate(integration: Integration): this; | ||
/** | ||
* Registers the specified Storage with AgileTs. | ||
* | ||
* After a successful registration, | ||
* [Agile Sub Instances](https://agile-ts.org/docs/introduction/#agile-sub-instance) such as States | ||
* can be persisted in the external Storage. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#registerstorage) | ||
* | ||
* @public | ||
* Registers new Storage as Agile Storage | ||
* @param storage - new Storage | ||
* @param config - Config | ||
* @param storage - Storage to be registered. | ||
* @param config - Configuration object | ||
*/ | ||
registerStorage(storage: Storage, config?: RegisterConfigInterface): this; | ||
/** | ||
* Returns a boolean indicating whether any Integration | ||
* has been registered with AgileTs or not. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#hasintegration) | ||
* | ||
* @public | ||
* Checks if Agile has any registered Integration | ||
*/ | ||
hasIntegration(): boolean; | ||
/** | ||
* Returns a boolean indicating whether any Storage | ||
* has been registered with AgileTs or not. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#hasstorage) | ||
* | ||
* @public | ||
* Checks if Agile has any registered Storage | ||
*/ | ||
hasStorage(): boolean; | ||
} | ||
/** | ||
* @param logJobs - Allow Agile Logs | ||
* @param waitForMount - If Agile should wait until the component mounts | ||
* @param storageConfig - To configure Agile Storage | ||
* @param bindGlobal - Binds Agile Instance Global | ||
*/ | ||
export interface CreateAgileConfigInterface { | ||
/** | ||
* Configures the logging behaviour of AgileTs. | ||
* @default { | ||
prefix: 'Agile', | ||
active: true, | ||
level: Logger.level.WARN, | ||
canUseCustomStyles: true, | ||
allowedTags: ['runtime', 'storage', 'subscription', 'multieditor'], | ||
} | ||
*/ | ||
logConfig?: CreateLoggerConfigInterface; | ||
/** | ||
* Whether the Subscription Container shouldn't be ready | ||
* until the UI-Component it represents has been mounted. | ||
* @default true | ||
*/ | ||
waitForMount?: boolean; | ||
/** | ||
* Whether the Local Storage should be registered as a Agile Storage by default. | ||
* @default true | ||
*/ | ||
localStorage?: boolean; | ||
/** | ||
* Whether the Agile Instance should be globally bound (globalThis) | ||
* and thus be globally available. | ||
* @default false | ||
*/ | ||
bindGlobal?: boolean; | ||
} | ||
/** | ||
* @param waitForMount - If Agile should wait until the component mounts | ||
*/ | ||
export interface AgileConfigInterface { | ||
/** | ||
* Whether the Subscription Container shouldn't be ready | ||
* until the UI-Component it represents has been mounted. | ||
* @default true | ||
*/ | ||
waitForMount: boolean; | ||
} |
@@ -30,7 +30,6 @@ "use strict"; | ||
Agile.logger = new internal_1.Logger(config.logConfig); | ||
Agile.logger.success('Created new AgileInstance ', this, Agile.logger); | ||
if (config.bindGlobal) { | ||
internal_1.LogCodeManager.log('10:00:00', [], this, Agile.logger); | ||
if (config.bindGlobal) | ||
if (!internal_1.globalBind(Agile.globalKey, this)) | ||
Agile.logger.warn('Be careful with binding multiple Agile Instances globally in one Application!'); | ||
} | ||
internal_1.LogCodeManager.log('10:02:00'); | ||
} | ||
@@ -46,16 +45,14 @@ createStorage(config) { | ||
} | ||
createComputed(computeFunction, configOrDeps, deps) { | ||
let _deps; | ||
let _config; | ||
createComputed(computeFunction, configOrDeps) { | ||
let _config = {}; | ||
if (Array.isArray(configOrDeps)) { | ||
_deps = configOrDeps; | ||
_config = {}; | ||
_config = internal_1.flatMerge(_config, { | ||
computedDeps: configOrDeps, | ||
}); | ||
} | ||
else { | ||
_config = configOrDeps || {}; | ||
_deps = deps || []; | ||
if (configOrDeps) | ||
_config = configOrDeps; | ||
} | ||
return new internal_1.Computed(this, computeFunction, internal_1.flatMerge(_config, { | ||
computedDeps: _deps, | ||
})); | ||
return new internal_1.Computed(this, computeFunction, _config); | ||
} | ||
@@ -62,0 +59,0 @@ integrate(integration) { |
import { Collection, CollectionKey, CreatePersistentConfigInterface, DefaultItem, Group, GroupKey, ItemKey, Persistent, PersistentKey, StorageKey } from '../internal'; | ||
export declare class CollectionPersistent<DataType extends object = DefaultItem> extends Persistent { | ||
export declare class CollectionPersistent<DataType extends Object = DefaultItem> extends Persistent { | ||
collection: () => Collection<DataType>; | ||
@@ -8,67 +8,94 @@ static defaultGroupSideEffectKey: string; | ||
/** | ||
* Internal Class for managing the permanent persistence of a Collection. | ||
* | ||
* @internal | ||
* Collection Persist Manager - Handles permanent storing of Collection Value | ||
* @param collection - Collection that gets stored | ||
* @param config - Config | ||
* @param collection - Collection to be persisted. | ||
* @param config - Configuration object | ||
*/ | ||
constructor(collection: Collection<DataType>, config?: CreatePersistentConfigInterface); | ||
/** | ||
* Loads the persisted value into the Collection | ||
* or persists the Collection value in the corresponding Storage. | ||
* This behaviour depends on whether the Collection has been persisted before. | ||
* | ||
* @internal | ||
* Updates Key/Name of Persistent | ||
* @param value - New Key/Name of Persistent | ||
*/ | ||
setKey(value?: StorageKey): Promise<void>; | ||
initialLoading(): Promise<void>; | ||
/** | ||
* Loads Collection Instances (like Items or Groups) from the corresponding Storage | ||
* and sets up side effects that dynamically update | ||
* the Storage value when the Collection (Instances) changes. | ||
* | ||
* @internal | ||
* Loads/Saves Storage Value for the first Time | ||
* @param storageItemKey - Prefix Storage key of the to load Collection Instances. | ||
* | default = Persistent.key | | ||
* @return Whether the loading of the persisted Collection Instances and setting up of the corresponding side effects was successful. | ||
*/ | ||
initialLoading(): Promise<void>; | ||
loadPersistedValue(storageItemKey?: PersistentKey): Promise<boolean>; | ||
/** | ||
* Persists Collection Instances (like Items or Groups) in the corresponding Storage | ||
* and sets up side effects that dynamically update | ||
* the Storage value when the Collection (Instances) changes. | ||
* | ||
* @internal | ||
* Loads Collection from Storage | ||
* @param storageKey - Prefix Key of Persisted Instances (default PersistentKey) | ||
* @return Success? | ||
* @param storageItemKey - Prefix Storage key of the to persist Collection Instances. | ||
* | default = Persistent.key | | ||
* @return Whether the persisting of the Collection Instances and the setting up of the corresponding side effects was successful. | ||
*/ | ||
loadPersistedValue(storageKey?: PersistentKey): Promise<boolean>; | ||
persistValue(storageItemKey?: PersistentKey): Promise<boolean>; | ||
/** | ||
* Sets up side effects to keep the Storage value in sync | ||
* with the Collection (Instances) value. | ||
* | ||
* @internal | ||
* Sets everything up so that the Collection gets saved in the Storage | ||
* @param storageKey - Prefix Key of Persisted Instances (default PersistentKey) | ||
* @return Success? | ||
* @param storageItemKey - Prefix Storage key of the to remove Collection Instances. | ||
* | default = Persistent.key | | ||
*/ | ||
persistValue(storageKey?: PersistentKey): Promise<boolean>; | ||
setupSideEffects(storageItemKey?: PersistentKey): void; | ||
/** | ||
* Removes the Collection from the corresponding Storage. | ||
* -> Collection is no longer persisted | ||
* | ||
* @internal | ||
* Removes Collection from the Storage | ||
* @param storageKey - Prefix Key of Persisted Instances (default PersistentKey) | ||
* @return Success? | ||
* @param storageItemKey - Prefix Storage key of the persisted Collection Instances. | ||
* | default = Persistent.key | | ||
* @return Whether the removal of the Collection Instances was successful. | ||
*/ | ||
removePersistedValue(storageKey?: PersistentKey): Promise<boolean>; | ||
removePersistedValue(storageItemKey?: PersistentKey): Promise<boolean>; | ||
/** | ||
* Formats the specified key so that it can be used as a valid Storage key | ||
* and returns the formatted variant of it. | ||
* | ||
* If no formatable key (`undefined`/`null`) was provided, | ||
* an attempt is made to use the Collection identifier key as Storage key. | ||
* | ||
* @internal | ||
* Formats Storage Key | ||
* @param key - Key that gets formatted | ||
* @param key - Storage key to be formatted. | ||
*/ | ||
formatKey(key?: StorageKey): StorageKey | undefined; | ||
formatKey(key: StorageKey | undefined | null): StorageKey | undefined; | ||
/** | ||
* Adds and removes Items from the Storage based on the Group value. | ||
* | ||
* @internal | ||
* Rebuilds Storage depending on Group | ||
* @param group - Group | ||
* @param key - Prefix Key of Persisted Instances (default PersistentKey) | ||
* @param group - Group whose Items are to be dynamically added or removed from the Storage. | ||
* @param storageItemKey - Prefix Storage key of the persisted Collection Instances. | ||
* | default = Persistent.key | | ||
*/ | ||
rebuildStorageSideEffect(group: Group<DataType>, key?: PersistentKey): void; | ||
rebuildStorageSideEffect(group: Group<DataType>, storageItemKey?: PersistentKey): void; | ||
/** | ||
* Builds valid Item Storage key based on the 'Collection Item Persist Pattern'. | ||
* | ||
* @internal | ||
* Build Item StorageKey with Collection Persist Pattern | ||
* @param itemKey - Key of Item | ||
* @param collectionKey - Key of Collection | ||
* @param itemKey - Key identifier of Item | ||
* @param collectionKey - Key identifier of Collection | ||
*/ | ||
static getItemStorageKey(itemKey?: ItemKey, collectionKey?: CollectionKey): string; | ||
static getItemStorageKey(itemKey: ItemKey | undefined | null, collectionKey: CollectionKey | undefined | null): string; | ||
/** | ||
* Builds valid Item Storage key based on the 'Collection Group Persist Pattern'. | ||
* | ||
* @internal | ||
* Build Group StorageKey with Collection Persist Pattern | ||
* @param groupKey - Key of Group | ||
* @param collectionKey - Key of Collection | ||
* @param groupKey - Key identifier of Group | ||
* @param collectionKey - Key identifier of Collection | ||
*/ | ||
static getGroupStorageKey(groupKey?: GroupKey, collectionKey?: CollectionKey): string; | ||
static getGroupStorageKey(groupKey: GroupKey | undefined | null, collectionKey: CollectionKey | undefined | null): string; | ||
} |
@@ -33,20 +33,2 @@ "use strict"; | ||
} | ||
setKey(value) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const oldKey = this._key; | ||
const wasReady = this.ready; | ||
if (value === this._key) | ||
return; | ||
this._key = value || internal_1.Persistent.placeHolderKey; | ||
const isValid = this.validatePersistent(); | ||
if (!wasReady) { | ||
if (isValid) | ||
yield this.initialLoading(); | ||
return; | ||
} | ||
yield this.removePersistedValue(oldKey); | ||
if (isValid) | ||
yield this.persistValue(value); | ||
}); | ||
} | ||
initialLoading() { | ||
@@ -62,8 +44,8 @@ const _super = Object.create(null, { | ||
} | ||
loadPersistedValue(storageKey) { | ||
loadPersistedValue(storageItemKey) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (!this.ready) | ||
return false; | ||
const _storageKey = storageKey || this._key; | ||
const isPersisted = yield this.agileInstance().storages.get(_storageKey, this.config.defaultStorageKey); | ||
const _storageItemKey = storageItemKey !== null && storageItemKey !== void 0 ? storageItemKey : this._key; | ||
const isPersisted = yield this.agileInstance().storages.get(_storageItemKey, this.config.defaultStorageKey); | ||
if (!isPersisted) | ||
@@ -73,19 +55,38 @@ return false; | ||
var _a, _b; | ||
const defaultGroup = this.collection().getGroup(this.collection().config.defaultGroupKey); | ||
if (!defaultGroup) | ||
const defaultGroup = this.collection().getDefaultGroup(); | ||
if (defaultGroup == null) | ||
return false; | ||
defaultGroup.persist({ | ||
const defaultGroupStorageKey = CollectionPersistent.getGroupStorageKey(defaultGroup._key, _storageItemKey); | ||
defaultGroup.persist(defaultGroupStorageKey, { | ||
loadValue: false, | ||
followCollectionPersistKeyPattern: true, | ||
defaultStorageKey: this.config.defaultStorageKey || undefined, | ||
storageKeys: this.storageKeys, | ||
followCollectionPersistKeyPattern: false, | ||
}); | ||
if ((_a = defaultGroup.persistent) === null || _a === void 0 ? void 0 : _a.ready) { | ||
yield ((_b = defaultGroup.persistent) === null || _b === void 0 ? void 0 : _b.initialLoading()); | ||
defaultGroup.isPersisted = true; | ||
} | ||
if ((_a = defaultGroup.persistent) === null || _a === void 0 ? void 0 : _a.ready) | ||
yield defaultGroup.persistent.initialLoading(); | ||
for (const itemKey of defaultGroup._value) { | ||
const itemStorageKey = CollectionPersistent.getItemStorageKey(itemKey, _storageKey); | ||
const storageValue = yield this.agileInstance().storages.get(itemStorageKey, this.config.defaultStorageKey); | ||
if (!storageValue) | ||
continue; | ||
this.collection().collect(storageValue); | ||
const item = this.collection().getItem(itemKey); | ||
const itemStorageKey = CollectionPersistent.getItemStorageKey(itemKey, _storageItemKey); | ||
if (item != null) { | ||
item.persist(itemStorageKey, { | ||
defaultStorageKey: this.config.defaultStorageKey || undefined, | ||
storageKeys: this.storageKeys, | ||
followCollectionPersistKeyPattern: false, | ||
}); | ||
} | ||
else { | ||
const dummyItem = this.collection().createPlaceholderItem(itemKey); | ||
dummyItem === null || dummyItem === void 0 ? void 0 : dummyItem.persist(itemStorageKey, { | ||
loadValue: false, | ||
defaultStorageKey: this.config.defaultStorageKey || undefined, | ||
storageKeys: this.storageKeys, | ||
followCollectionPersistKeyPattern: false, | ||
}); | ||
if ((_b = dummyItem === null || dummyItem === void 0 ? void 0 : dummyItem.persistent) === null || _b === void 0 ? void 0 : _b.ready) { | ||
const loadedPersistedValueIntoItem = yield dummyItem.persistent.loadPersistedValue(itemStorageKey); | ||
if (loadedPersistedValueIntoItem) | ||
this.collection().assignItem(dummyItem, { overwrite: false }); | ||
} | ||
} | ||
} | ||
@@ -96,23 +97,31 @@ return true; | ||
if (success) | ||
yield this.persistValue(_storageKey); | ||
this.setupSideEffects(_storageItemKey); | ||
return success; | ||
}); | ||
} | ||
persistValue(storageKey) { | ||
persistValue(storageItemKey) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (!this.ready) | ||
return false; | ||
const _storageKey = storageKey || this._key; | ||
const defaultGroup = this.collection().getGroup(this.collection().config.defaultGroupKey); | ||
if (!defaultGroup) | ||
const _storageItemKey = storageItemKey !== null && storageItemKey !== void 0 ? storageItemKey : this._key; | ||
const defaultGroup = this.collection().getDefaultGroup(); | ||
if (defaultGroup == null) | ||
return false; | ||
this.agileInstance().storages.set(_storageKey, true, this.storageKeys); | ||
if (!defaultGroup.isPersisted) | ||
defaultGroup.persist({ followCollectionPersistKeyPattern: true }); | ||
defaultGroup.addSideEffect(CollectionPersistent.defaultGroupSideEffectKey, () => this.rebuildStorageSideEffect(defaultGroup, _storageKey), { weight: 0 }); | ||
const defaultGroupStorageKey = CollectionPersistent.getGroupStorageKey(defaultGroup._key, _storageItemKey); | ||
this.agileInstance().storages.set(_storageItemKey, true, this.storageKeys); | ||
defaultGroup.persist(defaultGroupStorageKey, { | ||
defaultStorageKey: this.config.defaultStorageKey || undefined, | ||
storageKeys: this.storageKeys, | ||
followCollectionPersistKeyPattern: false, | ||
}); | ||
for (const itemKey of defaultGroup._value) { | ||
const item = this.collection().getItem(itemKey); | ||
const itemStorageKey = CollectionPersistent.getItemStorageKey(itemKey, _storageKey); | ||
item === null || item === void 0 ? void 0 : item.persist(itemStorageKey); | ||
const itemStorageKey = CollectionPersistent.getItemStorageKey(itemKey, _storageItemKey); | ||
item === null || item === void 0 ? void 0 : item.persist(itemStorageKey, { | ||
defaultStorageKey: this.config.defaultStorageKey || undefined, | ||
storageKeys: this.storageKeys, | ||
followCollectionPersistKeyPattern: false, | ||
}); | ||
} | ||
this.setupSideEffects(_storageItemKey); | ||
this.isPersisted = true; | ||
@@ -122,3 +131,10 @@ return true; | ||
} | ||
removePersistedValue(storageKey) { | ||
setupSideEffects(storageItemKey) { | ||
const _storageItemKey = storageItemKey !== null && storageItemKey !== void 0 ? storageItemKey : this._key; | ||
const defaultGroup = this.collection().getDefaultGroup(); | ||
if (defaultGroup == null) | ||
return; | ||
defaultGroup.addSideEffect(CollectionPersistent.defaultGroupSideEffectKey, (instance) => this.rebuildStorageSideEffect(instance, _storageItemKey), { weight: 0 }); | ||
} | ||
removePersistedValue(storageItemKey) { | ||
var _a, _b; | ||
@@ -128,12 +144,14 @@ return __awaiter(this, void 0, void 0, function* () { | ||
return false; | ||
const _storageKey = storageKey || this._key; | ||
const defaultGroup = this.collection().getGroup(this.collection().config.defaultGroupKey); | ||
const _storageItemKey = storageItemKey !== null && storageItemKey !== void 0 ? storageItemKey : this._key; | ||
const defaultGroup = this.collection().getDefaultGroup(); | ||
if (!defaultGroup) | ||
return false; | ||
this.agileInstance().storages.remove(_storageKey, this.storageKeys); | ||
(_a = defaultGroup.persistent) === null || _a === void 0 ? void 0 : _a.removePersistedValue(); | ||
const defaultGroupStorageKey = CollectionPersistent.getGroupStorageKey(defaultGroup._key, _storageItemKey); | ||
this.agileInstance().storages.remove(_storageItemKey, this.storageKeys); | ||
(_a = defaultGroup.persistent) === null || _a === void 0 ? void 0 : _a.removePersistedValue(defaultGroupStorageKey); | ||
defaultGroup.removeSideEffect(CollectionPersistent.defaultGroupSideEffectKey); | ||
for (const itemKey of defaultGroup._value) { | ||
const item = this.collection().getItem(itemKey); | ||
(_b = item === null || item === void 0 ? void 0 : item.persistent) === null || _b === void 0 ? void 0 : _b.removePersistedValue(); | ||
const itemStorageKey = CollectionPersistent.getItemStorageKey(itemKey, _storageItemKey); | ||
(_b = item === null || item === void 0 ? void 0 : item.persistent) === null || _b === void 0 ? void 0 : _b.removePersistedValue(itemStorageKey); | ||
} | ||
@@ -145,15 +163,14 @@ this.isPersisted = false; | ||
formatKey(key) { | ||
const collection = this.collection(); | ||
if (key == null && collection._key) | ||
return collection._key; | ||
if (key == null && this.collection()._key) | ||
return this.collection()._key; | ||
if (key == null) | ||
return; | ||
if (collection._key == null) | ||
collection._key = key; | ||
if (this.collection()._key == null) | ||
this.collection()._key = key; | ||
return key; | ||
} | ||
rebuildStorageSideEffect(group, key) { | ||
rebuildStorageSideEffect(group, storageItemKey) { | ||
var _a; | ||
const collection = group.collection(); | ||
const _key = key || ((_a = collection.persistent) === null || _a === void 0 ? void 0 : _a._key); | ||
const _storageItemKey = storageItemKey || ((_a = collection.persistent) === null || _a === void 0 ? void 0 : _a._key); | ||
if (group.previousStateValue.length === group._value.length) | ||
@@ -164,11 +181,10 @@ return; | ||
addedKeys.forEach((itemKey) => { | ||
var _a; | ||
const item = collection.getItem(itemKey); | ||
const _itemKey = CollectionPersistent.getItemStorageKey(itemKey, _key); | ||
if (!item) | ||
return; | ||
if (!item.isPersisted) | ||
item.persist(_itemKey); | ||
else | ||
(_a = item.persistent) === null || _a === void 0 ? void 0 : _a.persistValue(_itemKey); | ||
const itemStorageKey = CollectionPersistent.getItemStorageKey(itemKey, _storageItemKey); | ||
if (item != null && !item.isPersisted) | ||
item.persist(itemStorageKey, { | ||
defaultStorageKey: this.config.defaultStorageKey || undefined, | ||
storageKeys: this.storageKeys, | ||
followCollectionPersistKeyPattern: false, | ||
}); | ||
}); | ||
@@ -178,7 +194,5 @@ removedKeys.forEach((itemKey) => { | ||
const item = collection.getItem(itemKey); | ||
const _itemKey = CollectionPersistent.getItemStorageKey(itemKey, _key); | ||
if (!item) | ||
return; | ||
if (item.isPersisted) | ||
(_a = item.persistent) === null || _a === void 0 ? void 0 : _a.removePersistedValue(_itemKey); | ||
const itemStorageKey = CollectionPersistent.getItemStorageKey(itemKey, _storageItemKey); | ||
if (item != null && item.isPersisted) | ||
(_a = item.persistent) === null || _a === void 0 ? void 0 : _a.removePersistedValue(itemStorageKey); | ||
}); | ||
@@ -188,3 +202,3 @@ } | ||
if (itemKey == null || collectionKey == null) | ||
internal_1.Agile.logger.warn('Failed to build unique Item StorageKey!'); | ||
internal_1.LogCodeManager.log('1A:02:00'); | ||
if (itemKey == null) | ||
@@ -200,3 +214,3 @@ itemKey = 'unknown'; | ||
if (groupKey == null || collectionKey == null) | ||
internal_1.Agile.logger.warn('Failed to build unique Group StorageKey!'); | ||
internal_1.LogCodeManager.log('1A:02:01'); | ||
if (groupKey == null) | ||
@@ -203,0 +217,0 @@ groupKey = 'unknown'; |
@@ -1,6 +0,7 @@ | ||
import { Agile, Item, Group, GroupKey, Selector, SelectorKey, StorageKey, GroupConfigInterface, CollectionPersistent, GroupAddConfigInterface, SideEffectConfigInterface, SelectorConfigInterface } from '../internal'; | ||
export declare class Collection<DataType extends object = DefaultItem> { | ||
import { Agile, Item, Group, GroupKey, Selector, SelectorKey, StorageKey, GroupConfigInterface, CollectionPersistent, GroupAddConfigInterface, SideEffectConfigInterface, SelectorConfigInterface, PatchOptionConfigInterface } from '../internal'; | ||
export declare class Collection<DataType extends Object = DefaultItem, GroupValueType = Array<ItemKey>> { | ||
agileInstance: () => Agile; | ||
config: CollectionConfigInterface; | ||
private initialConfig; | ||
_key?: CollectionKey; | ||
size: number; | ||
@@ -10,3 +11,2 @@ data: { | ||
}; | ||
_key?: CollectionKey; | ||
isPersisted: boolean; | ||
@@ -22,41 +22,86 @@ persistent: CollectionPersistent<DataType> | undefined; | ||
/** | ||
* A Collection manages a reactive set of Information | ||
* that we need to remember globally at a later point in time. | ||
* While providing a toolkit to use and mutate this set of Information. | ||
* | ||
* It is designed for arrays of data objects following the same pattern. | ||
* | ||
* Each of these data object must have a unique `primaryKey` to be correctly identified later. | ||
* | ||
* You can create as many global Collections as you need. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/) | ||
* | ||
* @public | ||
* Collection - Class that holds a List of Objects with key and causes rerender on subscribed Components | ||
* @param agileInstance - An instance of Agile | ||
* @param config - Config | ||
* @param agileInstance - Instance of Agile the Collection belongs to. | ||
* @param config - Configuration object | ||
*/ | ||
constructor(agileInstance: Agile, config?: CollectionConfig<DataType>); | ||
/** | ||
* Updates the key/name identifier of the Collection. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/properties#key) | ||
* | ||
* @public | ||
* Set Key/Name of Collection | ||
* @param value - New key/name identifier. | ||
*/ | ||
set key(value: CollectionKey | undefined); | ||
/** | ||
* Returns the key/name identifier of the Collection. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/properties#key) | ||
* | ||
* @public | ||
* Get Key/Name of Collection | ||
*/ | ||
get key(): CollectionKey | undefined; | ||
/** | ||
* Updates the key/name identifier of the Collection. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#setkey) | ||
* | ||
* @public | ||
* Set Key/Name of Collection | ||
* @param value - New Key/Name of Collection | ||
* @param value - New key/name identifier. | ||
*/ | ||
setKey(value: CollectionKey | undefined): this; | ||
/** | ||
* Creates a new Group without associating it to the Collection. | ||
* | ||
* This way of creating a Group is intended for use in the Collection configuration object, | ||
* where the `constructor()` takes care of the binding. | ||
* | ||
* After a successful initiation of the Collection we recommend using `createGroup()`, | ||
* because it automatically connects the Group to the Collection. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#group) | ||
* | ||
* @public | ||
* Group - Holds Items of this Collection | ||
* @param initialItems - Initial ItemKeys of Group | ||
* @param config - Config | ||
* @param initialItems - Key/Name identifiers of the Items to be clustered by the Group. | ||
* @param config - Configuration object | ||
*/ | ||
Group(initialItems?: Array<ItemKey>, config?: GroupConfigInterface): Group<DataType>; | ||
/** | ||
* Creates a new Selector without associating it to the Collection. | ||
* | ||
* This way of creating a Selector is intended for use in the Collection configuration object, | ||
* where the `constructor()` takes care of the binding. | ||
* | ||
* After a successful initiation of the Collection we recommend using `createSelector()`, | ||
* because it automatically connects the Group to the Collection. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#selector) | ||
* | ||
* @public | ||
* Selector - Represents an Item of this Collection | ||
* @param initialKey - Key of Item that the Selector represents | ||
* @param config - Config | ||
* @param initialKey - Key/Name identifier of the Item to be represented by the Selector. | ||
* @param config - Configuration object | ||
*/ | ||
Selector(initialKey: ItemKey, config?: SelectorConfigInterface): Selector<DataType>; | ||
Selector(initialKey: ItemKey | null, config?: SelectorConfigInterface): Selector<DataType>; | ||
/** | ||
* Sets up the specified Groups or Group keys | ||
* and assigns them to the Collection if they are valid. | ||
* | ||
* It also instantiates and assigns the default Group to the Collection. | ||
* The default Group reflects the default pattern of the Collection. | ||
* | ||
* @internal | ||
* Instantiates Groups | ||
* @param groups - Entire Groups or Group keys to be set up. | ||
*/ | ||
@@ -67,4 +112,7 @@ initGroups(groups: { | ||
/** | ||
* Sets up the specified Selectors or Selector keys | ||
* and assigns them to the Collection if they are valid. | ||
* | ||
* @internal | ||
* Instantiates Selectors | ||
* @param selectors - Entire Selectors or Selector keys to be set up. | ||
*/ | ||
@@ -75,234 +123,449 @@ initSelectors(selectors: { | ||
/** | ||
* Appends new data objects following the same pattern to the end of the Collection. | ||
* | ||
* Each collected `data object` requires a unique identifier at the primaryKey property (by default 'id') | ||
* to be correctly identified later. | ||
* | ||
* For example, if we collect some kind of user object, | ||
* it must contain such unique identifier at 'id' | ||
* to be added to the Collection. | ||
* ``` | ||
* MY_COLLECTION.collect({id: '1', name: 'jeff'}); // valid | ||
* MY_COLLECTION.collect({name: 'frank'}); // invalid | ||
* ``` | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#collect) | ||
* | ||
* @public | ||
* Collect Item/s | ||
* @param data - Data that gets added to Collection | ||
* @param groupKeys - Add collected Item/s to certain Groups | ||
* @param config - Config | ||
* @param data - Data objects or entire Items to be added. | ||
* @param groupKeys - Group/s to which the specified data objects or Items are to be added. | ||
* @param config - Configuration object | ||
*/ | ||
collect(data: DataType | Array<DataType>, groupKeys?: GroupKey | Array<GroupKey>, config?: CollectConfigInterface<DataType>): this; | ||
collect(data: DataType | Item<DataType> | Array<DataType | Item<DataType>>, groupKeys?: GroupKey | Array<GroupKey>, config?: CollectConfigInterface<DataType>): this; | ||
/** | ||
* Updates the Item `data object` with the specified `object with changes`, if the Item exists. | ||
* By default the `object with changes` is merged into the Item `data object` at top level. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#update) | ||
* | ||
* @public | ||
* Updates Item at provided Key | ||
* @param itemKey - ItemKey of Item that gets updated | ||
* @param changes - Changes that will be merged into the Item (flatMerge) | ||
* @param config - Config | ||
* @param itemKey - Key/Name identifier of the Item to be updated. | ||
* @param changes - Object with changes to be merged into the Item data object. | ||
* @param config - Configuration object | ||
*/ | ||
update(itemKey: ItemKey, changes: DefaultItem | DataType, config?: UpdateConfigInterface): Item<DataType> | undefined; | ||
/** | ||
* Creates a new Group and associates it to the Collection. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#createGroup) | ||
* | ||
* @public | ||
* Creates new Group that can hold Items of Collection | ||
* @param groupKey - Name/Key of Group | ||
* @param initialItems - Initial ItemKeys of Group | ||
* @param groupKey - Unique identifier of the Group to be created. | ||
* @param initialItems - Key/Name identifiers of the Items to be clustered by the Group. | ||
*/ | ||
createGroup(groupKey: GroupKey, initialItems?: Array<ItemKey>): Group<DataType>; | ||
/** | ||
* Returns a boolean indicating whether a Group with the specified `groupKey` | ||
* exists in the Collection or not. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#hasgroup) | ||
* | ||
* @public | ||
* Check if Group exists in Collection | ||
* @param groupKey - Key/Name of Group | ||
* @param config - Config | ||
* @param groupKey - Key/Name identifier of the Group to be checked for existence. | ||
* @param config - Configuration object | ||
*/ | ||
hasGroup(groupKey: GroupKey | undefined, config?: HasConfigInterface): boolean; | ||
/** | ||
* Retrieves a single Group with the specified key/name identifier from the Collection. | ||
* | ||
* If the to retrieve Group doesn't exist, `undefined` is returned. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroup) | ||
* | ||
* @public | ||
* Get Group by Key/Name | ||
* @param groupKey - Key/Name of Group | ||
* @param config - Config | ||
* @param groupKey - Key/Name identifier of the Group. | ||
* @param config - Configuration object | ||
*/ | ||
getGroup(groupKey: GroupKey | undefined, config?: HasConfigInterface): Group<DataType> | undefined; | ||
getGroup(groupKey: GroupKey | undefined | null, config?: HasConfigInterface): Group<DataType> | undefined; | ||
/** | ||
* Retrieves the default Group from the Collection. | ||
* | ||
* Every Collection should have a default Group, | ||
* which represents the default pattern of the Collection. | ||
* | ||
* If the default Group, for what ever reason, doesn't exist, `undefined` is returned. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getdefaultgroup) | ||
* | ||
* @public | ||
* Get default Group of Collection | ||
*/ | ||
getDefaultGroup(): Group<DataType> | undefined; | ||
/** | ||
* Retrieves a single Group with the specified key/name identifier from the Collection. | ||
* | ||
* If the to retrieve Group doesn't exist, a reference Group is returned. | ||
* This has the advantage that Components that have the reference Group bound to themselves | ||
* are rerenderd when the original Group is created. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroupwithreference) | ||
* | ||
* @public | ||
* Get Group by Key/Name or a Reference to it if it doesn't exist yet | ||
* @param groupKey - Name/Key of Group | ||
* @param groupKey - Key/Name identifier of the Group. | ||
*/ | ||
getGroupWithReference(groupKey: GroupKey): Group<DataType>; | ||
/** | ||
* Removes a Group with the specified key/name identifier from the Collection, | ||
* if it exists in the Collection. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removegroup) | ||
* | ||
* @public | ||
* Removes Group by Key/Name | ||
* @param groupKey - Name/Key of Group | ||
* @param groupKey - Key/Name identifier of the Group to be removed. | ||
*/ | ||
removeGroup(groupKey: GroupKey): this; | ||
/** | ||
* Returns the count of registered Groups in the Collection. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroupcount) | ||
* | ||
* @public | ||
* Creates new Selector that represents an Item of the Collection | ||
* @param selectorKey - Name/Key of Selector | ||
* @param itemKey - Key of Item which the Selector represents | ||
*/ | ||
createSelector(selectorKey: SelectorKey, itemKey: ItemKey): Selector<DataType>; | ||
getGroupCount(): number; | ||
/** | ||
* Creates a new Selector and associates it to the Collection. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#createSelector) | ||
* | ||
* @public | ||
* Creates new Selector that represents an Item of the Collection | ||
* @param itemKey - Key of Item which the Selector represents | ||
* @param selectorKey - Unique identifier of the Selector to be created. | ||
* @param itemKey - Key/Name identifier of the Item to be represented by the Selector. | ||
*/ | ||
createSelector(selectorKey: SelectorKey, itemKey: ItemKey | null): Selector<DataType>; | ||
/** | ||
* Creates a new Selector and associates it to the Collection. | ||
* | ||
* The specified `itemKey` is used as the unique identifier key of the new Selector. | ||
* ``` | ||
* MY_COLLECTION.select('1'); | ||
* // is equivalent to | ||
* MY_COLLECTION.createSelector('1', '1'); | ||
* ``` | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#select) | ||
* | ||
* @public | ||
* @param itemKey - Key/Name identifier of the Item to be represented by the Selector | ||
* and used as unique identifier of the Selector. | ||
*/ | ||
select(itemKey: ItemKey): Selector<DataType>; | ||
/** | ||
* Returns a boolean indicating whether a Selector with the specified `selectorKey` | ||
* exists in the Collection or not. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#hasselector) | ||
* | ||
* @public | ||
* Check if Selector exists in Collection | ||
* @param selectorKey - Key/Name of Selector | ||
* @param config - Config | ||
* @param selectorKey - Key/Name identifier of the Selector to be checked for existence. | ||
* @param config - Configuration object | ||
*/ | ||
hasSelector(selectorKey: SelectorKey | undefined, config?: HasConfigInterface): boolean; | ||
/** | ||
* Retrieves a single Selector with the specified key/name identifier from the Collection. | ||
* | ||
* If the to retrieve Selector doesn't exist, `undefined` is returned. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getselector) | ||
* | ||
* @public | ||
* Get Selector by Key/Name | ||
* @param selectorKey - Key/Name of Selector | ||
* @param config - Config | ||
* @param selectorKey - Key/Name identifier of the Selector. | ||
* @param config - Configuration object | ||
*/ | ||
getSelector(selectorKey: SelectorKey | undefined, config?: HasConfigInterface): Selector<DataType> | undefined; | ||
getSelector(selectorKey: SelectorKey | undefined | null, config?: HasConfigInterface): Selector<DataType> | undefined; | ||
/** | ||
* Retrieves a single Selector with the specified key/name identifier from the Collection. | ||
* | ||
* If the to retrieve Selector doesn't exist, a reference Selector is returned. | ||
* This has the advantage that Components that have the reference Selector bound to themselves | ||
* are rerenderd when the original Selector is created. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getselectorwithreference) | ||
* | ||
* @public | ||
* Get Selector by Key/Name or a Reference to it if it doesn't exist yet | ||
* @param selectorKey - Name/Key of Selector | ||
* @param selectorKey - Key/Name identifier of the Selector. | ||
*/ | ||
getSelectorWithReference(selectorKey: SelectorKey): Selector<DataType>; | ||
/** | ||
* Removes a Selector with the specified key/name identifier from the Collection, | ||
* if it exists in the Collection. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removeselector) | ||
* | ||
* @public | ||
* Removes Selector by Key/Name | ||
* @param selectorKey - Name/Key of Selector | ||
* @param selectorKey - Key/Name identifier of the Selector to be removed. | ||
*/ | ||
removeSelector(selectorKey: SelectorKey): this; | ||
/** | ||
* Returns the count of registered Selectors in the Collection. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getselectorcount) | ||
* | ||
* @public | ||
* Check if Item exists in Collection | ||
* @param itemKey - Key/Name of Item | ||
* @param config - Config | ||
*/ | ||
getSelectorCount(): number; | ||
/** | ||
* Returns a boolean indicating whether a Item with the specified `itemKey` | ||
* exists in the Collection or not. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#hasitem) | ||
* | ||
* @public | ||
* @param itemKey - Key/Name identifier of the Item. | ||
* @param config - Configuration object | ||
*/ | ||
hasItem(itemKey: ItemKey | undefined, config?: HasConfigInterface): boolean; | ||
/** | ||
* Retrieves a single Item with the specified key/name identifier from the Collection. | ||
* | ||
* If the to retrieve Item doesn't exist, `undefined` is returned. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getitem) | ||
* | ||
* @public | ||
* Get Item by Key/Name | ||
* @param itemKey - ItemKey of Item | ||
* @param config - Config | ||
* @param itemKey - Key/Name identifier of the Item. | ||
* @param config - Configuration object | ||
*/ | ||
getItem(itemKey: ItemKey | undefined, config?: HasConfigInterface): Item<DataType> | undefined; | ||
getItem(itemKey: ItemKey | undefined | null, config?: HasConfigInterface): Item<DataType> | undefined; | ||
/** | ||
* Retrieves a single Item with the specified key/name identifier from the Collection. | ||
* | ||
* If the to retrieve Item doesn't exist, a reference Item is returned. | ||
* This has the advantage that Components that have the reference Item bound to themselves | ||
* are rerenderd when the original Item is created. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getitemwithreference) | ||
* | ||
* @public | ||
* Get Item by Key/Name or a Reference to it if it doesn't exist yet | ||
* @param itemKey - Key/Name of Item | ||
* @param itemKey - Key/Name identifier of the Item. | ||
*/ | ||
getItemWithReference(itemKey: ItemKey): Item<DataType>; | ||
/** | ||
* Creates a placeholder Item | ||
* that can be used to hold a reference to a not existing Item. | ||
* | ||
* @internal | ||
* @param itemKey - Unique identifier of the to create placeholder Item. | ||
* @param addToCollection - Whether to add the Item to be created to the Collection. | ||
*/ | ||
createPlaceholderItem(itemKey: ItemKey, addToCollection?: boolean): Item<DataType>; | ||
/** | ||
* Retrieves the value (data object) of a single Item | ||
* with the specified key/name identifier from the Collection. | ||
* | ||
* If the to retrieve Item containing the value doesn't exist, `undefined` is returned. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getitemvalue) | ||
* | ||
* @public | ||
* Get Value of Item by Key/Name | ||
* @param itemKey - ItemKey of Item that holds the Value | ||
* @param config - Config | ||
* @param itemKey - Key/Name identifier of the Item. | ||
* @param config - Configuration object | ||
*/ | ||
getItemValue(itemKey: ItemKey | undefined, config?: HasConfigInterface): DataType | undefined; | ||
/** | ||
* Retrieves all Items from the Collection. | ||
* ``` | ||
* MY_COLLECTION.getAllItems(); | ||
* // is equivalent to | ||
* MY_COLLECTION.getDefaultGroup().items; | ||
* ``` | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getallitems) | ||
* | ||
* @public | ||
* Get all Items of Collection | ||
* @param config - Config | ||
* @param config - Configuration object | ||
*/ | ||
getAllItems(config?: HasConfigInterface): Array<Item<DataType>>; | ||
/** | ||
* Retrieves the values (data objects) of all Items from the Collection. | ||
* ``` | ||
* MY_COLLECTION.getAllItemValues(); | ||
* // is equivalent to | ||
* MY_COLLECTION.getDefaultGroup().output; | ||
* ``` | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getallitemvalues) | ||
* | ||
* @public | ||
* Get all Values of Items in a Collection | ||
* @param config - Config | ||
* @param config - Configuration object | ||
*/ | ||
getAllItemValues(config?: HasConfigInterface): Array<DataType>; | ||
/** | ||
* Preserves the Collection `value` in the corresponding external Storage. | ||
* | ||
* The Collection key/name is used as the unique identifier for the Persistent. | ||
* If that is not desired or the Collection has no unique identifier, | ||
* please specify a separate unique identifier for the Persistent. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#persist) | ||
* | ||
* @public | ||
* Stores Collection Value into Agile Storage permanently | ||
* @param config - Config | ||
* @param config - Configuration object | ||
*/ | ||
persist(config?: CollectionPersistentConfigInterface): this; | ||
/** | ||
* Preserves the Collection `value` in the corresponding external Storage. | ||
* | ||
* The specified key is used as the unique identifier for the Persistent. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#persist) | ||
* | ||
* @public | ||
* Stores Collection Value into Agile Storage permanently | ||
* @param key - Key/Name of created Persistent (Note: Key required if Collection has no set Key!) | ||
* @param config - Config | ||
* @param key - Key/Name identifier of Persistent. | ||
* @param config - Configuration object | ||
*/ | ||
persist(key?: StorageKey, config?: CollectionPersistentConfigInterface): this; | ||
/** | ||
* Fires immediately after the persisted `value` | ||
* is loaded into the Collection from a corresponding external Storage. | ||
* | ||
* Registering such callback function makes only sense | ||
* when the Collection is [persisted](https://agile-ts.org/docs/core/collection/methods/#persist) in an external Storage. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#onload) | ||
* | ||
* @public | ||
* Callback Function that gets called if the persisted Value gets loaded into the Collection for the first Time | ||
* Note: Only useful for persisted Collections! | ||
* @param callback - Callback Function | ||
* @param callback - A function to be executed after the externally persisted `value` was loaded into the Collection. | ||
*/ | ||
onLoad(callback: (success: boolean) => void): this; | ||
/** | ||
* Removes all Items from the Collection | ||
* and resets all Groups and Selectors of the Collection. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#reset) | ||
* | ||
* @public | ||
* Get count of registered Groups in Collection | ||
*/ | ||
getGroupCount(): number; | ||
/** | ||
* @public | ||
* Get count of registered Selectors in Collection | ||
*/ | ||
getSelectorCount(): number; | ||
/** | ||
* @public | ||
* Resets this Collection | ||
*/ | ||
reset(): this; | ||
/** | ||
* Puts `itemKeys/s` into Group/s. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#put) | ||
* | ||
* @public | ||
* Puts ItemKey/s into Group/s (GroupKey/s) | ||
* @param itemKeys - ItemKey/s that get added to provided Group/s | ||
* @param groupKeys - Group/s to which the ItemKey/s get added | ||
* @param config - Config | ||
* @param itemKeys - `itemKey/s` to be put into the specified Group/s. | ||
* @param groupKeys - Key/Name Identifier/s of the Group/s the specified `itemKey/s` are to put in. | ||
* @param config - Configuration object | ||
*/ | ||
put(itemKeys: ItemKey | Array<ItemKey>, groupKeys: GroupKey | Array<GroupKey>, config?: GroupAddConfigInterface): this; | ||
/** | ||
* Moves specified `itemKey/s` from one Group to another Group. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#move) | ||
* | ||
* @public | ||
* Move ItemKey/s from one Group to another | ||
* @param itemKeys - ItemKey/s that are moved | ||
* @param oldGroupKey - GroupKey of the Group that currently keeps the Items at itemKey/s | ||
* @param newGroupKey - GroupKey of the Group into which the Items at itemKey/s are moved | ||
* @param config - Config | ||
* @param itemKeys - `itemKey/s` to be moved. | ||
* @param oldGroupKey - Key/Name Identifier of the Group the `itemKey/s` are moved from. | ||
* @param newGroupKey - Key/Name Identifier of the Group the `itemKey/s` are moved in. | ||
* @param config - Configuration object | ||
*/ | ||
move(itemKeys: ItemKey | Array<ItemKey>, oldGroupKey: GroupKey, newGroupKey: GroupKey, config?: GroupAddConfigInterface): this; | ||
/** | ||
* Updates the key/name identifier of the Item | ||
* and returns a boolean indicating | ||
* whether the Item identifier was updated successfully. | ||
* | ||
* @internal | ||
* Updates Key/Name of Item in all Instances (Group, Selector, ..) | ||
* @param oldItemKey - Old ItemKey | ||
* @param newItemKey - New ItemKey | ||
* @param config - Config | ||
* @param oldItemKey - Old key/name Item identifier. | ||
* @param newItemKey - New key/name Item identifier. | ||
* @param config - Configuration object | ||
*/ | ||
updateItemKey(oldItemKey: ItemKey, newItemKey: ItemKey, config?: UpdateItemKeyConfigInterface): boolean; | ||
/** | ||
* Returns all key/name identifiers of the Group/s containing the specified `itemKey`. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroupkeysthathaveitemkey) | ||
* | ||
* @public | ||
* Gets GroupKeys that contain the passed ItemKey | ||
* @param itemKey - ItemKey | ||
* @param itemKey - `itemKey` to be contained in Group/s. | ||
*/ | ||
getGroupKeysThatHaveItemKey(itemKey: ItemKey): Array<GroupKey>; | ||
/** | ||
* Removes Item/s from: | ||
* | ||
* - `.everywhere()`: | ||
* Removes Item/s from the entire Collection and all its Groups and Selectors (i.e. from everywhere) | ||
* ``` | ||
* MY_COLLECTION.remove('1').everywhere(); | ||
* // is equivalent to | ||
* MY_COLLECTION.removeItems('1'); | ||
* ``` | ||
* - `.fromGroups()`: | ||
* Removes Item/s only from specified Groups. | ||
* ``` | ||
* MY_COLLECTION.remove('1').fromGroups(['1', '2']); | ||
* // is equivalent to | ||
* MY_COLLECTION.removeFromGroups('1', ['1', '2']); | ||
* ``` | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#remove) | ||
* | ||
* @public | ||
* Remove Items from Collection | ||
* @param itemKeys - ItemKey/s that get removed | ||
* @param itemKeys - Item/s with identifier/s to be removed. | ||
*/ | ||
remove(itemKeys: ItemKey | Array<ItemKey>): { | ||
fromGroups: (groups: Array<ItemKey> | ItemKey) => Collection<DataType>; | ||
everywhere: () => Collection<DataType>; | ||
everywhere: (config?: RemoveItemsConfigInterface) => Collection<DataType>; | ||
}; | ||
/** | ||
* Remove Item/s from specified Group/s. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removefromgroups) | ||
* | ||
* @public | ||
* Removes Item/s from Group/s | ||
* @param itemKeys - ItemKey/s that get removed from Group/s | ||
* @param groupKeys - GroupKey/s of Group/s form which the ItemKey/s will be removed | ||
* @param itemKeys - Key/Name Identifier/s of the Item/s to be removed from the Group/s. | ||
* @param groupKeys - Key/Name Identifier/s of the Group/s the Item/s are to remove from. | ||
*/ | ||
removeFromGroups(itemKeys: ItemKey | Array<ItemKey>, groupKeys: GroupKey | Array<GroupKey>): this; | ||
/** | ||
* Removes Item/s from the entire Collection and all the Collection's Groups and Selectors. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removeitems) | ||
* | ||
* @public | ||
* Removes Item completely from Collection | ||
* @param itemKeys - ItemKey/s of Item/s | ||
* @param itemKeys - Key/Name identifier/s of the Item/s to be removed from the entire Collection. | ||
* @param config - Configuration object | ||
*/ | ||
removeItems(itemKeys: ItemKey | Array<ItemKey>): this; | ||
removeItems(itemKeys: ItemKey | Array<ItemKey>, config?: RemoveItemsConfigInterface): this; | ||
/** | ||
* Assigns the provided `data` object to an already existing Item | ||
* with specified key/name identifier found in the `data` object. | ||
* If the Item doesn't exist yet, a new Item with the `data` object as value | ||
* is created and assigned to the Collection. | ||
* | ||
* Returns a boolean indicating | ||
* whether the `data` object was assigned/updated successfully. | ||
* | ||
* @internal | ||
* Updates existing or creates Item from provided Data | ||
* @param data - Data | ||
* @param config - Config | ||
* @param data - Data object | ||
* @param config - Configuration object | ||
*/ | ||
setData(data: DataType, config?: SetDataConfigInterface): boolean; | ||
assignData(data: DataType, config?: AssignDataConfigInterface): boolean; | ||
/** | ||
* Assigns the specified Item to the Collection | ||
* at the key/name identifier of the Item. | ||
* | ||
* And returns a boolean indicating | ||
* whether the Item was assigned successfully. | ||
* | ||
* @internal | ||
* Rebuilds Groups that include the provided ItemKey | ||
* @itemKey - Item Key | ||
* @config - Config | ||
* @param item - Item to be added. | ||
* @param config - Configuration object | ||
*/ | ||
assignItem(item: Item<DataType>, config?: AssignItemConfigInterface): boolean; | ||
/** | ||
* Rebuilds all Groups that contain the specified `itemKey`. | ||
* | ||
* @internal | ||
* @itemKey - `itemKey` Groups must contain to be rebuilt. | ||
* @config - Configuration object | ||
*/ | ||
rebuildGroupsThatIncludeItemKey(itemKey: ItemKey, config?: RebuildGroupsThatIncludeItemKeyConfigInterface): void; | ||
@@ -313,94 +576,187 @@ } | ||
export declare type ItemKey = string | number; | ||
/** | ||
* @param key - Key/Name of Collection | ||
* @param groups - Groups of Collection | ||
* @param selectors - Selectors of Collection | ||
* @param primaryKey - Name of Property that holds the PrimaryKey (default = id) | ||
* @param defaultGroupKey - Key/Name of Default Group that holds all collected Items | ||
* @param initialData - Initial Data of Collection | ||
*/ | ||
export interface CreateCollectionConfigInterface<DataType = DefaultItem> { | ||
/** | ||
* Initial Groups of the Collection. | ||
* @default [] | ||
*/ | ||
groups?: { | ||
[key: string]: Group<any>; | ||
} | string[]; | ||
/** | ||
* Initial Selectors of the Collection | ||
* @default [] | ||
*/ | ||
selectors?: { | ||
[key: string]: Selector<any>; | ||
} | string[]; | ||
/** | ||
* Key/Name identifier of the Collection. | ||
* @default undefined | ||
*/ | ||
key?: CollectionKey; | ||
/** | ||
* Key/Name of the property | ||
* which represents the unique Item identifier | ||
* in collected data objects. | ||
* @default 'id' | ||
*/ | ||
primaryKey?: string; | ||
/** | ||
* Key/Name identifier of the default Group that is created shortly after instantiation. | ||
* The default Group represents the default pattern of the Collection. | ||
* @default 'default' | ||
*/ | ||
defaultGroupKey?: GroupKey; | ||
/** | ||
* Initial data objects of the Collection. | ||
* @default [] | ||
*/ | ||
initialData?: Array<DataType>; | ||
} | ||
/** | ||
* @param primaryKey - Name of Property that holds the PrimaryKey (default = id) | ||
* @param defaultGroupKey - Key/Name of Default Group that holds all collected Items | ||
*/ | ||
export declare type CollectionConfig<DataType extends Object = DefaultItem> = CreateCollectionConfigInterface<DataType> | ((collection: Collection<DataType>) => CreateCollectionConfigInterface<DataType>); | ||
export interface CollectionConfigInterface { | ||
/** | ||
* Key/Name of the property | ||
* which represents the unique Item identifier | ||
* in collected data objects. | ||
* @default 'id' | ||
*/ | ||
primaryKey: string; | ||
/** | ||
* Key/Name identifier of the default Group that is created shortly after instantiation. | ||
* The default Group represents the default pattern of the Collection. | ||
* @default 'default' | ||
*/ | ||
defaultGroupKey: ItemKey; | ||
} | ||
/** | ||
* @param patch - If Item gets patched into existing Item with the same Id | ||
* @param method - Way of adding Item to Collection (push, unshift) | ||
* @param forEachItem - Gets called for each Item that got collected | ||
* @param background - If collecting an Item happens in the background (-> not causing any rerender) | ||
* @param select - If collected Items get selected with a Selector | ||
*/ | ||
export interface CollectConfigInterface<DataType = any> { | ||
patch?: boolean; | ||
export interface CollectConfigInterface<DataType = any> extends AssignDataConfigInterface { | ||
/** | ||
* In which way the collected data should be added to the Collection. | ||
* - 'push' = at the end | ||
* - 'unshift' = at the beginning | ||
* https://www.tutorialspoint.com/what-are-the-differences-between-unshift-and-push-methods-in-javascript | ||
* @default 'push' | ||
*/ | ||
method?: 'push' | 'unshift'; | ||
forEachItem?: (data: DataType, key: ItemKey, index: number) => void; | ||
background?: boolean; | ||
/** | ||
* Performs the specified action for each collected data object. | ||
* @default undefined | ||
*/ | ||
forEachItem?: (data: DataType | Item<DataType>, key: ItemKey, success: boolean, index: number) => void; | ||
/** | ||
* Whether to create a Selector for each collected data object. | ||
* @default false | ||
*/ | ||
select?: boolean; | ||
} | ||
/** | ||
* @param patch - If Data gets merged into the current Data | ||
* @param background - If updating an Item happens in the background (-> not causing any rerender) | ||
*/ | ||
export interface UpdateConfigInterface { | ||
patch?: boolean | { | ||
addNewProperties?: boolean; | ||
}; | ||
/** | ||
* Whether to merge the data object with changes into the existing Item data object | ||
* or overwrite the existing Item data object entirely. | ||
* @default true | ||
*/ | ||
patch?: boolean | PatchOptionConfigInterface; | ||
/** | ||
* Whether to update the data object in background. | ||
* So that the UI isn't notified of these changes and thus doesn't rerender. | ||
* @default false | ||
*/ | ||
background?: boolean; | ||
} | ||
/** | ||
* @param background - If updating the primaryKey of an Item happens in the background (-> not causing any rerender) | ||
*/ | ||
export interface UpdateItemKeyConfigInterface { | ||
/** | ||
* Whether to update the Item key/name identifier in background | ||
* So that the UI isn't notified of these changes and thus doesn't rerender. | ||
* @default false | ||
*/ | ||
background?: boolean; | ||
} | ||
/** | ||
* @param background - If assigning a new value happens in the background (-> not causing any rerender) | ||
* @param force - Force creating and performing Job | ||
* @param sideEffects - If Side Effects of Group gets executed | ||
*/ | ||
export interface RebuildGroupsThatIncludeItemKeyConfigInterface { | ||
/** | ||
* Whether to rebuilt the Group in background. | ||
* So that the UI isn't notified of these changes and thus doesn't rerender. | ||
* @default false | ||
*/ | ||
background?: boolean; | ||
force?: boolean; | ||
/** | ||
* Whether to execute the defined side effects. | ||
* @default true | ||
*/ | ||
sideEffects?: SideEffectConfigInterface; | ||
} | ||
/** | ||
* @param notExisting - If placeholder can be found | ||
*/ | ||
export interface HasConfigInterface { | ||
/** | ||
* Whether Items that do not officially exist, | ||
* such as placeholder Items, can be found | ||
* @default true | ||
*/ | ||
notExisting?: boolean; | ||
} | ||
/** | ||
* @param loadValue - If Persistent loads the persisted value into the Collection | ||
* @param storageKeys - Key/Name of Storages which gets used to persist the Collection Value (NOTE: If not passed the default Storage will be used) | ||
* @param defaultStorageKey - Default Storage Key (if not provided it takes the first index of storageKeys or the AgileTs default Storage) | ||
*/ | ||
export interface CollectionPersistentConfigInterface { | ||
/** | ||
* Whether the Persistent should automatically load | ||
* the persisted value into the Collection after its instantiation. | ||
* @default true | ||
*/ | ||
loadValue?: boolean; | ||
/** | ||
* Key/Name identifier of Storages | ||
* in which the Collection value should be or is persisted. | ||
* @default [`defaultStorageKey`] | ||
*/ | ||
storageKeys?: StorageKey[]; | ||
/** | ||
* Key/Name identifier of the default Storage of the specified Storage keys. | ||
* | ||
* The Collection value is loaded from the default Storage by default | ||
* and is only loaded from the remaining Storages (`storageKeys`) | ||
* if the loading from the default Storage failed. | ||
* | ||
* @default first index of the specified Storage keys or the AgileTs default Storage key | ||
*/ | ||
defaultStorageKey?: StorageKey; | ||
} | ||
/** | ||
* @param patch - If Data gets patched into existing Item | ||
* @param background - If assigning Data happens in background | ||
*/ | ||
export interface SetDataConfigInterface { | ||
export interface RemoveItemsConfigInterface { | ||
/** | ||
* Whether to remove not officially existing Items (such as placeholder Items). | ||
* Keep in mind that sometimes it won't remove an Item entirely | ||
* as another Instance (like a Selector) might need to keep reference to it. | ||
* https://github.com/agile-ts/agile/pull/152 | ||
* @default false | ||
*/ | ||
notExisting?: boolean; | ||
/** | ||
* Whether to remove Selectors that have selected an Item to be removed. | ||
* @default false | ||
*/ | ||
removeSelector?: boolean; | ||
} | ||
export interface AssignDataConfigInterface { | ||
/** | ||
* When the Item identifier of the to assign data object already exists in the Collection, | ||
* whether to merge the newly assigned data into the existing one | ||
* or overwrite the existing one entirely. | ||
* @default true | ||
*/ | ||
patch?: boolean; | ||
/** | ||
* Whether to assign the data object to the Collection in background. | ||
* So that the UI isn't notified of these changes and thus doesn't rerender. | ||
* @default false | ||
*/ | ||
background?: boolean; | ||
} | ||
export declare type CollectionConfig<DataType extends object = DefaultItem> = CreateCollectionConfigInterface<DataType> | ((collection: Collection<DataType>) => CreateCollectionConfigInterface<DataType>); | ||
export interface AssignItemConfigInterface { | ||
/** | ||
* If an Item with the Item identifier already exists, | ||
* whether to overwrite it entirely with the new one. | ||
* @default false | ||
*/ | ||
overwrite?: boolean; | ||
/** | ||
* Whether to assign the Item to the Collection in background. | ||
* So that the UI isn't notified of these changes and thus doesn't rerender. | ||
* @default false | ||
*/ | ||
background?: boolean; | ||
} |
@@ -14,6 +14,2 @@ "use strict"; | ||
this.agileInstance = () => agileInstance; | ||
this.config = { | ||
defaultGroupKey: 'default', | ||
primaryKey: 'id', | ||
}; | ||
let _config = typeof config === 'function' ? config(this) : config; | ||
@@ -34,5 +30,7 @@ _config = internal_1.defineConfig(_config, { | ||
this.initSelectors(_config.selectors); | ||
this.isInstantiated = true; | ||
if (_config.initialData) | ||
this.collect(_config.initialData); | ||
this.isInstantiated = true; | ||
for (const key in this.selectors) | ||
this.selectors[key].reselect(); | ||
} | ||
@@ -49,3 +47,3 @@ set key(value) { | ||
this._key = value; | ||
if (value && ((_a = this.persistent) === null || _a === void 0 ? void 0 : _a._key) === oldKey) | ||
if (value != null && ((_a = this.persistent) === null || _a === void 0 ? void 0 : _a._key) === oldKey) | ||
(_b = this.persistent) === null || _b === void 0 ? void 0 : _b.setKey(value); | ||
@@ -55,7 +53,6 @@ return this; | ||
Group(initialItems, config = {}) { | ||
var _a; | ||
if (this.isInstantiated) { | ||
const key = (config === null || config === void 0 ? void 0 : config.key) || internal_1.generateId(); | ||
internal_1.Agile.logger.warn("After the instantiation we recommend using 'MY_COLLECTION.createGroup' instead of 'MY_COLLECTION.Group'"); | ||
if ((config === null || config === void 0 ? void 0 : config.key) == null) | ||
internal_1.Agile.logger.warn(`Failed to find key for creation of Group. Group with random key '${key}' got created!`); | ||
const key = (_a = config.key) !== null && _a !== void 0 ? _a : internal_1.generateId(); | ||
internal_1.LogCodeManager.log('1B:02:00'); | ||
return this.createGroup(key, initialItems); | ||
@@ -66,7 +63,6 @@ } | ||
Selector(initialKey, config = {}) { | ||
var _a; | ||
if (this.isInstantiated) { | ||
const key = (config === null || config === void 0 ? void 0 : config.key) || internal_1.generateId(); | ||
internal_1.Agile.logger.warn("After the instantiation we recommend using 'MY_COLLECTION.createSelector' instead of 'MY_COLLECTION.Selector'"); | ||
if ((config === null || config === void 0 ? void 0 : config.key) == null) | ||
internal_1.Agile.logger.warn(`Failed to find key for creation of Selector. Selector with random key '${key}' got created!`); | ||
const key = (_a = config.key) !== null && _a !== void 0 ? _a : internal_1.generateId(); | ||
internal_1.LogCodeManager.log('1B:02:01'); | ||
return this.createSelector(key, initialKey); | ||
@@ -128,22 +124,32 @@ } | ||
_groupKeys.push(defaultGroupKey); | ||
_groupKeys.forEach((key) => !this.groups[key] && this.createGroup(key)); | ||
_groupKeys.forEach((key) => this.groups[key] == null && this.createGroup(key)); | ||
_data.forEach((data, index) => { | ||
const itemKey = data[primaryKey]; | ||
const success = this.setData(data, { | ||
patch: config.patch, | ||
background: config.background, | ||
}); | ||
if (!success) | ||
return this; | ||
_groupKeys.forEach((groupKey) => { | ||
var _a; | ||
(_a = this.getGroup(groupKey)) === null || _a === void 0 ? void 0 : _a.add(itemKey, { | ||
method: config.method, | ||
let itemKey; | ||
let success = false; | ||
if (data instanceof internal_1.Item) { | ||
success = this.assignItem(data, { | ||
background: config.background, | ||
}); | ||
}); | ||
if (config.select) | ||
this.createSelector(itemKey, itemKey); | ||
itemKey = data._key; | ||
} | ||
else { | ||
success = this.assignData(data, { | ||
patch: config.patch, | ||
background: config.background, | ||
}); | ||
itemKey = data[primaryKey]; | ||
} | ||
if (success) { | ||
_groupKeys.forEach((groupKey) => { | ||
var _a; | ||
(_a = this.getGroup(groupKey)) === null || _a === void 0 ? void 0 : _a.add(itemKey, { | ||
method: config.method, | ||
background: config.background, | ||
}); | ||
}); | ||
if (config.select) | ||
this.createSelector(itemKey, itemKey); | ||
} | ||
if (config.forEachItem) | ||
config.forEachItem(data, itemKey, index); | ||
config.forEachItem(data, itemKey, success, index); | ||
}); | ||
@@ -159,8 +165,8 @@ return this; | ||
}); | ||
if (!item) { | ||
internal_1.Agile.logger.error(`Item with key/name '${itemKey}' doesn't exist in Collection '${this._key}'!`); | ||
if (item == null) { | ||
internal_1.LogCodeManager.log('1B:03:00', [itemKey, this._key]); | ||
return undefined; | ||
} | ||
if (!internal_1.isValidObject(changes)) { | ||
internal_1.Agile.logger.error(`You have to pass an valid Changes Object to update '${itemKey}' in '${this._key}'!`); | ||
internal_1.LogCodeManager.log('1B:03:01', [itemKey, this._key]); | ||
return undefined; | ||
@@ -170,4 +176,3 @@ } | ||
const newItemKey = changes[primaryKey] || oldItemKey; | ||
const updateItemKey = oldItemKey !== newItemKey; | ||
if (updateItemKey) | ||
if (oldItemKey !== newItemKey) | ||
this.updateItemKey(oldItemKey, newItemKey, { | ||
@@ -188,6 +193,6 @@ background: config.background, | ||
} | ||
if (!config.patch) { | ||
else { | ||
if (changes[this.config.primaryKey] !== itemKey) { | ||
changes[this.config.primaryKey] = itemKey; | ||
internal_1.Agile.logger.warn(`By overwriting the whole Item don't forget passing the correct primaryKey!`, changes); | ||
internal_1.LogCodeManager.log('1B:02:02', [], changes); | ||
} | ||
@@ -202,8 +207,7 @@ item.set(changes, { | ||
let group = this.getGroup(groupKey, { notExisting: true }); | ||
if (!this.isInstantiated) { | ||
internal_1.Agile.logger.warn("We recommend to use 'MY_COLLECTION.Group' instead of 'MY_COLLECTION.createGroup' in the Collection config!"); | ||
} | ||
if (group) { | ||
if (!this.isInstantiated) | ||
internal_1.LogCodeManager.log('1B:02:03'); | ||
if (group != null) { | ||
if (!group.isPlaceholder) { | ||
internal_1.Agile.logger.warn(`Group with the name '${groupKey}' already exists!`); | ||
internal_1.LogCodeManager.log('1B:03:02', [groupKey]); | ||
return group; | ||
@@ -226,5 +230,5 @@ } | ||
const group = groupKey ? this.groups[groupKey] : undefined; | ||
if (!group || (!config.notExisting && group.isPlaceholder)) | ||
if (group == null || (!config.notExisting && !group.exists)) | ||
return undefined; | ||
internal_1.ComputedTracker.tracked(group.observer); | ||
internal_1.ComputedTracker.tracked(group.observers['value']); | ||
return group; | ||
@@ -237,3 +241,3 @@ } | ||
let group = this.getGroup(groupKey, { notExisting: true }); | ||
if (!group) { | ||
if (group == null) { | ||
group = new internal_1.Group(this, [], { | ||
@@ -245,21 +249,22 @@ key: groupKey, | ||
} | ||
internal_1.ComputedTracker.tracked(group.observer); | ||
internal_1.ComputedTracker.tracked(group.observers['value']); | ||
return group; | ||
} | ||
removeGroup(groupKey) { | ||
if (!this.groups[groupKey]) { | ||
internal_1.Agile.logger.warn(`Group with the key/name '${groupKey}' doesn't exist!`); | ||
return this; | ||
} | ||
delete this.groups[groupKey]; | ||
if (this.groups[groupKey] != null) | ||
delete this.groups[groupKey]; | ||
return this; | ||
} | ||
getGroupCount() { | ||
let size = 0; | ||
Object.keys(this.groups).map(() => size++); | ||
return size; | ||
} | ||
createSelector(selectorKey, itemKey) { | ||
let selector = this.getSelector(selectorKey, { notExisting: true }); | ||
if (!this.isInstantiated) { | ||
internal_1.Agile.logger.warn("We recommend to use 'MY_COLLECTION.Selector' instead of 'MY_COLLECTION.createSelector' in the Collection config!"); | ||
} | ||
if (selector) { | ||
if (!this.isInstantiated) | ||
internal_1.LogCodeManager.log('1B:02:04'); | ||
if (selector != null) { | ||
if (!selector.isPlaceholder) { | ||
internal_1.Agile.logger.warn(`Selector with the name '${selectorKey}' already exists!`); | ||
internal_1.LogCodeManager.log('1B:03:03', [selectorKey]); | ||
return selector; | ||
@@ -287,5 +292,5 @@ } | ||
const selector = selectorKey ? this.selectors[selectorKey] : undefined; | ||
if (!selector || (!config.notExisting && selector.isPlaceholder)) | ||
if (selector == null || (!config.notExisting && !selector.exists)) | ||
return undefined; | ||
internal_1.ComputedTracker.tracked(selector.observer); | ||
internal_1.ComputedTracker.tracked(selector.observers['value']); | ||
return selector; | ||
@@ -295,4 +300,4 @@ } | ||
let selector = this.getSelector(selectorKey, { notExisting: true }); | ||
if (!selector) { | ||
selector = new internal_1.Selector(this, 'unknown', { | ||
if (selector == null) { | ||
selector = new internal_1.Selector(this, null, { | ||
key: selectorKey, | ||
@@ -303,15 +308,17 @@ isPlaceholder: true, | ||
} | ||
internal_1.ComputedTracker.tracked(selector.observer); | ||
internal_1.ComputedTracker.tracked(selector.observers['value']); | ||
return selector; | ||
} | ||
removeSelector(selectorKey) { | ||
var _a; | ||
if (!this.selectors[selectorKey]) { | ||
internal_1.Agile.logger.warn(`Selector with the key/name '${selectorKey}' doesn't exist!`); | ||
return this; | ||
if (this.selectors[selectorKey] != null) { | ||
this.selectors[selectorKey].unselect(); | ||
delete this.selectors[selectorKey]; | ||
} | ||
(_a = this.selectors[selectorKey]) === null || _a === void 0 ? void 0 : _a.unselect(); | ||
delete this.selectors[selectorKey]; | ||
return this; | ||
} | ||
getSelectorCount() { | ||
let size = 0; | ||
Object.keys(this.selectors).map(() => size++); | ||
return size; | ||
} | ||
hasItem(itemKey, config = {}) { | ||
@@ -325,5 +332,5 @@ return !!this.getItem(itemKey, config); | ||
const item = itemKey != null ? this.data[itemKey] : undefined; | ||
if (!item || (!config.notExisting && !item.exists)) | ||
if (item == null || (!config.notExisting && !item.exists)) | ||
return undefined; | ||
internal_1.ComputedTracker.tracked(item.observer); | ||
internal_1.ComputedTracker.tracked(item.observers['value']); | ||
return item; | ||
@@ -333,12 +340,16 @@ } | ||
let item = this.getItem(itemKey, { notExisting: true }); | ||
if (!item) { | ||
item = new internal_1.Item(this, { | ||
[this.config.primaryKey]: itemKey, | ||
dummy: 'item', | ||
}, { | ||
isPlaceholder: true, | ||
}); | ||
if (item == null) | ||
item = this.createPlaceholderItem(itemKey, true); | ||
internal_1.ComputedTracker.tracked(item.observers['value']); | ||
return item; | ||
} | ||
createPlaceholderItem(itemKey, addToCollection = false) { | ||
const item = new internal_1.Item(this, { | ||
[this.config.primaryKey]: itemKey, | ||
dummy: 'item', | ||
}, { isPlaceholder: true }); | ||
if (addToCollection && | ||
!Object.prototype.hasOwnProperty.call(this.data, itemKey)) | ||
this.data[itemKey] = item; | ||
} | ||
internal_1.ComputedTracker.tracked(item.observer); | ||
internal_1.ComputedTracker.tracked(item.observers['value']); | ||
return item; | ||
@@ -348,3 +359,3 @@ } | ||
const item = this.getItem(itemKey, config); | ||
if (!item) | ||
if (item == null) | ||
return undefined; | ||
@@ -364,3 +375,3 @@ return item.value; | ||
else { | ||
items = (defaultGroup === null || defaultGroup === void 0 ? void 0 : defaultGroup.items) || []; | ||
items = (defaultGroup === null || defaultGroup === void 0 ? void 0 : defaultGroup.getItems()) || []; | ||
} | ||
@@ -389,4 +400,4 @@ return items; | ||
}); | ||
if (this.persistent) | ||
internal_1.Agile.logger.warn(`By persisting the Collection '${this._key}' twice you overwrite the old Persistent Instance!`); | ||
if (this.persistent != null && this.isPersisted) | ||
return this; | ||
this.persistent = new internal_1.CollectionPersistent(this, { | ||
@@ -401,24 +412,13 @@ instantiate: _config.loadValue, | ||
onLoad(callback) { | ||
if (this.persistent) { | ||
this.persistent.onLoad = callback; | ||
if (this.isPersisted) | ||
callback(true); | ||
if (!this.persistent) | ||
return this; | ||
if (!internal_1.isFunction(callback)) { | ||
internal_1.LogCodeManager.log('00:03:01', ['OnLoad Callback', 'function']); | ||
return this; | ||
} | ||
else { | ||
internal_1.Agile.logger.error(`Please make sure you persist the Collection '${this._key}' before using the 'onLoad' function!`); | ||
} | ||
this.persistent.onLoad = callback; | ||
if (this.isPersisted) | ||
callback(true); | ||
return this; | ||
} | ||
getGroupCount() { | ||
let size = 0; | ||
for (const group in this.groups) | ||
size++; | ||
return size; | ||
} | ||
getSelectorCount() { | ||
let size = 0; | ||
for (const selector in this.selectors) | ||
size++; | ||
return size; | ||
} | ||
reset() { | ||
@@ -456,6 +456,6 @@ var _a, _b; | ||
}); | ||
if (!item || oldItemKey === newItemKey) | ||
if (item == null || oldItemKey === newItemKey) | ||
return false; | ||
if (this.hasItem(newItemKey)) { | ||
internal_1.Agile.logger.warn(`Couldn't update ItemKey from '${oldItemKey}' to '${newItemKey}' because an Item with the key/name '${newItemKey}' already exists!`); | ||
internal_1.LogCodeManager.log('1B:03:04', [oldItemKey, newItemKey, this._key]); | ||
return false; | ||
@@ -468,6 +468,9 @@ } | ||
}); | ||
(_a = item.persistent) === null || _a === void 0 ? void 0 : _a.setKey(internal_1.CollectionPersistent.getItemStorageKey(newItemKey, this._key)); | ||
if (item.persistent != null && | ||
item.persistent._key === | ||
internal_1.CollectionPersistent.getItemStorageKey(oldItemKey, this._key)) | ||
(_a = item.persistent) === null || _a === void 0 ? void 0 : _a.setKey(internal_1.CollectionPersistent.getItemStorageKey(newItemKey, this._key)); | ||
for (const groupKey in this.groups) { | ||
const group = this.getGroup(groupKey, { notExisting: true }); | ||
if (!group || !group.has(oldItemKey)) | ||
if (group == null || !group.has(oldItemKey)) | ||
continue; | ||
@@ -478,6 +481,6 @@ group.replace(oldItemKey, newItemKey, { background: config.background }); | ||
const selector = this.getSelector(selectorKey, { notExisting: true }); | ||
if (!selector) | ||
if (selector == null) | ||
continue; | ||
if (selector.hasSelected(newItemKey)) { | ||
selector.select(newItemKey, { | ||
if (selector.hasSelected(newItemKey, false)) { | ||
selector.reselect({ | ||
force: true, | ||
@@ -487,5 +490,5 @@ background: config.background, | ||
} | ||
if (selector.hasSelected(oldItemKey)) | ||
if (selector.hasSelected(oldItemKey, false)) | ||
selector.select(newItemKey, { | ||
background: config === null || config === void 0 ? void 0 : config.background, | ||
background: config.background, | ||
}); | ||
@@ -498,3 +501,3 @@ } | ||
for (const groupKey in this.groups) { | ||
const group = this.getGroup(groupKey, { notExisting: true }); | ||
const group = this.groups[groupKey]; | ||
if (group === null || group === void 0 ? void 0 : group.has(itemKey)) | ||
@@ -508,3 +511,3 @@ groupKeys.push(groupKey); | ||
fromGroups: (groups) => this.removeFromGroups(itemKeys, groups), | ||
everywhere: () => this.removeItems(itemKeys), | ||
everywhere: (config) => this.removeItems(itemKeys, config || {}), | ||
}; | ||
@@ -519,3 +522,3 @@ } | ||
const group = this.getGroup(groupKey, { notExisting: true }); | ||
if (!group || !group.has(itemKey)) | ||
if (!(group === null || group === void 0 ? void 0 : group.has(itemKey))) | ||
return; | ||
@@ -531,9 +534,14 @@ group.remove(itemKey); | ||
} | ||
removeItems(itemKeys) { | ||
removeItems(itemKeys, config = {}) { | ||
config = internal_1.defineConfig(config, { | ||
notExisting: false, | ||
removeSelector: false, | ||
}); | ||
const _itemKeys = internal_1.normalizeArray(itemKeys); | ||
_itemKeys.forEach((itemKey) => { | ||
var _a; | ||
const item = this.getItem(itemKey, { notExisting: true }); | ||
if (!item) | ||
var _a, _b; | ||
const item = this.getItem(itemKey, { notExisting: config.notExisting }); | ||
if (item == null) | ||
return; | ||
const wasPlaceholder = item.isPlaceholder; | ||
for (const groupKey in this.groups) { | ||
@@ -548,12 +556,17 @@ const group = this.getGroup(groupKey, { notExisting: true }); | ||
const selector = this.getSelector(selectorKey, { notExisting: true }); | ||
if (selector === null || selector === void 0 ? void 0 : selector.hasSelected(itemKey)) | ||
selector === null || selector === void 0 ? void 0 : selector.select(itemKey, { force: true }); | ||
if (selector != null && selector.hasSelected(itemKey, false)) { | ||
if (config.removeSelector) { | ||
this.removeSelector((_b = selector._key) !== null && _b !== void 0 ? _b : 'unknown'); | ||
} | ||
else { | ||
selector.reselect({ force: true }); | ||
} | ||
} | ||
} | ||
this.size--; | ||
if (!wasPlaceholder) | ||
this.size--; | ||
}); | ||
return this; | ||
} | ||
setData(data, config = {}) { | ||
const _data = internal_1.copy(data); | ||
const primaryKey = this.config.primaryKey; | ||
assignData(data, config = {}) { | ||
config = internal_1.defineConfig(config, { | ||
@@ -563,29 +576,66 @@ patch: false, | ||
}); | ||
const _data = internal_1.copy(data); | ||
const primaryKey = this.config.primaryKey; | ||
if (!internal_1.isValidObject(_data)) { | ||
internal_1.Agile.logger.error(`Item Data of Collection '${this._key}' has to be an valid Object!`); | ||
internal_1.LogCodeManager.log('1B:03:05', [this._key]); | ||
return false; | ||
} | ||
if (!Object.prototype.hasOwnProperty.call(_data, primaryKey)) { | ||
internal_1.Agile.logger.error(`Collection '${this._key}' Item Data has to contain a primaryKey property called '${this.config.primaryKey}'!`); | ||
return false; | ||
internal_1.LogCodeManager.log('1B:02:05', [this._key, primaryKey]); | ||
_data[primaryKey] = internal_1.generateId(); | ||
} | ||
const itemKey = _data[primaryKey]; | ||
let item = this.getItem(itemKey, { notExisting: true }); | ||
const item = this.getItem(itemKey, { notExisting: true }); | ||
const wasPlaceholder = (item === null || item === void 0 ? void 0 : item.isPlaceholder) || false; | ||
const createItem = !item; | ||
if (!createItem && config.patch) | ||
item === null || item === void 0 ? void 0 : item.patch(_data, { background: config.background }); | ||
if (!createItem && !config.patch) | ||
item === null || item === void 0 ? void 0 : item.set(_data, { background: config.background }); | ||
if (createItem) { | ||
item = new internal_1.Item(this, _data); | ||
this.data[itemKey] = item; | ||
this.rebuildGroupsThatIncludeItemKey(itemKey, { | ||
if (item != null) { | ||
if (config.patch) { | ||
item.patch(_data, { background: config.background }); | ||
} | ||
else { | ||
item.set(_data, { background: config.background }); | ||
} | ||
} | ||
else { | ||
this.assignItem(new internal_1.Item(this, _data), { | ||
background: config.background, | ||
}); | ||
} | ||
if (createItem || wasPlaceholder) | ||
if (wasPlaceholder) | ||
this.size++; | ||
return true; | ||
} | ||
assignItem(item, config = {}) { | ||
config = internal_1.defineConfig(config, { | ||
overwrite: false, | ||
background: false, | ||
}); | ||
const primaryKey = this.config.primaryKey; | ||
let itemKey = item._value[primaryKey]; | ||
let increaseCollectionSize = true; | ||
if (!Object.prototype.hasOwnProperty.call(item._value, primaryKey)) { | ||
internal_1.LogCodeManager.log('1B:02:05', [this._key, primaryKey]); | ||
itemKey = internal_1.generateId(); | ||
item.patch({ [this.config.primaryKey]: itemKey }, { background: config.background }); | ||
item._key = itemKey; | ||
} | ||
if (item.collection() !== this) { | ||
internal_1.LogCodeManager.log('1B:03:06', [this._key, item.collection()._key]); | ||
return false; | ||
} | ||
if (this.getItem(itemKey, { notExisting: true }) != null) { | ||
if (!config.overwrite) { | ||
this.assignData(item._value); | ||
return true; | ||
} | ||
else | ||
increaseCollectionSize = false; | ||
} | ||
this.data[itemKey] = item; | ||
this.rebuildGroupsThatIncludeItemKey(itemKey, { | ||
background: config.background, | ||
}); | ||
if (increaseCollectionSize) | ||
this.size++; | ||
return true; | ||
} | ||
rebuildGroupsThatIncludeItemKey(itemKey, config = {}) { | ||
@@ -602,5 +652,4 @@ config = internal_1.defineConfig(config, { | ||
if (group === null || group === void 0 ? void 0 : group.has(itemKey)) { | ||
group === null || group === void 0 ? void 0 : group.ingest({ | ||
group === null || group === void 0 ? void 0 : group.rebuild({ | ||
background: config === null || config === void 0 ? void 0 : config.background, | ||
force: true, | ||
sideEffects: config === null || config === void 0 ? void 0 : config.sideEffects, | ||
@@ -607,0 +656,0 @@ storage: false, |
@@ -1,33 +0,75 @@ | ||
import { State, Collection, DefaultItem, StateKey, StateRuntimeJobConfigInterface } from '../internal'; | ||
export declare class Item<DataType extends object = DefaultItem> extends State<DataType> { | ||
import { State, Collection, StateKey, StateRuntimeJobConfigInterface, SelectorKey, PersistentKey, StatePersistentConfigInterface, DefaultItem } from '../internal'; | ||
export declare class Item<DataType extends Object = DefaultItem> extends State<DataType> { | ||
collection: () => Collection<DataType>; | ||
static updateGroupSideEffectKey: string; | ||
isSelected: boolean; | ||
collection: () => Collection<DataType>; | ||
selectedBy: Set<SelectorKey>; | ||
/** | ||
* An extension of the State Class that represents a single data object of a Collection. | ||
* | ||
* It can be used independently, but is always synchronized with the Collection. | ||
* | ||
* @public | ||
* Item of Collection | ||
* @param collection - Collection to which the Item belongs | ||
* @param data - Data that the Item holds | ||
* @param config - Config | ||
* @param collection - Collection to which the Item belongs. | ||
* @param data - Data object to be represented by the Item. | ||
* @param config - Configuration object | ||
*/ | ||
constructor(collection: Collection<DataType>, data: DataType, config?: ItemConfigInterface); | ||
/** | ||
* Updates the key/name identifier of Item. | ||
* | ||
* @internal | ||
* Updates Key/Name of State | ||
* @param value - New Key/Name of State | ||
* @param config - Config | ||
* @param value - New key/name identifier. | ||
* @param config - Configuration object | ||
*/ | ||
setKey(value: StateKey | undefined, config?: StateRuntimeJobConfigInterface): this; | ||
/** | ||
* Preserves the Item `value` in the corresponding external Storage. | ||
* | ||
* The Item key/name is used as the unique identifier for the Persistent. | ||
* If that is not desired or the Item has no unique identifier, | ||
* please specify a separate unique identifier for the Persistent. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#persist) | ||
* | ||
* @public | ||
* @param config - Configuration object | ||
*/ | ||
persist(config?: ItemPersistConfigInterface): this; | ||
/** | ||
* Preserves the Item `value` in the corresponding external Storage. | ||
* | ||
* The specified key is used as the unique identifier for the Persistent. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#persist) | ||
* | ||
* @public | ||
* @param key - Key/Name identifier of Persistent. | ||
* @param config - Configuration object | ||
*/ | ||
persist(key?: PersistentKey, config?: ItemPersistConfigInterface): this; | ||
/** | ||
* Adds side effect to Item | ||
* that rebuilds all Groups containing the specified Item identifier | ||
* whenever the Item changes. | ||
* | ||
* @internal | ||
* Adds rebuildGroupThatIncludeItemKey to the Item sideEffects | ||
* @param itemKey - ItemKey at which the groups has to rebuild | ||
* @param itemKey - Item identifier that has to be contained in Groups. | ||
*/ | ||
addRebuildGroupThatIncludeItemKeySideEffect(itemKey: StateKey): void; | ||
} | ||
/** | ||
* @param isPlaceholder - If Item is initially a Placeholder | ||
*/ | ||
export interface ItemConfigInterface { | ||
/** | ||
* Whether the Item should be a placeholder | ||
* and therefore should only exist in the background. | ||
* @default false | ||
*/ | ||
isPlaceholder?: boolean; | ||
} | ||
export interface ItemPersistConfigInterface extends StatePersistentConfigInterface { | ||
/** | ||
* Whether to format the specified Storage key following the Collection Item Storage key pattern. | ||
* `_${collectionKey}_item_${itemKey}` | ||
* @default true | ||
*/ | ||
followCollectionPersistKeyPattern?: boolean; | ||
} |
@@ -11,5 +11,7 @@ "use strict"; | ||
}); | ||
this.isSelected = false; | ||
this.selectedBy = new Set(); | ||
this.collection = () => collection; | ||
this.addRebuildGroupThatIncludeItemKeySideEffect(this._key != null ? this._key : 'unknown'); | ||
if (this._key != null) { | ||
this.addRebuildGroupThatIncludeItemKeySideEffect(this._key); | ||
} | ||
} | ||
@@ -32,13 +34,36 @@ setKey(value, config = {}) { | ||
this.addRebuildGroupThatIncludeItemKeySideEffect(value); | ||
this.patch({ [this.collection().config.primaryKey]: value }, { | ||
sideEffects: config.sideEffects, | ||
background: config.background, | ||
force: config.force, | ||
storage: config.storage, | ||
overwrite: config.overwrite, | ||
this.patch({ [this.collection().config.primaryKey]: value }, config); | ||
return this; | ||
} | ||
persist(keyOrConfig = {}, config = {}) { | ||
let _config; | ||
let key; | ||
if (internal_1.isValidObject(keyOrConfig)) { | ||
_config = keyOrConfig; | ||
key = this._key; | ||
} | ||
else { | ||
_config = config || {}; | ||
key = keyOrConfig; | ||
} | ||
_config = internal_1.defineConfig(_config, { | ||
loadValue: true, | ||
followCollectionPersistKeyPattern: true, | ||
storageKeys: [], | ||
defaultStorageKey: null, | ||
}); | ||
if (_config.followCollectionPersistKeyPattern) { | ||
key = internal_1.CollectionPersistent.getItemStorageKey(key || this._key, this.collection()._key); | ||
} | ||
super.persist(key, { | ||
loadValue: _config.loadValue, | ||
storageKeys: _config.storageKeys, | ||
defaultStorageKey: _config.defaultStorageKey, | ||
}); | ||
return this; | ||
} | ||
addRebuildGroupThatIncludeItemKeySideEffect(itemKey) { | ||
this.addSideEffect(Item.updateGroupSideEffectKey, (instance, config) => instance.collection().rebuildGroupsThatIncludeItemKey(itemKey, config), { weight: 100 }); | ||
this.addSideEffect(Item.updateGroupSideEffectKey, (instance, config) => { | ||
instance.collection().rebuildGroupsThatIncludeItemKey(itemKey, config); | ||
}, { weight: 100 }); | ||
} | ||
@@ -45,0 +70,0 @@ } |
import { Collection, DefaultItem, Item, ItemKey, State, StateRuntimeJobConfigInterface } from '../internal'; | ||
export declare class Selector<DataType extends object = DefaultItem> extends State<DataType | undefined> { | ||
static dummyItemKey: string; | ||
export declare class Selector<DataType extends Object = DefaultItem> extends State<DataType | null> { | ||
collection: () => Collection<DataType>; | ||
static rebuildSelectorSideEffectKey: string; | ||
static rebuildItemSideEffectKey: string; | ||
collection: () => Collection<DataType>; | ||
item: Item<DataType> | undefined; | ||
_itemKey: ItemKey; | ||
_item: Item<DataType> | null; | ||
_itemKey: ItemKey | null; | ||
/** | ||
* A Selector represents an Item from a Collection in the long term. | ||
* It can be mutated dynamically and remains in sync with the Collection. | ||
* | ||
* Components that need one piece of data from a Collection such as the "current user" | ||
* would benefit from using Selectors. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/selector) | ||
* | ||
* @public | ||
* Represents Item of Collection | ||
* @param collection - Collection that contains the Item | ||
* @param itemKey - ItemKey of Item that the Selector represents | ||
* @param config - Config | ||
* @param collection - Collection to which the Selector belongs. | ||
* @param itemKey - Key/Name identifier of the Item to be represented by the Selector. | ||
* @param config - Configuration object | ||
*/ | ||
constructor(collection: Collection<DataType>, itemKey: ItemKey, config?: SelectorConfigInterface); | ||
constructor(collection: Collection<DataType>, itemKey: ItemKey | null, config?: SelectorConfigInterface); | ||
/** | ||
* Returns the `itemKey` currently selected by the Selector. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/selector/properties#itemkey) | ||
* | ||
* @public | ||
* Set ItemKey that the Selector represents | ||
*/ | ||
set itemKey(value: ItemKey); | ||
get itemKey(): ItemKey | null; | ||
/** | ||
* Updates the currently selected Item of the Selector | ||
* based on the specified `itemKey`. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/selector/properties#itemkey) | ||
* | ||
* @public | ||
* Get ItemKey that the Selector represents | ||
* @param value - New key/name identifier of the Item to be represented by the Selector. | ||
*/ | ||
get itemKey(): ItemKey; | ||
set itemKey(value: ItemKey | null); | ||
/** | ||
* Retrieves the Item currently selected by the Selector. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/selector/properties#item) | ||
* | ||
* @public | ||
* Select new ItemKey | ||
* @param itemKey - New ItemKey | ||
* @param config - Config | ||
*/ | ||
select(itemKey: ItemKey, config?: StateRuntimeJobConfigInterface): this; | ||
get item(): Item<DataType> | null; | ||
/** | ||
* Updates the currently selected Item of the Selector | ||
* based on the specified Item. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/selector/properties#item) | ||
* | ||
* @public | ||
* Unselects current selected Item | ||
* @param config - Config | ||
* @param value - New Item to be represented by the Selector. | ||
*/ | ||
set item(value: Item<DataType> | null); | ||
/** | ||
* Updates the currently selected Item of the Selector. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/selector/methods#select) | ||
* | ||
* @public | ||
* @param itemKey - New key/name identifier of the Item to be represented by the Selector. | ||
* @param config - Configuration object | ||
*/ | ||
select(itemKey: ItemKey | null, config?: StateRuntimeJobConfigInterface): this; | ||
/** | ||
* Reselects the currently selected Item. | ||
* | ||
* This might be helpful if the Selector failed to select the Item correctly before | ||
* and therefore should try to select it again. | ||
* | ||
* You can use the 'hasSelected()' method to check | ||
* whether the 'selected' Item is selected correctly. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/selector/methods#reselect) | ||
* | ||
* @public | ||
* @param config - Configuration object | ||
*/ | ||
reselect(config?: StateRuntimeJobConfigInterface): this; | ||
/** | ||
* Unselects the currently selected Item. | ||
* | ||
* Therefore, it sets the `itemKey` and `item` property to `undefined`, | ||
* since the Selector no longer represents any Item. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/selector/methods#unselect) | ||
* | ||
* @public | ||
* @param config - Configuration object | ||
*/ | ||
unselect(config?: StateRuntimeJobConfigInterface): this; | ||
/** | ||
* Checks if Selector has selected passed ItemKey | ||
* @param itemKey - ItemKey | ||
* Returns a boolean indicating whether an Item with the specified `itemKey` | ||
* is selected by the Selector or not. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/selector/methods#hasselected) | ||
* | ||
* @public | ||
* @param itemKey - Key/Name identifier of the Item. | ||
* @param correctlySelected - Whether the Item has to be selected correctly. | ||
*/ | ||
hasSelected(itemKey: ItemKey): boolean; | ||
hasSelected(itemKey: ItemKey | null, correctlySelected?: boolean): boolean; | ||
/** | ||
* Rebuilds the Selector. | ||
* During this process, it updates the Selector `value` based on the Item `value`. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/collection/selector/methods#rebuild) | ||
* | ||
* @public | ||
* Rebuilds Selector | ||
* @param config - Config | ||
* @param config - Configuration object | ||
*/ | ||
@@ -53,9 +119,14 @@ rebuildSelector(config?: StateRuntimeJobConfigInterface): this; | ||
export declare type SelectorKey = string | number; | ||
/** | ||
* @param key - Key/Name of Selector | ||
* @param isPlaceholder - If Selector is initially a Placeholder | ||
*/ | ||
export interface SelectorConfigInterface { | ||
/** | ||
* Key/Name identifier of the Selector. | ||
* @default undefined | ||
*/ | ||
key?: SelectorKey; | ||
/** | ||
* Whether the Selector should be a placeholder | ||
* and therefore should only exist in the background. | ||
* @default false | ||
*/ | ||
isPlaceholder?: boolean; | ||
} |
@@ -7,25 +7,29 @@ "use strict"; | ||
constructor(collection, itemKey, config = {}) { | ||
super(collection.agileInstance(), undefined, config); | ||
config = internal_1.defineConfig(config, { | ||
isPlaceholder: false, | ||
}); | ||
super(collection.agileInstance(), null, config); | ||
this.collection = () => collection; | ||
this.item = undefined; | ||
this._itemKey = Selector.dummyItemKey; | ||
this._item = null; | ||
this._itemKey = !config.isPlaceholder && itemKey != null ? itemKey : null; | ||
this._key = config === null || config === void 0 ? void 0 : config.key; | ||
this.isPlaceholder = true; | ||
if (!config.isPlaceholder) | ||
if (this._itemKey != null) | ||
this.select(itemKey, { overwrite: true }); | ||
} | ||
get itemKey() { | ||
return this._itemKey; | ||
} | ||
set itemKey(value) { | ||
this.select(value); | ||
} | ||
get itemKey() { | ||
return this._itemKey; | ||
get item() { | ||
return this._item; | ||
} | ||
set item(value) { | ||
if (value === null || value === void 0 ? void 0 : value._key) | ||
this.select(value._key); | ||
} | ||
select(itemKey, config = {}) { | ||
const oldItem = this.collection().getItem(this._itemKey, { | ||
notExisting: true, | ||
}); | ||
const newItem = this.collection().getItemWithReference(itemKey); | ||
var _a, _b; | ||
config = internal_1.defineConfig(config, { | ||
@@ -38,18 +42,20 @@ background: false, | ||
force: false, | ||
overwrite: (oldItem === null || oldItem === void 0 ? void 0 : oldItem.isPlaceholder) || false, | ||
overwrite: (_b = (_a = this._item) === null || _a === void 0 ? void 0 : _a.isPlaceholder) !== null && _b !== void 0 ? _b : false, | ||
storage: true, | ||
}); | ||
if (this.hasSelected(itemKey) && !config.force) { | ||
internal_1.Agile.logger.warn(`Selector has already selected '${itemKey}'!`); | ||
if ((!this.collection().isInstantiated || this.hasSelected(itemKey)) && | ||
!config.force) | ||
return this; | ||
} | ||
this.unselect({ background: true }); | ||
this.unselect({ background: itemKey != null }); | ||
if (itemKey == null) | ||
return this; | ||
const newItem = this.collection().getItemWithReference(itemKey); | ||
this._itemKey = itemKey; | ||
this.item = newItem; | ||
newItem.isSelected = true; | ||
this._item = newItem; | ||
newItem.selectedBy.add(this._key); | ||
newItem.addSideEffect(Selector.rebuildSelectorSideEffectKey, (instance, config) => this.rebuildSelector(config), { weight: 100 }); | ||
this.addSideEffect(Selector.rebuildItemSideEffectKey, (instance, config) => { | ||
var _a, _b; | ||
if (!((_a = instance.item) === null || _a === void 0 ? void 0 : _a.isPlaceholder)) | ||
(_b = instance.item) === null || _b === void 0 ? void 0 : _b.set(instance._value, Object.assign(Object.assign({}, config), { | ||
if (!((_a = instance._item) === null || _a === void 0 ? void 0 : _a.isPlaceholder)) | ||
(_b = instance._item) === null || _b === void 0 ? void 0 : _b.set(instance._value, Object.assign(Object.assign({}, config), { | ||
sideEffects: { | ||
@@ -64,2 +70,8 @@ enabled: true, | ||
} | ||
reselect(config = {}) { | ||
if ((this._itemKey != null && !this.hasSelected(this._itemKey)) || | ||
config.force) | ||
this.select(this._itemKey, config); | ||
return this; | ||
} | ||
unselect(config = {}) { | ||
@@ -69,11 +81,12 @@ const item = this.collection().getItem(this._itemKey, { | ||
}); | ||
if (item) { | ||
item.isSelected = false; | ||
if (item != null) { | ||
item.selectedBy.delete(this._key); | ||
item.removeSideEffect(Selector.rebuildSelectorSideEffectKey); | ||
item.removeSideEffect(Selector.rebuildItemSideEffectKey); | ||
if (item.isPlaceholder) | ||
if (item.isPlaceholder && | ||
this._itemKey != null) | ||
delete this.collection().data[this._itemKey]; | ||
} | ||
this.item = undefined; | ||
this._itemKey = Selector.dummyItemKey; | ||
this._item = null; | ||
this._itemKey = null; | ||
this.rebuildSelector(config); | ||
@@ -83,14 +96,16 @@ this.isPlaceholder = true; | ||
} | ||
hasSelected(itemKey) { | ||
const isSelected = this._itemKey === itemKey; | ||
if (!this.item) | ||
return isSelected; | ||
return isSelected && this.item.isSelected; | ||
hasSelected(itemKey, correctlySelected = true) { | ||
if (correctlySelected) { | ||
return (this._itemKey === itemKey && | ||
this._item != null && | ||
this._item.selectedBy.has(this._key)); | ||
} | ||
return this._itemKey === itemKey; | ||
} | ||
rebuildSelector(config = {}) { | ||
if (!this.item || this.item.isPlaceholder) { | ||
this.set(undefined, config); | ||
if (this._item == null || this._item.isPlaceholder) { | ||
this.set(null, config); | ||
return this; | ||
} | ||
this.set(this.item._value, config); | ||
this.set(this._item._value, config); | ||
return this; | ||
@@ -100,4 +115,3 @@ } | ||
exports.Selector = Selector; | ||
Selector.dummyItemKey = 'unknown'; | ||
Selector.rebuildSelectorSideEffectKey = 'rebuildSelector'; | ||
Selector.rebuildItemSideEffectKey = 'rebuildItem'; |
@@ -6,9 +6,18 @@ import { Observer } from '../runtime/observer'; | ||
/** | ||
* Helper Class for automatic tracking used Observers (dependencies) in a compute function. | ||
* | ||
* @internal | ||
* Starts tracking Observers | ||
*/ | ||
constructor(); | ||
/** | ||
* Activates Computed Tracker to globally track used Observers. | ||
* | ||
* @internal | ||
*/ | ||
static track(): void; | ||
/** | ||
* Tracks the passed Observer and caches it | ||
* when the Computed Tracker is actively tracking. | ||
* | ||
* @internal | ||
* Adds passed Observer to tracked Observers, if ComputedTracker is currently tracking | ||
* @param observer - Observer | ||
@@ -18,6 +27,8 @@ */ | ||
/** | ||
* Returns the latest tracked Observers | ||
* and stops the Computed Tracker from tracking any more Observers. | ||
* | ||
* @internal | ||
* Returns tracked Observers and stops tracking anymore Observers | ||
*/ | ||
static getTrackedObservers(): Array<Observer>; | ||
} |
@@ -5,2 +5,4 @@ "use strict"; | ||
class ComputedTracker { | ||
constructor() { | ||
} | ||
static track() { | ||
@@ -7,0 +9,0 @@ this.isTracking = true; |
import { State, Agile, Observer, StateConfigInterface, Collection, StateIngestConfigInterface } from '../internal'; | ||
export declare class Computed<ComputedValueType = any> extends State<ComputedValueType> { | ||
agileInstance: () => Agile; | ||
computeFunction: () => ComputedValueType; | ||
deps: Array<Observer>; | ||
config: ComputedConfigInterface; | ||
computeFunction: ComputeFunctionType<ComputedValueType>; | ||
deps: Set<Observer>; | ||
hardCodedDeps: Array<Observer>; | ||
/** | ||
* A Computed is an extension of the State Class | ||
* that computes its value based on a specified compute function. | ||
* | ||
* The computed value will be cached to avoid unnecessary recomputes | ||
* and is only recomputed when one of its direct dependencies changes. | ||
* | ||
* Direct dependencies can be States and Collections. | ||
* So when, for example, a dependent State value changes, the computed value is recomputed. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/computed/) | ||
* | ||
* @public | ||
* Computed - Function that recomputes its value if a dependency changes | ||
* @param agileInstance - An instance of Agile | ||
* @param computeFunction - Function for computing value | ||
* @param config - Config | ||
* @param agileInstance - Instance of Agile the Computed belongs to. | ||
* @param computeFunction - Function to compute the computed value. | ||
* @param config - Configuration object | ||
*/ | ||
constructor(agileInstance: Agile, computeFunction: () => ComputedValueType, config?: ComputedConfigInterface); | ||
constructor(agileInstance: Agile, computeFunction: ComputeFunctionType<ComputedValueType>, config?: CreateComputedConfigInterface); | ||
/** | ||
* Forces a recomputation of the cached value with the compute function. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/computed/methods/#recompute) | ||
* | ||
* @public | ||
* Recomputes Value of Computed | ||
* @param config - Config | ||
* @param config - Configuration object | ||
*/ | ||
recompute(config?: RecomputeConfigInterface): this; | ||
/** | ||
* Assigns a new function to the Computed Class for computing its value. | ||
* | ||
* The dependencies of the new compute function are automatically detected | ||
* and accordingly updated. | ||
* | ||
* An initial computation is performed with the new function | ||
* to change the obsolete cached value. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/computed/methods/#updatecomputefunction) | ||
* | ||
* @public | ||
* Applies new compute Function to Computed | ||
* @param computeFunction - New Function for computing value | ||
* @param deps - Hard coded dependencies of Computed Function | ||
* @param config - Config | ||
* @param computeFunction - New function to compute the value of the Computed Class. | ||
* @param deps - Hard coded dependencies on which the Computed Class depends. | ||
* @param config - Configuration object | ||
*/ | ||
updateComputeFunction(computeFunction: () => ComputedValueType, deps?: Array<SubscribableAgileInstancesType>, config?: UpdateComputeFunctionConfigInterface): this; | ||
updateComputeFunction(computeFunction: () => ComputedValueType, deps?: Array<DependableAgileInstancesType>, config?: RecomputeConfigInterface): this; | ||
/** | ||
* Computes and returns the new value of the Computed Class | ||
* and autodetects used dependencies in the compute function. | ||
* | ||
* @internal | ||
* Recomputes value and adds missing dependencies to Computed | ||
* @param config - Configuration object | ||
*/ | ||
compute(config?: ComputeConfigInterface): ComputedValueType; | ||
patch(): this; | ||
compute(config?: ComputeConfigInterface): Promise<ComputedValueType>; | ||
/** | ||
* Not usable in Computed Class. | ||
*/ | ||
persist(): this; | ||
invert(): this; | ||
} | ||
/** | ||
* @param computedDeps - Hard coded dependencies of Computed Function | ||
*/ | ||
export interface ComputedConfigInterface extends StateConfigInterface { | ||
computedDeps?: Array<SubscribableAgileInstancesType>; | ||
export declare type ComputeFunctionType<ComputedValueType = any> = () => ComputedValueType | Promise<ComputedValueType>; | ||
export interface CreateComputedConfigInterface extends StateConfigInterface { | ||
/** | ||
* Hard-coded dependencies the Computed Class should depend on. | ||
* @default [] | ||
*/ | ||
computedDeps?: Array<DependableAgileInstancesType>; | ||
/** | ||
* Whether the Computed should automatically detect | ||
* used dependencies in the specified compute method. | ||
* | ||
* Note that the automatic dependency detection does not work | ||
* in an asynchronous compute method! | ||
* | ||
* @default true if the compute method isn't asynchronous, otherwise false | ||
*/ | ||
autodetect?: boolean; | ||
} | ||
/** | ||
* @param autodetect - If dependencies get autodetected | ||
*/ | ||
export interface ComputedConfigInterface { | ||
/** | ||
* Whether the Computed can automatically detect | ||
* used dependencies in the compute method. | ||
* | ||
* Note that the automatic dependency detection does not work | ||
* in an asynchronous compute method! | ||
* | ||
* @default true if the compute method isn't asynchronous, otherwise false | ||
*/ | ||
autodetect: boolean; | ||
} | ||
export interface ComputeConfigInterface { | ||
/** | ||
* Whether the Computed can automatically detect | ||
* used dependencies in the compute method. | ||
* | ||
* Note that the automatic dependency detection does not work | ||
* in an asynchronous compute method! | ||
* | ||
* @default true | ||
*/ | ||
autodetect?: boolean; | ||
} | ||
/** | ||
* @param overwriteDeps - If old hardCoded deps get overwritten | ||
*/ | ||
export interface UpdateComputeFunctionConfigInterface extends RecomputeConfigInterface { | ||
overwriteDeps?: boolean; | ||
} | ||
export interface RecomputeConfigInterface extends StateIngestConfigInterface, ComputeConfigInterface { | ||
} | ||
declare type SubscribableAgileInstancesType = State | Collection | Observer; | ||
export {}; | ||
export declare type DependableAgileInstancesType = State | Collection<any> | Observer; |
"use strict"; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -7,16 +16,23 @@ exports.Computed = void 0; | ||
constructor(agileInstance, computeFunction, config = {}) { | ||
super(agileInstance, computeFunction(), { | ||
super(agileInstance, null, { | ||
key: config.key, | ||
dependents: config.dependents, | ||
}); | ||
this.deps = []; | ||
this.deps = new Set(); | ||
this.hardCodedDeps = []; | ||
config = internal_1.defineConfig(config, { | ||
computedDeps: [], | ||
autodetect: !internal_1.isAsyncFunction(computeFunction), | ||
}); | ||
this.agileInstance = () => agileInstance; | ||
this.computeFunction = computeFunction; | ||
this.hardCodedDeps = internal_1.extractObservers(config.computedDeps).filter((dep) => dep !== undefined); | ||
this.deps = this.hardCodedDeps; | ||
this.recompute({ autodetect: true }); | ||
this.config = { | ||
autodetect: config.autodetect, | ||
}; | ||
this.hardCodedDeps = internal_1.extractRelevantObservers(config.computedDeps).filter((dep) => dep !== undefined); | ||
this.deps = new Set(this.hardCodedDeps); | ||
this.deps.forEach((observer) => { | ||
observer.addDependent(this.observers['value']); | ||
}); | ||
this.recompute({ autodetect: config.autodetect, overwrite: true }); | ||
} | ||
@@ -27,3 +43,5 @@ recompute(config = {}) { | ||
}); | ||
this.observer.ingestValue(this.compute({ autodetect: config.autodetect }), internal_1.removeProperties(config, ['autodetect'])); | ||
this.compute({ autodetect: config.autodetect }).then((result) => { | ||
this.observers['value'].ingestValue(result, internal_1.removeProperties(config, ['autodetect'])); | ||
}); | ||
return this; | ||
@@ -33,11 +51,12 @@ } | ||
config = internal_1.defineConfig(config, { | ||
overwriteDeps: true, | ||
autodetect: true, | ||
autodetect: this.config.autodetect, | ||
}); | ||
const newDeps = internal_1.extractObservers(deps).filter((dep) => dep !== undefined); | ||
if (config.overwriteDeps) | ||
this.hardCodedDeps = newDeps; | ||
else | ||
this.hardCodedDeps = this.hardCodedDeps.concat(newDeps); | ||
this.deps = this.hardCodedDeps; | ||
this.deps.forEach((observer) => { | ||
observer.removeDependent(this.observers['value']); | ||
}); | ||
this.hardCodedDeps = internal_1.extractRelevantObservers(deps).filter((dep) => dep !== undefined); | ||
this.deps = new Set(this.hardCodedDeps); | ||
this.deps.forEach((observer) => { | ||
observer.addDependent(this.observers['value']); | ||
}); | ||
this.computeFunction = computeFunction; | ||
@@ -48,32 +67,33 @@ this.recompute(internal_1.removeProperties(config, ['overwriteDeps'])); | ||
compute(config = {}) { | ||
config = internal_1.defineConfig(config, { | ||
autodetect: true, | ||
return __awaiter(this, void 0, void 0, function* () { | ||
config = internal_1.defineConfig(config, { | ||
autodetect: this.config.autodetect, | ||
}); | ||
if (config.autodetect) | ||
internal_1.ComputedTracker.track(); | ||
const computedValue = this.computeFunction(); | ||
if (config.autodetect) { | ||
const foundDeps = internal_1.ComputedTracker.getTrackedObservers(); | ||
this.deps.forEach((observer) => { | ||
if (!foundDeps.includes(observer) && | ||
!this.hardCodedDeps.includes(observer)) { | ||
this.deps.delete(observer); | ||
observer.removeDependent(this.observers['value']); | ||
} | ||
}); | ||
foundDeps.forEach((observer) => { | ||
if (!this.deps.has(observer)) { | ||
this.deps.add(observer); | ||
observer.addDependent(this.observers['value']); | ||
} | ||
}); | ||
} | ||
return computedValue; | ||
}); | ||
if (config.autodetect) | ||
internal_1.ComputedTracker.track(); | ||
const computedValue = this.computeFunction(); | ||
if (config.autodetect) { | ||
const foundDeps = internal_1.ComputedTracker.getTrackedObservers(); | ||
const newDeps = []; | ||
this.hardCodedDeps.concat(foundDeps).forEach((observer) => { | ||
newDeps.push(observer); | ||
observer.depend(this.observer); | ||
}); | ||
this.deps = newDeps; | ||
} | ||
return computedValue; | ||
} | ||
patch() { | ||
internal_1.Agile.logger.error("You can't use patch method on ComputedState!"); | ||
return this; | ||
} | ||
persist() { | ||
internal_1.Agile.logger.error("You can't use persist method on ComputedState!"); | ||
internal_1.LogCodeManager.log('19:03:00'); | ||
return this; | ||
} | ||
invert() { | ||
internal_1.Agile.logger.error("You can't use invert method on ComputedState!"); | ||
return this; | ||
} | ||
} | ||
exports.Computed = Computed; |
@@ -6,26 +6,37 @@ import { Agile, Integration } from '../internal'; | ||
/** | ||
* The Integrations Class manages all Integrations for an Agile Instance | ||
* and provides an interface to easily update | ||
* and invoke functions in all registered Integrations. | ||
* | ||
* @internal | ||
* Integrations - Manages Integrations of Agile | ||
* @param agileInstance - An Instance of Agile | ||
* @param agileInstance - Instance of Agile the Integrations belongs to. | ||
*/ | ||
constructor(agileInstance: Agile); | ||
/** | ||
* @internal | ||
* Integrates Framework(Integration) into Agile | ||
* @param integration - Integration/Framework that gets integrated | ||
* Integrates the specified Integration into AgileTs | ||
* and sets it to ready when the binding was successful. | ||
* | ||
* @public | ||
* @param integration - Integration to be integrated into AgileTs. | ||
*/ | ||
integrate(integration: Integration): Promise<boolean>; | ||
/** | ||
* @internal | ||
* Updates registered and ready Integrations | ||
* -> calls 'updateMethod' in all registered and ready Integrations | ||
* @param componentInstance - Component that gets updated | ||
* @param updatedData - Properties that differ from the last Value | ||
* Updates the specified UI-Component Instance | ||
* with the updated data object in all registered Integrations that are ready. | ||
* | ||
* In doing so, it calls the `updateMethod()` method | ||
* in all registered Integrations with the specified parameters. | ||
* | ||
* @public | ||
* @param componentInstance - Component Instance to be updated. | ||
* @param updatedData - Data object with updated data. | ||
*/ | ||
update(componentInstance: any, updatedData: Object): void; | ||
/** | ||
* @internal | ||
* Check if at least one Integration got registered | ||
* Returns a boolean indicating whether any Integration | ||
* has been registered with the Agile Instance or not. | ||
* | ||
* @public | ||
*/ | ||
hasIntegration(): boolean; | ||
} |
@@ -23,3 +23,3 @@ "use strict"; | ||
if (!integration._key) { | ||
internal_1.Agile.logger.error('Failed to integrate framework! Invalid Integration!', integration._key); | ||
internal_1.LogCodeManager.log('18:03:00', [integration._key], integration); | ||
return false; | ||
@@ -33,3 +33,3 @@ } | ||
integration.integrated = true; | ||
internal_1.Agile.logger.success(`Integrated '${integration._key}' into AgileTs`); | ||
internal_1.LogCodeManager.log('18:00:00', [integration._key], integration); | ||
return true; | ||
@@ -41,3 +41,3 @@ }); | ||
if (!integration.ready) { | ||
internal_1.Agile.logger.warn(`Integration '${integration.key}' isn't ready yet!`); | ||
internal_1.LogCodeManager.log('18:02:00', [integration._key]); | ||
return; | ||
@@ -44,0 +44,0 @@ } |
@@ -9,34 +9,64 @@ import { Agile } from '../internal'; | ||
/** | ||
* An Integration is an interface to a UI-Framework, | ||
* and allows the easy interaction with that Framework. | ||
* | ||
* Due to the Integration, AgileTs can be integrated into almost any UI-Framework | ||
* without a huge overhead. | ||
* | ||
* @public | ||
* Integration - Represents a Framework/Integration of Agile | ||
* @param config - Config | ||
* @param config - Configuration object | ||
*/ | ||
constructor(config: CreateIntegrationConfig<F, C>); | ||
/** | ||
* Updates the key/name identifier of the Integration. | ||
* | ||
* @public | ||
* Set Value of Integration | ||
* @param value - New key/name identifier. | ||
*/ | ||
set key(key: IntegrationKey); | ||
set key(value: IntegrationKey); | ||
/** | ||
* Returns the key/name identifier of the Integration. | ||
* | ||
* @public | ||
* Get Value of Integration | ||
*/ | ||
get key(): IntegrationKey; | ||
} | ||
/** | ||
* @param key - Key/Name of Integration | ||
* @param frameworkInstance - An Instance of the Framework that this Integration represents (for instance React) | ||
*/ | ||
export interface CreateIntegrationConfig<F = any, C = any> extends IntegrationMethods<C> { | ||
/** | ||
* Key/Name identifier of the Integration. | ||
* @default undefined | ||
*/ | ||
key: string; | ||
/** | ||
* An Instance of the UI-Framework to be represented by the Integration. | ||
* For example, in the case of React, the React Instance. | ||
* @default undefined | ||
*/ | ||
frameworkInstance?: F; | ||
} | ||
/** | ||
* @param bind - Binds the Framework/Integration to Agile | Will be called after a successful integration | ||
* @param updateMethod - Will be called if a Observer updates his subs (Only in Component based Subscriptions!) | ||
*/ | ||
export interface IntegrationMethods<C = any> { | ||
/** | ||
* Binds the Integration to an Agile Instance. | ||
* | ||
* This method is called shortly after the Integration was registered with an Agile Instance. | ||
* It is intended to set up things that are important | ||
* for an seamless integration into AgileTs on the UI-Framework side. | ||
* | ||
* @param agileInstance - Agile Instance into which the Integration is to be integrated. | ||
* @return Indicating whether the to integrate Integration is ready on the Framework side. | ||
*/ | ||
bind?: (agileInstance: Agile) => Promise<boolean>; | ||
/** | ||
* Method to apply the updated data to the provided UI-Component | ||
* in order to trigger a re-render on it. | ||
* | ||
* This method is called when the value of an [Agile Sub Instance](https://agile-ts.org/docs/introduction/#agile-sub-instance) | ||
* bound to the specified UI-Component changes ([Component based Subscription](https://agile-ts.org/docs/core/integration/#component-based)). | ||
* The updated Agile Sub Instance values were mapped in the provided `updatedData` object. | ||
* | ||
* @param componentInstance - Component Instance of the to update UI-Component. | ||
* @param updatedData - Data object containing the updated data. | ||
*/ | ||
updateMethod?: (componentInstance: C, updatedData: Object) => void; | ||
} | ||
export declare type IntegrationKey = string | number; |
@@ -15,4 +15,4 @@ "use strict"; | ||
} | ||
set key(key) { | ||
this._key = key; | ||
set key(value) { | ||
this._key = value; | ||
} | ||
@@ -19,0 +19,0 @@ get key() { |
export * from '@agile-ts/logger'; | ||
export * from './logCodeManager'; | ||
export * from './utils'; | ||
@@ -23,2 +24,3 @@ export * from '@agile-ts/utils'; | ||
export * from './collection/group'; | ||
export * from './collection/group/group.observer'; | ||
export * from './collection/item'; | ||
@@ -25,0 +27,0 @@ export * from './collection/selector'; |
@@ -14,2 +14,3 @@ "use strict"; | ||
__exportStar(require("@agile-ts/logger"), exports); | ||
__exportStar(require("./logCodeManager"), exports); | ||
__exportStar(require("./utils"), exports); | ||
@@ -36,2 +37,3 @@ __exportStar(require("@agile-ts/utils"), exports); | ||
__exportStar(require("./collection/group"), exports); | ||
__exportStar(require("./collection/group/group.observer"), exports); | ||
__exportStar(require("./collection/item"), exports); | ||
@@ -38,0 +40,0 @@ __exportStar(require("./collection/selector"), exports); |
@@ -6,62 +6,114 @@ import { Agile, SubscriptionContainer, RuntimeJob } from '../internal'; | ||
jobQueue: Array<RuntimeJob>; | ||
jobsToRerender: Array<RuntimeJob>; | ||
notReadyJobsToRerender: Set<RuntimeJob>; | ||
jobsToRerender: Array<RuntimeJob>; | ||
isPerformingJobs: boolean; | ||
/** | ||
* The Runtime queues and executes incoming Observer-based Jobs | ||
* to prevent [race conditions](https://en.wikipedia.org/wiki/Race_condition#:~:text=A%20race%20condition%20or%20race,the%20possible%20behaviors%20is%20undesirable.) | ||
* and optimized the re-rendering of the Observer's subscribed UI-Components. | ||
* | ||
* Each queued Job is executed when it is its turn | ||
* by calling the Job Observer's `perform()` method. | ||
* | ||
* After successful execution, the Job is added to a re-render queue, | ||
* which is first put into the browser's 'Bucket' and started to work off | ||
* when resources are left. | ||
* | ||
* The re-render queue is designed for optimizing the render count | ||
* by batching multiple re-render Jobs of the same UI-Component | ||
* and ignoring re-render requests for unmounted UI-Components. | ||
* | ||
* @internal | ||
* Runtime - Performs ingested Observers | ||
* @param agileInstance - An instance of Agile | ||
* @param agileInstance - Instance of Agile the Runtime belongs to. | ||
*/ | ||
constructor(agileInstance: Agile); | ||
/** | ||
* @internal | ||
* Ingests Job into Runtime that gets performed | ||
* @param job - Job | ||
* @param config - Config | ||
* Adds the specified Observer-based Job to the internal Job queue, | ||
* where it is executed when it is its turn. | ||
* | ||
* After successful execution, the Job is assigned to the re-render queue, | ||
* where all the Observer's subscribed Subscription Containers (UI-Components) | ||
* are updated (re-rendered). | ||
* | ||
* @public | ||
* @param job - Job to be added to the Job queue. | ||
* @param config - Configuration object | ||
*/ | ||
ingest(job: RuntimeJob, config?: IngestConfigInterface): void; | ||
/** | ||
* Performs the specified Job | ||
* and assigns it to the re-render queue if necessary. | ||
* | ||
* After the execution of the provided Job, it is checked whether | ||
* there are still Jobs left in the Job queue. | ||
* - If so, the next Job in the `jobQueue` is performed. | ||
* - If not, the `jobsToRerender` queue is started to work off. | ||
* | ||
* @internal | ||
* Performs Job and adds it to the rerender queue if necessary | ||
* @param job - Job that gets performed | ||
* @param job - Job to be performed. | ||
*/ | ||
perform(job: RuntimeJob): void; | ||
/** | ||
* Processes the `jobsToRerender` queue by updating (causing a re-render on) | ||
* the subscribed Subscription Containers (UI-Components) of each Job Observer. | ||
* | ||
* It returns a boolean indicating whether | ||
* any Subscription Container (UI-Component) was updated (re-rendered) or not. | ||
* | ||
* @internal | ||
* Updates/Rerenders all Subscribed Components (SubscriptionContainer) of the Job (Observer) | ||
* @return If any subscriptionContainer got updated (-> triggered a rerender on the Component it represents) | ||
*/ | ||
updateSubscribers(): boolean; | ||
/** | ||
* Extracts the Subscription Containers (UI-Components) | ||
* to be updated (re-rendered) from the specified Runtime Jobs. | ||
* | ||
* @internal | ||
* Finds key of Observer (Job) in subsObject and adds it to 'changedObjectKeys' | ||
* @param subscriptionContainer - Object based SubscriptionContainer | ||
* @param job - Job that holds the searched Observer | ||
* @param jobs - Jobs from which to extract the Subscription Containers to be updated. | ||
*/ | ||
handleObjectBasedSubscription(subscriptionContainer: SubscriptionContainer, job: RuntimeJob): void; | ||
extractToUpdateSubscriptionContainer(jobs: Array<RuntimeJob>): Array<SubscriptionContainer>; | ||
/** | ||
* Updates the specified Subscription Containers. | ||
* | ||
* Updating a Subscription Container triggers a re-render | ||
* on the Component it represents, based on the type of the Subscription Containers. | ||
* | ||
* @internal | ||
* Builds Object out of changedObjectKeys with Observer Value | ||
* @param subscriptionContainer - Object based SubscriptionContainer | ||
* @param subscriptionsToUpdate - Subscription Containers to be updated. | ||
*/ | ||
getObjectBasedProps(subscriptionContainer: SubscriptionContainer): { | ||
updateSubscriptionContainer(subscriptionsToUpdate: Array<SubscriptionContainer>): void; | ||
/** | ||
* Maps the values of the updated Observers (`updatedSubscribers`) | ||
* of the specified Subscription Container into a key map object. | ||
* | ||
* The key containing the Observer value is extracted from the Observer itself | ||
* or from the Subscription Container's `subscriberKeysWeakMap`. | ||
* | ||
* @internal | ||
* @param subscriptionContainer - Subscription Container from which the `updatedSubscribers` are to be mapped into a key map. | ||
*/ | ||
getUpdatedObserverValues(subscriptionContainer: SubscriptionContainer): { | ||
[key: string]: any; | ||
}; | ||
/** | ||
* Returns a boolean indicating whether the specified Subscription Container can be updated or not, | ||
* based on its selector functions (`selectorsWeakMap`). | ||
* | ||
* This is done by checking the '.value' and the '.previousValue' property of the Observer represented by the Job. | ||
* If a selected property differs, the Subscription Container (UI-Component) is allowed to update (re-render) | ||
* and `true` is returned. | ||
* | ||
* If the Subscription Container has no selector function at all, `true` is returned. | ||
* | ||
* @internal | ||
* Checks if the subscriptionContainer should be updated. | ||
* Therefore it reviews the '.value' and the '.previousValue' property of the Observer the Job represents. | ||
* If a property at the proxy detected path differs, the subscriptionContainer is allowed to update. | ||
* @param subscriptionContainer - SubscriptionContainer | ||
* @param job - Job | ||
* @return {boolean} If the subscriptionContainer should be updated | ||
* -> If a from the Proxy Tree detected property differs from the same property in the previous value | ||
* or the passed subscriptionContainer isn't properly proxy based | ||
* @param subscriptionContainer - Subscription Container to be checked if it can be updated. | ||
* @param job - Job containing the Observer that is subscribed to the Subscription Container. | ||
*/ | ||
handleProxyBasedSubscription(subscriptionContainer: SubscriptionContainer, job: RuntimeJob): boolean; | ||
handleSelectors(subscriptionContainer: SubscriptionContainer, job: RuntimeJob): boolean; | ||
} | ||
/** | ||
* @param perform - If Job gets performed immediately | ||
*/ | ||
export interface IngestConfigInterface { | ||
/** | ||
* Whether the ingested Job should be performed immediately | ||
* or added to the queue first and then executed when it is his turn. | ||
*/ | ||
perform?: boolean; | ||
} |
@@ -9,4 +9,5 @@ "use strict"; | ||
this.jobQueue = []; | ||
this.jobsToRerender = []; | ||
this.notReadyJobsToRerender = new Set(); | ||
this.jobsToRerender = []; | ||
this.isPerformingJobs = false; | ||
this.agileInstance = () => agileInstance; | ||
@@ -16,6 +17,8 @@ } | ||
config = internal_1.defineConfig(config, { | ||
perform: true, | ||
perform: !this.isPerformingJobs, | ||
}); | ||
this.jobQueue.push(job); | ||
internal_1.Agile.logger.if.tag(['runtime']).info(`Created Job '${job._key}'`, job); | ||
internal_1.Agile.logger.if | ||
.tag(['runtime']) | ||
.info(internal_1.LogCodeManager.getLog('16:01:00', [job._key]), job); | ||
if (config.perform) { | ||
@@ -28,10 +31,13 @@ const performJob = this.jobQueue.shift(); | ||
perform(job) { | ||
this.isPerformingJobs = true; | ||
this.currentJob = job; | ||
job.observer.perform(job); | ||
job.performed = true; | ||
job.observer.dependents.forEach((observer) => observer.ingest({ perform: false })); | ||
job.observer.dependents.forEach((observer) => observer.ingest()); | ||
if (job.rerender) | ||
this.jobsToRerender.push(job); | ||
this.currentJob = null; | ||
internal_1.Agile.logger.if.tag(['runtime']).info(`Completed Job '${job._key}'`, job); | ||
internal_1.Agile.logger.if | ||
.tag(['runtime']) | ||
.info(internal_1.LogCodeManager.getLog('16:01:01', [job._key]), job); | ||
if (this.jobQueue.length > 0) { | ||
@@ -43,2 +49,3 @@ const performJob = this.jobQueue.shift(); | ||
else { | ||
this.isPerformingJobs = false; | ||
if (this.jobsToRerender.length > 0) { | ||
@@ -52,40 +59,43 @@ setTimeout(() => { | ||
updateSubscribers() { | ||
if (!this.agileInstance().hasIntegration()) { | ||
this.jobsToRerender = []; | ||
this.notReadyJobsToRerender = new Set(); | ||
const jobsToRerender = this.jobsToRerender.concat(Array.from(this.notReadyJobsToRerender)); | ||
this.notReadyJobsToRerender = new Set(); | ||
this.jobsToRerender = []; | ||
if (!this.agileInstance().hasIntegration() || jobsToRerender.length <= 0) | ||
return false; | ||
} | ||
if (this.jobsToRerender.length <= 0 && | ||
this.notReadyJobsToRerender.size <= 0) | ||
const subscriptionContainerToUpdate = this.extractToUpdateSubscriptionContainer(jobsToRerender); | ||
if (subscriptionContainerToUpdate.length <= 0) | ||
return false; | ||
this.updateSubscriptionContainer(subscriptionContainerToUpdate); | ||
return true; | ||
} | ||
extractToUpdateSubscriptionContainer(jobs) { | ||
const subscriptionsToUpdate = new Set(); | ||
const jobsToRerender = this.jobsToRerender.concat(Array.from(this.notReadyJobsToRerender)); | ||
this.notReadyJobsToRerender = new Set(); | ||
this.jobsToRerender = []; | ||
jobsToRerender.forEach((job) => { | ||
jobs.forEach((job) => { | ||
job.subscriptionContainersToUpdate.forEach((subscriptionContainer) => { | ||
let updateSubscriptionContainer = true; | ||
if (!subscriptionContainer.ready) { | ||
if (!job.config.numberOfTriesToUpdate || | ||
job.triesToUpdate < job.config.numberOfTriesToUpdate) { | ||
job.triesToUpdate++; | ||
if (!job.config.maxTriesToUpdate || | ||
job.timesTriedToUpdateCount < job.config.maxTriesToUpdate) { | ||
job.timesTriedToUpdateCount++; | ||
this.notReadyJobsToRerender.add(job); | ||
internal_1.Agile.logger.warn("SubscriptionContainer/Component isn't ready to rerender!", subscriptionContainer); | ||
internal_1.LogCodeManager.log('16:02:00', [subscriptionContainer.key], subscriptionContainer); | ||
} | ||
else { | ||
internal_1.Agile.logger.warn(`Job with not ready SubscriptionContainer/Component was removed from the runtime after ${job.config.numberOfTriesToUpdate} tries to avoid an overflow.`, subscriptionContainer); | ||
internal_1.LogCodeManager.log('16:02:01', [job.config.maxTriesToUpdate], subscriptionContainer); | ||
} | ||
return; | ||
} | ||
if (subscriptionContainer.isObjectBased) | ||
this.handleObjectBasedSubscription(subscriptionContainer, job); | ||
const updateSubscriptionContainer = subscriptionContainer.proxyBased | ||
? this.handleProxyBasedSubscription(subscriptionContainer, job) | ||
: true; | ||
if (updateSubscriptionContainer) | ||
updateSubscriptionContainer = | ||
updateSubscriptionContainer && | ||
this.handleSelectors(subscriptionContainer, job); | ||
if (updateSubscriptionContainer) { | ||
subscriptionContainer.updatedSubscribers.add(job.observer); | ||
subscriptionsToUpdate.add(subscriptionContainer); | ||
} | ||
job.subscriptionContainersToUpdate.delete(subscriptionContainer); | ||
}); | ||
}); | ||
if (subscriptionsToUpdate.size <= 0) | ||
return false; | ||
return Array.from(subscriptionsToUpdate); | ||
} | ||
updateSubscriptionContainer(subscriptionsToUpdate) { | ||
subscriptionsToUpdate.forEach((subscriptionContainer) => { | ||
@@ -95,57 +105,29 @@ if (subscriptionContainer instanceof internal_1.CallbackSubscriptionContainer) | ||
if (subscriptionContainer instanceof internal_1.ComponentSubscriptionContainer) | ||
this.agileInstance().integrations.update(subscriptionContainer.component, this.getObjectBasedProps(subscriptionContainer)); | ||
this.agileInstance().integrations.update(subscriptionContainer.component, this.getUpdatedObserverValues(subscriptionContainer)); | ||
subscriptionContainer.updatedSubscribers.clear(); | ||
}); | ||
internal_1.Agile.logger.if | ||
.tag(['runtime']) | ||
.info('Updated/Rerendered Subscriptions', subscriptionsToUpdate); | ||
return true; | ||
.info(internal_1.LogCodeManager.getLog('16:01:02'), subscriptionsToUpdate); | ||
} | ||
handleObjectBasedSubscription(subscriptionContainer, job) { | ||
let foundKey = null; | ||
if (!subscriptionContainer.isObjectBased) | ||
return; | ||
for (const key in subscriptionContainer.subsObject) | ||
if (subscriptionContainer.subsObject[key] === job.observer) | ||
foundKey = key; | ||
if (foundKey) | ||
subscriptionContainer.observerKeysToUpdate.push(foundKey); | ||
} | ||
getObjectBasedProps(subscriptionContainer) { | ||
getUpdatedObserverValues(subscriptionContainer) { | ||
var _a; | ||
const props = {}; | ||
if (subscriptionContainer.subsObject) | ||
for (const updatedKey of subscriptionContainer.observerKeysToUpdate) | ||
props[updatedKey] = (_a = subscriptionContainer.subsObject[updatedKey]) === null || _a === void 0 ? void 0 : _a.value; | ||
subscriptionContainer.observerKeysToUpdate = []; | ||
for (const observer of subscriptionContainer.updatedSubscribers) { | ||
const key = (_a = subscriptionContainer.subscriberKeysWeakMap.get(observer)) !== null && _a !== void 0 ? _a : observer.key; | ||
if (key != null) | ||
props[key] = observer.value; | ||
} | ||
return props; | ||
} | ||
handleProxyBasedSubscription(subscriptionContainer, job) { | ||
if (!subscriptionContainer.proxyBased || | ||
!job.observer._key || | ||
!subscriptionContainer.proxyKeyMap[job.observer._key]) | ||
handleSelectors(subscriptionContainer, job) { | ||
var _a; | ||
const selectorMethods = (_a = subscriptionContainer.selectorsWeakMap.get(job.observer)) === null || _a === void 0 ? void 0 : _a.methods; | ||
if (selectorMethods == null) | ||
return true; | ||
const paths = subscriptionContainer.proxyKeyMap[job.observer._key].paths; | ||
if (paths) { | ||
for (const path of paths) { | ||
let newValue = job.observer.value; | ||
let newValueDeepness = 0; | ||
for (const branch of path) { | ||
if (!internal_1.isValidObject(newValue, true)) | ||
break; | ||
newValue = newValue[branch]; | ||
newValueDeepness++; | ||
} | ||
let previousValue = job.observer.previousValue; | ||
let previousValueDeepness = 0; | ||
for (const branch of path) { | ||
if (!internal_1.isValidObject(previousValue, true)) | ||
break; | ||
previousValue = previousValue[branch]; | ||
previousValueDeepness++; | ||
} | ||
if (internal_1.notEqual(newValue, previousValue) || | ||
newValueDeepness !== previousValueDeepness) { | ||
return true; | ||
} | ||
} | ||
const previousValue = job.observer.previousValue; | ||
const newValue = job.observer.value; | ||
for (const selectorMethod of selectorMethods) { | ||
if (internal_1.notEqual(selectorMethod(newValue), selectorMethod(previousValue))) | ||
return true; | ||
} | ||
@@ -152,0 +134,0 @@ return false; |
@@ -11,60 +11,113 @@ import { Agile, StateKey, RuntimeJob, SubscriptionContainer, IngestConfigInterface, CreateRuntimeJobConfigInterface } from '../internal'; | ||
/** | ||
* An Observer manages the subscriptions to Subscription Containers (UI-Components) | ||
* and dependencies to other Observers (Agile Classes) | ||
* for an Agile Class such as the `State Class`. | ||
* | ||
* Agile Classes often use an Observer as an interface to the Runtime. | ||
* In doing so, they ingest their own Observer into the Runtime | ||
* when the Agile Class has changed in such a way | ||
* that these changes need to be applied to UI-Components | ||
* or dependent other Observers. | ||
* | ||
* After the Observer has been ingested into the Runtime | ||
* wrapped into a Runtime-Job, it is first added to the Jobs queue | ||
* to prevent race conditions. | ||
* When it is executed, the Observer's `perform()` method is called, | ||
* where the accordingly changes are applied to the Agile Class. | ||
* | ||
* Now that the Job was performed, it is added to the rerender queue, | ||
* where the subscribed Subscription Container (UI-Components) | ||
* of the Observer are updated (re-rendered). | ||
* | ||
* Note that the Observer itself is no standalone class | ||
* and should be adapted to the Agile Class needs it belongs to. | ||
* | ||
* @internal | ||
* Observer - Handles subscriptions and dependencies of an Agile Class and is like an instance to the Runtime | ||
* Note: No stand alone class!! | ||
* @param agileInstance - An instance of Agile | ||
* @param config - Config | ||
* @param agileInstance - Instance of Agile the Observer belongs to. | ||
* @param config - Configuration object | ||
*/ | ||
constructor(agileInstance: Agile, config?: CreateObserverConfigInterface<ValueType>); | ||
/** | ||
* @internal | ||
* Set Key/Name of Observer | ||
* Updates the key/name identifier of the Observer. | ||
* | ||
* @public | ||
* @param value - New key/name identifier. | ||
*/ | ||
set key(value: StateKey | undefined); | ||
/** | ||
* @internal | ||
* Get Key/Name of Observer | ||
* Returns the key/name identifier of the Observer. | ||
* | ||
* @public | ||
*/ | ||
get key(): StateKey | undefined; | ||
/** | ||
* @internal | ||
* Ingests Observer into Runtime | ||
* @param config - Configuration | ||
* Passes the Observer into the runtime wrapped into a Runtime-Job | ||
* where it is executed accordingly. | ||
* | ||
* During the execution the runtime performs the Observer's `perform()` method, | ||
* updates its dependents and re-renders the UI-Components it is subscribed to. | ||
* | ||
* @public | ||
* @param config - Configuration object | ||
*/ | ||
ingest(config?: ObserverIngestConfigInterface): void; | ||
/** | ||
* @internal | ||
* Performs Job of Runtime | ||
* @param job - Job that gets performed | ||
* Method executed by the Runtime to perform the Runtime-Job, | ||
* previously ingested via the `ingest()` method. | ||
* | ||
* Note that this method should be overwritten | ||
* to correctly apply the changes to the Agile Class | ||
* the Observer belongs to. | ||
* | ||
* @public | ||
* @param job - Runtime-Job to be performed. | ||
*/ | ||
perform(job: RuntimeJob): void; | ||
/** | ||
* @internal | ||
* Adds Dependent to Observer which gets ingested into the Runtime whenever this Observer mutates | ||
* @param observer - Observer that will depend on this Observer | ||
* Makes the specified Observer depend on the Observer. | ||
* | ||
* A dependent Observer is always ingested into the Runtime, | ||
* when the Observer it depends on has been ingested too. | ||
* | ||
* @public | ||
* @param observer - Observer to depend on the Observer. | ||
*/ | ||
depend(observer: Observer): void; | ||
addDependent(observer: Observer): void; | ||
/** | ||
* @internal | ||
* Adds Subscription to Observer | ||
* @param subscriptionContainer - SubscriptionContainer(Component) that gets subscribed by this Observer | ||
* Makes the specified Observer no longer depend on the Observer. | ||
* | ||
* @public | ||
* @param observer - Observer to no longer depend on the Observer. | ||
*/ | ||
subscribe(subscriptionContainer: SubscriptionContainer): void; | ||
removeDependent(observer: Observer): void; | ||
} | ||
export interface CreateObserverConfigInterface<ValueType = any> { | ||
/** | ||
* @internal | ||
* Removes Subscription from Observer | ||
* @param subscriptionContainer - SubscriptionContainer(Component) that gets unsubscribed by this Observer | ||
* Initial Observers to depend on the Observer. | ||
* @default [] | ||
*/ | ||
unsubscribe(subscriptionContainer: SubscriptionContainer): void; | ||
} | ||
/** | ||
* @param deps - Initial Dependents of Observer | ||
* @param subs - Initial Subscriptions of Observer | ||
* @param key - Key/Name of Observer | ||
* @param value - Initial Value of Observer | ||
*/ | ||
export interface CreateObserverConfigInterface<ValueType = any> { | ||
dependents?: Array<Observer>; | ||
/** | ||
* Initial Subscription Containers the Observer is subscribed to. | ||
* @default [] | ||
*/ | ||
subs?: Array<SubscriptionContainer>; | ||
/** | ||
* Key/Name identifier of the Observer. | ||
* @default undefined | ||
*/ | ||
key?: ObserverKey; | ||
/** | ||
* Initial value of the Observer. | ||
* | ||
* The value of an Observer is given to the Integration's `updateMethod()` method | ||
* (Component Subscription Container) where it can be, | ||
* for example, merged in a local State Management property of the UI-Component | ||
* it is subscribed to. | ||
* | ||
* Also the selection of specific properties of an Agile Class value | ||
* is based on the Observer `value` and `previousValue`. | ||
* | ||
* @default undefined | ||
*/ | ||
value?: ValueType; | ||
@@ -71,0 +124,0 @@ } |
@@ -18,4 +18,4 @@ "use strict"; | ||
this.previousValue = config.value; | ||
(_a = config.dependents) === null || _a === void 0 ? void 0 : _a.forEach((observer) => this.depend(observer)); | ||
(_b = config.subs) === null || _b === void 0 ? void 0 : _b.forEach((subscriptionContainer) => this.subscribe(subscriptionContainer)); | ||
(_a = config.dependents) === null || _a === void 0 ? void 0 : _a.forEach((observer) => this.addDependent(observer)); | ||
(_b = config.subs) === null || _b === void 0 ? void 0 : _b.forEach((subscriptionContainer) => subscriptionContainer.addSubscription(this)); | ||
} | ||
@@ -29,2 +29,3 @@ set key(value) { | ||
ingest(config = {}) { | ||
var _a; | ||
config = internal_1.defineConfig(config, { | ||
@@ -43,3 +44,3 @@ perform: true, | ||
background: config.background, | ||
key: config.key || this._key, | ||
key: (_a = config.key) !== null && _a !== void 0 ? _a : `${this._key != null ? this._key + '_' : ''}${internal_1.generateId()}`, | ||
}); | ||
@@ -51,21 +52,13 @@ this.agileInstance().runtime.ingest(job, { | ||
perform(job) { | ||
internal_1.Agile.logger.warn("Perform function isn't Set in Observer! Be aware that Observer is no stand alone class!"); | ||
internal_1.LogCodeManager.log('17:03:00'); | ||
} | ||
depend(observer) { | ||
addDependent(observer) { | ||
if (!this.dependents.has(observer)) | ||
this.dependents.add(observer); | ||
} | ||
subscribe(subscriptionContainer) { | ||
if (!this.subscribedTo.has(subscriptionContainer)) { | ||
this.subscribedTo.add(subscriptionContainer); | ||
subscriptionContainer.subscribers.add(this); | ||
} | ||
removeDependent(observer) { | ||
if (this.dependents.has(observer)) | ||
this.dependents.delete(observer); | ||
} | ||
unsubscribe(subscriptionContainer) { | ||
if (this.subscribedTo.has(subscriptionContainer)) { | ||
this.subscribedTo.delete(subscriptionContainer); | ||
subscriptionContainer.subscribers.delete(this); | ||
} | ||
} | ||
} | ||
exports.Observer = Observer; |
import { Observer, SubscriptionContainer } from '../internal'; | ||
export declare class RuntimeJob<ObserverType extends Observer = Observer> { | ||
config: RuntimeJobConfigInterface; | ||
_key?: RuntimeJobKey; | ||
config: RuntimeJobConfigInterface; | ||
observer: ObserverType; | ||
rerender: boolean; | ||
subscriptionContainersToUpdate: Set<SubscriptionContainer>; | ||
timesTriedToUpdateCount: number; | ||
performed: boolean; | ||
subscriptionContainersToUpdate: Set<SubscriptionContainer>; | ||
triesToUpdate: number; | ||
/** | ||
* A Runtime Job is sent to the Runtime on behalf of the Observer it represents. | ||
* | ||
* In the Runtime, the Observer is performed via its `perform()` method | ||
* and the Subscription Containers (UI-Components) | ||
* to which it is subscribed are updated (re-rendered) accordingly. | ||
* | ||
* @internal | ||
* Job - Represents Observer that gets performed by the Runtime | ||
* @param observer - Observer | ||
* @param config - Config | ||
* @param observer - Observer to be represented by the Runtime Job. | ||
* @param config - Configuration object | ||
*/ | ||
constructor(observer: ObserverType, config?: CreateRuntimeJobConfigInterface); | ||
/** | ||
* Updates the key/name identifier of the Runtime Job. | ||
* | ||
* @public | ||
* @param value - New key/name identifier. | ||
*/ | ||
set key(value: RuntimeJobKey | undefined); | ||
/** | ||
* Returns the key/name identifier of the Runtime Job. | ||
* | ||
* @public | ||
*/ | ||
get key(): RuntimeJobKey | undefined; | ||
set key(value: RuntimeJobKey | undefined); | ||
} | ||
export declare type RuntimeJobKey = string | number; | ||
/** | ||
* @param key - Key/Name of RuntimeJob | ||
*/ | ||
export interface CreateRuntimeJobConfigInterface extends RuntimeJobConfigInterface { | ||
/** | ||
* Key/Name identifier of the Runtime Job. | ||
* @default undefined | ||
*/ | ||
key?: RuntimeJobKey; | ||
} | ||
/** | ||
* @param background - If Job gets executed in the background -> not causing any rerender | ||
* @param sideEffects - If SideEffects get executed | ||
* @param force - Force performing Job | ||
* @param numberOfTriesToUpdate - How often the runtime should try to update not ready SubscriptionContainers of this Job | ||
* If 'null' the runtime tries to update the not ready SubscriptionContainer until they are ready (infinite). | ||
* But be aware that this can lead to an overflow of 'old' Jobs after some time. (affects performance) | ||
*/ | ||
export interface RuntimeJobConfigInterface { | ||
/** | ||
* Whether to perform the Runtime Job in background. | ||
* So that the Subscription Containers (UI-Components) aren't notified | ||
* of these changes and thus doesn't update (re-render). | ||
* @default false | ||
*/ | ||
background?: boolean; | ||
/** | ||
* Configuration of the execution of defined side effects. | ||
* @default {enabled: true, exclude: []} | ||
*/ | ||
sideEffects?: SideEffectConfigInterface; | ||
/** | ||
* Whether the Runtime Job should be forced through the runtime | ||
* although it might be useless from the current viewpoint of the runtime. | ||
* @default false | ||
*/ | ||
force?: boolean; | ||
numberOfTriesToUpdate?: number | null; | ||
/** | ||
* How often the Runtime should try to update not ready Subscription Containers | ||
* subscribed by the Observer which the Job represents. | ||
* | ||
* When `null` the Runtime tries to update the not ready Subscription Containers | ||
* until they are ready (infinite). | ||
* @default 3 | ||
*/ | ||
maxTriesToUpdate?: number | null; | ||
} | ||
/** | ||
* @param enabled - If SideEffects get executed | ||
* @param exclude - SideEffect at Keys that doesn't get executed | ||
*/ | ||
export interface SideEffectConfigInterface { | ||
/** | ||
* Whether to execute the defined side effects. | ||
* @default true | ||
*/ | ||
enabled?: boolean; | ||
/** | ||
* Side effect key identifier that won't be executed. | ||
* @default [] | ||
*/ | ||
exclude?: string[]; | ||
} |
@@ -7,5 +7,5 @@ "use strict"; | ||
constructor(observer, config = {}) { | ||
this.subscriptionContainersToUpdate = new Set(); | ||
this.timesTriedToUpdateCount = 0; | ||
this.performed = false; | ||
this.subscriptionContainersToUpdate = new Set(); | ||
this.triesToUpdate = 0; | ||
config = internal_1.defineConfig(config, { | ||
@@ -18,3 +18,3 @@ background: false, | ||
force: false, | ||
numberOfTriesToUpdate: 3, | ||
maxTriesToUpdate: 3, | ||
}); | ||
@@ -25,3 +25,3 @@ this.config = { | ||
sideEffects: config.sideEffects, | ||
numberOfTriesToUpdate: config.numberOfTriesToUpdate, | ||
maxTriesToUpdate: config.maxTriesToUpdate, | ||
}; | ||
@@ -35,9 +35,9 @@ this.observer = observer; | ||
} | ||
set key(value) { | ||
this._key = value; | ||
} | ||
get key() { | ||
return this._key; | ||
} | ||
set key(value) { | ||
this._key = value; | ||
} | ||
} | ||
exports.RuntimeJob = RuntimeJob; |
import { Observer, SubscriptionContainer, SubscriptionContainerConfigInterface } from '../../../internal'; | ||
export declare class CallbackSubscriptionContainer extends SubscriptionContainer { | ||
/** | ||
* Callback function to trigger a re-render | ||
* on the UI-Component which is represented by the Subscription Container. | ||
*/ | ||
callback: Function; | ||
/** | ||
* A Callback Subscription Container represents a UI-Component in AgileTs | ||
* and triggers re-renders on the UI-Component via the specified callback function. | ||
* | ||
* The Callback Subscription Container doesn't keep track of the Component itself. | ||
* It only knows how to trigger re-renders on it by calling the callback function. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/integration#callback-based) | ||
* | ||
* @internal | ||
* CallbackSubscriptionContainer - Subscription Container for Callback based Subscriptions | ||
* @param callback - Callback Function that causes rerender on Component that is subscribed by Agile | ||
* @param subs - Initial Subscriptions | ||
* @param config - Config | ||
* @param callback - Callback function to cause a rerender on the Component | ||
* to be represented by the Subscription Container. | ||
* @param subs - Observers to be initial subscribed to the Subscription Container. | ||
* @param config - Configuration object | ||
*/ | ||
constructor(callback: Function, subs?: Array<Observer>, config?: SubscriptionContainerConfigInterface); | ||
constructor(callback: Function, subs: Array<Observer> | { | ||
[key: string]: Observer; | ||
}, config?: SubscriptionContainerConfigInterface); | ||
} |
@@ -6,3 +6,3 @@ "use strict"; | ||
class CallbackSubscriptionContainer extends internal_1.SubscriptionContainer { | ||
constructor(callback, subs = [], config = {}) { | ||
constructor(callback, subs, config = {}) { | ||
super(subs, config); | ||
@@ -9,0 +9,0 @@ this.callback = callback; |
import { Observer, SubscriptionContainer, SubscriptionContainerConfigInterface } from '../../../internal'; | ||
export declare class ComponentSubscriptionContainer extends SubscriptionContainer { | ||
component: any; | ||
export declare class ComponentSubscriptionContainer<C = any> extends SubscriptionContainer { | ||
/** | ||
* UI-Component which is represented by the Subscription Container | ||
* and mutated via the Integration's `updateMethod()` method | ||
* to cause re-renders on it. | ||
*/ | ||
component: C; | ||
/** | ||
* A Component Subscription Container represents a UI-Component in AgileTs | ||
* and triggers re-renders on the UI-Component by muting the specified Component Instance | ||
* via the Integration's `updateMethod()` method. | ||
* For example by updating a local State Management property of the Component | ||
* (like in React Class Components the `this.state` property). | ||
* | ||
* The Component Subscription Container keeps track of the Component itself, | ||
* to mutate it appropriately so that re-renders can be triggered on it. | ||
* | ||
* For this to work well, a Component Subscription Container is often object based. | ||
* Meaning that each Observer was provided in an object keymap | ||
* with a unique key identifier. | ||
* ``` | ||
* // Object based (guaranteed unique key identifier) | ||
* { | ||
* state1: Observer, | ||
* state2: Observer | ||
* } | ||
* | ||
* // Array based (no guaranteed unique key identifier) | ||
* [Observer, Observer] | ||
* ``` | ||
* Thus the Integration's 'updateMethod()' method can be called | ||
* with a complete object of updated Observer values. | ||
* ``` | ||
* updateMethod: (componentInstance, updatedData) => { | ||
* console.log(componentInstance); // Returns 'this.component' | ||
* console.log(updatedData); // Returns updated Observer values keymap (see below) | ||
* // { | ||
* // state1: Observer.value, | ||
* // state2: Observer.value, | ||
* // } | ||
* } | ||
* ``` | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/integration#component-based) | ||
* | ||
* @internal | ||
* ComponentSubscriptionContainer - SubscriptionContainer for Component based Subscription | ||
* @param component - Component that is subscribed by Agile | ||
* @param subs - Initial Subscriptions | ||
* @param config - Config | ||
* @param component - UI-Component to be represented by the Subscription Container | ||
* and mutated via the Integration's 'updateMethod()' method to trigger re-renders on it. | ||
* @param subs - Observers to be initial subscribed to the Subscription Container. | ||
* @param config - Configuration object | ||
*/ | ||
constructor(component: any, subs?: Array<Observer>, config?: SubscriptionContainerConfigInterface); | ||
constructor(component: C, subs: Array<Observer> | { | ||
[key: string]: Observer; | ||
}, config?: SubscriptionContainerConfigInterface); | ||
} |
@@ -6,3 +6,3 @@ "use strict"; | ||
class ComponentSubscriptionContainer extends internal_1.SubscriptionContainer { | ||
constructor(component, subs = [], config = {}) { | ||
constructor(component, subs, config = {}) { | ||
super(subs, config); | ||
@@ -9,0 +9,0 @@ this.component = component; |
import { Observer } from '../../../internal'; | ||
export declare class SubscriptionContainer { | ||
/** | ||
* Key/Name identifier of the Subscription Container. | ||
*/ | ||
key?: SubscriptionContainerKeyType; | ||
/** | ||
* Whether the Subscription Container | ||
* and the UI-Component it represents are ready. | ||
* | ||
* When both instances are ready, | ||
* the Subscription Container is allowed | ||
* to trigger re-renders on the UI-Component. | ||
*/ | ||
ready: boolean; | ||
/** | ||
* Unique identifier of the UI-Component | ||
* the Subscription Container represents. | ||
*/ | ||
componentId?: ComponentIdType; | ||
/** | ||
* Observers that are subscribed to the Subscription Container. | ||
* | ||
* The subscribed Observers use the Subscription Container | ||
* as an interface to the UI-Component it represents. | ||
* | ||
* Through the Subscription Container, the Observers can easily trigger re-renders | ||
* on the UI-Component, for example, when their value updates. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/integration#-subscriptions) | ||
*/ | ||
subscribers: Set<Observer>; | ||
proxyKeyMap: ProxyKeyMapInterface; | ||
proxyBased: boolean; | ||
/** | ||
* Temporary stores the subscribed Observers, | ||
* that were updated by the runtime | ||
* and are currently running through | ||
* the update (rerender) Subscription Container (UI-Component) process. | ||
*/ | ||
updatedSubscribers: Set<Observer>; | ||
/** | ||
* Whether the Subscription Container is object based. | ||
* | ||
* A Subscription Container is object based when the subscribed Observers | ||
* have been provided in an Observer keymap object | ||
* ``` | ||
* { | ||
* state1: Observer, | ||
* state2: Observer | ||
* } | ||
* ``` | ||
* Thus each Observer has its 'external' unique key stored in the `subscribersWeakMap`. | ||
* | ||
* Often Component based Subscriptions are object based, | ||
* because each Observer requires in such Subscription a unique identifier. | ||
* Mainly to be properly represented in the `updatedData` object | ||
* sent to the Integration's `updateMethod()` method | ||
* when the Subscription Container updates (re-renders the UI-Component). | ||
*/ | ||
isObjectBased: boolean; | ||
observerKeysToUpdate: Array<string>; | ||
subsObject?: { | ||
/** | ||
* Weak map for storing 'external' key identifiers for subscribed Observers. | ||
* | ||
* Why is the key not applied directly to the Observer? | ||
* | ||
* Because the key defined here should be only valid | ||
* for the scope of the Subscription Container. | ||
* | ||
* https://stackoverflow.com/questions/29413222/what-are-the-actual-uses-of-es6-weakmap | ||
*/ | ||
subscriberKeysWeakMap: WeakMap<Observer, string>; | ||
/** | ||
* Weak Map for storing selector functions for subscribed Observers. | ||
* | ||
* A selector function allows the partial subscription to an Observer value. | ||
* Only when the selected Observer value part changes, | ||
* the Subscription Container is updated (-> re-renders the UI-Component). | ||
* | ||
* Why are the selector functions not applied directly to the Observer? | ||
* | ||
* Because the selector function defined here should be only valid | ||
* for the scope of the Subscription Container. | ||
* | ||
* https://stackoverflow.com/questions/29413222/what-are-the-actual-uses-of-es6-weakmap | ||
*/ | ||
selectorsWeakMap: SelectorWeakMapType; | ||
/** | ||
* A Subscription Container represents a UI-Component in AgileTs | ||
* that can be subscribed by multiple Observer Instances. | ||
* | ||
* The subscribed Observers can use the Subscription Container as an interface | ||
* to the UI-Component it represents. | ||
* For example, to trigger re-renders on the UI-Component, | ||
* when their value has changed. | ||
* | ||
* @internal | ||
* @param subs - Observers to be initial subscribed to the Subscription Container. | ||
* @param config - Configuration object | ||
*/ | ||
constructor(subs: Array<Observer> | { | ||
[key: string]: Observer; | ||
}; | ||
}, config?: SubscriptionContainerConfigInterface); | ||
/** | ||
* Subscribes the specified Observer to the Subscription Container. | ||
* | ||
* @internal | ||
* SubscriptionContainer - Represents Component/(Way to rerender Component) that is subscribed by Observer/s (Agile) | ||
* -> Used to cause rerender on Component | ||
* @param subs - Initial Subscriptions | ||
* @param config - Config | ||
* @param sub - Observer to be subscribed to the Subscription Container | ||
* @param config - Configuration object | ||
*/ | ||
constructor(subs?: Array<Observer>, config?: SubscriptionContainerConfigInterface); | ||
addSubscription(sub: Observer, config?: AddSubscriptionMethodConfigInterface): void; | ||
/** | ||
* Unsubscribes the specified Observer from the Subscription Container. | ||
* | ||
* @internal | ||
* @param sub - Observer to be unsubscribed from the Subscription Container. | ||
*/ | ||
removeSubscription(sub: Observer): void; | ||
} | ||
export declare type SubscriptionContainerKeyType = string | number; | ||
/** | ||
* @param proxyKeyMap - A keymap with a 2 dimensional arrays with paths/routes to particular properties in the State at key. | ||
* The subscriptionContainer will then only rerender the Component, when a property at a given path changes. | ||
* Not anymore if anything in the State object mutates, although it might not even be displayed in the Component. | ||
* For example: | ||
* { | ||
* myState1: {paths: [['data', 'name']]}, | ||
* myState2: {paths: [['car', 'speed']]} | ||
* } | ||
* Now the subscriptionContain will only trigger a rerender on the Component | ||
* if 'data.name' in myState1 or 'car.speed' in myState2 changes. | ||
* If, for instance, 'data.age' in myState1 mutates it won't trigger a rerender, | ||
* since 'data.age' isn't represented in the proxyKeyMap. | ||
* | ||
* These particular paths can be tracked with the ProxyTree. | ||
* https://github.com/agile-ts/agile/tree/master/packages/proxytree | ||
* @param key - Key/Name of Subscription Container | ||
*/ | ||
export interface SubscriptionContainerConfigInterface { | ||
proxyKeyMap?: ProxyKeyMapInterface; | ||
/** | ||
* Key/Name identifier of the Subscription Container | ||
* @default undefined | ||
*/ | ||
key?: SubscriptionContainerKeyType; | ||
/** | ||
* Key/Name identifier of the UI-Component to be represented by the Subscription Container. | ||
* @default undefined | ||
*/ | ||
componentId?: ComponentIdType; | ||
/** | ||
* A Weak Map with a set of proxy paths to certain properties | ||
* in an Observer value for subscribed Observers. | ||
* | ||
* These paths are then selected via selector functions | ||
* which allow the partly subscription to an Observer value. | ||
* Only if the selected Observer value part changes, | ||
* the Subscription Container re-renders the UI-Component it represents. | ||
* | ||
* For example: | ||
* ``` | ||
* WeakMap: { | ||
* Observer1: {paths: [['data', 'name']]}, | ||
* Observer2: {paths: [['car', 'speed']]} | ||
* } | ||
* ``` | ||
* Now the Subscription Container will only trigger a re-render on the UI-Component | ||
* if 'data.name' in Observer1 or 'car.speed' in Observer2 updates. | ||
* If, for instance, 'data.age' in Observer1 mutates it won't trigger a re-render, | ||
* since 'data.age' isn't represented in the specified Proxy Weak Map. | ||
* | ||
* These particular paths can, for example, be tracked via the ProxyTree. | ||
* https://github.com/agile-ts/agile/tree/master/packages/proxytree | ||
* | ||
* @default new WeakMap() | ||
*/ | ||
proxyWeakMap?: ProxyWeakMapType; | ||
/** | ||
* A Weak Map with a set of selector functions for Observers. | ||
* | ||
* Selector functions allow the partly subscription to Observer values. | ||
* Only if the selected Observer value part changes, | ||
* the Subscription Container re-renders the UI-Component it represents. | ||
* | ||
* For example: | ||
* ``` | ||
* WeakMap: { | ||
* Observer1: {methods: [(value) => value.data.name]}, | ||
* Observer2: {methods: [(value) => value.car.speed]} | ||
* } | ||
* ``` | ||
* Now the Subscription Container will only trigger a re-render on the UI-Component | ||
* if 'data.name' in Observer1 or 'car.speed' in Observer2 updates. | ||
* If, for instance, 'data.age' in Observer1 mutates it won't trigger a re-render, | ||
* since 'data.age' isn't selected by any selector method in the specified Selector Weak Map. | ||
* | ||
* @default new WeakMap() | ||
*/ | ||
selectorWeakMap?: SelectorWeakMapType; | ||
} | ||
export interface ProxyKeyMapInterface { | ||
[key: string]: { | ||
paths: string[][]; | ||
}; | ||
export interface AddSubscriptionMethodConfigInterface { | ||
proxyPaths?: ProxyPathType[]; | ||
selectorMethods?: SelectorMethodType[]; | ||
key?: string; | ||
} | ||
export declare type ProxyPathType = string[]; | ||
export declare type ProxyWeakMapType = WeakMap<Observer, { | ||
paths: ProxyPathType[]; | ||
}>; | ||
export declare type SelectorMethodType<T = any> = (value: T) => any; | ||
export declare type SelectorWeakMapType<T = any> = WeakMap<Observer, { | ||
methods: SelectorMethodType<T>[]; | ||
}>; | ||
export declare type ComponentIdType = string | number; |
@@ -6,17 +6,59 @@ "use strict"; | ||
class SubscriptionContainer { | ||
constructor(subs = [], config = {}) { | ||
constructor(subs, config = {}) { | ||
var _a, _b, _c, _d; | ||
this.ready = false; | ||
this.proxyBased = false; | ||
this.updatedSubscribers = new Set(); | ||
this.isObjectBased = false; | ||
this.observerKeysToUpdate = []; | ||
config = internal_1.defineConfig(config, { | ||
proxyKeyMap: {}, | ||
proxyWeakMap: new WeakMap(), | ||
selectorWeakMap: new WeakMap(), | ||
key: internal_1.generateId(), | ||
}); | ||
this.subscribers = new Set(subs); | ||
this.subscribers = new Set(); | ||
this.key = config.key; | ||
this.proxyKeyMap = config.proxyKeyMap; | ||
this.proxyBased = internal_1.notEqual(this.proxyKeyMap, {}); | ||
this.componentId = config === null || config === void 0 ? void 0 : config.componentId; | ||
this.subscriberKeysWeakMap = new WeakMap(); | ||
this.selectorsWeakMap = new WeakMap(); | ||
this.isObjectBased = !Array.isArray(subs); | ||
for (const key in subs) { | ||
this.addSubscription(subs[key], { | ||
proxyPaths: (_b = (_a = config.proxyWeakMap) === null || _a === void 0 ? void 0 : _a.get(subs[key])) === null || _b === void 0 ? void 0 : _b.paths, | ||
selectorMethods: (_d = (_c = config.selectorWeakMap) === null || _c === void 0 ? void 0 : _c.get(subs[key])) === null || _d === void 0 ? void 0 : _d.methods, | ||
key: this.isObjectBased ? key : undefined, | ||
}); | ||
} | ||
} | ||
addSubscription(sub, config = {}) { | ||
var _a, _b, _c, _d; | ||
const toAddSelectorMethods = (_a = config.selectorMethods) !== null && _a !== void 0 ? _a : []; | ||
const proxyPaths = (_b = config.proxyPaths) !== null && _b !== void 0 ? _b : []; | ||
for (const path of proxyPaths) { | ||
toAddSelectorMethods.push((value) => { | ||
let _value = value; | ||
for (const branch of path) { | ||
if (!internal_1.isValidObject(_value, true)) | ||
break; | ||
_value = _value[branch]; | ||
} | ||
return _value; | ||
}); | ||
} | ||
const existingSelectorMethods = (_d = (_c = this.selectorsWeakMap.get(sub)) === null || _c === void 0 ? void 0 : _c.methods) !== null && _d !== void 0 ? _d : []; | ||
const newSelectorMethods = existingSelectorMethods.concat(toAddSelectorMethods); | ||
if (newSelectorMethods.length > 0) | ||
this.selectorsWeakMap.set(sub, { methods: newSelectorMethods }); | ||
if (config.key != null) | ||
this.subscriberKeysWeakMap.set(sub, config.key); | ||
this.subscribers.add(sub); | ||
sub.subscribedTo.add(this); | ||
} | ||
removeSubscription(sub) { | ||
if (this.subscribers.has(sub)) { | ||
this.selectorsWeakMap.delete(sub); | ||
this.subscriberKeysWeakMap.delete(sub); | ||
this.subscribers.delete(sub); | ||
sub.subscribedTo.delete(this); | ||
} | ||
} | ||
} | ||
exports.SubscriptionContainer = SubscriptionContainer; |
@@ -8,15 +8,67 @@ import { Agile, Observer, SubscriptionContainer, ComponentSubscriptionContainer, CallbackSubscriptionContainer, SubscriptionContainerConfigInterface } from '../../internal'; | ||
/** | ||
* The Subscription Controller manages and simplifies the subscription to UI-Components. | ||
* | ||
* Thus it creates Subscription Containers (Interfaces to UI-Components) | ||
* and assigns them to the specified Observers. | ||
* These Observers can then easily trigger re-renders on UI-Components | ||
* via the created Subscription Containers. | ||
* | ||
* @internal | ||
* SubController - Handles subscriptions to Components | ||
* @param agileInstance - An instance of Agile | ||
* @param agileInstance - Instance of Agile the Subscription Controller belongs to. | ||
*/ | ||
constructor(agileInstance: Agile); | ||
/** | ||
* @internal | ||
* Subscribe with Object shaped Subscriptions | ||
* @param integrationInstance - Callback Function or Component | ||
* @param subs - Initial Subscription Object | ||
* @param config - Config | ||
* Creates a so called Subscription Container that represents an UI-Component in AgileTs. | ||
* Such Subscription Container know how to trigger a re-render on the UI-Component it represents | ||
* through the provided `integrationInstance`. | ||
* | ||
* Currently, there are two different ways the Subscription Container can trigger a re-render on the UI-Component. | ||
* - 1. Via a callback function that directly triggers a rerender on the UI-Component. | ||
* (= Callback based Subscription) | ||
* [Learn more..](https://agile-ts.org/docs/core/integration/#callback-based) | ||
* - 2. Via the Component instance itself. | ||
* For example by mutating a local State Management property. | ||
* (= Component based Subscription) | ||
* [Learn more..](https://agile-ts.org/docs/core/integration/#component-based) | ||
* | ||
* The in an array specified Observers are then automatically subscribed | ||
* to the created Subscription Container and thus to the UI-Component it represents. | ||
* | ||
* @public | ||
* @param integrationInstance - Callback function or Component Instance to trigger a rerender on a UI-Component. | ||
* @param subs - Observers to be subscribed to the to create Subscription Container. | ||
* @param config - Configuration object | ||
*/ | ||
subscribeWithSubsObject(integrationInstance: any, subs?: { | ||
subscribe(integrationInstance: any, subs: Array<Observer>, config?: RegisterSubscriptionConfigInterface): SubscriptionContainer; | ||
/** | ||
* Creates a so called Subscription Container that represents an UI-Component in AgileTs. | ||
* Such Subscription Container know how to trigger a re-render on the UI-Component it represents | ||
* through the provided `integrationInstance`. | ||
* | ||
* Currently, there are two different ways the Subscription Container can trigger a re-render on the UI-Component. | ||
* - 1. Via a callback function that directly triggers a rerender on the UI-Component. | ||
* (= Callback based Subscription) | ||
* [Learn more..](https://agile-ts.org/docs/core/integration/#callback-based) | ||
* - 2. Via the Component instance itself. | ||
* For example by mutating a local State Management property. | ||
* (= Component based Subscription) | ||
* [Learn more..](https://agile-ts.org/docs/core/integration/#component-based) | ||
* | ||
* The in an object keymap specified Observers are then automatically subscribed | ||
* to the created Subscription Container and thus to the UI-Component it represents. | ||
* | ||
* The advantage of subscribing the Observer via a object keymap, | ||
* is that each Observer has its own unique 'external' key identifier. | ||
* Such key identifier is, for example, required when merging the Observer value into | ||
* a local UI-Component State Management property. | ||
* ``` | ||
* this.state = {...this.state, {state1: Observer1.value, state2: Observer2.value}} | ||
* ``` | ||
* | ||
* @public | ||
* @param integrationInstance - Callback function or Component Instance to trigger a rerender on a UI-Component. | ||
* @param subs - Observers to be subscribed to the to create Subscription Container. | ||
* @param config - Configuration object | ||
*/ | ||
subscribe(integrationInstance: any, subs: { | ||
[key: string]: Observer; | ||
@@ -30,61 +82,62 @@ }, config?: RegisterSubscriptionConfigInterface): { | ||
/** | ||
* @internal | ||
* Subscribe with Array shaped Subscriptions | ||
* @param integrationInstance - Callback Function or Component | ||
* @param subs - Initial Subscription Array | ||
* @param config - Config | ||
* Removes the Subscription Container extracted from the specified 'subscriptionInstance' | ||
* from all Observers that were subscribed to it. | ||
* | ||
* We should always unregister a Subscription Container when it is no longer in use. | ||
* For example, when the UI-Component it represents has been unmounted. | ||
* | ||
* @public | ||
* @param subscriptionInstance - UI-Component that contains an instance of a Subscription Container | ||
* or a Subscription Container to be unsubscribed/unregistered. | ||
*/ | ||
subscribeWithSubsArray(integrationInstance: any, subs?: Array<Observer>, config?: RegisterSubscriptionConfigInterface): SubscriptionContainer; | ||
/** | ||
* @internal | ||
* Unsubscribes SubscriptionContainer(Component) | ||
* @param subscriptionInstance - SubscriptionContainer or Component that holds an SubscriptionContainer | ||
*/ | ||
unsubscribe(subscriptionInstance: any): void; | ||
/** | ||
* Returns a newly created Component based Subscription Container. | ||
* | ||
* @internal | ||
* Registers SubscriptionContainer and decides weather integrationInstance is a callback or component based Subscription | ||
* @param integrationInstance - Callback Function or Component | ||
* @param subs - Initial Subscriptions | ||
* @param config - Config | ||
* @param componentInstance - UI-Component to be represented by the Subscription Container | ||
* and mutated via the Integration's 'updateMethod()' method to trigger re-renders on it. | ||
* @param subs - Observers to be initial subscribed to the Subscription Container. | ||
* @param config - Configuration object. | ||
*/ | ||
registerSubscription(integrationInstance: any, subs?: Array<Observer>, config?: RegisterSubscriptionConfigInterface): SubscriptionContainer; | ||
createComponentSubscriptionContainer(componentInstance: any, subs: { | ||
[key: string]: Observer; | ||
} | Array<Observer>, config?: RegisterSubscriptionConfigInterface): ComponentSubscriptionContainer; | ||
/** | ||
* Returns a newly created Callback based Subscription Container. | ||
* | ||
* @internal | ||
* Registers Component based Subscription and applies SubscriptionContainer to Component. | ||
* If an instance called 'subscriptionContainers' exists in Component it will push the new SubscriptionContainer to this Array, | ||
* otherwise it creates a new Instance called 'subscriptionContainer' which holds the new SubscriptionContainer | ||
* @param componentInstance - Component that got subscribed by Observer/s | ||
* @param subs - Initial Subscriptions | ||
* @param config - Config | ||
* @param callbackFunction - Callback function to cause a rerender on the Component | ||
* to be represented by the Subscription Container. | ||
* @param subs - Observers to be initial subscribed to the Subscription Container. | ||
* @param config - Configuration object | ||
*/ | ||
registerComponentSubscription(componentInstance: any, subs?: Array<Observer>, config?: RegisterSubscriptionConfigInterface): ComponentSubscriptionContainer; | ||
createCallbackSubscriptionContainer(callbackFunction: () => void, subs: { | ||
[key: string]: Observer; | ||
} | Array<Observer>, config?: RegisterSubscriptionConfigInterface): CallbackSubscriptionContainer; | ||
/** | ||
* @internal | ||
* Registers Callback based Subscription | ||
* @param callbackFunction - Callback Function that causes rerender on Component which got subscribed by Observer/s | ||
* @param subs - Initial Subscriptions | ||
* @param config - Config | ||
* Notifies the Subscription Containers representing the specified UI-Component (`componentInstance`) | ||
* that the UI-Component they represent has been mounted. | ||
* | ||
* @public | ||
* @param componentInstance - Component Instance containing Subscription Containers to be mounted. | ||
*/ | ||
registerCallbackSubscription(callbackFunction: () => void, subs?: Array<Observer>, config?: RegisterSubscriptionConfigInterface): CallbackSubscriptionContainer; | ||
/** | ||
* @internal | ||
* Mounts Component based SubscriptionContainer | ||
* @param componentInstance - SubscriptionContainer(Component) that gets mounted | ||
*/ | ||
mount(componentInstance: any): void; | ||
/** | ||
* @internal | ||
* Unmounts Component based SubscriptionContainer | ||
* @param componentInstance - SubscriptionContainer(Component) that gets unmounted | ||
* Notifies the Subscription Containers representing the specified UI-Component (`componentInstance`) | ||
* that the UI-Component they represent has been unmounted. | ||
* | ||
* @public | ||
* @param componentInstance - Component Instance containing Subscription Containers to be unmounted | ||
*/ | ||
unmount(componentInstance: any): void; | ||
} | ||
/** | ||
* @param waitForMount - Whether the subscriptionContainer should only become ready | ||
* when the Component has been mounted. (default = agileInstance.config.waitForMount) | ||
*/ | ||
interface RegisterSubscriptionConfigInterface extends SubscriptionContainerConfigInterface { | ||
/** | ||
* Whether the Subscription Container shouldn't be ready | ||
* until the UI-Component it represents has been mounted. | ||
* @default agileInstance.config.waitForMount | ||
*/ | ||
waitForMount?: boolean; | ||
} | ||
export {}; |
@@ -12,24 +12,16 @@ "use strict"; | ||
} | ||
subscribeWithSubsObject(integrationInstance, subs = {}, config = {}) { | ||
const props = {}; | ||
const subsArray = []; | ||
for (const key in subs) | ||
subsArray.push(subs[key]); | ||
const subscriptionContainer = this.registerSubscription(integrationInstance, subsArray, config); | ||
subscriptionContainer.isObjectBased = true; | ||
subscriptionContainer.subsObject = subs; | ||
for (const key in subs) { | ||
const observer = subs[key]; | ||
observer.subscribe(subscriptionContainer); | ||
if (observer.value) | ||
props[key] = observer.value; | ||
subscribe(integrationInstance, subs, config = {}) { | ||
config = internal_1.defineConfig(config, { | ||
waitForMount: this.agileInstance().config.waitForMount, | ||
}); | ||
const subscriptionContainer = internal_1.isFunction(integrationInstance) | ||
? this.createCallbackSubscriptionContainer(integrationInstance, subs, config) | ||
: this.createComponentSubscriptionContainer(integrationInstance, subs, config); | ||
if (subscriptionContainer.isObjectBased && !Array.isArray(subs)) { | ||
const props = {}; | ||
for (const key in subs) | ||
if (subs[key].value) | ||
props[key] = subs[key].value; | ||
return { subscriptionContainer, props }; | ||
} | ||
return { | ||
subscriptionContainer: subscriptionContainer, | ||
props: props, | ||
}; | ||
} | ||
subscribeWithSubsArray(integrationInstance, subs = [], config = {}) { | ||
const subscriptionContainer = this.registerSubscription(integrationInstance, subs, config); | ||
subs.forEach((observer) => observer.subscribe(subscriptionContainer)); | ||
return subscriptionContainer; | ||
@@ -41,3 +33,3 @@ } | ||
subscriptionContainer.subscribers.forEach((observer) => { | ||
observer.unsubscribe(subscriptionContainer); | ||
subscriptionContainer.removeSubscription(observer); | ||
}); | ||
@@ -50,3 +42,3 @@ }; | ||
.tag(['runtime', 'subscription']) | ||
.info('Unregistered Callback based Subscription ', subscriptionInstance); | ||
.info(internal_1.LogCodeManager.getLog('15:01:00'), subscriptionInstance); | ||
return; | ||
@@ -59,14 +51,6 @@ } | ||
.tag(['runtime', 'subscription']) | ||
.info('Unregistered Component based Subscription ', subscriptionInstance); | ||
.info(internal_1.LogCodeManager.getLog('15:01:01'), subscriptionInstance); | ||
return; | ||
} | ||
if (subscriptionInstance.componentSubscriptionContainer) { | ||
unsub(subscriptionInstance.componentSubscriptionContainer); | ||
this.componentSubs.delete(subscriptionInstance.componentSubscriptionContainer); | ||
internal_1.Agile.logger.if | ||
.tag(['runtime', 'subscription']) | ||
.info('Unregistered Component based Subscription ', subscriptionInstance); | ||
return; | ||
} | ||
if (subscriptionInstance.componentSubscriptionContainers && | ||
if (subscriptionInstance['componentSubscriptionContainers'] !== null && | ||
Array.isArray(subscriptionInstance.componentSubscriptionContainers)) { | ||
@@ -78,20 +62,8 @@ subscriptionInstance.componentSubscriptionContainers.forEach((subContainer) => { | ||
.tag(['runtime', 'subscription']) | ||
.info('Unregistered Component based Subscription ', subscriptionInstance); | ||
.info(internal_1.LogCodeManager.getLog('15:01:01'), subscriptionInstance); | ||
}); | ||
return; | ||
} | ||
internal_1.Agile.logger.if | ||
.tag(['runtime', 'subscription']) | ||
.warn(`Couldn't find anything to unregister in `, subscriptionInstance); | ||
return; | ||
} | ||
registerSubscription(integrationInstance, subs = [], config = {}) { | ||
config = internal_1.defineConfig(config, { | ||
waitForMount: this.agileInstance().config.waitForMount, | ||
}); | ||
if (internal_1.isFunction(integrationInstance)) | ||
return this.registerCallbackSubscription(integrationInstance, subs, config); | ||
return this.registerComponentSubscription(integrationInstance, subs, config); | ||
} | ||
registerComponentSubscription(componentInstance, subs = [], config = {}) { | ||
createComponentSubscriptionContainer(componentInstance, subs, config = {}) { | ||
const componentSubscriptionContainer = new internal_1.ComponentSubscriptionContainer(componentInstance, subs, internal_1.removeProperties(config, ['waitForMount'])); | ||
@@ -105,13 +77,15 @@ this.componentSubs.add(componentSubscriptionContainer); | ||
componentSubscriptionContainer.ready = true; | ||
if (componentInstance.componentSubscriptionContainers && | ||
if (componentInstance['componentSubscriptionContainers'] != null && | ||
Array.isArray(componentInstance.componentSubscriptionContainers)) | ||
componentInstance.componentSubscriptionContainers.push(componentSubscriptionContainer); | ||
else | ||
componentInstance.componentSubscriptionContainer = componentSubscriptionContainer; | ||
componentInstance['componentSubscriptionContainers'] = [ | ||
componentSubscriptionContainer, | ||
]; | ||
internal_1.Agile.logger.if | ||
.tag(['runtime', 'subscription']) | ||
.info('Registered Component based Subscription ', componentSubscriptionContainer); | ||
.info(internal_1.LogCodeManager.getLog('15:01:02'), componentSubscriptionContainer); | ||
return componentSubscriptionContainer; | ||
} | ||
registerCallbackSubscription(callbackFunction, subs = [], config = {}) { | ||
createCallbackSubscriptionContainer(callbackFunction, subs, config = {}) { | ||
const callbackSubscriptionContainer = new internal_1.CallbackSubscriptionContainer(callbackFunction, subs, internal_1.removeProperties(config, ['waitForMount'])); | ||
@@ -122,13 +96,15 @@ this.callbackSubs.add(callbackSubscriptionContainer); | ||
.tag(['runtime', 'subscription']) | ||
.info('Registered Callback based Subscription ', callbackSubscriptionContainer); | ||
.info(internal_1.LogCodeManager.getLog('15:01:03'), callbackSubscriptionContainer); | ||
return callbackSubscriptionContainer; | ||
} | ||
mount(componentInstance) { | ||
if (componentInstance.componentSubscriptionContainer) | ||
componentInstance.componentSubscriptionContainer.ready = true; | ||
if (componentInstance['componentSubscriptionContainers'] != null && | ||
Array.isArray(componentInstance.componentSubscriptionContainers)) | ||
componentInstance.componentSubscriptionContainers.map((c) => (c.ready = true)); | ||
this.mountedComponents.add(componentInstance); | ||
} | ||
unmount(componentInstance) { | ||
if (componentInstance.componentSubscriptionContainer) | ||
componentInstance.componentSubscriptionContainer.ready = false; | ||
if (componentInstance['componentSubscriptionContainers'] != null && | ||
Array.isArray(componentInstance.componentSubscriptionContainers)) | ||
componentInstance.componentSubscriptionContainers.map((c) => (c.ready = false)); | ||
this.mountedComponents.delete(componentInstance); | ||
@@ -135,0 +111,0 @@ } |
@@ -13,3 +13,3 @@ /// <reference types="node" /> | ||
nextStateValue: ValueType; | ||
observer: StateObserver<ValueType>; | ||
observers: StateObserversInterface<ValueType>; | ||
sideEffects: { | ||
@@ -27,242 +27,410 @@ [key: string]: SideEffectInterface<State<ValueType>>; | ||
/** | ||
* A State manages a piece of Information | ||
* that we need to remember globally at a later point in time. | ||
* While providing a toolkit to use and mutate this piece of Information. | ||
* | ||
* You can create as many global States as you need. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/) | ||
* | ||
* @public | ||
* State - Class that holds one Value and causes rerender on subscribed Components | ||
* @param agileInstance - An instance of Agile | ||
* @param initialValue - Initial Value of State | ||
* @param config - Config | ||
* @param agileInstance - Instance of Agile the State belongs to. | ||
* @param initialValue - Initial value of the State. | ||
* @param config - Configuration object | ||
*/ | ||
constructor(agileInstance: Agile, initialValue: ValueType, config?: StateConfigInterface); | ||
/** | ||
* Assigns a new value to the State | ||
* and rerenders all subscribed Components. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/properties#value) | ||
* | ||
* @public | ||
* Set Value of State | ||
* @param value - New State value. | ||
*/ | ||
set value(value: ValueType); | ||
/** | ||
* Returns a reference-free version of the current State value. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/properties#value) | ||
* | ||
* @public | ||
* Get Value of State | ||
*/ | ||
get value(): ValueType; | ||
/** | ||
* Updates the key/name identifier of the State. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/properties#key) | ||
* | ||
* @public | ||
* Set Key/Name of State | ||
* @param value - New key/name identifier. | ||
*/ | ||
set key(value: StateKey | undefined); | ||
/** | ||
* Returns the key/name identifier of the State. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/properties#key) | ||
* | ||
* @public | ||
* Get Key/Name of State | ||
*/ | ||
get key(): StateKey | undefined; | ||
/** | ||
* @internal | ||
* Updates Key/Name of State | ||
* @param value - New Key/Name of State | ||
* Updates the key/name identifier of the State. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#setkey) | ||
* | ||
* @public | ||
* @param value - New key/name identifier. | ||
*/ | ||
setKey(value: StateKey | undefined): this; | ||
/** | ||
* Assigns a new value to the State | ||
* and re-renders all subscribed UI-Components. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#set) | ||
* | ||
* @public | ||
* Updates Value of State | ||
* @param value - new State Value | ||
* @param config - Config | ||
* @param value - New State value | ||
* @param config - Configuration object | ||
*/ | ||
set(value: ValueType | ((value: ValueType) => ValueType), config?: StateIngestConfigInterface): this; | ||
/** | ||
* Ingests the State without any specified new value into the runtime. | ||
* | ||
* Since no new value was defined either the new State value is computed | ||
* based on a compute method (Computed Class) | ||
* or the `nextStateValue` is taken as the next State value. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#ingest) | ||
* | ||
* @internal | ||
* Ingests nextStateValue, computedValue into Runtime | ||
* @param config - Config | ||
* @param config - Configuration object | ||
*/ | ||
ingest(config?: StateIngestConfigInterface): this; | ||
/** | ||
* Assigns a primitive type to the State | ||
* which constrains the State value on the specified type | ||
* to ensure basic typesafety in Javascript. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#type) | ||
* | ||
* @public | ||
* Assign primitive type to State Value | ||
* Note: This function is mainly thought for JS users | ||
* @param type - wished Type ('String', 'Boolean', 'Array', 'Object', 'Number') | ||
* @param type - Primitive type the State value must follow (`String`, `Boolean`, `Array`, `Object`, `Number`). | ||
*/ | ||
type(type: any): this; | ||
/** | ||
* Undoes the latest State value change. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#undo) | ||
* | ||
* @public | ||
* Undoes latest State Value change | ||
* @param config - Config | ||
* @param config - Configuration object | ||
*/ | ||
undo(config?: StateIngestConfigInterface): this; | ||
/** | ||
* Resets the State value to its initial value. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#reset) | ||
* | ||
* @public | ||
* Resets State to its initial Value | ||
* @param config - Config | ||
* @param config - Configuration object | ||
*/ | ||
reset(config?: StateIngestConfigInterface): this; | ||
/** | ||
* Merges the specified `targetWithChanges` object into the current State value. | ||
* This merge can differ for different value combinations: | ||
* - If the current State value is an `object`, it does a partial update for the object. | ||
* - If the current State value is an `array` and the specified argument is an array too, | ||
* it concatenates the current State value with the value of the argument. | ||
* - If the current State value is neither an `object` nor an `array`, the patch can't be performed. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#patch) | ||
* | ||
* @public | ||
* Patches Object with changes into State Value | ||
* Note: Only useful if State is an Object | ||
* @param targetWithChanges - Object that holds changes which get patched into State Value | ||
* @param config - Config | ||
* @param targetWithChanges - Object to be merged into the current State value. | ||
* @param config - Configuration object | ||
*/ | ||
patch(targetWithChanges: Object, config?: PatchConfigInterface): this; | ||
/** | ||
* Fires on each State value change. | ||
* | ||
* Returns the key/name identifier of the created watcher callback. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#watch) | ||
* | ||
* @public | ||
* Watches State and detects State changes | ||
* @param callback - Callback Function that gets called if the State Value changes | ||
* @return Key of Watcher | ||
* @param callback - A function to be executed on each State value change. | ||
*/ | ||
watch(callback: StateWatcherCallback<ValueType>): string; | ||
/** | ||
* Fires on each State value change. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#watch) | ||
* | ||
* @public | ||
* Watches State and detects State changes | ||
* @param key - Key/Name of Watcher Function | ||
* @param callback - Callback Function that gets called if the State Value changes | ||
* @param key - Key/Name identifier of the watcher callback. | ||
* @param callback - A function to be executed on each State value change. | ||
*/ | ||
watch(key: string, callback: StateWatcherCallback<ValueType>): this; | ||
/** | ||
* Removes a watcher callback with the specified key/name identifier from the State. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#removewatcher) | ||
* | ||
* @public | ||
* Removes Watcher at given Key | ||
* @param key - Key of Watcher that gets removed | ||
* @param key - Key/Name identifier of the watcher callback to be removed. | ||
*/ | ||
removeWatcher(key: string): this; | ||
/** | ||
* Returns a boolean indicating whether a watcher callback with the specified `key` | ||
* exists in the State or not. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#haswatcher) | ||
* | ||
* @public | ||
* Creates a Watcher that gets once called when the State Value changes for the first time and than destroys itself | ||
* @param callback - Callback Function that gets called if the State Value changes | ||
* @param key - Key/Name identifier of the watcher callback to be checked for existence. | ||
*/ | ||
onInaugurated(callback: StateWatcherCallback<ValueType>): this; | ||
hasWatcher(key: string): boolean; | ||
/** | ||
* Fires on the initial State value assignment and then destroys itself. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#oninaugurated) | ||
* | ||
* @public | ||
* Checks if watcher at given Key exists | ||
* @param key - Key/Name of Watcher | ||
* @param callback - A function to be executed after the first State value assignment. | ||
*/ | ||
hasWatcher(key: string): boolean; | ||
onInaugurated(callback: StateWatcherCallback<ValueType>): this; | ||
/** | ||
* Preserves the State `value` in the corresponding external Storage. | ||
* | ||
* The State key/name is used as the unique identifier for the Persistent. | ||
* If that is not desired or the State has no unique identifier, | ||
* please specify a separate unique identifier for the Persistent. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#persist) | ||
* | ||
* @public | ||
* Stores State Value into Agile Storage permanently | ||
* @param config - Config | ||
* @param config - Configuration object | ||
*/ | ||
persist(config?: StatePersistentConfigInterface): this; | ||
/** | ||
* Preserves the State `value` in the corresponding external Storage. | ||
* | ||
* The specified key is used as the unique identifier for the Persistent. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#persist) | ||
* | ||
* @public | ||
* Stores State Value into Agile Storage permanently | ||
* @param key - Key/Name of created Persistent (Note: Key required if State has no set Key!) | ||
* @param config - Config | ||
* @param key - Key/Name identifier of Persistent. | ||
* @param config - Configuration object | ||
*/ | ||
persist(key?: PersistentKey, config?: StatePersistentConfigInterface): this; | ||
/** | ||
* Fires immediately after the persisted `value` | ||
* is loaded into the State from a corresponding external Storage. | ||
* | ||
* Registering such callback function makes only sense | ||
* when the State is [persisted](https://agile-ts.org/docs/core/state/methods/#persist). | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#onload) | ||
* | ||
* @public | ||
* Callback Function that gets called if the persisted Value gets loaded into the State for the first Time | ||
* Note: Only useful for persisted States! | ||
* @param callback - Callback Function | ||
* @param callback - A function to be executed after the externally persisted `value` was loaded into the State. | ||
*/ | ||
onLoad(callback: (success: boolean) => void): this; | ||
/** | ||
* Repeatedly calls the specified callback function, | ||
* with a fixed time delay between each call. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#interval) | ||
* | ||
* @public | ||
* Calls callback at certain intervals in milliseconds and assigns the callback return value to the State | ||
* @param callback- Callback that is called on each interval and should return the new State value | ||
* @param ms - The intervals in milliseconds | ||
* @param handler - A function to be executed every delay milliseconds. | ||
* @param delay - The time, in milliseconds (thousandths of a second), | ||
* the timer should delay in between executions of the specified function. | ||
*/ | ||
interval(callback: (value: ValueType) => ValueType, ms?: number): this; | ||
interval(handler: (value: ValueType) => ValueType, delay?: number): this; | ||
/** | ||
* Cancels a active timed, repeating action | ||
* which was previously established by a call to `interval()`. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#clearinterval) | ||
* | ||
* @public | ||
* Clears the current Interval | ||
*/ | ||
clearInterval(): void; | ||
/** | ||
* Returns a boolean indicating whether the State exists. | ||
* | ||
* It calculates the value based on the `computeExistsMethod()` | ||
* and whether the State is a placeholder. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#exists) | ||
* | ||
* @public | ||
* Creates fresh copy of State Value (-> No reference to State Value) | ||
*/ | ||
copy(): ValueType; | ||
get exists(): boolean; | ||
/** | ||
* Defines the method used to compute the existence of the State. | ||
* | ||
* It is retrieved on each `exists()` method call | ||
* to determine whether the State exists or not. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#computeexists) | ||
* | ||
* @public | ||
* Checks if State exists | ||
* @param method - Method to compute the existence of the State. | ||
*/ | ||
get exists(): boolean; | ||
computeExists(method: ComputeExistsMethod<ValueType>): this; | ||
/** | ||
* Defines the method used to compute the value of the State. | ||
* | ||
* It is retrieved on each State value change, | ||
* in order to compute the new State value | ||
* based on the specified compute method. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#computevalue) | ||
* | ||
* @public | ||
* Function that computes the exists status of the State | ||
* @param method - Computed Function | ||
* @param method - Method to compute the value of the State. | ||
*/ | ||
computeExists(method: ComputeExistsMethod<ValueType>): this; | ||
computeValue(method: ComputeValueMethod<ValueType>): this; | ||
/** | ||
* Returns a boolean indicating whether the specified value is equal to the current State value. | ||
* | ||
* Equivalent to `===` with the difference that it looks at the value | ||
* and not on the reference in the case of objects. | ||
* | ||
* @public | ||
* Equivalent to === | ||
* @param value - Value that gets checked if its equals to the State Value | ||
* @param value - Value to be compared with the current State value. | ||
*/ | ||
is(value: ValueType): boolean; | ||
/** | ||
* Returns a boolean indicating whether the specified value is not equal to the current State value. | ||
* | ||
* Equivalent to `!==` with the difference that it looks at the value | ||
* and not on the reference in the case of objects. | ||
* | ||
* @public | ||
* Equivalent to !== | ||
* @param value - Value that gets checked if its not equals to the State Value | ||
* @param value - Value to be compared with the current State value. | ||
*/ | ||
isNot(value: ValueType): boolean; | ||
/** | ||
* Inverts the current State value. | ||
* | ||
* Some examples are: | ||
* - `'jeff'` -> `'ffej'` | ||
* - `true` -> `false` | ||
* - `[1, 2, 3]` -> `[3, 2, 1]` | ||
* - `10` -> `-10` | ||
* | ||
* @public | ||
* Inverts State Value | ||
* Note: Only useful with boolean based States | ||
*/ | ||
invert(): this; | ||
/** | ||
* @public | ||
* Function that recomputes State Value if it changes | ||
* @param method - Computed Function | ||
*/ | ||
computeValue(method: ComputeValueMethod<ValueType>): this; | ||
/** | ||
* | ||
* Registers a `callback` function that is executed in the `runtime` | ||
* as a side effect of State changes. | ||
* | ||
* For example, it is called when the State value changes from 'jeff' to 'hans'. | ||
* | ||
* A typical side effect of a State change | ||
* could be the updating of the external Storage value. | ||
* | ||
* @internal | ||
* Adds SideEffect to State | ||
* @param key - Key/Name of SideEffect | ||
* @param callback - Callback Function that gets called on every State Value change | ||
* @param config - Config | ||
* @param key - Key/Name identifier of the to register side effect. | ||
* @param callback - Callback function to be fired on each State value change. | ||
* @param config - Configuration object | ||
*/ | ||
addSideEffect<Instance extends State<ValueType>>(key: string, callback: SideEffectFunctionType<Instance>, config?: AddSideEffectConfigInterface): this; | ||
/** | ||
* Removes a side effect callback with the specified key/name identifier from the State. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#removesideeffect) | ||
* | ||
* @internal | ||
* Removes SideEffect at given Key | ||
* @param key - Key of the SideEffect that gets removed | ||
* @param key - Key/Name identifier of the side effect callback to be removed. | ||
*/ | ||
removeSideEffect(key: string): this; | ||
/** | ||
* Returns a boolean indicating whether a side effect callback with the specified `key` | ||
* exists in the State or not. | ||
* | ||
* [Learn more..](https://agile-ts.org/docs/core/state/methods/#hassideeffect) | ||
* | ||
* @internal | ||
* Checks if sideEffect at given Key exists | ||
* @param key - Key of SideEffect | ||
* @param key - Key/Name identifier of the side effect callback to be checked for existence. | ||
*/ | ||
hasSideEffect(key: string): boolean; | ||
/** | ||
* Returns a boolean indicating whether the passed value | ||
* is of the before defined State `valueType` or not. | ||
* | ||
* @internal | ||
* Checks if Value has correct valueType (js) | ||
* Note: If no valueType set, it returns true | ||
* @param value - Value that gets checked for its correct Type | ||
* @param value - Value to be checked for the correct type. | ||
*/ | ||
hasCorrectType(value: any): boolean; | ||
/** | ||
* Returns the persistable value of the State. | ||
* | ||
* @internal | ||
* Returns public Value of State | ||
*/ | ||
getPublicValue(): ValueType; | ||
getPersistableValue(): any; | ||
} | ||
export declare type StateKey = string | number; | ||
export interface StateObserversInterface<ValueType = any> { | ||
/** | ||
* @internal | ||
* Returns Value that gets written into the Agile Storage | ||
* Observer responsible for the value of the State. | ||
*/ | ||
getPersistableValue(): any; | ||
value: StateObserver<ValueType>; | ||
} | ||
export declare type StateKey = string | number; | ||
/** | ||
* @param key - Key/Name of State | ||
* @param deps - Initial deps of State | ||
* @param isPlaceholder - If State is initially a Placeholder | ||
*/ | ||
export interface StateConfigInterface { | ||
/** | ||
* Key/Name identifier of the State. | ||
* @default undefined | ||
*/ | ||
key?: StateKey; | ||
/** | ||
* Observers that depend on the State. | ||
* @default [] | ||
*/ | ||
dependents?: Array<Observer>; | ||
/** | ||
* Whether the State should be a placeholder | ||
* and therefore should only exist in the background. | ||
* @default false | ||
*/ | ||
isPlaceholder?: boolean; | ||
} | ||
/** | ||
* @param addNewProperties - If new Properties gets added to the State Value | ||
*/ | ||
export interface PatchConfigInterface extends StateIngestConfigInterface { | ||
export interface PatchConfigInterface extends StateIngestConfigInterface, PatchOptionConfigInterface { | ||
} | ||
export interface PatchOptionConfigInterface { | ||
/** | ||
* Whether to add new properties to the object during the merge. | ||
* @default true | ||
*/ | ||
addNewProperties?: boolean; | ||
} | ||
/** | ||
* @param loadValue - If Persistent loads the persisted value into the State | ||
* @param storageKeys - Key/Name of Storages which gets used to persist the State Value (NOTE: If not passed the default Storage will be used) | ||
* @param defaultStorageKey - Default Storage Key (if not provided it takes the first index of storageKeys or the AgileTs default Storage) | ||
*/ | ||
export interface StatePersistentConfigInterface { | ||
/** | ||
* Whether the Persistent should automatically load | ||
* the persisted value into the State after its instantiation. | ||
* @default true | ||
*/ | ||
loadValue?: boolean; | ||
/** | ||
* Key/Name identifier of Storages | ||
* in which the State value should be or is persisted. | ||
* @default [`defaultStorageKey`] | ||
*/ | ||
storageKeys?: StorageKey[]; | ||
/** | ||
* Key/Name identifier of the default Storage of the specified Storage keys. | ||
* | ||
* The State value is loaded from the default Storage by default | ||
* and is only loaded from the remaining Storages (`storageKeys`) | ||
* if the loading from the default Storage failed. | ||
* | ||
* @default first index of the specified Storage keys or the AgileTs default Storage key | ||
*/ | ||
defaultStorageKey?: StorageKey; | ||
@@ -273,18 +441,25 @@ } | ||
export declare type ComputeExistsMethod<T = any> = (value: T) => boolean; | ||
export declare type SideEffectFunctionType<Instance extends State<any>> = (instance: Instance, properties?: { | ||
export declare type SideEffectFunctionType<Instance extends State> = (instance: Instance, properties?: { | ||
[key: string]: any; | ||
}) => void; | ||
/** | ||
* @param callback - Callback Function that gets called on every State Value change | ||
* @param weight - When the sideEffect gets executed. The higher, the earlier it gets executed. | ||
*/ | ||
export interface SideEffectInterface<Instance extends State<any>> { | ||
export interface SideEffectInterface<Instance extends State> { | ||
/** | ||
* Callback function to be called on every State value change. | ||
* @return () => {} | ||
*/ | ||
callback: SideEffectFunctionType<Instance>; | ||
/** | ||
* Weight of the side effect. | ||
* The weight determines the order of execution of the registered side effects. | ||
* The higher the weight, the earlier it is executed. | ||
*/ | ||
weight: number; | ||
} | ||
/** | ||
* @param weight - When the sideEffect gets executed. The higher, the earlier it gets executed. | ||
*/ | ||
export interface AddSideEffectConfigInterface { | ||
/** | ||
* Weight of the side effect. | ||
* The weight determines the order of execution of the registered side effects. | ||
* The higher the weight, the earlier it is executed. | ||
*/ | ||
weight?: number; | ||
} |
@@ -9,2 +9,3 @@ "use strict"; | ||
this.isPlaceholder = false; | ||
this.observers = {}; | ||
this.sideEffects = {}; | ||
@@ -19,3 +20,3 @@ this.isPersisted = false; | ||
this._key = config.key; | ||
this.observer = new internal_1.StateObserver(this, { | ||
this.observers['value'] = new internal_1.StateObserver(this, { | ||
key: config.key, | ||
@@ -39,4 +40,4 @@ dependents: config.dependents, | ||
get value() { | ||
internal_1.ComputedTracker.tracked(this.observer); | ||
return this._value; | ||
internal_1.ComputedTracker.tracked(this.observers['value']); | ||
return internal_1.copy(this._value); | ||
} | ||
@@ -53,4 +54,5 @@ set key(value) { | ||
this._key = value; | ||
this.observer._key = value; | ||
if (value && ((_a = this.persistent) === null || _a === void 0 ? void 0 : _a._key) === oldKey) | ||
for (const observerKey in this.observers) | ||
this.observers[observerKey]._key = value; | ||
if (value != null && ((_a = this.persistent) === null || _a === void 0 ? void 0 : _a._key) === oldKey) | ||
(_b = this.persistent) === null || _b === void 0 ? void 0 : _b.setKey(value); | ||
@@ -67,14 +69,14 @@ return this; | ||
if (!this.hasCorrectType(_value)) { | ||
const message = `Incorrect type (${typeof _value}) was provided.`; | ||
if (!config.force) { | ||
internal_1.Agile.logger.error(message); | ||
internal_1.LogCodeManager.log(config.force ? '14:02:00' : '14:03:00', [ | ||
typeof _value, | ||
this.valueType, | ||
]); | ||
if (!config.force) | ||
return this; | ||
} | ||
internal_1.Agile.logger.warn(message); | ||
} | ||
this.observer.ingestValue(_value, config); | ||
this.observers['value'].ingestValue(_value, config); | ||
return this; | ||
} | ||
ingest(config = {}) { | ||
this.observer.ingest(config); | ||
this.observers['value'].ingest(config); | ||
return this; | ||
@@ -85,3 +87,3 @@ } | ||
if (!supportedTypes.includes(type.name)) { | ||
internal_1.Agile.logger.warn(`'${type}' is not supported! Supported types: String, Boolean, Array, Object, Number`); | ||
internal_1.LogCodeManager.log('14:03:01', [type]); | ||
return this; | ||
@@ -105,10 +107,19 @@ } | ||
if (!internal_1.isValidObject(this.nextStateValue, true)) { | ||
internal_1.Agile.logger.error("You can't use the patch method on a non object based States!"); | ||
internal_1.LogCodeManager.log('14:03:02'); | ||
return this; | ||
} | ||
if (!internal_1.isValidObject(targetWithChanges, true)) { | ||
internal_1.Agile.logger.error('TargetWithChanges has to be an Object!'); | ||
internal_1.LogCodeManager.log('00:03:01', ['TargetWithChanges', 'object']); | ||
return this; | ||
} | ||
this.nextStateValue = internal_1.flatMerge(internal_1.copy(this.nextStateValue), targetWithChanges, { addNewProperties: config.addNewProperties }); | ||
if (Array.isArray(targetWithChanges) && | ||
Array.isArray(this.nextStateValue)) { | ||
this.nextStateValue = [ | ||
...this.nextStateValue, | ||
...targetWithChanges, | ||
]; | ||
} | ||
else { | ||
this.nextStateValue = internal_1.flatMerge(this.nextStateValue, targetWithChanges, { addNewProperties: config.addNewProperties }); | ||
} | ||
this.ingest(internal_1.removeProperties(config, ['addNewProperties'])); | ||
@@ -130,9 +141,5 @@ return this; | ||
if (!internal_1.isFunction(_callback)) { | ||
internal_1.Agile.logger.error('A Watcher Callback Function has to be typeof Function!'); | ||
internal_1.LogCodeManager.log('00:03:01', ['Watcher Callback', 'function']); | ||
return this; | ||
} | ||
if (this.watchers[key]) { | ||
internal_1.Agile.logger.error(`Watcher Callback Function with the key/name '${key}' already exists!`); | ||
return this; | ||
} | ||
this.watchers[key] = _callback; | ||
@@ -145,2 +152,5 @@ return generateKey ? key : this; | ||
} | ||
hasWatcher(key) { | ||
return !!this.watchers[key]; | ||
} | ||
onInaugurated(callback) { | ||
@@ -154,5 +164,2 @@ const watcherKey = 'InauguratedWatcherKey'; | ||
} | ||
hasWatcher(key) { | ||
return !!this.watchers[key]; | ||
} | ||
persist(keyOrConfig = {}, config = {}) { | ||
@@ -174,5 +181,4 @@ let _config; | ||
}); | ||
if (this.persistent) { | ||
internal_1.Agile.logger.warn(`By persisting the State '${this._key}' twice you overwrite the old Persistent Instance!`, this.persistent); | ||
} | ||
if (this.persistent != null && this.isPersisted) | ||
return this; | ||
this.persistent = new internal_1.StatePersistent(this, { | ||
@@ -187,5 +193,7 @@ instantiate: _config.loadValue, | ||
onLoad(callback) { | ||
if (!this.persistent) { | ||
internal_1.Agile.logger.error(`Please make sure you persist the State '${this._key}' before using the 'onLoad' function!`); | ||
if (!this.persistent) | ||
return this; | ||
if (!internal_1.isFunction(callback)) { | ||
internal_1.LogCodeManager.log('00:03:01', ['OnLoad Callback', 'function']); | ||
return this; | ||
} | ||
@@ -197,10 +205,14 @@ this.persistent.onLoad = callback; | ||
} | ||
interval(callback, ms) { | ||
interval(handler, delay) { | ||
if (!internal_1.isFunction(handler)) { | ||
internal_1.LogCodeManager.log('00:03:01', ['Interval Callback', 'function']); | ||
return this; | ||
} | ||
if (this.currentInterval) { | ||
internal_1.Agile.logger.warn(`You can only have one interval active!`, this.currentInterval); | ||
internal_1.LogCodeManager.log('14:03:03', [], this.currentInterval); | ||
return this; | ||
} | ||
this.currentInterval = setInterval(() => { | ||
this.set(callback(this._value)); | ||
}, ms !== null && ms !== void 0 ? ms : 1000); | ||
this.set(handler(this._value)); | ||
}, delay !== null && delay !== void 0 ? delay : 1000); | ||
return this; | ||
@@ -214,5 +226,2 @@ } | ||
} | ||
copy() { | ||
return internal_1.copy(this.value); | ||
} | ||
get exists() { | ||
@@ -223,3 +232,3 @@ return !this.isPlaceholder && this.computeExistsMethod(this.value); | ||
if (!internal_1.isFunction(method)) { | ||
internal_1.Agile.logger.error(`A 'computeExistsMethod' has to be a function!`); | ||
internal_1.LogCodeManager.log('00:03:01', ['Compute Exists Method', 'function']); | ||
return this; | ||
@@ -230,2 +239,11 @@ } | ||
} | ||
computeValue(method) { | ||
if (!internal_1.isFunction(method)) { | ||
internal_1.LogCodeManager.log('00:03:01', ['Compute Value Method', 'function']); | ||
return this; | ||
} | ||
this.computeValueMethod = method; | ||
this.set(this.nextStateValue); | ||
return this; | ||
} | ||
is(value) { | ||
@@ -238,19 +256,21 @@ return internal_1.equal(value, this.value); | ||
invert() { | ||
if (typeof this._value === 'boolean') { | ||
this.set(!this._value); | ||
switch (typeof this.nextStateValue) { | ||
case 'boolean': | ||
this.set(!this.nextStateValue); | ||
break; | ||
case 'object': | ||
if (Array.isArray(this.nextStateValue)) | ||
this.set(this.nextStateValue.reverse()); | ||
break; | ||
case 'string': | ||
this.set(this.nextStateValue.split('').reverse().join('')); | ||
break; | ||
case 'number': | ||
this.set((this.nextStateValue * -1)); | ||
break; | ||
default: | ||
internal_1.LogCodeManager.log('14:03:04', [typeof this.nextStateValue]); | ||
} | ||
else { | ||
internal_1.Agile.logger.error('You can only invert boolean based States!'); | ||
} | ||
return this; | ||
} | ||
computeValue(method) { | ||
if (!internal_1.isFunction(method)) { | ||
internal_1.Agile.logger.error(`A 'computeValueMethod' has to be a function!`); | ||
return this; | ||
} | ||
this.computeValueMethod = method; | ||
this.set(method(this.nextStateValue)); | ||
return this; | ||
} | ||
addSideEffect(key, callback, config = {}) { | ||
@@ -261,3 +281,3 @@ config = internal_1.defineConfig(config, { | ||
if (!internal_1.isFunction(callback)) { | ||
internal_1.Agile.logger.error('A sideEffect function has to be a function!'); | ||
internal_1.LogCodeManager.log('00:03:01', ['Side Effect Callback', 'function']); | ||
return this; | ||
@@ -284,7 +304,2 @@ } | ||
} | ||
getPublicValue() { | ||
if (this['output'] !== undefined) | ||
return this['output']; | ||
return this._value; | ||
} | ||
getPersistableValue() { | ||
@@ -291,0 +306,0 @@ return this._value; |
@@ -1,2 +0,2 @@ | ||
import { Observer, State, ObserverKey, SubscriptionContainer, IngestConfigInterface, StateRuntimeJob, CreateStateRuntimeJobConfigInterface } from '../internal'; | ||
import { Observer, State, IngestConfigInterface, StateRuntimeJob, CreateStateRuntimeJobConfigInterface, SubscriptionContainer, ObserverKey } from '../internal'; | ||
export declare class StateObserver<ValueType = any> extends Observer { | ||
@@ -6,42 +6,73 @@ state: () => State<ValueType>; | ||
/** | ||
* A State Observer manages the subscriptions to Subscription Containers (UI-Components) | ||
* and dependencies to other Observers (Agile Classes) | ||
* for a State Class. | ||
* | ||
* @internal | ||
* State Observer - Handles State changes, dependencies (-> Interface to Runtime) | ||
* @param state - State | ||
* @param config - Config | ||
* @param state - Instance of State the Observer belongs to. | ||
* @param config - Configuration object | ||
*/ | ||
constructor(state: State<ValueType>, config?: CreateStateObserverConfigInterface); | ||
/** | ||
* Passes the State Observer into the runtime wrapped into a Runtime-Job | ||
* where it is executed accordingly. | ||
* | ||
* During the execution the runtime applies the `nextStateValue` | ||
* or the `computedValue` (Computed Class) to the State, | ||
* updates its dependents and re-renders the UI-Components it is subscribed to. | ||
* | ||
* @internal | ||
* Ingests nextStateValue or computedValue into Runtime and applies it to the State | ||
* @param config - Config | ||
* @param config - Configuration object | ||
*/ | ||
ingest(config?: StateIngestConfigInterface): void; | ||
/** | ||
* Passes the State Observer into the runtime wrapped into a Runtime-Job | ||
* where it is executed accordingly. | ||
* | ||
* During the execution the runtime applies the specified `newStateValue` to the State, | ||
* updates its dependents and re-renders the UI-Components it is subscribed to. | ||
* | ||
* @internal | ||
* Ingests new State Value into Runtime and applies it to the State | ||
* @param newStateValue - New Value of the State | ||
* @param config - Config | ||
* @param newStateValue - New value to be applied to the State. | ||
* @param config - Configuration object. | ||
*/ | ||
ingestValue(newStateValue: ValueType, config?: StateIngestConfigInterface): void; | ||
/** | ||
* Method executed by the Runtime to perform the Runtime-Job, | ||
* previously ingested via the `ingest()` or `ingestValue()` method. | ||
* | ||
* Thereby the previously defined `nextStateValue` is assigned to the State. | ||
* Also side effects (like calling watcher callbacks) of a State change are executed. | ||
* | ||
* @internal | ||
* Performs Job that holds this Observer | ||
* @param job - Job | ||
* @param job - Runtime-Job to be performed. | ||
*/ | ||
perform(job: StateRuntimeJob): void; | ||
/** | ||
* Performs the side effects of applying the next State value to the State. | ||
* | ||
* Side effects are, for example, calling the watcher callbacks | ||
* or executing the side effects defined in the State Class | ||
* like 'rebuildGroup' or 'rebuildStateStorageValue'. | ||
* | ||
* @internal | ||
* SideEffects of Job | ||
* @param job - Job | ||
* @param job - Job that is currently performed. | ||
*/ | ||
sideEffects(job: StateRuntimeJob): void; | ||
} | ||
/** | ||
* @param dependents - Initial Dependents of State Observer | ||
* @param subs - Initial Subscriptions of State Observer | ||
* @param key - Key/Name of State Observer | ||
*/ | ||
export interface CreateStateObserverConfigInterface { | ||
/** | ||
* Initial Observers to depend on the Observer. | ||
* @default [] | ||
*/ | ||
dependents?: Array<Observer>; | ||
/** | ||
* Initial Subscription Containers the Observer is subscribed to. | ||
* @default [] | ||
*/ | ||
subs?: Array<SubscriptionContainer>; | ||
/** | ||
* Key/Name identifier of the Observer. | ||
* @default undefined | ||
*/ | ||
key?: ObserverKey; | ||
@@ -48,0 +79,0 @@ } |
@@ -13,10 +13,13 @@ "use strict"; | ||
const state = this.state(); | ||
let newStateValue; | ||
if (state instanceof internal_1.Computed) | ||
newStateValue = state.compute(); | ||
else | ||
newStateValue = state.nextStateValue; | ||
this.ingestValue(newStateValue, config); | ||
if (state instanceof internal_1.Computed) { | ||
state.compute().then((result) => { | ||
this.ingestValue(result, config); | ||
}); | ||
} | ||
else { | ||
this.ingestValue(state.nextStateValue, config); | ||
} | ||
} | ||
ingestValue(newStateValue, config = {}) { | ||
var _a; | ||
const state = this.state(); | ||
@@ -33,2 +36,3 @@ config = internal_1.defineConfig(config, { | ||
overwrite: false, | ||
maxTriesToUpdate: 3, | ||
}); | ||
@@ -50,3 +54,4 @@ if (state.isPlaceholder) { | ||
overwrite: config.overwrite, | ||
key: config.key || this._key, | ||
key: (_a = config.key) !== null && _a !== void 0 ? _a : `${this._key != null ? this._key + '_' : ''}${internal_1.generateId()}_value`, | ||
maxTriesToUpdate: config.maxTriesToUpdate, | ||
}); | ||
@@ -58,7 +63,7 @@ this.agileInstance().runtime.ingest(job, { | ||
perform(job) { | ||
const state = job.observer.state(); | ||
const previousValue = internal_1.copy(state.getPublicValue()); | ||
const observer = job.observer; | ||
const state = observer.state(); | ||
state.previousStateValue = internal_1.copy(state._value); | ||
state._value = internal_1.copy(job.observer.nextStateValue); | ||
state.nextStateValue = internal_1.copy(job.observer.nextStateValue); | ||
state._value = internal_1.copy(observer.nextStateValue); | ||
state.nextStateValue = internal_1.copy(observer.nextStateValue); | ||
if (job.config.overwrite) { | ||
@@ -71,4 +76,4 @@ state.initialStateValue = internal_1.copy(state._value); | ||
this.sideEffects(job); | ||
job.observer.value = internal_1.copy(state.getPublicValue()); | ||
job.observer.previousValue = previousValue; | ||
job.observer.previousValue = internal_1.copy(observer.value); | ||
job.observer.value = internal_1.copy(state._value); | ||
} | ||
@@ -80,3 +85,3 @@ sideEffects(job) { | ||
if (internal_1.isFunction(state.watchers[watcherKey])) | ||
state.watchers[watcherKey](state.getPublicValue(), watcherKey); | ||
state.watchers[watcherKey](state._value, watcherKey); | ||
if ((_b = (_a = job.config) === null || _a === void 0 ? void 0 : _a.sideEffects) === null || _b === void 0 ? void 0 : _b.enabled) { | ||
@@ -83,0 +88,0 @@ const sideEffectArray = internal_1.createArrayFromObject(state.sideEffects); |
@@ -1,60 +0,85 @@ | ||
import { CreatePersistentConfigInterface, Persistent, PersistentKey, State, StorageKey } from '../internal'; | ||
import { CreatePersistentConfigInterface, Persistent, PersistentKey, State } from '../internal'; | ||
export declare class StatePersistent<ValueType = any> extends Persistent { | ||
state: () => State; | ||
static storeValueSideEffectKey: string; | ||
state: () => State; | ||
/** | ||
* Internal Class for managing the permanent persistence of a State. | ||
* | ||
* @internal | ||
* State Persist Manager - Handles permanent storing of State Value | ||
* @param state - State that gets stored | ||
* @param config - Config | ||
* @param state - State to be persisted. | ||
* @param config - Configuration object | ||
*/ | ||
constructor(state: State<ValueType>, config?: CreatePersistentConfigInterface); | ||
/** | ||
* Loads the persisted value into the State | ||
* or persists the State value in the corresponding Storage. | ||
* This behaviour depends on whether the State has been persisted before. | ||
* | ||
* @internal | ||
* Updates Key/Name of Persistent | ||
* @param value - New Key/Name of Persistent | ||
*/ | ||
setKey(value?: StorageKey): Promise<void>; | ||
initialLoading(): Promise<void>; | ||
/** | ||
* Loads the State from the corresponding Storage | ||
* and sets up side effects that dynamically update | ||
* the Storage value when the State changes. | ||
* | ||
* @internal | ||
* Loads/Saves Storage Value for the first Time | ||
* @param storageItemKey - Storage key of the to load State Instance. | ||
* | default = Persistent.key | | ||
* @return Whether the loading of the persisted State Instance and the setting up of the corresponding side effects was successful. | ||
*/ | ||
initialLoading(): Promise<void>; | ||
loadPersistedValue(storageItemKey?: PersistentKey): Promise<boolean>; | ||
/** | ||
* Persists the State in the corresponding Storage | ||
* and sets up side effects that dynamically update | ||
* the Storage value when the State changes. | ||
* | ||
* @internal | ||
* Loads State Value from the Storage | ||
* @param storageKey - Prefix Key of Persisted Instances (default PersistentKey) | ||
* @return Success? | ||
* @param storageItemKey - Storage key of the to persist State Instance. | ||
* | default = Persistent.key | | ||
* @return Whether the persisting of the State Instance and setting up of the corresponding side effects was successful. | ||
*/ | ||
loadPersistedValue(storageKey?: PersistentKey): Promise<boolean>; | ||
persistValue(storageItemKey?: PersistentKey): Promise<boolean>; | ||
/** | ||
* Sets up side effects to keep the Storage value in sync | ||
* with the current State value. | ||
* | ||
* @internal | ||
* Sets everything up so that the State gets saved in the Storage on every Value change | ||
* @param storageKey - Prefix Key of Persisted Instances (default PersistentKey) | ||
* @return Success? | ||
* @param storageItemKey - Storage key of the persisted State Instance. | ||
* | default = Persistent.key | | ||
*/ | ||
persistValue(storageKey?: PersistentKey): Promise<boolean>; | ||
setupSideEffects(storageItemKey?: PersistentKey): void; | ||
/** | ||
* Removes the State from the corresponding Storage. | ||
* -> State is no longer persisted | ||
* | ||
* @internal | ||
* Removes State Value form the Storage | ||
* @param storageKey - Prefix Key of Persisted Instances (default PersistentKey) | ||
* @return Success? | ||
* @param storageItemKey - Storage key of the to remove State Instance. | ||
* | default = Persistent.key | | ||
* @return Whether the removal of the persisted State Instance was successful. | ||
*/ | ||
removePersistedValue(storageKey?: PersistentKey): Promise<boolean>; | ||
removePersistedValue(storageItemKey?: PersistentKey): Promise<boolean>; | ||
/** | ||
* Formats the specified key so that it can be used as a valid Storage key | ||
* and returns the formatted variant of it. | ||
* | ||
* If no formatable key (`undefined`/`null`) was provided, | ||
* an attempt is made to use the State identifier key as Storage key. | ||
* | ||
* @internal | ||
* Formats Storage Key | ||
* @param key - Key that gets formatted | ||
* @param key - Storage key to be formatted. | ||
*/ | ||
formatKey(key?: PersistentKey): PersistentKey | undefined; | ||
formatKey(key: PersistentKey | undefined | null): PersistentKey | undefined; | ||
/** | ||
* Rebuilds Storage value based on the current State value. | ||
* | ||
* @internal | ||
* Rebuilds Storage depending on the State Value (Saves current State Value into the Storage) | ||
* @param state - State that holds the new Value | ||
* @param storageKey - StorageKey where value should be persisted | ||
* @param config - Config | ||
* @param state - State whose current value to be applied to the Storage value. | ||
* @param storageItemKey - Storage key of the persisted State Instance. | ||
* | default = Persistent.key | | ||
* @param config - Configuration object | ||
*/ | ||
rebuildStorageSideEffect(state: State<ValueType>, storageKey: PersistentKey, config?: { | ||
rebuildStorageSideEffect(state: State<ValueType>, storageItemKey: PersistentKey, config?: { | ||
[key: string]: any; | ||
}): void; | ||
} |
@@ -33,20 +33,2 @@ "use strict"; | ||
} | ||
setKey(value) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const oldKey = this._key; | ||
const wasReady = this.ready; | ||
if (value === this._key) | ||
return; | ||
this._key = value || internal_1.Persistent.placeHolderKey; | ||
const isValid = this.validatePersistent(); | ||
if (!wasReady) { | ||
if (isValid) | ||
yield this.initialLoading(); | ||
return; | ||
} | ||
yield this.removePersistedValue(oldKey); | ||
if (isValid) | ||
yield this.persistValue(value); | ||
}); | ||
} | ||
initialLoading() { | ||
@@ -62,24 +44,25 @@ const _super = Object.create(null, { | ||
} | ||
loadPersistedValue(storageKey) { | ||
loadPersistedValue(storageItemKey) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (!this.ready) | ||
return false; | ||
const _storageKey = storageKey || this._key; | ||
const loadedValue = yield this.agileInstance().storages.get(_storageKey, this.config.defaultStorageKey); | ||
if (!loadedValue) | ||
const _storageItemKey = storageItemKey !== null && storageItemKey !== void 0 ? storageItemKey : this._key; | ||
const loadedValue = yield this.agileInstance().storages.get(_storageItemKey, this.config.defaultStorageKey); | ||
if (loadedValue == null) | ||
return false; | ||
this.state().set(loadedValue, { storage: false }); | ||
yield this.persistValue(_storageKey); | ||
this.state().set(loadedValue, { | ||
storage: false, | ||
overwrite: true, | ||
}); | ||
this.setupSideEffects(_storageItemKey); | ||
return true; | ||
}); | ||
} | ||
persistValue(storageKey) { | ||
persistValue(storageItemKey) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (!this.ready) | ||
return false; | ||
const _storageKey = storageKey || this._key; | ||
this.state().addSideEffect(StatePersistent.storeValueSideEffectKey, (instance, config) => { | ||
this.rebuildStorageSideEffect(this.state(), _storageKey, config); | ||
}, { weight: 0 }); | ||
this.rebuildStorageSideEffect(this.state(), _storageKey); | ||
const _storageItemKey = storageItemKey !== null && storageItemKey !== void 0 ? storageItemKey : this._key; | ||
this.setupSideEffects(_storageItemKey); | ||
this.rebuildStorageSideEffect(this.state(), _storageItemKey); | ||
this.isPersisted = true; | ||
@@ -89,9 +72,15 @@ return true; | ||
} | ||
removePersistedValue(storageKey) { | ||
setupSideEffects(storageItemKey) { | ||
const _storageItemKey = storageItemKey !== null && storageItemKey !== void 0 ? storageItemKey : this._key; | ||
this.state().addSideEffect(StatePersistent.storeValueSideEffectKey, (instance, config) => { | ||
this.rebuildStorageSideEffect(this.state(), _storageItemKey, config); | ||
}, { weight: 0 }); | ||
} | ||
removePersistedValue(storageItemKey) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (!this.ready) | ||
return false; | ||
const _storageKey = storageKey || this._key; | ||
const _storageItemKey = storageItemKey || this._key; | ||
this.state().removeSideEffect(StatePersistent.storeValueSideEffectKey); | ||
this.agileInstance().storages.remove(_storageKey, this.storageKeys); | ||
this.agileInstance().storages.remove(_storageItemKey, this.storageKeys); | ||
this.isPersisted = false; | ||
@@ -102,15 +91,14 @@ return true; | ||
formatKey(key) { | ||
const state = this.state(); | ||
if (!key && state._key) | ||
return state._key; | ||
if (!key) | ||
if (key == null && this.state()._key) | ||
return this.state()._key; | ||
if (key == null) | ||
return; | ||
if (!state._key) | ||
state._key = key; | ||
if (this.state()._key == null) | ||
this.state()._key = key; | ||
return key; | ||
} | ||
rebuildStorageSideEffect(state, storageKey, config = {}) { | ||
if (config.storage !== undefined && !config.storage) | ||
return; | ||
this.agileInstance().storages.set(storageKey, this.state().getPersistableValue(), this.storageKeys); | ||
rebuildStorageSideEffect(state, storageItemKey, config = {}) { | ||
if (config['storage'] == null || config.storage) { | ||
this.agileInstance().storages.set(storageItemKey, this.state().getPersistableValue(), this.storageKeys); | ||
} | ||
} | ||
@@ -117,0 +105,0 @@ } |
import { RuntimeJob, RuntimeJobConfigInterface, RuntimeJobKey, StateObserver } from '../internal'; | ||
export declare class StateRuntimeJob extends RuntimeJob<StateObserver> { | ||
config: StateRuntimeJobConfigInterface; | ||
/** | ||
* A State Runtime Job is sent to the Runtime on behalf of the State Observer it represents. | ||
* | ||
* In the Runtime, the State Observer is performed via its `perform()` method | ||
* and the Subscription Containers (UI-Components) | ||
* to which it is subscribed are updated (re-rendered) accordingly. | ||
* | ||
* @internal | ||
* @param observer - State Observer to be represented by the State Runtime Job. | ||
* @param config - Configuration object | ||
*/ | ||
constructor(observer: StateObserver, config?: CreateStateRuntimeJobConfigInterface); | ||
} | ||
/** | ||
* @param key - Key/Name of Job | ||
*/ | ||
export interface CreateStateRuntimeJobConfigInterface extends StateRuntimeJobConfigInterface { | ||
/** | ||
* Key/Name identifier of the State Runtime Job. | ||
* @default undefined | ||
*/ | ||
key?: RuntimeJobKey; | ||
} | ||
/** | ||
* @param overwrite - If whole State Value gets overwritten with Job Value | ||
* @param storage - If Job Value can be saved in Storage | ||
*/ | ||
export interface StateRuntimeJobConfigInterface extends RuntimeJobConfigInterface { | ||
/** | ||
* Whether to overwrite the whole State with the new State value. | ||
* @default false | ||
*/ | ||
overwrite?: boolean; | ||
/** | ||
* If the State is persisted, | ||
* whether to apply the new State value to the external Storages. | ||
* @default true | ||
*/ | ||
storage?: boolean; | ||
} |
@@ -17,2 +17,3 @@ "use strict"; | ||
overwrite: false, | ||
maxTriesToUpdate: 3, | ||
}); | ||
@@ -25,2 +26,3 @@ this.config = { | ||
overwrite: config.overwrite, | ||
maxTriesToUpdate: config.maxTriesToUpdate, | ||
}; | ||
@@ -27,0 +29,0 @@ } |
@@ -10,78 +10,149 @@ import { Agile, Storage, Persistent, StorageKey, StorageItemKey } from '../internal'; | ||
/** | ||
* The Storages Class manages all external Storages for an Agile Instance | ||
* and provides an interface to easily store, | ||
* load and remove values from multiple Storages at once. | ||
* | ||
* @internal | ||
* Storages - Manages Storages of Agile | ||
* @param agileInstance - An Instance of Agile | ||
* @param config - Config | ||
* @param agileInstance - Instance of Agile the Storages belongs to. | ||
* @param config - Configuration object | ||
*/ | ||
constructor(agileInstance: Agile, config?: CreateStoragesConfigInterface); | ||
/** | ||
* Instantiates and registers the | ||
* [Local Storage](https://developer.mozilla.org/de/docs/Web/API/Window/localStorage). | ||
* | ||
* Note that the Local Storage is only available in a web environment. | ||
* | ||
* @internal | ||
* Instantiates Local Storage | ||
*/ | ||
instantiateLocalStorage(): boolean; | ||
/** | ||
* @internal | ||
* Register new Storage as Agile Storage | ||
* @param storage - new Storage | ||
* @param config - Config | ||
* Registers the specified Storage with AgileTs | ||
* and updates the Persistent Instances that have already attempted | ||
* to use the previously unregistered Storage. | ||
* | ||
* @public | ||
* @param storage - Storage to be registered with AgileTs. | ||
* @param config - Configuration object | ||
*/ | ||
register(storage: Storage, config?: RegisterConfigInterface): boolean; | ||
/** | ||
* @internal | ||
* Get Storage at Key/Name | ||
* @param storageKey - Key/Name of Storage | ||
* Retrieves a single Storage with the specified key/name identifier | ||
* from the Storages Class. | ||
* | ||
* If the to retrieve Storage doesn't exist, `undefined` is returned. | ||
* | ||
* @public | ||
* @param storageKey - Key/Name identifier of the Storage. | ||
*/ | ||
getStorage(storageKey: StorageKey | undefined | null): Storage | undefined; | ||
/** | ||
* @internal | ||
* Gets value at provided Key | ||
* @param key - Key of Storage property | ||
* @param storageKey - Key/Name of Storage from which the Item is fetched (if not provided default Storage will be used) | ||
* Retrieves the stored value at the specified Storage Item key | ||
* from the defined external Storage (`storageKey`). | ||
* | ||
* When no Storage has been specified, | ||
* the value is retrieved from the default Storage. | ||
* | ||
* @public | ||
* @param storageItemKey - Key/Name identifier of the value to be retrieved. | ||
* @param storageKey - Key/Name identifier of the external Storage | ||
* from which the value is to be retrieved. | ||
*/ | ||
get<GetType = any>(key: StorageItemKey, storageKey?: StorageKey): Promise<GetType | undefined>; | ||
get<GetType = any>(storageItemKey: StorageItemKey, storageKey?: StorageKey): Promise<GetType | undefined>; | ||
/** | ||
* @internal | ||
* Saves/Updates value at provided Key | ||
* @param key - Key of Storage property | ||
* @param value - new Value that gets set at provided Key | ||
* @param storageKeys - Key/Name of Storages where the Value gets set (if not provided default Storage will be used) | ||
* Stores or updates the value at the specified Storage Item key | ||
* in the defined external Storages (`storageKeys`). | ||
* | ||
* When no Storage has been specified, | ||
* the value is stored/updated in the default Storage. | ||
* | ||
* @public | ||
* @param storageItemKey - Key/Name identifier of the value to be stored. | ||
* @param value - Value to be stored in an external Storage. | ||
* @param storageKeys - Key/Name identifier of the external Storage | ||
* where the value is to be stored. | ||
*/ | ||
set(key: StorageItemKey, value: any, storageKeys?: StorageKey[]): void; | ||
set(storageItemKey: StorageItemKey, value: any, storageKeys?: StorageKey[]): void; | ||
/** | ||
* @internal | ||
* Removes value at provided Key | ||
* @param key - Key of Storage property | ||
* @param storageKeys - Key/Name of Storages where the Value gets removed (if not provided default Storage will be used) | ||
* Removes the value at the specified Storage Item key | ||
* from the defined external Storages (`storageKeys`). | ||
* | ||
* When no Storage has been specified, | ||
* the value is removed from the default Storage. | ||
* | ||
* @public | ||
* @param storageItemKey - Key/Name identifier of the value to be removed. | ||
* @param storageKeys - Key/Name identifier of the external Storage | ||
* from which the value is to be removed. | ||
*/ | ||
remove(key: StorageItemKey, storageKeys?: StorageKey[]): void; | ||
remove(storageItemKey: StorageItemKey, storageKeys?: StorageKey[]): void; | ||
/** | ||
* @internal | ||
* Check if at least one Storage got registered | ||
* Returns a boolean indicating whether any Storage | ||
* has been registered with the Agile Instance or not. | ||
* | ||
* @public | ||
*/ | ||
hasStorage(): boolean; | ||
/** | ||
* @internal | ||
* Checks if localStorage is available in this Environment | ||
* Returns a boolean indication whether the | ||
* [Local Storage](https://developer.mozilla.org/de/docs/Web/API/Window/localStorage) | ||
* is available in the current environment. | ||
* | ||
* @public | ||
*/ | ||
static localStorageAvailable(): boolean; | ||
} | ||
/** | ||
* @param localStorage - If Local Storage should be instantiated | ||
* @param defaultStorage - Default Storage Key | ||
*/ | ||
export interface CreateStoragesConfigInterface { | ||
/** | ||
* Whether to register the Local Storage by default. | ||
* Note that the Local Storage is only available in a web environment. | ||
* @default false | ||
*/ | ||
localStorage?: boolean; | ||
/** | ||
* Key/Name identifier of the default Storage. | ||
* | ||
* The default Storage represents the default Storage of the Storages Class, | ||
* on which executed actions are performed if no specific Storage was specified. | ||
* | ||
* Also, the persisted value is loaded from the default Storage by default, | ||
* since only one persisted value can be applied. | ||
* If the loading of the value from the default Storage failed, | ||
* an attempt is made to load the value from the remaining Storages. | ||
* | ||
* @default undefined | ||
*/ | ||
defaultStorageKey?: StorageKey; | ||
} | ||
/** | ||
* @param defaultStorage - Default Storage Key | ||
*/ | ||
export interface StoragesConfigInterface { | ||
/** | ||
* Key/Name identifier of the default Storage. | ||
* | ||
* The default Storage represents the default Storage of the Storages Class, | ||
* on which executed actions are performed if no specific Storage was specified. | ||
* | ||
* Also, the persisted value is loaded from the default Storage by default, | ||
* since only one persisted value can be applied. | ||
* If the loading of the value from the default Storage failed, | ||
* an attempt is made to load the value from the remaining Storages. | ||
* | ||
* @default undefined | ||
*/ | ||
defaultStorageKey: StorageKey | null; | ||
} | ||
/** | ||
* @param default - If the registered Storage gets the default Storage | ||
*/ | ||
export interface RegisterConfigInterface { | ||
/** | ||
* Whether the to register Storage should become the default Storage. | ||
* | ||
* The default Storage represents the default Storage of the Storages Class, | ||
* on which executed actions are performed if no specific Storage was specified. | ||
* | ||
* Also, the persisted value is loaded from the default Storage by default, | ||
* since only one persisted value can be applied. | ||
* If the loading of the value from the default Storage failed, | ||
* an attempt is made to load the value from the remaining Storages. | ||
* | ||
* @default false | ||
*/ | ||
default?: boolean; | ||
} |
@@ -20,3 +20,3 @@ "use strict"; | ||
if (!Storages.localStorageAvailable()) { | ||
internal_1.Agile.logger.warn('Local Storage is here not available, to use Storage functionalities like persist please provide a custom Storage!'); | ||
internal_1.LogCodeManager.log('11:02:00'); | ||
return false; | ||
@@ -38,8 +38,7 @@ } | ||
if (Object.prototype.hasOwnProperty.call(this.storages, storage.key)) { | ||
internal_1.Agile.logger.error(`Storage with the key/name '${storage.key}' already exists`); | ||
internal_1.LogCodeManager.log('11:03:00', [storage.key]); | ||
return false; | ||
} | ||
if (!hasRegisteredAnyStorage && config.default === false) { | ||
internal_1.Agile.logger.warn('Be aware that Agile has to assign the first added Storage as default Storage!'); | ||
} | ||
if (!hasRegisteredAnyStorage && config.default === false) | ||
internal_1.LogCodeManager.log('11:02:01'); | ||
if (!hasRegisteredAnyStorage) | ||
@@ -57,3 +56,3 @@ config.default = true; | ||
} | ||
if (!persistent.config.defaultStorageKey) { | ||
if (persistent.config.defaultStorageKey == null) { | ||
persistent.assignStorageKeys(); | ||
@@ -72,7 +71,7 @@ const isValid = persistent.validatePersistent(); | ||
if (!storage) { | ||
internal_1.Agile.logger.error(`Storage with the key/name '${storageKey}' doesn't exist!`); | ||
internal_1.LogCodeManager.log('11:03:01', [storageKey]); | ||
return undefined; | ||
} | ||
if (!storage.ready) { | ||
internal_1.Agile.logger.error(`Storage with the key/name '${storageKey}' isn't ready yet!`); | ||
internal_1.LogCodeManager.log('11:03:02', [storageKey]); | ||
return undefined; | ||
@@ -82,5 +81,5 @@ } | ||
} | ||
get(key, storageKey) { | ||
get(storageItemKey, storageKey) { | ||
if (!this.hasStorage()) { | ||
internal_1.Agile.logger.error('No Storage found! Please provide at least one Storage.'); | ||
internal_1.LogCodeManager.log('11:03:03'); | ||
return Promise.resolve(undefined); | ||
@@ -91,25 +90,25 @@ } | ||
if (storage) | ||
return storage.get(key); | ||
return storage.get(storageItemKey); | ||
} | ||
const defaultStorage = this.getStorage(this.config.defaultStorageKey); | ||
return (defaultStorage === null || defaultStorage === void 0 ? void 0 : defaultStorage.get(key)) || Promise.resolve(undefined); | ||
return ((defaultStorage === null || defaultStorage === void 0 ? void 0 : defaultStorage.get(storageItemKey)) || Promise.resolve(undefined)); | ||
} | ||
set(key, value, storageKeys) { | ||
set(storageItemKey, value, storageKeys) { | ||
var _a; | ||
if (!this.hasStorage()) { | ||
internal_1.Agile.logger.error('No Storage found! Please provide at least one Storage.'); | ||
internal_1.LogCodeManager.log('11:03:04'); | ||
return; | ||
} | ||
if (storageKeys) { | ||
if (storageKeys != null) { | ||
for (const storageKey of storageKeys) | ||
(_a = this.getStorage(storageKey)) === null || _a === void 0 ? void 0 : _a.set(key, value); | ||
(_a = this.getStorage(storageKey)) === null || _a === void 0 ? void 0 : _a.set(storageItemKey, value); | ||
return; | ||
} | ||
const defaultStorage = this.getStorage(this.config.defaultStorageKey); | ||
defaultStorage === null || defaultStorage === void 0 ? void 0 : defaultStorage.set(key, value); | ||
defaultStorage === null || defaultStorage === void 0 ? void 0 : defaultStorage.set(storageItemKey, value); | ||
} | ||
remove(key, storageKeys) { | ||
remove(storageItemKey, storageKeys) { | ||
var _a; | ||
if (!this.hasStorage()) { | ||
internal_1.Agile.logger.error('No Storage found! Please provide at least one Storage.'); | ||
internal_1.LogCodeManager.log('11:03:05'); | ||
return; | ||
@@ -119,7 +118,7 @@ } | ||
for (const storageKey of storageKeys) | ||
(_a = this.getStorage(storageKey)) === null || _a === void 0 ? void 0 : _a.remove(key); | ||
(_a = this.getStorage(storageKey)) === null || _a === void 0 ? void 0 : _a.remove(storageItemKey); | ||
return; | ||
} | ||
const defaultStorage = this.getStorage(this.config.defaultStorageKey); | ||
defaultStorage === null || defaultStorage === void 0 ? void 0 : defaultStorage.remove(key); | ||
defaultStorage === null || defaultStorage === void 0 ? void 0 : defaultStorage.remove(storageItemKey); | ||
} | ||
@@ -126,0 +125,0 @@ hasStorage() { |
@@ -12,72 +12,124 @@ import { Agile, StorageKey } from '../internal'; | ||
/** | ||
* A Persistent manages the permanent persistence | ||
* of an Agile Class such as the `State Class` in external Storages. | ||
* | ||
* Note that the Persistent itself is no standalone class | ||
* and should be adapted to the Agile Class needs it belongs to. | ||
* | ||
* @internal | ||
* Persistent - Handles storing of Agile Instances | ||
* Note: No stand alone class!! | ||
* @param agileInstance - An instance of Agile | ||
* @param config - Config | ||
* @param agileInstance - Instance of Agile the Persistent belongs to. | ||
* @param config - Configuration object | ||
*/ | ||
constructor(agileInstance: Agile, config?: CreatePersistentConfigInterface); | ||
/** | ||
* @internal | ||
* Set Key/Name of Persistent | ||
* Updates the key/name identifier of the Persistent. | ||
* | ||
* @public | ||
* @param value - New key/name identifier. | ||
*/ | ||
set key(value: StorageKey); | ||
/** | ||
* @internal | ||
* Get Key/Name of Persistent | ||
* Returns the key/name identifier of the Persistent. | ||
* | ||
* @public | ||
*/ | ||
get key(): StorageKey; | ||
/** | ||
* Updates the key/name identifier of the Persistent. | ||
* | ||
* @public | ||
* Sets Key/Name of Persistent | ||
* @param value - New Key/Name of Persistent | ||
* @param value - New key/name identifier. | ||
*/ | ||
setKey(value: StorageKey): void; | ||
setKey(value?: StorageKey): Promise<void>; | ||
/** | ||
* Instantiates the Persistent by assigning the specified Storage keys to it | ||
* and validating it to make sure everything was setup correctly. | ||
* | ||
* This was moved out of the `constructor()` | ||
* because some classes (that extend the Persistent) need to configure some | ||
* things before they can properly instantiate the parent Persistent. | ||
* | ||
* @internal | ||
* Instantiates this Class | ||
* Note: Had to outsource it from the constructor because some extending classes | ||
* have to define some stuff before being able to instantiate the parent (this) | ||
* @param config - Config | ||
* @param config - Configuration object | ||
*/ | ||
instantiatePersistent(config?: InstantiatePersistentConfigInterface): void; | ||
/** | ||
* Returns a boolean indicating whether the Persistent was setup correctly | ||
* and is able to persist a value permanently in an external Storage. | ||
* | ||
* Based on the tapped boolean value, | ||
* the Persistent's `ready` property is updated. | ||
* | ||
* @internal | ||
* Validates Persistent and updates its 'ready' property | ||
*/ | ||
validatePersistent(): boolean; | ||
/** | ||
* Assigns the specified Storage identifiers to the Persistent | ||
* and extracts the default Storage if necessary. | ||
* | ||
* When no Storage key was provided the default Storage | ||
* of the Agile Instance is applied to the Persistent. | ||
* | ||
* @internal | ||
* Assign new StorageKeys to Persistent and overwrite the old ones | ||
* @param storageKeys - New Storage Keys | ||
* @param defaultStorageKey - Key of default Storage | ||
* @param storageKeys - Key/Name identifier of the Storages to be assigned. | ||
* @param defaultStorageKey - Key/Name identifier of the default Storage. | ||
*/ | ||
assignStorageKeys(storageKeys?: StorageKey[], defaultStorageKey?: StorageKey): void; | ||
/** | ||
* Stores or loads the Persistent value | ||
* from the external Storages for the first time. | ||
* | ||
* @internal | ||
* Loads/Saves Storage Value for the first Time | ||
*/ | ||
initialLoading(): Promise<void>; | ||
/** | ||
* Loads the Persistent value from the corresponding Storage. | ||
* | ||
* Note that this method should be overwritten | ||
* to correctly apply the changes to the Agile Class | ||
* the Persistent belongs to. | ||
* | ||
* @internal | ||
* Loads Value from Storage | ||
* @return Success? | ||
* @param storageItemKey - Storage key of the to load value. | ||
* | default = Persistent.key | | ||
* @return Whether the loading of the persisted value was successful. | ||
*/ | ||
loadPersistedValue(): Promise<boolean>; | ||
loadPersistedValue(storageItemKey?: PersistentKey): Promise<boolean>; | ||
/** | ||
* Persists the Persistent value in the corresponding Storage. | ||
* | ||
* Note that this method should be overwritten | ||
* to correctly apply the changes to the Agile Class | ||
* the Persistent belongs to. | ||
* | ||
* @internal | ||
* Saves/Updates Value in Storage | ||
* @return Success? | ||
* @param storageItemKey - Storage key of the to persist value | ||
* | default = Persistent.key | | ||
* @return Whether the persisting of the value was successful. | ||
*/ | ||
persistValue(): Promise<boolean>; | ||
persistValue(storageItemKey?: PersistentKey): Promise<boolean>; | ||
/** | ||
* Removes the Persistent value from the corresponding Storage. | ||
* -> Persistent value is no longer persisted | ||
* | ||
* Note that this method should be overwritten | ||
* to correctly apply the changes to the Agile Class | ||
* the Persistent belongs to. | ||
* | ||
* @internal | ||
* Removes Value form Storage | ||
* @return Success? | ||
* @param storageItemKey - Storage key of the to remove value. | ||
* | default = Persistent.key | | ||
* @return Whether the removal of the persisted value was successful. | ||
*/ | ||
removePersistedValue(): Promise<boolean>; | ||
removePersistedValue(storageItemKey?: PersistentKey): Promise<boolean>; | ||
/** | ||
* Formats the specified key so that it can be used as a valid Storage key | ||
* and returns the formatted variant of it. | ||
* | ||
* Note that this method should be overwritten | ||
* to correctly apply the changes to the Agile Class | ||
* the Persistent belongs to. | ||
* | ||
* @internal | ||
* Validates Storage Key | ||
* @param key - Key that gets validated | ||
* @param key - Storage key to be formatted. | ||
*/ | ||
@@ -87,29 +139,68 @@ formatKey(key?: PersistentKey): PersistentKey | undefined; | ||
export declare type PersistentKey = string | number; | ||
/** | ||
* @param key - Key/Name of Persistent | ||
* @param storageKeys - Keys of Storages in that the persisted Value gets saved | ||
* @param defaultStorage - Default Storage Key | ||
* @param instantiate - If Persistent gets Instantiated immediately | ||
*/ | ||
export interface CreatePersistentConfigInterface { | ||
/** | ||
* Key/Name identifier of the Persistent. | ||
*/ | ||
key?: PersistentKey; | ||
/** | ||
* Key/Name identifier of Storages | ||
* in which the Persistent value is to be persisted | ||
* or is already persisted. | ||
* @default [`defaultStorageKey`] | ||
*/ | ||
storageKeys?: StorageKey[]; | ||
/** | ||
* Key/Name identifier of the default Storage of the specified Storage keys. | ||
* | ||
* The persisted value is loaded from the default Storage by default, | ||
* since only one persisted value can be applied. | ||
* If the loading of the value from the default Storage failed, | ||
* an attempt is made to load the value from the remaining Storages. | ||
* | ||
* @default first index of the specified Storage keys or the Agile Instance's default Storage key | ||
*/ | ||
defaultStorageKey?: StorageKey; | ||
/** | ||
* Whether the Persistent should be instantiated immediately | ||
* or whether this should be done manually. | ||
* @default true | ||
*/ | ||
instantiate?: boolean; | ||
} | ||
/** | ||
* @param defaultStorageKey - Default Storage Key | ||
*/ | ||
export interface PersistentConfigInterface { | ||
/** | ||
* Key/Name identifier of the default Storage of the specified Storage keys. | ||
* | ||
* The persisted value is loaded from the default Storage by default, | ||
* since only one persisted value can be applied. | ||
* If the loading of the value from the default Storage failed, | ||
* an attempt is made to load the value from the remaining Storages. | ||
* | ||
* @default first index of the specified Storage keys or the Agile Instance's default Storage key | ||
*/ | ||
defaultStorageKey: StorageKey | null; | ||
} | ||
/** | ||
* @param key - Key/Name of Persistent | ||
* @param storageKeys - Keys of Storages in that the persisted Value gets saved | ||
* @param defaultStorageKey - Default Storage Key (if not provided it takes the first index of storageKeys or the AgileTs default Storage) | ||
*/ | ||
export interface InstantiatePersistentConfigInterface { | ||
/** | ||
* Key/Name identifier of the Persistent. | ||
*/ | ||
key?: PersistentKey; | ||
/** | ||
* Key/Name identifier of Storages | ||
* in which the Persistent value is to be persisted | ||
* or is already persisted. | ||
* @default [`defaultStorageKey`] | ||
*/ | ||
storageKeys?: StorageKey[]; | ||
/** | ||
* Key/Name identifier of the default Storage of the specified Storage keys. | ||
* | ||
* The persisted value is loaded from the default Storage by default, | ||
* since only one persisted value can be applied. | ||
* If the loading of the value from the default Storage failed, | ||
* an attempt is made to load the value from the remaining Storages. | ||
* | ||
* @default first index of the specified Storage keys or the Agile Instance's default Storage key | ||
*/ | ||
defaultStorageKey?: StorageKey; | ||
} |
@@ -43,6 +43,22 @@ "use strict"; | ||
setKey(value) { | ||
this._key = value; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const oldKey = this._key; | ||
const wasReady = this.ready; | ||
if (value === this._key) | ||
return; | ||
this._key = value !== null && value !== void 0 ? value : Persistent.placeHolderKey; | ||
const isValid = this.validatePersistent(); | ||
if (!wasReady) { | ||
if (isValid) | ||
yield this.initialLoading(); | ||
return; | ||
} | ||
yield this.removePersistedValue(oldKey); | ||
if (isValid) | ||
yield this.persistValue(value); | ||
}); | ||
} | ||
instantiatePersistent(config = {}) { | ||
this._key = this.formatKey(config.key) || Persistent.placeHolderKey; | ||
var _a; | ||
this._key = (_a = this.formatKey(config.key)) !== null && _a !== void 0 ? _a : Persistent.placeHolderKey; | ||
this.assignStorageKeys(config.storageKeys, config.defaultStorageKey); | ||
@@ -54,7 +70,7 @@ this.validatePersistent(); | ||
if (this._key === Persistent.placeHolderKey) { | ||
internal_1.Agile.logger.error('No valid persist Key found! Please provide a Key or assign one to the parent instance.'); | ||
internal_1.LogCodeManager.log('12:03:00'); | ||
isValid = false; | ||
} | ||
if (!this.config.defaultStorageKey || this.storageKeys.length <= 0) { | ||
internal_1.Agile.logger.error('No persist Storage Key found! Please provide at least one Storage Key.'); | ||
internal_1.LogCodeManager.log('12:03:01'); | ||
isValid = false; | ||
@@ -64,3 +80,3 @@ } | ||
if (!this.agileInstance().storages.storages[key]) { | ||
internal_1.Agile.logger.error(`Storage '${key}' doesn't exist yet. Please provide only existing StorageKeys!`); | ||
internal_1.LogCodeManager.log('12:03:02', [this._key, key]); | ||
isValid = false; | ||
@@ -75,6 +91,4 @@ } | ||
const _storageKeys = internal_1.copy(storageKeys); | ||
if (defaultStorageKey && !_storageKeys.includes(defaultStorageKey)) { | ||
internal_1.Agile.logger.warn(`Default Storage Key '${defaultStorageKey}' isn't contained in storageKeys!`, _storageKeys); | ||
if (defaultStorageKey && !_storageKeys.includes(defaultStorageKey)) | ||
_storageKeys.push(defaultStorageKey); | ||
} | ||
if (_storageKeys.length <= 0) { | ||
@@ -85,3 +99,3 @@ this.config.defaultStorageKey = storages.config.defaultStorageKey; | ||
else { | ||
this.config.defaultStorageKey = defaultStorageKey || _storageKeys[0]; | ||
this.config.defaultStorageKey = defaultStorageKey !== null && defaultStorageKey !== void 0 ? defaultStorageKey : _storageKeys[0]; | ||
} | ||
@@ -99,17 +113,17 @@ this.storageKeys = _storageKeys; | ||
} | ||
loadPersistedValue() { | ||
loadPersistedValue(storageItemKey) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
internal_1.Agile.logger.error(`'loadPersistedValue' function isn't Set in Persistent! Be aware that Persistent is no stand alone class!`); | ||
internal_1.LogCodeManager.log('00:03:00', ['loadPersistedValue', 'Persistent']); | ||
return false; | ||
}); | ||
} | ||
persistValue() { | ||
persistValue(storageItemKey) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
internal_1.Agile.logger.error(`'persistValue' function isn't Set in Persistent! Be aware that Persistent is no stand alone class!`); | ||
internal_1.LogCodeManager.log('00:03:00', ['persistValue', 'Persistent']); | ||
return false; | ||
}); | ||
} | ||
removePersistedValue() { | ||
removePersistedValue(storageItemKey) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
internal_1.Agile.logger.error(`'removePersistedValue' function isn't Set in Persistent! Be aware that Persistent is no stand alone class!`); | ||
internal_1.LogCodeManager.log('00:03:00', ['removePersistedValue', 'Persistent']); | ||
return false; | ||
@@ -116,0 +130,0 @@ }); |
export declare class Storage { | ||
config: StorageConfigInterface; | ||
key: StorageKey; | ||
ready: boolean; | ||
methods: StorageMethodsInterface; | ||
config: StorageConfigInterface; | ||
/** | ||
* A Storage is an interface to an external Storage, | ||
* and allows the easy interaction with that Storage. | ||
* | ||
* Due to the Storage, AgileTs can easily persist its Instances in almost any Storage | ||
* without a huge overhead. | ||
* | ||
* @public | ||
* Storage - Interface for storing Items permanently | ||
* @param config - Config | ||
* @param config - Configuration object | ||
*/ | ||
constructor(config: CreateStorageConfigInterface); | ||
/** | ||
* Returns a boolean indicating whether the Storage is valid | ||
* and can be used to persist Instances in it or not. | ||
* | ||
* @public | ||
* Validates Storage Methods | ||
*/ | ||
validate(): boolean; | ||
/** | ||
* @internal | ||
* Gets value at provided Key (normal) | ||
* Note: Only use this if you are 100% sure this Storage doesn't work async | ||
* @param key - Key of Storage property | ||
* Synchronously retrieves the stored value | ||
* at the specified Storage Item key from the external Storage. | ||
* | ||
* When the retrieved value is a JSON-String it is parsed automatically. | ||
* | ||
* @public | ||
* @param key - Key/Name identifier of the value to be retrieved. | ||
*/ | ||
normalGet<GetTpe = any>(key: StorageItemKey): GetTpe | undefined; | ||
/** | ||
* @internal | ||
* Gets value at provided Key (async) | ||
* @param key - Key of Storage property | ||
* Asynchronously retrieves the stored value | ||
* at the specified Storage Item key from the external Storage. | ||
* | ||
* When the retrieved value is a JSON-String it is parsed automatically. | ||
* | ||
* @public | ||
* @param key - Key/Name identifier of the value to be retrieved. | ||
*/ | ||
get<GetTpe = any>(key: StorageItemKey): Promise<GetTpe | undefined>; | ||
/** | ||
* Stores or updates the value at the specified Storage Item key | ||
* in the external Storage. | ||
* | ||
* @public | ||
* Saves/Updates value at provided Key | ||
* @param key - Key of Storage property | ||
* @param value - new Value that gets set | ||
* @param key - Key/Name identifier of the value to be stored or updated. | ||
* @param value - Value to be stored. | ||
*/ | ||
set(key: StorageItemKey, value: any): void; | ||
/** | ||
* Removes the value at the specified Storage Item key | ||
* from the external Storage. | ||
* | ||
* @public | ||
* Removes value at provided Key | ||
* @param key - Key of Storage property | ||
* @param key - Key/Name identifier of the value to be removed. | ||
*/ | ||
remove(key: StorageItemKey): void; | ||
/** | ||
* Generates and returns a valid Storage key based on the specified key. | ||
* | ||
* @internal | ||
* Creates Storage Key from provided key | ||
* @param key - Key that gets converted into a Storage Key | ||
* @param key - Key to be converted into a valid Storage key. | ||
*/ | ||
@@ -52,27 +71,46 @@ getStorageKey(key: StorageItemKey): string; | ||
export declare type StorageItemKey = string | number; | ||
/** | ||
* @param key - Key/Name of Storage | ||
* @param methods - Storage methods like (get, set, remove) | ||
*/ | ||
export interface CreateStorageConfigInterface extends StorageConfigInterface { | ||
/** | ||
* Key/Name identifier of the Storage. | ||
* @default undefined | ||
*/ | ||
key: string; | ||
/** | ||
* Storage methods for interacting with the external Storage. | ||
* @default undefined | ||
*/ | ||
methods: StorageMethodsInterface; | ||
} | ||
/** | ||
* @param get - Get Method of Storage (gets items from storage) | ||
* @param set - Set Method of Storage (saves/updates items in storage) | ||
* @param remove - Remove Methods of Storage (removes items from storage) | ||
*/ | ||
export interface StorageMethodsInterface { | ||
/** | ||
* Method to retrieve a value at the specified key from the external Storage. | ||
* | ||
* @param key - Key/Name identifier of the value to be retrieved. | ||
*/ | ||
get: (key: string) => any; | ||
/** | ||
* Method to store or update a value at the specified key in the external Storage. | ||
* | ||
* @param key - Key/Name identifier of the value to be stored or updated. | ||
* @param value - Value to be stored. | ||
*/ | ||
set: (key: string, value: any) => void; | ||
/** | ||
* Method to remove a value at the specified key from the external Storage. | ||
* | ||
* @param key - Key/Name identifier of the value to be removed. | ||
*/ | ||
remove: (key: string) => void; | ||
} | ||
/** | ||
* @param async - If its an async storage | ||
* @param prefix - Prefix of Storage Property | ||
*/ | ||
export interface StorageConfigInterface { | ||
/** | ||
* Whether the external Storage represented by the Storage Class works async. | ||
* @default Automatically detected via the `isAsyncFunction()` method | ||
*/ | ||
async?: boolean; | ||
/** | ||
* Prefix to be added before each persisted value key/name identifier. | ||
* @default 'agile' | ||
*/ | ||
prefix?: string; | ||
} |
@@ -29,11 +29,11 @@ "use strict"; | ||
if (!internal_1.isFunction((_a = this.methods) === null || _a === void 0 ? void 0 : _a.get)) { | ||
internal_1.Agile.logger.error("Your GET StorageMethod isn't valid!"); | ||
internal_1.LogCodeManager.log('13:03:00', ['get']); | ||
return false; | ||
} | ||
if (!internal_1.isFunction((_b = this.methods) === null || _b === void 0 ? void 0 : _b.set)) { | ||
internal_1.Agile.logger.error("Your SET StorageMethod isn't valid!"); | ||
internal_1.LogCodeManager.log('13:03:00', ['set']); | ||
return false; | ||
} | ||
if (!internal_1.isFunction((_c = this.methods) === null || _c === void 0 ? void 0 : _c.remove)) { | ||
internal_1.Agile.logger.error("Your REMOVE StorageMethod isn't valid!"); | ||
internal_1.LogCodeManager.log('13:03:00', ['remove']); | ||
return false; | ||
@@ -45,9 +45,11 @@ } | ||
if (!this.ready || !this.methods.get) | ||
return; | ||
return undefined; | ||
if (internal_1.isAsyncFunction(this.methods.get)) | ||
internal_1.Agile.logger.warn("Be aware that 'normalGet' returns a Promise with a stringified Value if using it in an async Storage!"); | ||
internal_1.LogCodeManager.log('13:02:00'); | ||
const res = this.methods.get(this.getStorageKey(key)); | ||
if (internal_1.isJsonString(res)) | ||
return JSON.parse(res); | ||
return res; | ||
const _res = internal_1.isJsonString(res) ? JSON.parse(res) : res; | ||
internal_1.Agile.logger.if | ||
.tag(['storage']) | ||
.info(internal_1.LogCodeManager.getLog('13:01:00', [this.key, this.getStorageKey(key)]), _res); | ||
return _res; | ||
} | ||
@@ -62,5 +64,10 @@ get(key) { | ||
(_a = this.methods) === null || _a === void 0 ? void 0 : _a.get(this.getStorageKey(key)).then((res) => { | ||
if (internal_1.isJsonString(res)) | ||
resolve(JSON.parse(res)); | ||
resolve(res); | ||
const _res = internal_1.isJsonString(res) ? JSON.parse(res) : res; | ||
internal_1.Agile.logger.if | ||
.tag(['storage']) | ||
.info(internal_1.LogCodeManager.getLog('13:01:00', [ | ||
this.key, | ||
this.getStorageKey(key), | ||
]), _res); | ||
resolve(_res); | ||
}).catch(reject); | ||
@@ -72,2 +79,5 @@ }); | ||
return; | ||
internal_1.Agile.logger.if | ||
.tag(['storage']) | ||
.info(internal_1.LogCodeManager.getLog('13:01:01', [this.key, this.getStorageKey(key)]), value); | ||
this.methods.set(this.getStorageKey(key), JSON.stringify(value)); | ||
@@ -78,2 +88,5 @@ } | ||
return; | ||
internal_1.Agile.logger.if | ||
.tag(['storage']) | ||
.info(internal_1.LogCodeManager.getLog('13:01:02', [this.key, this.getStorageKey(key)])); | ||
this.methods.remove(this.getStorageKey(key)); | ||
@@ -80,0 +93,0 @@ } |
import { Agile, Observer } from './internal'; | ||
/** | ||
* Extracts an Instance of Agile from the specified Instance. | ||
* When no valid Agile Instance was found, | ||
* it returns the global bound Agile Instance or `undefined`. | ||
* | ||
* @internal | ||
* Tries to get an Instance of Agile from provided Instance | ||
* If no agileInstance found it returns the global bound Agile Instance | ||
* @param instance - Instance that might hold an Agile Instance | ||
* @param instance - Instance to extract the Agile Instance from. | ||
*/ | ||
export declare function getAgileInstance(instance: any): Agile | undefined; | ||
/** | ||
* @private | ||
* Extract Observers from specific Instances | ||
* @param instances - Instances that will be formatted | ||
* Extracts all Observers from the specified Instances | ||
* and returns them in the given order. | ||
* | ||
* ``` | ||
* const response = extractObservers([myState, myGroup, undefined]); | ||
* console.log(response); // See below | ||
* { | ||
* {value: Observer}, | ||
* {value: Observer, output: Observer}, | ||
* {} | ||
* } | ||
* ``` | ||
* | ||
* @internal | ||
* @param instances - Instances to extract the Observers from. | ||
*/ | ||
export declare function extractObservers(instances: any): Array<Observer | undefined>; | ||
export declare function extractObservers(instances: Array<any>): Array<{ | ||
[key: string]: Observer | undefined; | ||
}>; | ||
/** | ||
* Extracts all Observers from the specified Instance. | ||
* | ||
* ``` | ||
* const response = extractObservers(myState); | ||
* console.log(response); // See below | ||
* { | ||
* value: Observer | ||
* } | ||
* ``` | ||
* | ||
* @internal | ||
* Binds passed Instance globally at passed Key | ||
* @param instances - Instance to extract the Observers from. | ||
*/ | ||
export declare function extractObservers(instances: any): { | ||
[key: string]: Observer | undefined; | ||
}; | ||
/** | ||
* Extracts the most relevant Observers | ||
* from the specified Instance/s in array shape | ||
* and returns the extracted Observers in the given order. | ||
* | ||
* What type of Observer is extracted from an Instance, | ||
* depends on the specified `observerType`. | ||
* If no `observerType` is specified, the Observers found in the dependency | ||
* are selected in the following `observerType` order. | ||
* 1. `output` | ||
* 2. `value` | ||
* | ||
* @internal | ||
* @param instances - Instances in array shape to extract the Observers from. | ||
* @param observerType - Type of Observer to be extracted. | ||
*/ | ||
export declare function extractRelevantObservers<X extends Array<any>>(instances: X, observerType?: string): Array<Observer | undefined>; | ||
/** | ||
* Extracts the most relevant Observers | ||
* from the specified Instance/s in object shape | ||
* and returns the extracted Observers in the given order. | ||
* | ||
* What type of Observer is extracted from an Instance, | ||
* depends on the specified `observerType`. | ||
* If no `observerType` is specified, the Observers found in the dependency | ||
* are selected in the following `observerType` order. | ||
* 1. `output` | ||
* 2. `value` | ||
* | ||
* @internal | ||
* @param instances - Instances in object shape to extract the Observers from. | ||
* @param observerType - Type of Observer to be extracted. | ||
*/ | ||
export declare function extractRelevantObservers<X extends { | ||
[key: string]: any; | ||
}>(instances: X, observerType?: string): { | ||
[key: string]: Observer | undefined; | ||
}; | ||
/** | ||
* Retrieves the module with the specified key/name identifier | ||
* and returns `null` if the module couldn't be found. | ||
* | ||
* @param moduleName - Key/Name identifier of the module to be retrieved. | ||
* @param error - Whether to print an error, when the module couldn't be retrieved. | ||
*/ | ||
export declare function optionalRequire<PackageType = any>(moduleName: string, error?: boolean): PackageType | null; | ||
/** | ||
* Binds the specified Instance globally at the provided key identifier. | ||
* | ||
* Learn more about global bound instances: | ||
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis | ||
* https://blog.logrocket.com/what-is-globalthis-why-use-it/ | ||
* @param key - Key/Name of Instance | ||
* @param instance - Instance | ||
* @param overwrite - If already existing instance at passed Key gets overwritten | ||
* | ||
* @public | ||
* @param key - Key/Name identifier of the specified Instance. | ||
* @param instance - Instance to be bound globally. | ||
* @param overwrite - When already an Instance exists globally at the specified key, | ||
* whether to overwrite it with the new Instance. | ||
*/ | ||
export declare function globalBind(key: string, instance: any, overwrite?: boolean): boolean; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.globalBind = exports.extractObservers = exports.getAgileInstance = void 0; | ||
exports.globalBind = exports.optionalRequire = exports.extractRelevantObservers = exports.extractObservers = exports.getAgileInstance = void 0; | ||
const internal_1 = require("./internal"); | ||
@@ -17,3 +17,3 @@ function getAgileInstance(instance) { | ||
catch (e) { | ||
internal_1.Agile.logger.error('Failed to get Agile Instance from ', instance); | ||
internal_1.LogCodeManager.log('20:03:00', [], instance); | ||
} | ||
@@ -24,3 +24,3 @@ return undefined; | ||
function extractObservers(instances) { | ||
const instancesArray = []; | ||
const observers = []; | ||
const tempInstancesArray = internal_1.normalizeArray(instances, { | ||
@@ -30,23 +30,67 @@ createUndefinedArray: true, | ||
for (const instance of tempInstancesArray) { | ||
if (!instance) { | ||
instancesArray.push(undefined); | ||
if (instance == null) { | ||
observers.push({}); | ||
continue; | ||
} | ||
if (instance instanceof internal_1.Collection) { | ||
instancesArray.push(instance.getGroupWithReference(instance.config.defaultGroupKey).observer); | ||
observers.push(instance.getGroupWithReference(instance.config.defaultGroupKey) | ||
.observers); | ||
continue; | ||
} | ||
if (instance['observer'] && instance['observer'] instanceof internal_1.Observer) { | ||
instancesArray.push(instance['observer']); | ||
observers.push({ value: instance['observer'] }); | ||
continue; | ||
} | ||
if (instance['observers']) { | ||
const extractedObservers = {}; | ||
for (const key in instance['observers']) { | ||
if (instance['observers'][key] instanceof internal_1.Observer) { | ||
extractedObservers[key] = instance['observers'][key]; | ||
} | ||
} | ||
observers.push(extractedObservers); | ||
continue; | ||
} | ||
if (instance instanceof internal_1.Observer) { | ||
instancesArray.push(instance); | ||
observers.push({ value: instance }); | ||
continue; | ||
} | ||
instancesArray.push(undefined); | ||
observers.push({}); | ||
} | ||
return instancesArray; | ||
return Array.isArray(instances) ? observers : observers[0]; | ||
} | ||
exports.extractObservers = extractObservers; | ||
function extractRelevantObservers(instances, observerType) { | ||
var _a; | ||
const depsWithIndicator = {}; | ||
const depsWithNoIndicator = []; | ||
for (const depKey in instances) { | ||
const extractedObservers = extractObservers(instances[depKey]); | ||
let observer = undefined; | ||
if (observerType != null && extractedObservers[observerType] != null) | ||
observer = extractedObservers[observerType]; | ||
if (observerType == null) | ||
observer = (_a = extractedObservers['output']) !== null && _a !== void 0 ? _a : extractedObservers['value']; | ||
if (Array.isArray(instances)) | ||
depsWithNoIndicator.push(observer); | ||
else | ||
depsWithIndicator[depKey] = observer; | ||
} | ||
return Array.isArray(instances) ? depsWithNoIndicator : depsWithIndicator; | ||
} | ||
exports.extractRelevantObservers = extractRelevantObservers; | ||
function optionalRequire(moduleName, error = true) { | ||
let requiredPackage = null; | ||
try { | ||
requiredPackage = require(moduleName); | ||
} | ||
catch (e) { | ||
if (error) { | ||
internal_1.LogCodeManager.log('20:03:02', [moduleName]); | ||
console.log(e); | ||
} | ||
} | ||
return requiredPackage; | ||
} | ||
exports.optionalRequire = optionalRequire; | ||
function globalBind(key, instance, overwrite = false) { | ||
@@ -64,3 +108,3 @@ try { | ||
catch (e) { | ||
internal_1.Agile.logger.error(`Failed to create global Instance called '${key}'`); | ||
internal_1.LogCodeManager.log('20:03:01', [key]); | ||
} | ||
@@ -67,0 +111,0 @@ return false; |
{ | ||
"name": "@agile-ts/core", | ||
"version": "0.0.17", | ||
"version": "0.1.0", | ||
"author": "BennoDev", | ||
@@ -39,3 +39,4 @@ "license": "MIT", | ||
"test:coverage": "jest --coverage", | ||
"lint": "eslint src/**/*" | ||
"lint": "eslint src/**/*", | ||
"size": "yarn run build && size-limit" | ||
}, | ||
@@ -47,4 +48,4 @@ "devDependencies": { | ||
"dependencies": { | ||
"@agile-ts/utils": "^0.0.4", | ||
"@agile-ts/logger": "^0.0.4" | ||
"@agile-ts/utils": "^0.0.5", | ||
"@agile-ts/logger": "^0.0.5" | ||
}, | ||
@@ -51,0 +52,0 @@ "publishConfig": { |
@@ -52,3 +52,3 @@ <img src="https://raw.githubusercontent.com/agile-ts/agile/master/static/header_background.png" alt="AgileTs"> | ||
// -- myComponent.whatever ------------------------------------------ | ||
// -- MyComponent.whatever ------------------------------------------ | ||
@@ -86,4 +86,6 @@ // 3️⃣ Bind initialized State to desired UI-Component | ||
### 🚅 Straightforward | ||
Write minimalistic, boilerplate-free code that captures your intent. | ||
```ts | ||
const MY_STATE = App.createState('frank'); // Create State | ||
MY_STATE.set('jeff'); // Update State value | ||
@@ -106,2 +108,3 @@ MY_STATE.undo(); // Undo latest State value change | ||
MY_COLLECTION.collect({id: 2, name: "Dieter"}); | ||
MY_COLLECTION.update(1, {name: "Jeff"}); | ||
``` | ||
@@ -108,0 +111,0 @@ - Compute State depending on other States |
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
294111
62
7003
206
1
+ Added@agile-ts/logger@0.0.5(transitive)
+ Added@agile-ts/utils@0.0.5(transitive)
- Removed@agile-ts/logger@0.0.4(transitive)
- Removed@agile-ts/utils@0.0.4(transitive)
Updated@agile-ts/logger@^0.0.5
Updated@agile-ts/utils@^0.0.5