Socket
Socket
Sign inDemoInstall

@agile-ts/core

Package Overview
Dependencies
2
Maintainers
1
Versions
37
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.0.17 to 0.1.0

dist/collection/group/group.observer.d.ts

38

CHANGELOG.md
# 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 @@

190

dist/agile.d.ts

@@ -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;
}

25

dist/agile.js

@@ -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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc