Socket
Socket
Sign inDemoInstall

@agile-ts/core

Package Overview
Dependencies
Maintainers
1
Versions
37
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@agile-ts/core - npm Package Compare versions

Comparing version 0.0.3 to 0.0.4

CHANGELOG.md

81

dist/agile.d.ts

@@ -1,7 +0,2 @@

import { Runtime, Integration, SubController, State, Storage, StorageConfigInterface, Collection, DefaultDataItem, Computed, Event, EventConfig, DefaultEventPayload, Integrations } from './internal';
export interface AgileConfigInterface {
logJobs?: boolean;
waitForMount?: boolean;
storageConfig?: StorageConfigInterface;
}
import { Runtime, Integration, State, Storage, StorageConfigInterface, Collection, DefaultItem, Computed, Event, EventConfig, DefaultEventPayload, Integrations, Observer, SubController } from "./internal";
export declare class Agile {

@@ -14,51 +9,67 @@ config: AgileConfigInterface;

static initialIntegrations: Integration[];
/**
* @public
* Agile - Global state and logic framework for reactive Typescript & Javascript applications
* @param config - Config
*/
constructor(config?: AgileConfigInterface);
/**
* Use custom Integration
* @public
* Integrates framework into Agile
* @param integration - Integration that gets registered/integrated
*/
use(integration: Integration): this;
/**
* Create Agile API
* @param config Object
* @param config.options Object - Typescript default: RequestInit (headers, credentials, mode, etc...)
* @param config.baseURL String - Url to prepend to endpoints (without trailing slash)
* @param config.timeout Number - Time to wait for request before throwing error
* @public
* Storage - Handy Interface for storing Items permanently
* @param config - Config
*/
/**
* Create Agile Storage
*/
Storage: (config: StorageConfigInterface) => Storage;
/**
* Create Agile State
* @param initialState Any - the value to initialize a State instance with
* @key State key/name which identifies the state
* @public
* State - Class that holds one Value and causes rerender on subscribed Components
* @param initialValue - Initial Value of the State
* @param key - Key/Name of the State
*/
State: <ValueType>(initialState: ValueType, key?: string | undefined) => State<ValueType>;
State: <ValueType>(initialValue: ValueType, key?: string | undefined) => State<ValueType>;
/**
* Create Agile Collection
* @param config object | function returning object
* @param config.primaryKey string - The primary key for the collection.
* @param config.groups object - Define groups for this collection.
* @public
* Collection - Class that holds a List of Objects with key and causes rerender on subscribed Components
* @param config - Config
*/
Collection: <DataType = DefaultDataItem>(config?: import("./collection").CollectionConfigInterface | ((collection: Collection<DataType>) => import("./collection").CollectionConfigInterface) | undefined) => Collection<DataType>;
Collection: <DataType = DefaultItem>(config?: import("./collection").CollectionConfigInterface | ((collection: Collection<DataType>) => import("./collection").CollectionConfigInterface) | undefined) => Collection<DataType>;
/**
* Create a Agile computed function
* @param deps Array - An array of state items to depend on
* @param computeFunction Function - A function where the return value is the state, ran every time a dep changes
* @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
*/
Computed: <ComputedValueType = any>(computeFunction: () => ComputedValueType, deps?: State<any>[] | undefined) => Computed<ComputedValueType>;
Computed: <ComputedValueType = any>(computeFunction: () => ComputedValueType, deps?: (Observer<any> | State<any> | Event<DefaultEventPayload>)[] | undefined) => Computed<ComputedValueType>;
/**
* Create a Pulse Event
* @public
* Event - Class that holds a List of Functions which can be triggered at the same time
* @param config - Config
*/
Event: <PayloadType = DefaultEventPayload>(config?: EventConfig | undefined) => Event<PayloadType>;
/**
* Configures the Agile Storage
* @param storageConfig
* @public
* Configures Agile Storage
* @param storage - Storage that will get used as Agile Storage
*/
setStorage(storageConfig: StorageConfigInterface): void;
configureStorage(storage: Storage): void;
/**
* @internal
* Creates a global reference to the first pulse instance created this runtime
* @public
* Checks if Agile has any registered Integration
*/
private globalBind;
hasIntegration(): boolean;
}
/**
* @param logJobs - Allow Agile Logs
* @param waitForMount - If Agile should wait until the component mounts
* @param storageConfig - To configure Agile Storage
*/
export interface AgileConfigInterface {
logJobs?: boolean;
waitForMount?: boolean;
storageConfig?: StorageConfigInterface;
}

@@ -6,22 +6,18 @@ "use strict";

class Agile {
/**
* @public
* Agile - Global state and logic framework for reactive Typescript & Javascript applications
* @param config - Config
*/
constructor(config = {}) {
this.config = config;
//=========================================================================================================
// API
//=========================================================================================================
/**
* Create Agile API
* @param config Object
* @param config.options Object - Typescript default: RequestInit (headers, credentials, mode, etc...)
* @param config.baseURL String - Url to prepend to endpoints (without trailing slash)
* @param config.timeout Number - Time to wait for request before throwing error
*/
// public API = (config: apiConfig) => new API(config);
//=========================================================================================================
// Storage
//=========================================================================================================
/**
* Create Agile Storage
* @public
* Storage - Handy Interface for storing Items permanently
* @param config - Config
*/
this.Storage = (config) => new internal_1.Storage(this, config);
this.Storage = (config) => new internal_1.Storage(config);
//=========================================================================================================

@@ -31,7 +27,8 @@ // State

/**
* Create Agile State
* @param initialState Any - the value to initialize a State instance with
* @key State key/name which identifies the state
* @public
* State - Class that holds one Value and causes rerender on subscribed Components
* @param initialValue - Initial Value of the State
* @param key - Key/Name of the State
*/
this.State = (initialState, key) => new internal_1.State(this, initialState, key);
this.State = (initialValue, key) => new internal_1.State(this, initialValue, key);
//=========================================================================================================

@@ -41,6 +38,5 @@ // Collection

/**
* Create Agile Collection
* @param config object | function returning object
* @param config.primaryKey string - The primary key for the collection.
* @param config.groups object - Define groups for this collection.
* @public
* Collection - Class that holds a List of Objects with key and causes rerender on subscribed Components
* @param config - Config
*/

@@ -52,7 +48,10 @@ this.Collection = (config) => new internal_1.Collection(this, config);

/**
* Create a Agile computed function
* @param deps Array - An array of state items to depend on
* @param computeFunction Function - A function where the return value is the state, ran every time a dep changes
* @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
*/
this.Computed = (computeFunction, deps) => new internal_1.Computed(this, computeFunction, deps);
this.Computed = (computeFunction, deps
// @ts-ignore
) => new internal_1.Computed(this, computeFunction, deps);
//=========================================================================================================

@@ -62,13 +61,13 @@ // Event

/**
* Create a Pulse Event
* @public
* Event - Class that holds a List of Functions which can be triggered at the same time
* @param config - Config
*/
this.Event = (config) => new internal_1.Event(this, config);
this.integrations = new internal_1.Integrations(this);
this.runtime = new internal_1.Runtime(this);
this.subController = new internal_1.SubController(this);
this.runtime = new internal_1.Runtime(this);
this.storage = new internal_1.Storage(this, config.storageConfig || {});
// Bind Frameworks to Agile
this.integrations.bind();
// Creates a global agile instance..
this.globalBind();
this.storage = new internal_1.Storage(config.storageConfig || {});
// Create global instance of Agile
internal_1.globalBind("__agile__", this);
}

@@ -79,3 +78,5 @@ //=========================================================================================================

/**
* Use custom Integration
* @public
* Integrates framework into Agile
* @param integration - Integration that gets registered/integrated
*/

@@ -90,36 +91,26 @@ use(integration) {

/**
* Configures the Agile Storage
* @param storageConfig
* @public
* Configures Agile Storage
* @param storage - Storage that will get used as Agile Storage
*/
setStorage(storageConfig) {
// Get States which are already saved into a storage
const persistedStates = this.storage.persistedStates;
configureStorage(storage) {
// Get Observers that are already saved into a storage
const persistentInstances = this.storage.persistentInstances;
// Define new Storage
this.storage = new internal_1.Storage(this, storageConfig);
this.storage.persistedStates = persistedStates;
// Save all already saved states into the new Storage
this.storage.persistedStates.forEach(state => state.persist(state.key));
// Save all already saved collections into the new Storage
this.storage.persistedCollections.forEach(collection => collection.persist(collection.key));
this.storage = storage;
// Transfer already saved items into new Storage
persistentInstances.forEach((persistent) => persistent.initialLoading(persistent.key));
}
//=========================================================================================================
// Global Bind
// Has Integration
//=========================================================================================================
/**
* @internal
* Creates a global reference to the first pulse instance created this runtime
* @public
* Checks if Agile has any registered Integration
*/
globalBind() {
try {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis
// @ts-ignore
if (!globalThis.__agile)
globalThis.__agile = this;
}
catch (error) {
// fail silently
}
hasIntegration() {
return this.integrations.hasIntegration();
}
}
exports.Agile = Agile;
Agile.initialIntegrations = [];
Agile.initialIntegrations = []; // External added Integrations

@@ -1,42 +0,98 @@

import { Agile, State, Collection, DefaultDataItem, ItemKey } from '../internal';
export declare type GroupKey = string | number;
export interface GroupAddOptionsInterface {
method?: 'unshift' | 'push';
overwrite?: boolean;
background?: boolean;
}
export interface GroupConfigInterface {
key?: GroupKey;
}
export declare class Group<DataType = DefaultDataItem> extends State<Array<ItemKey>> {
import { Agile, State, Collection, DefaultItem, ItemKey, Item, StorageKey, StatePersistentConfigInterface } from "../internal";
export declare class Group<DataType = DefaultItem> extends State<Array<ItemKey>> {
collection: () => Collection<DataType>;
_output: Array<DataType>;
_states: Array<() => State<DataType>>;
notFoundPrimaryKeys: Array<ItemKey>;
_items: Array<() => Item<DataType>>;
notFoundItemKeys: Array<ItemKey>;
/**
* @public
* Group - Holds Items of Collection
* @param agileInstance - An instance of Agile
* @param collection - Collection to that the Group belongs
* @param initialItems - Initial Key of Items in this Group
* @param config - Config
*/
constructor(agileInstance: Agile, collection: Collection<DataType>, initialItems?: Array<ItemKey>, config?: GroupConfigInterface);
/**
* @public
* Get Item Values of Group
*/
get output(): Array<DataType>;
get states(): Array<State<DataType>>;
/**
* Checks if the group contains the primaryKey
* @public
* Get Items of Group
*/
has(primaryKey: ItemKey): boolean;
get items(): Array<Item<DataType>>;
/**
* Returns the size of the group
* @public
* Checks if Group contains ItemKey
* @param itemKey - ItemKey that gets checked
*/
has(itemKey: ItemKey): boolean;
/**
* @public
* Get size of Group (-> How many Items it contains)
*/
get size(): number;
/**
* Removes a item at primaryKey from the group
* @public
* Removes ItemKey/s from Group
* @param itemKeys - ItemKey/s that get removed from Group
* @param config - Config
*/
remove(itemKeys: ItemKey | ItemKey[], options?: {
background?: boolean;
}): this;
remove(itemKeys: ItemKey | ItemKey[], config?: GroupRemoveConfig): this;
/**
* Adds a key to a group
* @public
* Adds ItemKey/s to Group
* @param itemKeys - ItemKey/s that get added to the Group
* @param config - Config
*/
add(itemKeys: ItemKey | ItemKey[], options?: GroupAddOptionsInterface): this;
add(itemKeys: ItemKey | ItemKey[], config?: GroupAddConfig): this;
/**
* @public
* Stores Group Value into Agile Storage permanently
* @param config - Config
*/
persist(config?: GroupPersistConfigInterface): this;
/**
* @public
* Stores Group Value into Agile Storage permanently
* @param key - Storage Key (Note: not needed if Group has key/name)
* @param config - Config
*/
persist(key?: StorageKey, config?: GroupPersistConfigInterface): this;
/**
* @internal
* Will build the group -> it will set the output to the collection values
* Rebuilds Output and Items of Group
*/
build(): void;
rebuild(): void;
}
export declare type GroupKey = string | number;
/**
* @param method - Way of adding ItemKey to Group (push, unshift)
* @param overwrite - If adding ItemKey overwrites old ItemKey (-> otherwise it gets added to the end of the Group)
* @param background - If adding ItemKey happens in the background (-> not causing any rerender)
*/
export interface GroupAddConfig {
method?: "unshift" | "push";
overwrite?: boolean;
background?: boolean;
}
/**
* @param background - If removing ItemKey happens in the background (-> not causing any rerender)
*/
export interface GroupRemoveConfig {
background?: boolean;
}
/**
* @param key - Key/Name of Group
*/
export interface GroupConfigInterface {
key?: GroupKey;
}
/**
* @param useCollectionPattern - If Group storageKey follows the Collection Group StorageKey Pattern
*/
export interface GroupPersistConfigInterface extends StatePersistentConfigInterface {
followCollectionPattern?: boolean;
}

@@ -5,28 +5,41 @@ "use strict";

const internal_1 = require("../internal");
const perstist_1 = require("./perstist");
class Group extends internal_1.State {
/**
* @public
* Group - Holds Items of Collection
* @param agileInstance - An instance of Agile
* @param collection - Collection to that the Group belongs
* @param initialItems - Initial Key of Items in this Group
* @param config - Config
*/
constructor(agileInstance, collection, initialItems, config) {
super(agileInstance, initialItems || [], config === null || config === void 0 ? void 0 : config.key);
this._output = []; // Output of the group (Note: _value are only the keys of the collection items)
this._states = []; // States of the Group
this.notFoundPrimaryKeys = []; // Contains all key which can't be found in the collection
this._output = []; // Output of Group
this._items = []; // Items of Group
this.notFoundItemKeys = []; // Contains all keys of Group that can't be found in Collection
this.collection = () => collection;
// Set build() to state sideEffect
this.sideEffects = () => this.build();
// Set type of State to array because a group is an array of collection item keys
this.type(Array);
// Add rebuild to sideEffects so that it rebuilds the Group Output if the value changes
this.addSideEffect("buildGroup", () => this.rebuild());
// Initial Build
this.build();
this.rebuild();
}
/**
* @public
* Get Item Values of Group
*/
get output() {
// Add state(group) to foundState (for auto tracking used states in computed functions)
if (this.agileInstance().runtime.trackState)
this.agileInstance().runtime.foundStates.add(this);
// Add Group to tracked Observers (for auto tracking used observers in computed function)
if (this.agileInstance().runtime.trackObservers)
this.agileInstance().runtime.foundObservers.add(this.observer);
return this._output;
}
get states() {
// Add state(group) to foundState (for auto tracking used states in computed functions)
if (this.agileInstance().runtime.trackState)
this.agileInstance().runtime.foundStates.add(this);
return this._states.map(state => state());
/**
* @public
* Get Items of Group
*/
get items() {
// Add Group to tracked Observers (for auto tracking used observers in computed function)
if (this.agileInstance().runtime.trackObservers)
this.agileInstance().runtime.foundObservers.add(this.observer);
return this._items.map((item) => item());
}

@@ -37,6 +50,8 @@ //=========================================================================================================

/**
* Checks if the group contains the primaryKey
* @public
* Checks if Group contains ItemKey
* @param itemKey - ItemKey that gets checked
*/
has(primaryKey) {
return this.value.findIndex(key => key === primaryKey) !== -1;
has(itemKey) {
return this.value.findIndex((key) => key === itemKey) !== -1;
}

@@ -47,3 +62,4 @@ //=========================================================================================================

/**
* Returns the size of the group
* @public
* Get size of Group (-> How many Items it contains)
*/

@@ -57,31 +73,33 @@ get size() {

/**
* Removes a item at primaryKey from the group
* @public
* Removes ItemKey/s from Group
* @param itemKeys - ItemKey/s that get removed from Group
* @param config - Config
*/
remove(itemKeys, options = {}) {
remove(itemKeys, config = {}) {
const _itemKeys = internal_1.normalizeArray(itemKeys);
const notExistingCollectionItems = [];
// Merge default values into options
options = internal_1.defineConfig(options, {
background: false
const notExistingItemKeys = [];
let newGroupValue = internal_1.copy(this.nextStateValue); // Copying nextStateValue because somehow a reference exists between nextStateValue and value
config = internal_1.defineConfig(config, {
background: false,
});
_itemKeys.forEach(itemKey => {
// If item doesn't exist in collection add it to notExistingItems
if (!this.collection().findById(itemKey))
notExistingCollectionItems.push(itemKey);
// Check if primaryKey exists in group if not, return
if (this.value.findIndex(key => key === itemKey) === -1) {
console.error(`Agile: Couldn't find primaryKey '${itemKey}' in group`, this);
// Remove ItemKeys from Group
_itemKeys.forEach((itemKey) => {
// Check if itemKey exists in Group
if (!newGroupValue.includes(itemKey)) {
console.error(`Agile: Couldn't find itemKey '${itemKey}' in Group!`, this);
return;
}
// Remove primaryKey from nextState
this.nextState = this.nextState.filter((i) => i !== itemKey);
// Storage
if (this.key)
perstist_1.updateGroup(this.key, this.collection());
// Check if ItemKey exists in Collection
if (!this.collection().getItemById(itemKey))
notExistingItemKeys.push(itemKey);
// Remove ItemKey from Group
newGroupValue = newGroupValue.filter((key) => key !== itemKey);
});
// If all items don't exist in collection.. set background to true because the output won't change -> no rerender necessary
if (notExistingCollectionItems.length >= _itemKeys.length)
options.background = true;
// Set State to nextState
this.ingest(options);
this.nextStateValue = newGroupValue;
// If all removed ItemKeys doesn't exist in Collection -> no rerender necessary since output doesn't change
if (notExistingItemKeys.length >= _itemKeys.length)
config.background = true;
// Ingest nextStateValue into Runtime
this.ingest({ background: config.background });
return this;

@@ -93,81 +111,92 @@ }

/**
* Adds a key to a group
* @public
* Adds ItemKey/s to Group
* @param itemKeys - ItemKey/s that get added to the Group
* @param config - Config
*/
add(itemKeys, options = {}) {
add(itemKeys, config = {}) {
const _itemKeys = internal_1.normalizeArray(itemKeys);
const notExistingCollectionItems = [];
let newNextState = [...this.nextState]; // Had to create copy array otherwise also 'this.value' would change.. by changing 'this.nextState' directly.
// Merge default values into options
options = internal_1.defineConfig(options, {
method: 'push',
const notExistingItemKeys = []; // ItemKeys that don't exist in Collection
let newGroupValue = internal_1.copy(this.nextStateValue); // Copying nextStateValue because somehow a reference exists between nextStateValue and value
config = internal_1.defineConfig(config, {
method: "push",
overwrite: false,
background: false
background: false,
});
_itemKeys.forEach(itemKey => {
// Check if item already exists in group
const existsInGroup = newNextState.findIndex(key => key === itemKey) !== -1;
// If item doesn't exist in collection add it to notExistingItems
if (!this.collection().findById(itemKey))
notExistingCollectionItems.push(itemKey);
// Removes temporary key from group to overwrite it properly
if (options.overwrite)
newNextState = newNextState.filter((i) => i !== itemKey);
// If we do not want to overwrite and key already exists in group, exit
else if (existsInGroup)
return;
// Push or unshift into state
newNextState[options.method || 'push'](itemKey);
// Storage
if (this.key)
perstist_1.updateGroup(this.key, this.collection());
// Add ItemKeys to Group
_itemKeys.forEach((itemKey) => {
const existsInGroup = newGroupValue.includes(itemKey);
// Check if ItemKey exists in Collection
if (!this.collection().getItemById(itemKey))
notExistingItemKeys.push(itemKey);
// Remove ItemKey from Group if it should get overwritten and exists
if (existsInGroup) {
if (config.overwrite)
newGroupValue = newGroupValue.filter((key) => key !== itemKey);
else
return;
}
// Add new ItemKey to Group
newGroupValue[config.method || "push"](itemKey);
});
// If all items don't exist in collection.. set background to true because the output won't change -> no rerender necessary
if (notExistingCollectionItems.length >= _itemKeys.length)
options.background = true;
// Set nextState to newNextState
this.nextState = newNextState;
// Set State to nextState
this.ingest({ background: options.background });
this.nextStateValue = newGroupValue;
// If all added ItemKeys doesn't exist in Collection -> no rerender necessary since output doesn't change
if (notExistingItemKeys.length >= _itemKeys.length)
config.background = true;
// Ingest nextStateValue into Runtime
this.ingest({ background: config.background });
return this;
}
persist(keyOrConfig = {}, config = {}) {
let _config;
let key;
if (internal_1.isValidObject(keyOrConfig)) {
_config = keyOrConfig;
key = undefined;
}
else {
_config = config || {};
key = keyOrConfig;
}
_config = internal_1.defineConfig(_config, {
instantiate: true,
followCollectionPattern: false,
});
if (_config.followCollectionPattern) {
key = internal_1.CollectionPersistent.getGroupStorageKey(key || this.key, this.collection().key);
}
super.persist(key, { instantiate: _config.instantiate });
return this;
}
//=========================================================================================================
// Build
// Rebuild
//=========================================================================================================
/**
* @internal
* Will build the group -> it will set the output to the collection values
* Rebuilds Output and Items of Group
*/
build() {
this.notFoundPrimaryKeys = [];
// Check if _value is an array if not something went wrong because a group is always an array
if (!Array.isArray(this._value)) {
console.error("Agile: A group state has to be an array!");
return;
}
// Map though group _value (collectionKey array) and get their state from collection
const finalStates = this._value
.map((primaryKey) => {
// Get collection data at the primaryKey position
let data = this.collection().data[primaryKey];
// If no data found add this key to missing PrimaryKeys
if (!data) {
this.notFoundPrimaryKeys.push(primaryKey);
return;
}
return data;
}).filter(item => item !== undefined);
// Map though found States and return their publicValue
const finalOutput = finalStates
.map((state) => {
// @ts-ignore
return state.getPublicValue();
rebuild() {
const notFoundItemKeys = []; // Item Keys that couldn't be found in Collection
const groupItems = [];
let groupOutput;
// Create groupItems by finding Item at ItemKey in Collection
this._value.forEach((itemKey) => {
let data = this.collection().data[itemKey];
if (data)
groupItems.push(data);
else
notFoundItemKeys.push(itemKey);
});
// Log not found primaryKeys
if (this.notFoundPrimaryKeys.length > 0 && this.agileInstance().config.logJobs)
console.warn(`Agile: Couldn't find states with the primary keys in group '${this.key}'`, this.notFoundPrimaryKeys);
// @ts-ignore
this._states = finalStates.map(state => (() => state));
this._output = finalOutput;
// Create groupOutput out of groupItems
groupOutput = groupItems.map((item) => {
return item.getPublicValue();
});
// Logging
if (this.agileInstance().config.logJobs && notFoundItemKeys.length > 0)
console.warn(`Agile: Couldn't find some Items in Collection '${this.key}'`, notFoundItemKeys);
this._items = groupItems.map((item) => () => item);
this._output = groupOutput;
this.notFoundItemKeys = notFoundItemKeys;
}
}
exports.Group = Group;

@@ -1,26 +0,3 @@

import { Agile, Item, Group, GroupKey, Selector, SelectorKey, State, StateKey, StorageKey, GroupConfigInterface } from "../internal";
export declare type DefaultDataItem = {
[key: string]: any;
};
export declare type CollectionKey = string | number;
export declare type ItemKey = string | number;
export interface CollectionConfigInterface {
groups?: {
[key: string]: Group<any>;
} | string[];
selectors?: {
[key: string]: Selector<any>;
} | string[];
key?: CollectionKey;
primaryKey?: string;
defaultGroupKey?: ItemKey;
}
export interface CollectOptionsInterface<DataType = any> {
patch?: boolean;
method?: 'push' | 'unshift';
forEachItem?: (item: DataType, key: ItemKey, index: number) => void;
background?: boolean;
}
export declare type CollectionConfig<DataType = DefaultDataItem> = CollectionConfigInterface | ((collection: Collection<DataType>) => CollectionConfigInterface);
export declare class Collection<DataType = DefaultDataItem> {
import { Agile, Item, Group, GroupKey, Selector, SelectorKey, StateKey, StorageKey, GroupConfigInterface, CollectionPersistent } from "../internal";
export declare class Collection<DataType = DefaultItem> {
agileInstance: () => Agile;

@@ -33,3 +10,4 @@ config: CollectionConfigInterface;

_key?: CollectionKey;
isPersistCollection: boolean;
isPersisted: boolean;
persistent: CollectionPersistent | undefined;
groups: {

@@ -41,42 +19,111 @@ [key: string]: Group<any>;

};
/**
* @public
* Class that holds a List of Objects with key and causes rerender on subscribed Components
* @param agileInstance - An instance of Agile
* @param config - Config
*/
constructor(agileInstance: Agile, config?: CollectionConfig<DataType>);
/**
* @public
* Set Key/Name of Collection
*/
set key(value: StateKey | undefined);
/**
* @public
* Get Key/Name of Collection
*/
get key(): StateKey | undefined;
/**
* @public
* Set Key/Name of Collection
* @param value - New Key/Name of Collection
*/
setKey(value: StateKey | undefined): void;
/**
* @public
* Group - Holds Items of this Collection
* @param initialItems - Initial ItemKeys of Group
* @param config - Config
*/
Group(initialItems?: Array<ItemKey>, config?: GroupConfigInterface): Group<DataType>;
/**
* @public
* Selector - Represents an Item of this Collection
* @param initialKey - Key of Item that the Selector represents
* @param config - Config
*/
Selector(initialKey: ItemKey, config?: {
key?: SelectorKey;
}): Selector<DataType>;
/**
* @internal
* Init SubInstances like groups or selectors
* Instantiates Groups
*/
private initSubInstances;
private initGroups;
/**
* Collect iterable data into this collection.
* Note: Data items must include a primary key (id)
* @internal
* Instantiates Selectors
*/
collect(items: DataType | Array<DataType>, groups?: GroupKey | Array<GroupKey>, options?: CollectOptionsInterface<DataType>): void;
private initSelectors;
/**
* * Update data by updateKey(id) in a Agile Collection
* @public
* Collect Item/s
* @param items - Item/s that get collected and added to this Collection
* @param groups - Add collected Item/s to certain Groups
* @param config - Config
*/
update(updateKey: ItemKey, changes: DefaultDataItem | DataType, options?: {
addNewProperties?: boolean;
background?: boolean;
}): State | undefined;
collect(items: DataType | Array<DataType>, groups?: GroupKey | Array<GroupKey>, config?: CollectConfigInterface<DataType>): this;
/**
* Create a group instance on this collection
* @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
*/
createGroup(groupName: GroupKey, initialItems?: Array<ItemKey>): Group<DataType>;
update(itemKey: ItemKey, changes: DefaultItem | DataType, config?: UpdateConfigInterface): Item | undefined;
/**
* Create a selector instance on this collection
* @public
* Creates new Group that can hold Items of Collection
* @param groupKey - Name/Key of Group
* @param initialItems - Initial ItemKeys of Group
*/
createSelector(selectorName: SelectorKey, id: ItemKey): Selector<DataType>;
createGroup(groupKey: GroupKey, initialItems?: Array<ItemKey>): Group<DataType>;
/**
* Return an group from this collection as Group instance (extends State)
* @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
*/
getGroup(groupName: GroupKey): Group<DataType>;
createSelector(selectorKey: SelectorKey, itemKey: ItemKey): Selector<DataType>;
/**
* Return an selector from this collection as Selector instance (extends State)
* @public
* Get Group by Key/Name
* @param groupKey - Name/Key of Group
*/
getSelector(selectorName: SelectorKey): Selector<DataType> | undefined;
getGroup(groupKey: GroupKey): Group<DataType>;
/**
* Remove fromGroups or everywhere
* @public
* Removes Group by Key/Name
* @param groupKey - Name/Key of Selector
*/
remove(primaryKeys: ItemKey | Array<ItemKey>): {
removeGroup(groupKey: GroupKey): this;
/**
* @public
* Get Selector by Key/Name
* @param selectorKey - Name/Key of Selector
*/
getSelector(selectorKey: SelectorKey): Selector<DataType> | undefined;
/**
* @public
* Removes Selector by Key/Name
* @param selectorKey - Name/Key of Selector
*/
removeSelector(selectorKey: SelectorKey): this;
/**
* @public
* Remove Items from Group or from everywhere
* @param itemKeys - ItemKey/s that get removed
*/
remove(itemKeys: ItemKey | Array<ItemKey>): {
fromGroups: (groups: Array<ItemKey> | ItemKey) => void;

@@ -86,55 +133,127 @@ everywhere: () => void;

/**
* Return an item from this collection by primaryKey as Item instance (extends State)
* @public
* Get Item by Id
* @param itemKey - ItemKey of Item that might get found
*/
findById(id: ItemKey): Item<DataType> | undefined;
getItemById(itemKey: ItemKey): Item<DataType> | undefined;
/**
* Return a value from this collection by primaryKey
* @public
* Get Value of Item by Id
* @param itemKey - ItemKey of ItemValue that might get found
*/
getValueById(id: ItemKey): DataType | undefined;
getValueById(itemKey: ItemKey): DataType | undefined;
/**
* Saves the collection in the local storage or in a own configured storage
* @param key - the storage key (if no key passed it will take the collection key)
* @public
* Stores Collection Value in Agile Storage
* @param key - Storage Key (Note: not needed if Collection has key/name)
*/
persist(key?: StorageKey): this;
/**
* Create a group instance under this collection (can be used in function based config)
* @public
* Get count of registered Groups in Collection
*/
Group(initialItems?: Array<ItemKey>, config?: GroupConfigInterface): Group<DataType>;
getGroupCount(): number;
/**
* Create a selector instance under this collection (can be used in function based config)
* @public
* Get count of registered Selectors in Collection
*/
Selector(initialSelection: ItemKey, options?: {
key?: SelectorKey;
}): Selector<DataType>;
getSelectorCount(): number;
/**
* @internal
* This will properly change the key of a collection item
* Updates ItemKey of Item
* @param oldItemKey - Old ItemKey
* @param newItemKey - New ItemKey
* @param config - Config
*/
private updateItemPrimaryKeys;
private updateItemKey;
/**
* @internal
* Deletes Data from Groups
* @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
*/
removeFromGroups(primaryKeys: ItemKey | Array<ItemKey>, groups: GroupKey | Array<GroupKey>): void;
removeFromGroups(itemKeys: ItemKey | Array<ItemKey>, groupKeys: GroupKey | Array<GroupKey>): void;
/**
* @internal
* Deletes data directly form the collection
* @public
* Removes Item/s from Group/s
* @param itemKeys - ItemKey/s of Item/s that get removed from Collection
*/
removeData(primaryKeys: ItemKey | Array<ItemKey>): void;
removeItems(itemKeys: ItemKey | Array<ItemKey>): void;
/**
* @internal
* Save data directly into the collection
* Creates/Updates Item from provided Data and adds it to the Collection
* @param data - Data from which the Item gets created/updated
* @param config - Config
*/
saveData(data: DataType, options?: {
setData(data: DataType, config?: {
patch?: boolean;
background?: boolean;
}): ItemKey | null;
}): boolean;
/**
* @internal
* Rebuild the Groups which contains the primaryKey
* Rebuilds Groups that include the provided ItemKey
* @itemKey - Item Key
* @config - Config
*/
rebuildGroupsThatIncludePrimaryKey(primaryKey: ItemKey, options?: {
background?: boolean;
forceRerender?: boolean;
}): void;
rebuildGroupsThatIncludeItemKey(itemKey: ItemKey, config?: RebuildGroupsThatIncludeItemKeyConfigInterface): void;
}
export declare type DefaultItem = {
[key: string]: any;
};
export declare type CollectionKey = string | number;
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
*/
export interface CollectionConfigInterface {
groups?: {
[key: string]: Group<any>;
} | string[];
selectors?: {
[key: string]: Selector<any>;
} | string[];
key?: CollectionKey;
primaryKey?: string;
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)
*/
export interface CollectConfigInterface<DataType = any> {
patch?: boolean;
method?: "push" | "unshift";
forEachItem?: (data: DataType, key: ItemKey, index: number) => void;
background?: boolean;
}
/**
* @param addNewProperties - If properties that doesn't exist in base ItemData get added
* @param background - If updating an Item happens in the background (-> not causing any rerender)
*/
export interface UpdateConfigInterface {
addNewProperties?: boolean;
background?: boolean;
}
/**
* @param background - If updating the primaryKey of an Item happens in the background (-> not causing any rerender)
*/
export interface UpdateItemKeyConfigInterface {
background?: boolean;
}
/**
* @param background - If assigning a new value happens in the background (-> not causing any rerender)
* @param forceRerender - Force rerender no matter what happens
* @param sideEffects - If Side Effects of State get executed
*/
export interface RebuildGroupsThatIncludeItemKeyConfigInterface {
background?: boolean;
forceRerender?: boolean;
sideEffects?: boolean;
}
export declare type CollectionConfig<DataType = DefaultItem> = CollectionConfigInterface | ((collection: Collection<DataType>) => CollectionConfigInterface);

@@ -5,31 +5,39 @@ "use strict";

const internal_1 = require("../internal");
const perstist_1 = require("./perstist");
class Collection {
/**
* @public
* Class that holds a List of Objects with key and causes rerender on subscribed Components
* @param agileInstance - An instance of Agile
* @param config - Config
*/
constructor(agileInstance, config = {}) {
this.size = 0; // The amount of data items stored inside this collection
this.data = {}; // Collection data is stored here
this.isPersistCollection = false; // Is saved in storage
this.size = 0; // Amount of Items stored in Collection
this.data = {}; // Collection Data
this.isPersisted = false; // If Collection is stored in Storage
this.groups = {};
this.selectors = {};
this.agileInstance = () => agileInstance;
// If collection config is a function, execute and assign to config
if (typeof config === 'function')
if (typeof config === "function")
config = config(this);
// Assign defaults to config
this.config = internal_1.defineConfig(config, {
primaryKey: 'id',
primaryKey: "id",
groups: {},
selectors: {},
defaultGroupKey: 'default'
defaultGroupKey: "default",
});
// Set Key
this._key = this.config.key;
// Init Groups
this.initSubInstances('groups');
// Init Selectors
this.initSubInstances('selectors');
this.initGroups();
this.initSelectors();
}
/**
* @public
* Set Key/Name of Collection
*/
set key(value) {
this._key = value;
this.setKey(value);
}
/**
* @public
* Get Key/Name of Collection
*/
get key() {

@@ -39,45 +47,102 @@ return this._key;

//=========================================================================================================
// Init SubInstances
// Set Key
//=========================================================================================================
/**
* @public
* Set Key/Name of Collection
* @param value - New Key/Name of Collection
*/
setKey(value) {
const oldKey = this._key;
// Update State Key
this._key = value;
// Update Key in PersistManager
if (value !== undefined &&
this.persistent &&
this.persistent.key === oldKey)
this.persistent.key = value;
}
//=========================================================================================================
// Group
//=========================================================================================================
/**
* @public
* Group - Holds Items of this Collection
* @param initialItems - Initial ItemKeys of Group
* @param config - Config
*/
Group(initialItems, config) {
return new internal_1.Group(this.agileInstance(), this, initialItems, config);
}
//=========================================================================================================
// Selector
//=========================================================================================================
/**
* @public
* Selector - Represents an Item of this Collection
* @param initialKey - Key of Item that the Selector represents
* @param config - Config
*/
Selector(initialKey, config) {
return new internal_1.Selector(this, initialKey, config);
}
//=========================================================================================================
// Init Groups
//=========================================================================================================
/**
* @internal
* Init SubInstances like groups or selectors
* Instantiates Groups
*/
initSubInstances(type) {
const subInstance = internal_1.copy(this.config[type]) || {};
let subInstanceObject = {};
// If subInstance is array transform it to an object with the fitting class
if (Array.isArray(subInstance)) {
for (let i = 0; i < subInstance.length; i++) {
let instance;
switch (type) {
case "groups":
instance = new internal_1.Group(this.agileInstance(), this, [], { key: subInstance[i] });
break;
case "selectors":
instance = new internal_1.Selector(this, subInstance[i], { key: subInstance[i] });
break;
default:
instance = 'unknown';
}
subInstanceObject[subInstance[i]] = instance;
}
initGroups() {
const groups = internal_1.copy(this.config.groups);
if (!groups)
return;
let groupsObject = {};
// If groups is Array of SelectorNames transform it to Selector Object
if (Array.isArray(groups)) {
groups.forEach((groupKey) => {
groupsObject[groupKey] = new internal_1.Group(this.agileInstance(), this, [], {
key: groupKey,
});
});
}
else {
// If subInstance is Object.. set subInstanceObject to subInstance
subInstanceObject = subInstance;
else
groupsObject = groups;
// Add default Group
groupsObject[this.config.defaultGroupKey || "default"] = new internal_1.Group(this.agileInstance(), this, [], {
key: this.config.defaultGroupKey || "default",
});
// Set Key/Name of Group to property Name
for (let key in groupsObject)
if (!groupsObject[key].key)
groupsObject[key].key = key;
this.groups = groupsObject;
}
//=========================================================================================================
// Init Selectors
//=========================================================================================================
/**
* @internal
* Instantiates Selectors
*/
initSelectors() {
const selectors = internal_1.copy(this.config.selectors);
if (!selectors)
return;
let selectorsObject = {};
// If selectors is Array of SelectorNames transform it to Selector Object
if (Array.isArray(selectors)) {
selectors.forEach((selectorKey) => {
selectorsObject[selectorKey] = new internal_1.Selector(this, selectorKey, {
key: selectorKey,
});
});
}
// If groups.. add default group
if (type === "groups") {
if (!subInstanceObject[this.config.defaultGroupKey || 'default'])
subInstanceObject[this.config.defaultGroupKey || 'default'] = new internal_1.Group(this.agileInstance(), this, [], { key: this.config.defaultGroupKey || 'default' });
}
const keys = Object.keys(subInstanceObject);
for (let key of keys) {
// Set key to property name if it isn't set yet
if (!subInstanceObject[key].key)
subInstanceObject[key].key = key;
}
// Set Collection instance
this[type] = subInstanceObject;
else
selectorsObject = selectors;
// Set Key/Name of Selector to property Name
for (let key in selectorsObject)
if (!selectorsObject[key].key)
selectorsObject[key].key = key;
this.selectors = selectorsObject;
}

@@ -88,57 +153,55 @@ //=========================================================================================================

/**
* Collect iterable data into this collection.
* Note: Data items must include a primary key (id)
* @public
* Collect Item/s
* @param items - Item/s that get collected and added to this Collection
* @param groups - Add collected Item/s to certain Groups
* @param config - Config
*/
collect(items, groups, options = {}) {
collect(items, groups, config = {}) {
const _items = internal_1.normalizeArray(items);
const _groups = internal_1.normalizeArray(groups);
const defaultGroupKey = this.config.defaultGroupKey || 'default';
const groupsToRebuild = new Set();
// Assign defaults to options
options = internal_1.defineConfig(options, {
method: 'push',
const groupKeys = internal_1.normalizeArray(groups);
const defaultGroupKey = this.config.defaultGroupKey || "default";
const primaryKey = this.config.primaryKey || "id";
config = internal_1.defineConfig(config, {
method: "push",
background: false,
patch: false
patch: false,
});
// Add default group if it hasn't been added (default group contains all items)
if (_groups.findIndex(groupName => groupName === defaultGroupKey) === -1)
_groups.push(defaultGroupKey);
// Add default GroupKey, because Items get always added to default Group
if (!groupKeys.includes(defaultGroupKey))
groupKeys.push(defaultGroupKey);
// Create not existing Groups
_groups.forEach(groupName => !this.groups[groupName] && this.createGroup(groupName));
_items.forEach((item, index) => {
// Check if the item already exists in the Collection
const itemExists = !!this.data[item[this.config.primaryKey || 'id']];
// Save items into Collection
let key = this.saveData(item, { patch: options.patch, background: options.background });
// Return if key doesn't exist (something went wrong in saveData, Note: Error will be logged in saveData)
if (!key)
return;
// Call forEachItem method
if (options.forEachItem)
options.forEachItem(item, key, index);
// If item didn't exist.. check if the itemKey has already been added to a group before -> group need rebuild to has correct output
if (!itemExists) {
const groupKeys = Object.keys(this.groups);
groupKeys.forEach(groupName => {
// Get Group
const group = this.getGroup(groupName);
// Check if itemKey exists in Group if so push it to groupsToRebuild
if (group.value.findIndex(primaryKey => primaryKey === item[this.config.primaryKey || 'id']) !== -1)
groupsToRebuild.add(group);
groupKeys.forEach((groupName) => !this.groups[groupName] && this.createGroup(groupName));
// Instantiate Items
_items.forEach((data, index) => {
const itemKey = data[primaryKey];
const itemExistsInCollection = !!this.data[itemKey];
// Add Item to Collection
const success = this.setData(data, {
patch: config.patch,
background: config.background,
});
if (!success)
return this;
// Ingest Groups that include the ItemKey into Runtime, which than rebuilds the Group (because output of group changed)
if (!itemExistsInCollection) {
for (let groupKey in this.groups) {
const group = this.getGroup(groupKey);
if (group.value.includes(itemKey)) {
if (!config.background)
group.ingest({ forceRerender: true, storage: false });
}
}
}
// Add ItemKey to provided Groups
groupKeys.forEach((groupKey) => {
this.groups[groupKey].add(itemKey, {
method: config.method,
background: config.background,
});
}
// Add key to groups
_groups.forEach(groupName => {
// @ts-ignore
this.groups[groupName].add(key, { method: options.method, background: options.background });
});
if (config.forEachItem)
config.forEachItem(data, itemKey, index);
});
// Rebuild groups
groupsToRebuild.forEach(group => {
// Rebuild Group
group.build();
// Force Rerender to get the correct output in components
if (!options.background)
group.ingest({ forceRerender: true });
});
return this;
}

@@ -149,34 +212,41 @@ //=========================================================================================================

/**
* * Update data by updateKey(id) in a Agile Collection
* @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
*/
update(updateKey, changes, options = {}) {
// If item does not exist, return
if (!this.data.hasOwnProperty(updateKey)) {
console.error(`Agile: PrimaryKey '${updateKey} doesn't exist in collection `, this);
update(itemKey, changes, config = {}) {
if (!this.data.hasOwnProperty(itemKey)) {
console.error(`Agile: ItemKey '${itemKey} doesn't exist in Collection!`, this);
return undefined;
}
// Assign defaults to config
options = internal_1.defineConfig(options, {
addNewProperties: false,
background: false
if (!internal_1.isValidObject(changes)) {
console.error(`Agile: Changes have to be an Object!`, this);
return undefined;
}
const item = this.data[itemKey];
const primaryKey = this.config.primaryKey || "";
config = internal_1.defineConfig(config, {
addNewProperties: true,
background: false,
});
const itemState = this.data[updateKey];
const currentItemValue = internal_1.copy(itemState.value);
const primaryKey = this.config.primaryKey || '';
// Merge current Item value with changes
const finalItemValue = internal_1.flatMerge(currentItemValue, changes, { addNewProperties: options.addNewProperties });
// Check if something has changed (stringifying because of possible object or array)
if (JSON.stringify(finalItemValue) === JSON.stringify(itemState.nextState))
return this.data[finalItemValue[primaryKey]];
// Assign finalItemStateValue to nextState
itemState.nextState = finalItemValue;
// Set State to nextState
itemState.ingest({ background: options.background });
// If data key changes update it properly
if (currentItemValue[primaryKey] !== finalItemValue[primaryKey])
this.updateItemPrimaryKeys(currentItemValue[primaryKey], finalItemValue[primaryKey], { background: options.background });
// Rebuild all groups that includes the primaryKey
this.rebuildGroupsThatIncludePrimaryKey(finalItemValue[primaryKey], { background: options.background });
// Return data at primaryKey (updated State)
return this.data[finalItemValue[primaryKey]];
// Merge changes into ItemValue
const newItemValue = internal_1.flatMerge(internal_1.copy(item.nextStateValue), changes, {
addNewProperties: config.addNewProperties,
});
const oldItemKey = item.value[primaryKey];
const newItemKey = newItemValue[primaryKey];
const updateItemKey = oldItemKey !== newItemKey;
// Apply changes to Item
item.set(newItemValue, {
background: config.background,
storage: !updateItemKey,
});
// Update ItemKey of Item
if (updateItemKey)
this.updateItemKey(oldItemKey, newItemKey, {
background: config.background,
});
return this.data[newItemValue[primaryKey]];
}

@@ -187,17 +257,18 @@ //=========================================================================================================

/**
* Create a group instance on this collection
* @public
* Creates new Group that can hold Items of Collection
* @param groupKey - Name/Key of Group
* @param initialItems - Initial ItemKeys of Group
*/
createGroup(groupName, initialItems) {
// Check if Group already exist
if (this.groups.hasOwnProperty(groupName)) {
console.warn(`Agile: The Group with the name ${groupName} already exists!`);
return this.groups[groupName];
createGroup(groupKey, initialItems) {
if (this.groups.hasOwnProperty(groupKey)) {
console.warn(`Agile: Group with the name '${groupKey}' already exists!`);
return this.groups[groupKey];
}
// Create new Group
const group = new internal_1.Group(this.agileInstance(), this, initialItems, { key: groupName });
// Add new Group to groups
this.groups[groupName] = group;
// Log Job
const group = new internal_1.Group(this.agileInstance(), this, initialItems, { key: groupKey });
this.groups[groupKey] = group;
// Logging
if (this.agileInstance().config.logJobs)
console.log(`Agile: Created Group called '${groupName}'`, group);
console.log(`Agile: Created new Group called '${groupKey}'`, group);
return group;

@@ -209,17 +280,20 @@ }

/**
* Create a selector instance on this collection
* @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(selectorName, id) {
// Check if Selector already exist
if (this.selectors.hasOwnProperty(selectorName)) {
console.warn(`Agile: The Selector with the name ${selectorName} already exists!`);
return this.selectors[selectorName];
createSelector(selectorKey, itemKey) {
if (this.selectors.hasOwnProperty(selectorKey)) {
console.warn(`Agile: The Selector with the name '${selectorKey}' already exists!`);
return this.selectors[selectorKey];
}
// Create new Selector
const selector = new internal_1.Selector(this, id, { key: selectorName });
// Add new Selector to selectors
this.selectors[selectorName] = selector;
// Log Job
const selector = new internal_1.Selector(this, itemKey, {
key: selectorKey,
});
this.selectors[selectorKey] = selector;
// Logging
if (this.agileInstance().config.logJobs)
console.log(`Agile: Created Selector called '${selectorName}'`, selector);
console.log(`Agile: Created Selector called '${selectorKey}'`, selector);
return selector;

@@ -231,65 +305,109 @@ }

/**
* Return an group from this collection as Group instance (extends State)
* @public
* Get Group by Key/Name
* @param groupKey - Name/Key of Group
*/
getGroup(groupName) {
// Check if group exists
if (this.groups[groupName])
return this.groups[groupName];
console.warn(`Agile: Group with name '${groupName}' doesn't exist!`);
// Return empty group because it might get annoying to handle with undefined (can check if it exists with group.exists)
const group = new internal_1.Group(this.agileInstance(), this, [], { key: 'dummy' });
group.isPlaceholder = true;
return group;
getGroup(groupKey) {
if (!this.groups[groupKey]) {
console.warn(`Agile: Group with the key/name '${groupKey}' doesn't exist!`);
// Return empty group because it might get annoying to handle with undefined (can check if it exists with group.exists)
const group = new internal_1.Group(this.agileInstance(), this, [], {
key: "dummy",
});
group.isPlaceholder = true;
return group;
}
return this.groups[groupKey];
}
//=========================================================================================================
// Remove Selector
//=========================================================================================================
/**
* @public
* Removes Group by Key/Name
* @param groupKey - Name/Key of Selector
*/
removeGroup(groupKey) {
if (!this.groups[groupKey]) {
console.warn(`Agile: Group with the key/name '${groupKey}' doesn't exist!`);
return this;
}
delete this.groups[groupKey];
return this;
}
//=========================================================================================================
// Get Selector
//=========================================================================================================
/**
* Return an selector from this collection as Selector instance (extends State)
* @public
* Get Selector by Key/Name
* @param selectorKey - Name/Key of Selector
*/
getSelector(selectorName) {
// Check if selector exists
if (this.selectors[selectorName])
return this.selectors[selectorName];
console.warn(`Agile: Selector with name '${selectorName}' doesn't exist!`);
return undefined;
getSelector(selectorKey) {
if (!this.selectors[selectorKey]) {
console.warn(`Agile: Selector with the key/name '${selectorKey}' doesn't exist!`);
return undefined;
}
return this.selectors[selectorKey];
}
//=========================================================================================================
// Remove Selector
//=========================================================================================================
/**
* @public
* Removes Selector by Key/Name
* @param selectorKey - Name/Key of Selector
*/
removeSelector(selectorKey) {
if (!this.selectors[selectorKey]) {
console.warn(`Agile: Selector with the key/name '${selectorKey}' doesn't exist!`);
return this;
}
delete this.selectors[selectorKey];
return this;
}
//=========================================================================================================
// Remove
//=========================================================================================================
/**
* Remove fromGroups or everywhere
* @public
* Remove Items from Group or from everywhere
* @param itemKeys - ItemKey/s that get removed
*/
remove(primaryKeys) {
remove(itemKeys) {
return {
fromGroups: (groups) => this.removeFromGroups(primaryKeys, groups),
everywhere: () => this.removeData(primaryKeys)
fromGroups: (groups) => this.removeFromGroups(itemKeys, groups),
everywhere: () => this.removeItems(itemKeys),
};
}
//=========================================================================================================
// Find By Id
// Get Item by Id
//=========================================================================================================
/**
* Return an item from this collection by primaryKey as Item instance (extends State)
* @public
* Get Item by Id
* @param itemKey - ItemKey of Item that might get found
*/
findById(id) {
if (!this.data.hasOwnProperty(id) || !this.data[id].exists)
getItemById(itemKey) {
if (!this.data.hasOwnProperty(itemKey) || !this.data[itemKey].exists)
return undefined;
// Add state to foundState (for auto tracking used states in computed functions)
if (this.agileInstance().runtime.trackState)
this.agileInstance().runtime.foundStates.add(this.data[id]);
// Return data by id
return this.data[id];
const item = this.data[itemKey];
// Add State to tracked Observers (for auto tracking used observers in computed function)
if (this.agileInstance().runtime.trackObservers)
this.agileInstance().runtime.foundObservers.add(item.observer);
return item;
}
//=========================================================================================================
// Get Value By Id
// Get Value by Id
//=========================================================================================================
/**
* Return a value from this collection by primaryKey
* @public
* Get Value of Item by Id
* @param itemKey - ItemKey of ItemValue that might get found
*/
getValueById(id) {
let data = this.findById(id);
if (!data)
getValueById(itemKey) {
let item = this.getItemById(itemKey);
if (!item)
return undefined;
return data.value;
return item.value;
}

@@ -300,73 +418,84 @@ //=========================================================================================================

/**
* Saves the collection in the local storage or in a own configured storage
* @param key - the storage key (if no key passed it will take the collection key)
* @public
* Stores Collection Value in Agile Storage
* @param key - Storage Key (Note: not needed if Collection has key/name)
*/
persist(key) {
perstist_1.persistValue(this, key).then((value) => {
this.isPersistCollection = value;
});
// Update Persistent Key
if (this.persistent) {
if (key)
this.persistent.key = key;
return this;
}
// Create persistent -> Persist Value
this.persistent = new internal_1.CollectionPersistent(this.agileInstance(), this, key);
return this;
}
//=========================================================================================================
// Group
// Group Size
//=========================================================================================================
/**
* Create a group instance under this collection (can be used in function based config)
* @public
* Get count of registered Groups in Collection
*/
Group(initialItems, config) {
return new internal_1.Group(this.agileInstance(), this, initialItems, config);
getGroupCount() {
let size = 0;
for (let group in this.groups)
size++;
return size;
}
//=========================================================================================================
// Selector
// Selector Size
//=========================================================================================================
/**
* Create a selector instance under this collection (can be used in function based config)
* @public
* Get count of registered Selectors in Collection
*/
Selector(initialSelection, options) {
return new internal_1.Selector(this, initialSelection, options);
getSelectorCount() {
let size = 0;
for (let selector in this.selectors)
size++;
return size;
}
//=========================================================================================================
// Update Data Key
// Update Item Key
//=========================================================================================================
/**
* @internal
* This will properly change the key of a collection item
* Updates ItemKey of Item
* @param oldItemKey - Old ItemKey
* @param newItemKey - New ItemKey
* @param config - Config
*/
updateItemPrimaryKeys(oldKey, newKey, options) {
// If oldKey and newKey are the same, return
if (oldKey === newKey)
updateItemKey(oldItemKey, newItemKey, config) {
const item = this.getItemById(oldItemKey);
config = internal_1.defineConfig(config, {
background: false,
});
if (!item || oldItemKey === newItemKey)
return;
// Assign defaults to config
options = internal_1.defineConfig(options, {
background: false
});
// Create copy of data
const dataCopy = this.data[oldKey];
// Delete old reference
delete this.data[oldKey];
// Apply the data into data with new key
this.data[newKey] = dataCopy;
// Remove Item from old ItemKey and add Item to new ItemKey
delete this.data[oldItemKey];
this.data[newItemKey] = item;
// Update Key/Name of Item
item.key = newItemKey;
// Update persist Key of Item (Doesn't get changed by setting new item key because PersistKey is not ItemKey)
item.persist(internal_1.CollectionPersistent.getItemStorageKey(newItemKey, this.key));
// Update Groups
for (let groupName in this.groups) {
// Get Group
const group = this.getGroup(groupName);
// If Group does not contain oldKey, continue
if (group.value.findIndex(key => key === oldKey) === -1)
if (group.isPlaceholder || !group.has(oldItemKey))
continue;
// Replace the primaryKey at current index
group.nextState.splice(group.nextState.indexOf(oldKey), 1, newKey);
// Set State(Group) to nextState
group.ingest({ background: options === null || options === void 0 ? void 0 : options.background });
// Replace old ItemKey with new ItemKey
const newGroupValue = internal_1.copy(group.value);
newGroupValue.splice(newGroupValue.indexOf(oldItemKey), 1, newItemKey);
group.set(newGroupValue, { background: config === null || config === void 0 ? void 0 : config.background });
}
// Update Selector
// Update Selectors
for (let selectorName in this.selectors) {
// Get Selector
const selector = this.getSelector(selectorName);
if (!selector)
if (!selector || selector.itemKey !== oldItemKey)
continue;
// If Selector doesn't watch on the oldKey, continue
if (selector.id !== oldKey)
continue;
// Replace the oldKey with the newKey
selector.select(newKey, { background: options === null || options === void 0 ? void 0 : options.background });
// Replace old selected ItemKey with new ItemKey
selector.select(newItemKey, { background: config === null || config === void 0 ? void 0 : config.background });
}

@@ -378,123 +507,129 @@ }

/**
* @internal
* Deletes Data from Groups
* @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
*/
removeFromGroups(primaryKeys, groups) {
const _primaryKeys = internal_1.normalizeArray(primaryKeys);
const _groups = internal_1.normalizeArray(groups);
_groups.forEach(groupKey => {
// Return if group doesn't exist in collection
if (!this.groups[groupKey]) {
console.error(`Agile: Couldn't find group('${groupKey}) in collection`, this);
return;
}
// Remove primaryKeys from Group
_primaryKeys.forEach(primaryKey => {
removeFromGroups(itemKeys, groupKeys) {
const _itemKeys = internal_1.normalizeArray(itemKeys);
const _groupKeys = internal_1.normalizeArray(groupKeys);
_itemKeys.forEach((itemKey) => {
let removedFromGroupsCount = 0;
// Remove ItemKey from Groups
_groupKeys.forEach((groupKey) => {
const group = this.getGroup(groupKey);
group.remove(primaryKey);
if (!group)
return;
group.remove(itemKey);
removedFromGroupsCount++;
});
// If Item got removed from every Groups in Collection, remove it completely
if (removedFromGroupsCount >= this.getGroupCount())
this.removeItems(itemKey);
});
}
//=========================================================================================================
// Delete Data
// Remove Items
//=========================================================================================================
/**
* @internal
* Deletes data directly form the collection
* @public
* Removes Item/s from Group/s
* @param itemKeys - ItemKey/s of Item/s that get removed from Collection
*/
removeData(primaryKeys) {
const _primaryKeys = internal_1.normalizeArray(primaryKeys);
const groupKeys = Object.keys(this.groups);
const selectorKeys = Object.keys(this.selectors);
const itemKeys = Object.keys(this.data);
_primaryKeys.forEach(itemKey => {
// Check if primaryKey exists in collection, if not return
if (itemKeys.findIndex(key => itemKey.toString() === key) === -1) {
console.error(`Agile: Couldn't find primaryKey '${itemKey}' in collection`, this);
removeItems(itemKeys) {
const _itemKeys = internal_1.normalizeArray(itemKeys);
_itemKeys.forEach((itemKey) => {
var _a;
const item = this.getItemById(itemKey);
if (!item)
return;
// Remove Item from Groups
for (let groupKey in this.groups) {
const group = this.getGroup(groupKey);
if (group.has(itemKey))
group.remove(itemKey);
}
// Remove primaryKey from Groups (have to be above deleting the data because.. the remove function needs to know if the data exists or not)
groupKeys.forEach(groupKey => {
this.groups[groupKey].remove(itemKey);
});
// Remove Selectors with primaryKey
selectorKeys.forEach(selectorKey => {
delete this.selectors[selectorKey];
});
// Remove primaryKey from collection data
// TODO
// Remove Selectors that represents this Item
for (let selectorKey in this.selectors) {
const selector = this.getSelector(selectorKey);
if ((selector === null || selector === void 0 ? void 0 : selector.itemKey) === itemKey)
this.removeSelector(selectorKey);
}
// Remove Item from Storage
(_a = item.persistent) === null || _a === void 0 ? void 0 : _a.removeValue();
// Remove Item from Collection
delete this.data[itemKey];
// Decrease size
this.size--;
// Storage
perstist_1.removeItem(itemKey, this);
});
}
//=========================================================================================================
// Save Data
// Set Data
//=========================================================================================================
/**
* @internal
* Save data directly into the collection
* Creates/Updates Item from provided Data and adds it to the Collection
* @param data - Data from which the Item gets created/updated
* @param config - Config
*/
saveData(data, options = {}) {
// Transform data to any because otherwise I have many type errors (because not defined object)
// https://stackoverflow.com/questions/57350092/string-cant-be-used-to-index-type
const _data = data;
// Assign defaults to options
options = internal_1.defineConfig(options, {
setData(data, config = {}) {
const _data = data; // Transformed Data to any because of unknown Object (DataType)
const primaryKey = this.config.primaryKey || "id";
config = internal_1.defineConfig(config, {
patch: false,
background: false,
});
// Get primaryKey (default: 'id')
const primaryKey = this.config.primaryKey || 'id';
const itemKey = _data[primaryKey];
// Check if data is object if not return
if (!internal_1.isValidObject(_data)) {
console.error("Agile: Collections items has to be an object for now!");
return null;
console.error("Agile: Collections items has to be an object!");
return false;
}
// Check if data has primaryKey
if (!_data.hasOwnProperty(primaryKey)) {
console.error("Agile: Collections items need a own primaryKey. Here " + this.config.primaryKey);
return null;
console.error(`Agile: Collection Item needs a primary Key property called '${this.config.primaryKey}'!`);
return false;
}
// Create reference of data at the data key
const itemKey = _data[primaryKey];
let item = this.data[itemKey];
// If the data already exists and config is to patch, patch data
if (item && options.patch)
item.patch(_data, { background: options.background });
// If the data already exists and no config, overwrite data
else if (item)
item.set(_data, { background: options.background });
// If data does not exist.. create new Item set and increase the size
else {
// Create or update Item
if (item && config.patch)
item = item.patch(_data, { background: config.background });
if (item && !config.patch)
item = item.set(_data, { background: config.background });
if (!item) {
item = new internal_1.Item(this, _data);
this.size++;
}
// Set item at data itemKey
// Reset isPlaceholder of Item since it got an value
if (item.isPlaceholder)
item.isPlaceholder = false;
// Set new Item at itemKey
this.data[itemKey] = item;
// Storage
perstist_1.setItem(itemKey, this);
return itemKey;
return true;
}
//=========================================================================================================
// Rebuild Groups That Includes Primary Key
// Rebuild Groups That Includes Item Key
//=========================================================================================================
/**
* @internal
* Rebuild the Groups which contains the primaryKey
* Rebuilds Groups that include the provided ItemKey
* @itemKey - Item Key
* @config - Config
*/
rebuildGroupsThatIncludePrimaryKey(primaryKey, options) {
// Assign defaults to config
options = internal_1.defineConfig(options, {
rebuildGroupsThatIncludeItemKey(itemKey, config = {}) {
config = internal_1.defineConfig(config, {
background: false,
forceRerender: !(options === null || options === void 0 ? void 0 : options.background) // forceRerender false.. because forceRerender has more weight than background in runtime
forceRerender: !(config === null || config === void 0 ? void 0 : config.background),
sideEffects: true,
});
// Rebuild groups that includes primaryKey
// Rebuild Groups that include ItemKey
for (let groupKey in this.groups) {
// Get Group
const group = this.getGroup(groupKey);
// Check if group contains primaryKey if so rebuild it
if (group.has(primaryKey))
group.ingest({ background: options === null || options === void 0 ? void 0 : options.background, forceRerender: options === null || options === void 0 ? void 0 : options.forceRerender });
if (group.has(itemKey)) {
// group.rebuild(); Not necessary because a sideEffect of the Group is to rebuild it self
group.ingest({
background: config === null || config === void 0 ? void 0 : config.background,
forceRerender: config === null || config === void 0 ? void 0 : config.forceRerender,
sideEffects: config === null || config === void 0 ? void 0 : config.sideEffects,
storage: false,
});
}
}

@@ -501,0 +636,0 @@ }

@@ -1,6 +0,21 @@

import { State, Collection, DefaultDataItem } from '../internal';
export declare class Item<DataType = DefaultDataItem> extends State<DataType> {
import { State, Collection, DefaultItem, StateKey } from "../internal";
export declare class Item<DataType = DefaultItem> extends State<DataType> {
private collection;
output: DataType;
/**
* @public
* Item of Collection
* @param collection - Collection to which the Item belongs
* @param data - Data that the Item holds
*/
constructor(collection: Collection, data: DataType);
/**
* @public
* Set Key/Name of Item
*/
set key(value: StateKey | undefined);
/**
* @public
* Get Key/Name of Item
*/
get key(): StateKey | undefined;
}

@@ -6,2 +6,8 @@ "use strict";

class Item extends internal_1.State {
/**
* @public
* Item of Collection
* @param collection - Collection to which the Item belongs
* @param data - Data that the Item holds
*/
constructor(collection, data) {

@@ -11,10 +17,28 @@ var _a;

this.collection = () => collection;
// Setting key of item to the data primaryKey
this.key = data && data[((_a = collection.config) === null || _a === void 0 ? void 0 : _a.primaryKey) || 'id'];
// Add rebuildGroupsThatIncludePrimaryKey to sideEffects to rebuild the groups which includes the primaryKey if the state changes
this.sideEffects = () => collection.rebuildGroupsThatIncludePrimaryKey(this.key || '');
// Set type of State to object because a collection item is always an object
this.type(Object);
// Setting primaryKey of Data to Key/Name of Item
this.key = data[((_a = collection.config) === null || _a === void 0 ? void 0 : _a.primaryKey) || "id"];
}
/**
* @public
* Set Key/Name of Item
*/
set key(value) {
// Note can't use 'super.key' because of 'https://github.com/Microsoft/TypeScript/issues/338'
this.setKey(value);
if (!value)
return;
// Update rebuildGroupThatIncludePrimaryKey SideEffect
this.removeSideEffect("rebuildGroup");
this.addSideEffect("rebuildGroup", (properties) => this.collection().rebuildGroupsThatIncludeItemKey(value, properties));
}
/**
* @public
* Get Key/Name of Item
*/
get key() {
// Note can't use 'super.key' because of 'https://github.com/Microsoft/TypeScript/issues/338'
// Can't remove this getter function.. because the setter function is set in this class -> Error if not setter and getter function set
return this._key;
}
}
exports.Item = Item;

@@ -1,29 +0,52 @@

import { Collection, DefaultDataItem, ItemKey, Computed, StorageKey } from '../internal';
export declare type SelectorKey = string | number;
export interface SelectorConfigInterface {
key?: SelectorKey;
}
export declare class Selector<DataType = DefaultDataItem> extends Computed<DataType | undefined> {
import { Collection, DefaultItem, Item, ItemKey, State } from "../internal";
export declare class Selector<DataType = DefaultItem> extends State<DataType | undefined> {
collection: () => Collection<DataType>;
_id: ItemKey;
constructor(collection: Collection<DataType>, id: ItemKey, config?: SelectorConfigInterface);
set id(val: ItemKey);
get id(): ItemKey;
item: Item<DataType> | undefined;
_itemKey: ItemKey;
/**
* Changes the id on which the selector is watching
* @public
* Represents Item of Collection
* @param collection - Collection that contains the Item
* @param itemKey - ItemKey of Item that the Selector represents
* @param config - Config
*/
select(id: ItemKey, options?: {
background?: boolean;
sideEffects?: boolean;
}): this;
constructor(collection: Collection<DataType>, itemKey: ItemKey, config?: SelectorConfigInterface);
/**
* Saves the state in the local storage or in a own configured storage
* @param key - the storage key (if no key passed it will take the state key)
* @public
* Set ItemKey that the Selector represents
*/
persist(key?: StorageKey): this;
set itemKey(value: ItemKey);
/**
* @internal
* Will return the perstiable Value of this state..
* @public
* Get ItemKey that the Selector represents
*/
getPersistableValue(): StorageKey;
get itemKey(): ItemKey;
/**
* @public
* Select new ItemKey that the Selector will represents
* @param itemKey - New ItemKey
* @param config - Config
*/
select(itemKey: ItemKey, config?: SelectConfigInterface): this;
/**
* @public
* Rebuilds Selector
* @param config - Config
*/
rebuildSelector(config?: SelectConfigInterface): void;
}
export declare type SelectorKey = string | number;
/**
* @param key - Key/Name of Selector
*/
export interface SelectorConfigInterface {
key?: SelectorKey;
}
/**
* @param background - If selecting a new Item happens in the background (-> not causing any rerender)
* @param sideEffects - If Side Effects of Selector get executed
*/
export interface SelectConfigInterface {
background?: boolean;
sideEffects?: boolean;
}

@@ -5,95 +5,95 @@ "use strict";

const internal_1 = require("../internal");
const persist_1 = require("../state/persist");
class Selector extends internal_1.Computed {
constructor(collection, id, config) {
// If no key provided set it to dummy (dummyKey)
if (!id)
id = 'dummy';
// Instantiate Computed with 'computed' function
super(collection.agileInstance(), () => findData(collection, id));
if (config === null || config === void 0 ? void 0 : config.key)
this._key = config === null || config === void 0 ? void 0 : config.key;
class Selector extends internal_1.State {
/**
* @public
* Represents Item of Collection
* @param collection - Collection that contains the Item
* @param itemKey - ItemKey of Item that the Selector represents
* @param config - Config
*/
constructor(collection, itemKey, config) {
super(collection.agileInstance(), collection.getValueById(itemKey));
this.collection = () => collection;
this._id = id;
// Set type of State to object because a collection item is always an object
this.type(Object);
this.item = undefined;
this._itemKey = itemKey;
this.key = config === null || config === void 0 ? void 0 : config.key;
// Initial Select
this.select(itemKey);
}
set id(val) {
this.select(val);
/**
* @public
* Set ItemKey that the Selector represents
*/
set itemKey(value) {
this.select(value);
}
get id() {
return this._id;
/**
* @public
* Get ItemKey that the Selector represents
*/
get itemKey() {
return this._itemKey;
}
//=========================================================================================================
// Select
//=========================================================================================================
/**
* Changes the id on which the selector is watching
* @public
* Select new ItemKey that the Selector will represents
* @param itemKey - New ItemKey
* @param config - Config
*/
select(id, options) {
var _a;
// Assign defaults to config
options = internal_1.defineConfig(options, {
select(itemKey, config = {}) {
const oldItem = this.item;
let newItem = this.collection().getItemById(itemKey);
config = internal_1.defineConfig(config, {
background: false,
sideEffects: true
sideEffects: true,
});
// Remove item if its a placeholder because if so it won't be needed without being selected by this selector
if ((_a = this.collection().data[this.id]) === null || _a === void 0 ? void 0 : _a.isPlaceholder)
delete this.collection().data[this.id];
// Set _id to new id
this._id = id;
// Update Computed Function with new key(id)
this.updateComputeFunction(() => findData(this.collection(), id), [], options);
// Remove old Item from Collection if it is an Placeholder
if (oldItem === null || oldItem === void 0 ? void 0 : oldItem.isPlaceholder)
delete this.collection().data[this.itemKey];
// Create dummy Item to hold reference if Item with ItemKey doesn't exist
if (!newItem) {
newItem = new internal_1.Item(this.collection(), { id: itemKey });
newItem.isPlaceholder = true;
this.collection().data[itemKey] = newItem;
}
// Remove Selector sideEffect from old Item
oldItem === null || oldItem === void 0 ? void 0 : oldItem.removeSideEffect("rebuildSelector");
this._itemKey = itemKey;
this.item = newItem;
// Add Selector sideEffect to Item
newItem.addSideEffect("rebuildSelector", () => this.rebuildSelector(config));
// Rebuild Selector for instantiating new 'selected' ItemKey properly
this.rebuildSelector(config);
return this;
}
//=========================================================================================================
// Overwrite Persist
// RebuildSelector
//=========================================================================================================
/**
* Saves the state in the local storage or in a own configured storage
* @param key - the storage key (if no key passed it will take the state key)
* @public
* Rebuilds Selector
* @param config - Config
*/
persist(key) {
persist_1.persistValue(this, key);
return this;
rebuildSelector(config = {}) {
var _a, _b;
config = internal_1.defineConfig(config, {
background: false,
sideEffects: true,
});
// Set Selector Value to undefined if Item doesn't exist
if (!this.item || this.item.isPlaceholder) {
this._value = undefined;
return;
}
// Assign ItemValue to Selector
this.nextStateValue = internal_1.copy((_a = this.item) === null || _a === void 0 ? void 0 : _a.value);
// Fix initialStateValue and previousStateValue if they are still set from the Placeholder
if (internal_1.equal(this.item.initialStateValue, { id: this.itemKey }))
this.item.initialStateValue = internal_1.copy((_b = this.item) === null || _b === void 0 ? void 0 : _b.nextStateValue);
if (internal_1.equal(this.item.previousStateValue, { id: this.itemKey }))
this.item.previousStateValue = internal_1.copy(this.nextStateValue);
// Ingest nextStateValue into Runtime
this.ingest(config);
}
//=========================================================================================================
// Overwrite getPerstiableValue
//=========================================================================================================
/**
* @internal
* Will return the perstiable Value of this state..
*/
getPersistableValue() {
return this.id;
}
}
exports.Selector = Selector;
//=========================================================================================================
// Find Data
//=========================================================================================================
/**
* Computed function for the Selector
*/
function findData(collection, id) {
// Find data by id in collection
let item = collection.findById(id);
// If data is not found, create placeholder item, so that when real data is collected it maintains connection and causes a rerender
if (!item) {
const newItem = new internal_1.Item(collection, { id: id });
newItem.isPlaceholder = true;
collection.data[id] = newItem;
item = newItem;
}
// If initial State is still {id: id}.. because of placeholder item and the value isn't {id: id} -> had got real value.. set the initial State to this first real value
if (JSON.stringify(item.initialState) === JSON.stringify({ id: id }) && JSON.stringify(item.nextState) !== JSON.stringify({ id: id })) {
item.initialState = internal_1.copy(item.nextState);
item.previousState = internal_1.copy(item.nextState);
}
// Have to create the final Value here, to get added to the track states also if the item doesn't exist yet.. (otherwise auto tracking state wouldn't work -> this won't get called if the item changes )
const finalValue = item.value;
// If item doesn't exist return undefined.. otherwise it would return {id: id}
if (!item.exists)
return undefined;
return finalValue;
}

@@ -1,31 +0,56 @@

import { State, Agile } from '../internal';
import { State, Agile, Observer, StorageKey, StatePersistentConfigInterface } from "../internal";
export declare class Computed<ComputedValueType = any> extends State<ComputedValueType> {
agileInstance: () => Agile;
computeFunction: () => ComputedValueType;
deps: Array<State>;
hardCodedDeps: Array<State>;
constructor(agileInstance: Agile, computeFunction: () => ComputedValueType, deps?: Array<State>);
deps: Array<Observer>;
hardCodedDeps: Array<Observer>;
/**
* @public
* Computed - Function that recomputes its value if a dependency changes
* @param agileInstance - An instance of Agile
* @param computeFunction - Function for computing value
* @param deps - Hard coded dependencies of Computed Function
*/
constructor(agileInstance: Agile, computeFunction: () => ComputedValueType, deps?: Array<Observer | State | Event>);
/**
* @public
* Set Value of Computed
*/
set value(value: ComputedValueType);
/**
* @public
* Get Value of Computed
*/
get value(): ComputedValueType;
/**
* Will call the computeFunction and update the dependencies
* @public
* Recomputes Function Value
* -> Calls ComputeFunction and updates Dependencies of it
* @param config - Config
*/
recompute(options?: {
background?: boolean;
sideEffects?: boolean;
}): void;
recompute(config?: RecomputeConfigInterface): void;
/**
* Updates the Compute Function
* @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
*/
updateComputeFunction(computeFunction: () => ComputedValueType, deps?: Array<State>, options?: {
background?: boolean;
sideEffects?: boolean;
}): void;
updateComputeFunction(computeFunction: () => ComputedValueType, deps?: Array<Observer | State | Event>, config?: RecomputeConfigInterface): void;
/**
* @internal
* Will add auto tracked dependencies to this and calls the computeFunction
* Computes Value and adds missing Dependencies to Computed
*/
computeValue(): ComputedValueType;
patch(): this;
persist(key?: string): this;
persist(keyOrConfig?: StorageKey | StatePersistentConfigInterface, config?: StatePersistentConfigInterface): this;
invert(): this;
}
/**
* @param background - If recomputing value happens in the background (-> not causing any rerender)
* @param sideEffects - If Side Effects of Computed get executed
*/
export interface RecomputeConfigInterface {
background?: boolean;
sideEffects?: boolean;
}

@@ -6,20 +6,38 @@ "use strict";

class Computed extends internal_1.State {
/**
* @public
* Computed - Function that recomputes its value if a dependency changes
* @param agileInstance - An instance of Agile
* @param computeFunction - Function for computing value
* @param deps - Hard coded dependencies of Computed Function
*/
constructor(agileInstance, computeFunction, deps = []) {
super(agileInstance, computeFunction());
this.deps = [];
this.deps = []; // All Dependencies of Computed
this.hardCodedDeps = [];
this.agileInstance = () => agileInstance;
this.computeFunction = computeFunction;
this.hardCodedDeps = deps;
// Recompute for setting initial state value and adding missing dependencies
this.hardCodedDeps = deps
.map((dep) => dep["observer"] || undefined)
.filter((dep) => dep !== undefined);
// Recompute for setting initial value and adding missing dependencies
this.recompute();
}
/**
* @public
* Set Value of Computed
*/
set value(value) {
console.error('Agile: Can not mutate Computed value, please use recompute()');
console.error("Agile: You can't mutate Computed value!");
}
/**
* @public
* Get Value of Computed
*/
get value() {
// Note can't use 'super.value' because of 'https://github.com/Microsoft/TypeScript/issues/338'
// Add state to foundState (for auto tracking used states in computed functions)
if (this.agileInstance().runtime.trackState)
this.agileInstance().runtime.foundStates.add(this);
// Can't remove this getter function.. since the setter function is set in this class -> Error if not setter and getter function set
// Add State to tracked Observers (for auto tracking used observers in computed function)
if (this.agileInstance().runtime.trackObservers)
this.agileInstance().runtime.foundObservers.add(this.observer);
return this._value;

@@ -31,12 +49,13 @@ }

/**
* Will call the computeFunction and update the dependencies
* @public
* Recomputes Function Value
* -> Calls ComputeFunction and updates Dependencies of it
* @param config - Config
*/
recompute(options) {
// Assign defaults to config
options = internal_1.defineConfig(options, {
recompute(config) {
config = internal_1.defineConfig(config, {
background: false,
sideEffects: true
sideEffects: true,
});
// Set State to nextState
this.ingest(options);
this.ingest(config);
}

@@ -47,9 +66,15 @@ //=========================================================================================================

/**
* Updates the Compute Function
* @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
*/
updateComputeFunction(computeFunction, deps = [], options) {
updateComputeFunction(computeFunction, deps = [], config) {
this.computeFunction = computeFunction;
this.hardCodedDeps = deps;
// Recompute for setting initial state value and adding missing dependencies
this.recompute(options);
this.hardCodedDeps = deps
.map((dep) => dep["observer"] || undefined)
.filter((dep) => dep !== undefined);
// Recompute for setting initial Computed Function Value and adding missing Dependencies
this.recompute(config);
}

@@ -61,40 +86,37 @@ //=========================================================================================================

* @internal
* Will add auto tracked dependencies to this and calls the computeFunction
* Computes Value and adds missing Dependencies to Computed
*/
computeValue() {
// Set tracking state to true which will than track all states which for instance call state.value
this.agileInstance().runtime.trackState = true;
// Call computeFunction
this.agileInstance().runtime.trackObservers = true;
const computedValue = this.computeFunction();
// Get tracked states and set trackSate to false
let foundStates = this.agileInstance().runtime.getTrackedStates();
// Handle foundStates dependencies
// Get tracked Observers and disable Tracking Observers
let foundDeps = Array.from(this.agileInstance().runtime.getTrackedObservers());
// Handle foundDeps and hardCodedDeps
const newDeps = [];
foundStates.forEach(state => {
// Add the state to newDeps
newDeps.push(state);
// Add this as dependency of the state
state.dep.depend(this);
this.hardCodedDeps.concat(foundDeps).forEach((observer) => {
if (!observer)
return;
newDeps.push(observer);
// Make this Observer depending on Observer -> If value of Observer changes it will ingest this Observer into the Runtime
observer.depend(this.observer);
});
// Handle hardCoded dependencies
this.hardCodedDeps.forEach(state => {
// Add this as dependency of the state
state.dep.depend(this);
});
// Set deps
this.deps = [...this.hardCodedDeps, ...newDeps];
this.deps = newDeps;
return computedValue;
}
//=========================================================================================================
// Overwriting some functions which can't be used in computed
// Overwriting some functions which can't be used in Computed
//=========================================================================================================
patch() {
console.error('Agile: can not use patch method on Computed since the value is dynamic!');
console.error("Agile: You can't use patch method on Computed Function!");
return this;
}
persist(key) {
console.error('Agile: Computed state can not be persisted since the value is dynamic!', key);
persist(keyOrConfig = {}, config = {}) {
console.error("Agile: You can't use persist method on Computed Function!");
return this;
}
invert() {
console.error("Agile: You can't use invert method on Computed Function!");
return this;
}
}
exports.Computed = Computed;

@@ -1,54 +0,78 @@

import { Agile, StateKey } from '../internal';
export declare type DefaultEventPayload = {
[key: string]: any;
};
export declare type EventCallbackFunction<PayloadType = DefaultEventPayload> = (payload?: PayloadType) => void;
export declare type EventKey = string | number;
export interface EventConfig {
key?: EventKey;
enabled?: boolean;
maxUses?: number;
delay?: number;
}
import { Agile } from "../internal";
import { EventObserver } from "./event.observer";
export declare class Event<PayloadType = DefaultEventPayload> {
agileInstance: () => Agile;
config: EventConfig;
_key?: StateKey;
callbacks: Set<EventCallbackFunction<PayloadType>>;
_key?: EventKey;
uses: number;
callbacks: {
[key: string]: EventCallbackFunction<PayloadType>;
};
private currentTimeout;
private queue;
enabled: boolean;
observer: EventObserver;
payload: PayloadType;
/**
* @public
* Event - Class that holds a List of Functions which can be triggered at the same time
* @param agileInstance - An instance of Agile
* @param config - Config
*/
constructor(agileInstance: Agile, config?: EventConfig);
set key(value: StateKey | undefined);
get key(): StateKey | undefined;
/**
* Register Callback Function
* @public
* Set Key/Name of Event
*/
on(callback: EventCallbackFunction<PayloadType>): () => void;
set key(value: EventKey | undefined);
/**
* Run all callback Functions
* @public
* Get Key/Name of Event
*/
get key(): EventKey | undefined;
/**
* @public
* Registers new Callback Function that will be called if this Event gets triggered
* @param callback - Callback Function that gets called if the Event gets triggered
* * @return Key of Event
*/
on(callback: EventCallbackFunction<PayloadType>): string;
/**
* @public
* Registers new Callback Function that will be called if this Event gets triggered
* @param key - Key of Callback Function
* @param callback - Callback Function that gets called if the Event gets triggered
*/
on(key: string, callback: EventCallbackFunction<PayloadType>): this;
/**
* @public
* Triggers Event
* -> Calls all registered Callback Functions
* @param payload - Payload that gets passed into the Callback Functions
*/
trigger(payload?: PayloadType): this;
/**
* Disables the Event
* @public
* Disables Event
*/
disable(): this;
/**
* Enables the Event
* @public
* Enables Event
*/
enable(): this;
/**
* Resets the Event
* @public
* Resets Event
*/
reset(): this;
/**
* @internal
* Unsubs a callback
* @public
* Removes Callback Function at given Key
* @param key - Key of Callback Function that gets removed
*/
private unsub;
removeCallback(key: string): this;
/**
* @internal
* Call event instantly
* Triggers Event
*/

@@ -58,5 +82,24 @@ private normalTrigger;

* @internal
* Call event after config.delay
* Triggers Event with some delay (config.delay)
*/
private delayedTrigger;
}
export declare type EventKey = string | number;
export declare type DefaultEventPayload = {
[key: string]: any;
};
export declare type EventCallbackFunction<PayloadType = DefaultEventPayload> = (payload?: PayloadType) => void;
/**
* @param key - Key/Name of Event
* @param enabled - If Event can be triggered
* @param maxUses - How often the Event can be used/triggered
* @param delay - Delayed call of Event Callback Functions in seconds
* @param rerender - If triggering an Event should cause a rerender
*/
export interface EventConfig {
key?: EventKey;
enabled?: boolean;
maxUses?: number;
delay?: number;
rerender?: boolean;
}

@@ -5,36 +5,64 @@ "use strict";

const internal_1 = require("../internal");
const event_observer_1 = require("./event.observer");
class Event {
/**
* @public
* Event - Class that holds a List of Functions which can be triggered at the same time
* @param agileInstance - An instance of Agile
* @param config - Config
*/
constructor(agileInstance, config = {}) {
this.callbacks = new Set(); // Stores callback functions
this.uses = 0; // How often the event has been used
this.queue = []; // Queue if something is currently in timeout
this.uses = 0;
this.callbacks = {}; // All 'subscribed' callback function
this.queue = []; // Queue of delayed triggers
this.enabled = true;
this.agileInstance = () => agileInstance;
// Assign defaults to config
this.config = internal_1.defineConfig(config, {
enabled: true
enabled: true,
rerender: false,
});
// Set Key
this.observer = new event_observer_1.EventObserver(agileInstance, this, [], this.config.key);
this._key = this.config.key;
// Set Enabled
if (this.config.enabled !== undefined)
this.enabled = this.config.enabled;
this.enabled =
this.config.enabled !== undefined ? this.config.enabled : true;
}
/**
* @public
* Set Key/Name of Event
*/
set key(value) {
this._key = value;
this.observer.key = value;
}
/**
* @public
* Get Key/Name of Event
*/
get key() {
return this._key;
}
//=========================================================================================================
// On
//=========================================================================================================
/**
* Register Callback Function
*/
on(callback) {
const cleanUpFunction = () => this.unsub(callback);
// Add callback to Event Callbacks
this.callbacks.add(callback);
return cleanUpFunction;
on(keyOrCallback, callback) {
const generateKey = internal_1.isFunction(keyOrCallback);
let _callback;
let key;
if (generateKey) {
key = internal_1.generateId();
_callback = keyOrCallback;
}
else {
key = keyOrCallback;
_callback = callback;
}
// Check if Callback is a Function
if (!internal_1.isFunction(_callback)) {
console.error("Agile: A Event Callback Function has to be an function!");
return this;
}
// Check if Callback Function already exists
if (this.callbacks[key]) {
console.error(`Agile: Event Callback Function with the key/name ${key} already exists!`);
return this;
}
this.callbacks[key] = _callback;
return generateKey ? key : this;
}

@@ -45,6 +73,8 @@ //=========================================================================================================

/**
* Run all callback Functions
* @public
* Triggers Event
* -> Calls all registered Callback Functions
* @param payload - Payload that gets passed into the Callback Functions
*/
trigger(payload) {
// If event is disabled, return
if (!this.enabled)

@@ -62,3 +92,4 @@ return this;

/**
* Disables the Event
* @public
* Disables Event
*/

@@ -73,3 +104,4 @@ disable() {

/**
* Enables the Event
* @public
* Enables Event
*/

@@ -84,22 +116,23 @@ enable() {

/**
* Resets the Event
* @public
* Resets Event
*/
reset() {
// Set Enabled
this.enabled = this.config.enabled || true;
// Reset Uses
this.uses = 0;
// Clear active timeout
clearTimeout(this.currentTimeout);
if (this.currentTimeout)
clearTimeout(this.currentTimeout);
return this;
}
//=========================================================================================================
// Unsub
// Remove Callback
//=========================================================================================================
/**
* @internal
* Unsubs a callback
* @public
* Removes Callback Function at given Key
* @param key - Key of Callback Function that gets removed
*/
unsub(callback) {
this.callbacks.delete(callback);
removeCallback(key) {
delete this.callbacks[key];
return this;
}

@@ -111,12 +144,14 @@ //=========================================================================================================

* @internal
* Call event instantly
* Triggers Event
*/
normalTrigger(payload) {
// Call callbacks
this.callbacks.forEach(callback => callback(payload));
// Increase uses
// Call registered Callbacks
for (let key in this.callbacks)
this.callbacks[key](payload);
// Cause rerender
if (this.config.rerender)
this.observer.trigger();
this.uses++;
// Check if maxUses has been reached, if so disable event
// Disable Event if maxUses got reached
if (this.config.maxUses && this.uses >= this.config.maxUses)
// Disable Event
this.disable();

@@ -129,6 +164,6 @@ }

* @internal
* Call event after config.delay
* Triggers Event with some delay (config.delay)
*/
delayedTrigger(payload) {
// Check if a timeout is currently active if so add the payload to a queue
// Check if a Timeout is currently active if so add payload to queue
if (this.currentTimeout !== undefined) {

@@ -139,10 +174,7 @@ if (payload)

}
// Looper function which loops through the queue(payloads)
// Triggers Callback Functions and calls itself again if queue isn't empty
const looper = (payload) => {
this.currentTimeout = setTimeout(() => {
// Reset currentTimeout
this.currentTimeout = undefined;
// Call normalTrigger
this.normalTrigger(payload);
// If items are in queue, continue with them
if (this.queue.length > 0)

@@ -152,3 +184,2 @@ looper(this.queue.shift());

};
// Call looper with current Payload
looper(payload);

@@ -155,0 +186,0 @@ return;

@@ -1,3 +0,3 @@

import { Agile } from './internal';
export * from './internal';
import { Agile } from "./internal";
export * from "./internal";
export default Agile;

@@ -1,16 +0,23 @@

export * from './agile';
export * from './state';
export * from './state/dep';
export { Computed } from './computed';
export * from './collection';
export * from './collection/group';
export * from './collection/item';
export * from './collection/selector';
export * from './event';
export * from './runtime';
export * from './storage';
export * from './integrations';
export * from './runtime/subscription/sub';
export * from './runtime/subscription/CallbackSubscriptionContainer';
export * from './runtime/subscription/ComponentSubscriptionContainer';
export * from './utils';
export * from "./agile";
export * from "./runtime";
export * from "./runtime/observer";
export * from "./runtime/job";
export * from "./runtime/subscription/container/SubscriptionContainer";
export * from "./runtime/subscription/container/CallbackSubscriptionContainer";
export * from "./runtime/subscription/container/ComponentSubscriptionContainer";
export * from "./runtime/subscription/sub";
export * from "./storage";
export * from "./storage/persistent";
export * from "./state";
export * from "./state/state.observer";
export * from "./state/state.persistent";
export { Computed } from "./computed";
export * from "./collection";
export * from "./collection/group";
export * from "./collection/item";
export * from "./collection/selector";
export * from "./collection/collection.persistent";
export * from "./event";
export * from "./integrations";
export * from "./integrations/integration";
export * from "./utils";
"use strict";
// This file exposes Agile functions and types to the outside world.
// This file exposes Agile functions and types to the outside world
// It also serves as a cyclic dependency workaround

@@ -16,9 +16,20 @@ // https://medium.com/visual-development/how-to-fix-nasty-circular-dependency-issues-once-and-for-all-in-javascript-typescript-a04c987cf0de.

Object.defineProperty(exports, "__esModule", { value: true });
// !! All internal Agile modules must import from here.
// Otherwise we can run into annoying cycle dependencies
// !! All internal Agile modules must be imported from here!!
// Agile
__exportStar(require("./agile"), exports);
// Runtime
__exportStar(require("./runtime"), exports);
__exportStar(require("./runtime/observer"), exports);
__exportStar(require("./runtime/job"), exports);
__exportStar(require("./runtime/subscription/container/SubscriptionContainer"), exports);
__exportStar(require("./runtime/subscription/container/CallbackSubscriptionContainer"), exports);
__exportStar(require("./runtime/subscription/container/ComponentSubscriptionContainer"), exports);
__exportStar(require("./runtime/subscription/sub"), exports);
// Storage
__exportStar(require("./storage"), exports);
__exportStar(require("./storage/persistent"), exports);
// State
__exportStar(require("./state"), exports);
__exportStar(require("./state/dep"), exports);
__exportStar(require("./state/state.observer"), exports);
__exportStar(require("./state/state.persistent"), exports);
// Computed

@@ -32,12 +43,9 @@ var computed_1 = require("./computed");

__exportStar(require("./collection/selector"), exports);
__exportStar(require("./collection/collection.persistent"), exports);
// Event
__exportStar(require("./event"), exports);
// Internal Classes
__exportStar(require("./runtime"), exports);
__exportStar(require("./storage"), exports);
// Integrations
__exportStar(require("./integrations"), exports);
__exportStar(require("./runtime/subscription/sub"), exports);
__exportStar(require("./runtime/subscription/CallbackSubscriptionContainer"), exports);
__exportStar(require("./runtime/subscription/ComponentSubscriptionContainer"), exports);
__exportStar(require("./integrations/integration"), exports);
// Utils
__exportStar(require("./utils"), exports);

@@ -1,17 +0,2 @@

import { State, Agile, SubscriptionContainer } from '../internal';
export interface JobInterface {
state: State;
newStateValue?: any;
options?: {
background?: boolean;
sideEffects?: boolean;
forceRerender?: boolean;
};
}
export interface JobConfigInterface {
perform?: boolean;
background?: boolean;
sideEffects?: boolean;
forceRerender?: boolean;
}
import { Agile, SubscriptionContainer, Observer, Job, JobConfigInterface } from "../internal";
export declare class Runtime {

@@ -21,16 +6,24 @@ agileInstance: () => Agile;

private jobQueue;
private notReadyJobsToRerender;
private jobsToRerender;
trackState: boolean;
foundStates: Set<State>;
internalIngestKey: string;
trackObservers: boolean;
foundObservers: Set<Observer>;
/**
* @internal
* Runtime - Performs ingested Observers
* @param agileInstance - An instance of Agile
*/
constructor(agileInstance: Agile);
/**
* @internal
* Creates a Job out of State and new Value and than add it to a job queue
* Note: its not possible to set a state to undefined because undefined is used for internal activities!
* Ingests Observer into Runtime
* -> Creates Job which will be performed by the Runtime
* @param observer - Observer that gets performed by the Runtime
* @param config - Config
*/
ingest(state: State, newStateValue?: any, options?: JobConfigInterface): void;
ingest(observer: Observer, config: JobConfigInterface): void;
/**
* @internal
* Perform a State Update
* Performs Job and adds him to the rerender queue if necessary
* @param job - Job that gets performed
*/

@@ -40,15 +33,18 @@ private perform;

* @internal
* SideEffects are sideEffects of the perform function.. for instance the watchers
* Updates/Rerenders all Subscribed Components of the Job (Observer)
*/
private sideEffects;
private updateSubscribers;
/**
* @internal
* This will be update all Subscribers of complete jobs
* Finds updated Key of SubscriptionContainer and adds it to 'changedObjectKeys'
* @param subscriptionContainer - Object based SubscriptionContainer
* @param job - Job that holds the SubscriptionContainer
*/
private updateSubscribers;
handleObjectBasedSubscription(subscriptionContainer: SubscriptionContainer, job: Job): void;
/**
* @internal
* Builds an object out of propKeysChanged in the SubscriptionContainer
* Builds Object from 'changedObjectKeys' with new Values provided by Observers
* @param subscriptionContainer - SubscriptionContainer from which the Object gets built
*/
formatChangedPropKeys(subscriptionContainer: SubscriptionContainer): {
getObjectBasedProps(subscriptionContainer: SubscriptionContainer): {
[key: string]: any;

@@ -58,5 +54,5 @@ };

* @internal
* Will return all tracked States
* Returns tracked Observers and stops Runtime from tracking anymore Observers
*/
getTrackedStates(): Set<State<any>>;
getTrackedObservers(): Set<Observer>;
}

@@ -5,4 +5,8 @@ "use strict";

const internal_1 = require("../internal");
const CallbackSubscriptionContainer_1 = require("./subscription/CallbackSubscriptionContainer");
class Runtime {
/**
* @internal
* Runtime - Performs ingested Observers
* @param agileInstance - An instance of Agile
*/
constructor(agileInstance) {

@@ -12,7 +16,7 @@ // Queue system

this.jobQueue = [];
this.jobsToRerender = [];
// Used for tracking computed dependencies
this.trackState = false; // Check if agile should track states
this.foundStates = new Set(); // States which were tracked during the track time
this.internalIngestKey = "This is an Internal Key for ingesting internal stuff!";
this.notReadyJobsToRerender = []; // Jobs that are performed but not ready to rerender (wait for mount)
this.jobsToRerender = []; // Jobs that are performed and will be rendered
// Tracking - Used to track computed dependencies
this.trackObservers = false; // Check if Runtime have to track Observers
this.foundObservers = new Set(); // Observers that got tracked during the 'trackObservers' time
this.agileInstance = () => agileInstance;

@@ -25,43 +29,25 @@ }

* @internal
* Creates a Job out of State and new Value and than add it to a job queue
* Note: its not possible to set a state to undefined because undefined is used for internal activities!
* Ingests Observer into Runtime
* -> Creates Job which will be performed by the Runtime
* @param observer - Observer that gets performed by the Runtime
* @param config - Config
*/
ingest(state, newStateValue, options = {}) {
// Merge default values into options
options = internal_1.defineConfig(options, {
ingest(observer, config) {
config = internal_1.defineConfig(config, {
perform: true,
background: false,
sideEffects: true,
forceRerender: false
});
// Create Job
const job = {
state: state,
newStateValue: newStateValue,
options: {
background: options.background,
sideEffects: options.sideEffects,
forceRerender: options.forceRerender
}
};
// Grab nextState if newState not passed or compute if needed
if (newStateValue === this.internalIngestKey) {
if (job.state instanceof internal_1.Computed)
job.newStateValue = job.state.computeValue();
else
job.newStateValue = job.state.nextState;
}
// Check if state value und newStateValue are the same.. if so return except force Rerender (stringifying because of possible object or array)
if (JSON.stringify(state.value) === JSON.stringify(job.newStateValue) && !options.forceRerender) {
if (this.agileInstance().config.logJobs)
console.warn("Agile: Doesn't perform job because state values are the same! ", job);
return;
}
const job = new internal_1.Job(observer, {
background: config.background,
sideEffects: config.sideEffects,
storage: config.storage,
});
// Logging
if (this.agileInstance().config.logJobs)
console.log(`Agile: Created Job(${job.state.key})`, job);
// Push the Job to the Queue (safety.. that no Job get forgotten)
console.log(`Agile: Created Job(${job.observer.key})`, job);
// Add Job to JobQueue (-> no Job get missing)
this.jobQueue.push(job);
// Perform the Job
if (options.perform) {
// Perform Job
if (config.perform) {
const performJob = this.jobQueue.shift();

@@ -71,3 +57,3 @@ if (performJob)

else
console.warn("Agile: No Job in queue ", job);
console.warn("Agile: No Job in queue!");
}

@@ -80,28 +66,17 @@ }

* @internal
* Perform a State Update
* Performs Job and adds him to the rerender queue if necessary
* @param job - Job that gets performed
*/
perform(job) {
var _a, _b;
// Set Job to currentJob
this.currentJob = job;
// Set Previous State
job.state.previousState = internal_1.copy(job.state.value);
// Write new value into the State
job.state.privateWrite(job.newStateValue);
// Set isSet
job.state.isSet = job.newStateValue !== job.state.initialState;
// Set is placeholder to false, because it has got a value
if (job.state.isPlaceholder)
job.state.isPlaceholder = false;
// Perform SideEffects like watcher functions or state.sideEffects
this.sideEffects(job);
// Set Job as completed (The deps and subs of completed jobs will be updated)
if (!((_a = job.options) === null || _a === void 0 ? void 0 : _a.background) || ((_b = job.options) === null || _b === void 0 ? void 0 : _b.forceRerender))
// Perform Job
job.observer.perform(job);
job.performed = true;
if (job.rerender)
this.jobsToRerender.push(job);
// Reset Current Job
this.currentJob = null;
// Logging
if (this.agileInstance().config.logJobs)
console.log(`Agile: Completed Job(${job.state.key})`, job);
// Continue the Loop and perform the next job.. if no job is left update the Subscribers for each completed job
console.log(`Agile: Completed Job(${job.observer.key})`, job);
// Perform Jobs as long as Jobs are in queue, if no job left update/rerender Subscribers of performed Jobs
if (this.jobQueue.length > 0) {

@@ -112,3 +87,3 @@ const performJob = this.jobQueue.shift();

else
console.warn("Agile: Failed to perform Job ", job);
console.warn("Agile: No Job in queue!");
}

@@ -118,3 +93,2 @@ else {

setTimeout(() => {
// Cause rerender on Subscribers
this.updateSubscribers();

@@ -125,22 +99,2 @@ });

//=========================================================================================================
// Side Effect
//=========================================================================================================
/**
* @internal
* SideEffects are sideEffects of the perform function.. for instance the watchers
*/
sideEffects(job) {
var _a;
const state = job.state;
// Call Watchers
for (let watcher in state.watchers)
if (typeof state.watchers[watcher] === 'function')
state.watchers[watcher](state.getPublicValue());
// Call State SideEffects
if (typeof state.sideEffects === 'function' && ((_a = job.options) === null || _a === void 0 ? void 0 : _a.sideEffects))
state.sideEffects();
// Ingest Dependencies of State (Perform is false because it will be performed anyway after this sideEffect)
state.dep.deps.forEach((state) => this.ingest(state, this.internalIngestKey, { perform: false }));
}
//=========================================================================================================
// Update Subscribers

@@ -150,7 +104,5 @@ //=========================================================================================================

* @internal
* This will be update all Subscribers of complete jobs
* Updates/Rerenders all Subscribed Components of the Job (Observer)
*/
updateSubscribers() {
// Check if Agile has an integration because its useless to go trough this process without framework
// It won't happen anything because the state has no subs.. but this check here will maybe improve the performance
if (!this.agileInstance().integrations.hasIntegration()) {

@@ -160,71 +112,90 @@ this.jobsToRerender = [];

}
// Subscriptions that has to be updated (Set = For preventing double subscriptions without further checks)
// Subscriptions that has to be updated/rerendered (Set = For preventing double subscriptions without further checks)
const subscriptionsToUpdate = new Set();
// Map through Jobs to Rerender
this.jobsToRerender.forEach(job =>
// Map through subs of the current Job State
job.state.dep.subs.forEach(subscriptionContainer => {
// Check if subscriptionContainer is ready
if (!subscriptionContainer.ready)
console.warn("Agile: SubscriptionContainer isn't ready yet ", subscriptionContainer);
// For a Container that require props to be passed
if (subscriptionContainer.passProps) {
let localKey = null;
// Find the local Key for this update by comparing the State instance from this Job to the State instances in the propStates object
for (let key in subscriptionContainer.propStates)
if (subscriptionContainer.propStates[key] === job.state)
localKey = key;
// If matching key is found push it into the SubscriptionContainer propKeysChanged where it later will be build to an changed prop object
if (localKey)
subscriptionContainer.propKeysChanged.push(localKey);
}
subscriptionsToUpdate.add(subscriptionContainer);
}));
// Perform Component or Callback updates
subscriptionsToUpdate.forEach(subscriptionContainer => {
// If Callback based subscription call the Callback Function
if (subscriptionContainer instanceof CallbackSubscriptionContainer_1.CallbackSubscriptionContainer) {
// Handle Object based Jobs and check if Job is ready
this.jobsToRerender.concat(this.notReadyJobsToRerender).forEach((job) => {
job.observer.subs.forEach((subscriptionContainer) => {
// Check if Subscription is ready to rerender
if (!subscriptionContainer.ready) {
this.notReadyJobsToRerender.push(job);
if (this.agileInstance().config.logJobs)
console.warn("Agile: SubscriptionContainer/Component isn't ready to rerender!", subscriptionContainer);
return;
}
if (subscriptionContainer.isObjectBased)
this.handleObjectBasedSubscription(subscriptionContainer, job);
subscriptionsToUpdate.add(subscriptionContainer);
});
});
// Update Subscriptions that has to be updated/rerendered
subscriptionsToUpdate.forEach((subscriptionContainer) => {
// Call callback function if Callback based Subscription
if (subscriptionContainer instanceof internal_1.CallbackSubscriptionContainer)
subscriptionContainer.callback();
return;
}
// If Component based subscription call the updateMethod
this.agileInstance().integrations.update(subscriptionContainer.component, this.formatChangedPropKeys(subscriptionContainer));
// Call update method if Component based Subscription
if (subscriptionContainer instanceof internal_1.ComponentSubscriptionContainer)
this.agileInstance().integrations.update(subscriptionContainer.component, this.getObjectBasedProps(subscriptionContainer));
});
// Log Job
if (this.agileInstance().config.logJobs && subscriptionsToUpdate.size > 0)
console.log("Agile: Rerendered Components ", subscriptionsToUpdate);
// Reset Jobs to Rerender
// Logging
if (this.agileInstance().config.logJobs)
console.log("Agile: Updated/Rerendered Subscriptions ", subscriptionsToUpdate);
this.jobsToRerender = [];
}
//=========================================================================================================
// Format Changed Prop Keys
// Handle Object Based Subscription
//=========================================================================================================
/**
* @internal
* Builds an object out of propKeysChanged in the SubscriptionContainer
* Finds updated Key of SubscriptionContainer and adds it to 'changedObjectKeys'
* @param subscriptionContainer - Object based SubscriptionContainer
* @param job - Job that holds the SubscriptionContainer
*/
formatChangedPropKeys(subscriptionContainer) {
handleObjectBasedSubscription(subscriptionContainer, job) {
let localKey = null;
if (!subscriptionContainer.isObjectBased)
return;
// Find localKey of Job Observer in SubscriptionContainer
for (let key in subscriptionContainer.subsObject)
if (subscriptionContainer.subsObject[key] === job.observer)
localKey = key;
// Add localKey to changedObjectKeys
if (localKey)
subscriptionContainer.changedObjectKeys.push(localKey);
}
//=========================================================================================================
// Get Object Based Props
//=========================================================================================================
/**
* @internal
* Builds Object from 'changedObjectKeys' with new Values provided by Observers
* @param subscriptionContainer - SubscriptionContainer from which the Object gets built
*/
getObjectBasedProps(subscriptionContainer) {
const finalObject = {};
// Build Object
subscriptionContainer.propKeysChanged.forEach(changedKey => {
if (subscriptionContainer.propStates)
finalObject[changedKey] = subscriptionContainer.propStates[changedKey].value;
// Map trough changed Keys and build finalObject
subscriptionContainer.changedObjectKeys.forEach((changedKey) => {
// Check if Observer at changedKey has value property, if so add it to final Object
if (subscriptionContainer.subsObject &&
subscriptionContainer.subsObject[changedKey]["value"])
finalObject[changedKey] =
subscriptionContainer.subsObject[changedKey]["value"];
});
subscriptionContainer.changedObjectKeys = [];
return finalObject;
}
//=========================================================================================================
// Get Found State
// Get Tracked Observers
//=========================================================================================================
/**
* @internal
* Will return all tracked States
* Returns tracked Observers and stops Runtime from tracking anymore Observers
*/
getTrackedStates() {
const finalFoundStates = this.foundStates;
getTrackedObservers() {
const finalFoundObservers = this.foundObservers;
// Reset tracking
this.trackState = false;
this.foundStates = new Set();
return finalFoundStates;
this.trackObservers = false;
this.foundObservers = new Set();
return finalFoundObservers;
}
}
exports.Runtime = Runtime;

@@ -1,47 +0,66 @@

import { Agile, State } from '../../internal';
import { ComponentSubscriptionContainer } from "./ComponentSubscriptionContainer";
import { CallbackSubscriptionContainer } from "./CallbackSubscriptionContainer";
export declare type SubscriptionContainer = ComponentSubscriptionContainer | CallbackSubscriptionContainer;
import { Agile, Observer, SubscriptionContainer, ComponentSubscriptionContainer, CallbackSubscriptionContainer } from "../../internal";
export declare class SubController {
agileInstance: () => Agile;
components: Set<ComponentSubscriptionContainer>;
callbacks: Set<CallbackSubscriptionContainer>;
componentSubs: Set<ComponentSubscriptionContainer>;
callbackSubs: Set<CallbackSubscriptionContainer>;
/**
* @internal
* SubController - Handles subscriptions to Components
* @param agileInstance - An instance of Agile
*/
constructor(agileInstance: Agile);
/**
* Subscribe to Agile State with a returned object of props this props can than be returned by the component (See react-integration)
* @internal
* Subscribe with Object shaped Subscriptions
* @param integrationInstance - Callback Function or Component
* @param subs - Initial Subscription Object
*/
subscribeWithSubsObject(subscriptionInstance: any, subs?: {
[key: string]: State;
subscribeWithSubsObject(integrationInstance: any, subs?: {
[key: string]: Observer;
}): {
subscriptionContainer: SubscriptionContainer;
props: {
[key: string]: State['value'];
[key: string]: Observer["value"];
};
};
/**
* Subscribe to Agile State
* @internal
* Subscribe with Array shaped Subscriptions
* @param integrationInstance - Callback Function or Component
* @param subs - Initial Subscription Array
*/
subscribeWithSubsArray(subscriptionInstance: any, subs?: Array<State>): SubscriptionContainer;
subscribeWithSubsArray(integrationInstance: any, subs?: Array<Observer>): SubscriptionContainer;
/**
* Unsubscribe a component or callback
* @internal
* Unsubscribes SubscriptionContainer(Component)
* @param subscriptionInstance - SubscriptionContainer or Component that holds an SubscriptionContainer
*/
unsubscribe(subscriptionInstance: any): void;
/**
* Registers the Component/Callback Subscription and returns a SubscriptionContainer
* @internal
* Registers SubscriptionContainer and decides weather integrationInstance is a callback or component based Subscription
* @param integrationInstance - Callback Function or Component
* @param subs - Initial Subscriptions
*/
registerSubscription(integrationInstance: any, subs?: Array<State>): SubscriptionContainer;
registerSubscription(integrationInstance: any, subs?: Array<Observer>): SubscriptionContainer;
/**
* This will mount the component (Mounts currently only useful in Component based Subscription)
* @internal
* Registers Component based Subscription
* @param componentInstance - Component that got subscribed by Observer/s
* @param subs - Initial Subscriptions
*/
mount(integrationInstance: any): void;
private registerComponentSubscription;
/**
* Registers Component Subscription
* Note: Helper Function
* @internal
* Registers Callback based Subscription
* @param callbackFunction - Callback Function that causes rerender on Component which got subscribed by Observer/s
* @param subs - Initial Subscriptions
*/
private registerComponentSubscription;
private registerCallbackSubscription;
/**
* Registers Callback Subscription
* Note: Helper Function
* @internal
* Mounts Component based SubscriptionContainer
* @param componentInstance - SubscriptionContainer(Component) that gets mounted
*/
private registerCallbackSubscription;
mount(componentInstance: any): void;
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SubController = void 0;
const ComponentSubscriptionContainer_1 = require("./ComponentSubscriptionContainer");
const CallbackSubscriptionContainer_1 = require("./CallbackSubscriptionContainer");
//=========================================================================================================
// Controller
//=========================================================================================================
const internal_1 = require("../../internal");
class SubController {
/**
* @internal
* SubController - Handles subscriptions to Components
* @param agileInstance - An instance of Agile
*/
constructor(agileInstance) {
// Component based Subscription
this.components = new Set();
// Callback based Subscription
this.callbacks = new Set();
this.componentSubs = new Set(); // Holds all registered Component based Subscriptions
this.callbackSubs = new Set(); // Holds all registered Callback based Subscriptions
this.agileInstance = () => agileInstance;

@@ -21,23 +20,33 @@ }

/**
* Subscribe to Agile State with a returned object of props this props can than be returned by the component (See react-integration)
* @internal
* Subscribe with Object shaped Subscriptions
* @param integrationInstance - Callback Function or Component
* @param subs - Initial Subscription Object
*/
subscribeWithSubsObject(subscriptionInstance, subs = {}) {
const subscriptionContainer = this.registerSubscription(subscriptionInstance);
subscribeWithSubsObject(integrationInstance, subs = {}) {
const props = {};
subscriptionContainer.passProps = true;
subscriptionContainer.propStates = Object.assign({}, subs);
// Go through subs
let localKeys = Object.keys(subs);
localKeys.forEach(key => {
const state = subs[key];
// Add State to SubscriptionContainer Subs
subscriptionContainer.subs.add(state);
// Add SubscriptionContainer to State Subs
state.dep.subscribe(subscriptionContainer);
// Add state to props
props[key] = state.value;
});
// Create subsArray
const subsArray = [];
for (let key in subs) {
subsArray.push(subs[key]);
}
// Register Subscription -> decide weather subscriptionInstance is callback or component based
const subscriptionContainer = this.registerSubscription(integrationInstance, subsArray);
// Set SubscriptionContainer to Object based
subscriptionContainer.isObjectBased = true;
subscriptionContainer.subsObject = subs;
// Register subs
for (let key in subs) {
const observer = subs[key];
// Add Observer to SubscriptionContainer Subs
subscriptionContainer.subs.add(observer);
// Add SubscriptionContainer to Observer Subs
observer.subscribe(subscriptionContainer);
// Add Value to props if Observer has value
if (observer.value)
props[key] = observer.value;
}
return {
subscriptionContainer: subscriptionContainer,
props: props
props: props,
};

@@ -49,11 +58,16 @@ }

/**
* Subscribe to Agile State
* @internal
* Subscribe with Array shaped Subscriptions
* @param integrationInstance - Callback Function or Component
* @param subs - Initial Subscription Array
*/
subscribeWithSubsArray(subscriptionInstance, subs = []) {
const subscriptionContainer = this.registerSubscription(subscriptionInstance, subs);
subs.forEach(state => {
// Add State to SubscriptionContainer Subs
subscriptionContainer.subs.add(state);
// Add SubscriptionContainer to State Dependencies Subs
state.dep.subscribe(subscriptionContainer);
subscribeWithSubsArray(integrationInstance, subs = []) {
// Register Subscription -> decide weather subscriptionInstance is callback or component based
const subscriptionContainer = this.registerSubscription(integrationInstance, subs);
// Register subs
subs.forEach((observable) => {
// Add Observer to SubscriptionContainer Subs
subscriptionContainer.subs.add(observable);
// Add SubscriptionContainer to Observer Subs
observable.subscribe(subscriptionContainer);
});

@@ -66,16 +80,20 @@ return subscriptionContainer;

/**
* Unsubscribe a component or callback
* @internal
* Unsubscribes SubscriptionContainer(Component)
* @param subscriptionInstance - SubscriptionContainer or Component that holds an SubscriptionContainer
*/
unsubscribe(subscriptionInstance) {
// Helper function to unsubscribe callback or component based subscription
const unsub = (subscriptionContainer) => {
subscriptionContainer.ready = false;
// Removes SubscriptionContainer from State subs
subscriptionContainer.subs.forEach(state => {
state.dep.unsubscribe(subscriptionContainer);
// Removes SubscriptionContainer from Observer subs
subscriptionContainer.subs.forEach((observer) => {
observer.unsubscribe(subscriptionContainer);
});
};
// Check if subscriptionInstance is CallbackSubscriptionContainer
if (subscriptionInstance instanceof CallbackSubscriptionContainer_1.CallbackSubscriptionContainer)
// Unsubscribe callback based Subscription
if (subscriptionInstance instanceof internal_1.CallbackSubscriptionContainer)
unsub(subscriptionInstance);
// Check if subscriptionInstance has componentSubscriptionContainer.. which holds an instance of a ComponentSubscriptionContainer
// Unsubscribe component based Subscription
// Check if component/class has property componentSubscriptionContainer, which holds an instance of ComponentSubscriptionContainer
if (subscriptionInstance.componentSubscriptionContainer)

@@ -88,6 +106,9 @@ unsub(subscriptionInstance.componentSubscriptionContainer);

/**
* Registers the Component/Callback Subscription and returns a SubscriptionContainer
* @internal
* Registers SubscriptionContainer and decides weather integrationInstance is a callback or component based Subscription
* @param integrationInstance - Callback Function or Component
* @param subs - Initial Subscriptions
*/
registerSubscription(integrationInstance, subs = []) {
if (typeof integrationInstance === 'function')
if (internal_1.isFunction(integrationInstance))
return this.registerCallbackSubscription(integrationInstance, subs);

@@ -97,34 +118,23 @@ return this.registerComponentSubscription(integrationInstance, subs);

//=========================================================================================================
// Mount
//=========================================================================================================
/**
* This will mount the component (Mounts currently only useful in Component based Subscription)
*/
mount(integrationInstance) {
if (!integrationInstance.componentContainer)
return;
// Set Ready to true
integrationInstance.componentContainer.ready = true;
}
//=========================================================================================================
// Register Component Subscription
//=========================================================================================================
/**
* Registers Component Subscription
* Note: Helper Function
* @internal
* Registers Component based Subscription
* @param componentInstance - Component that got subscribed by Observer/s
* @param subs - Initial Subscriptions
*/
registerComponentSubscription(componentInstance, subs = []) {
// Create ComponentSubscriptionContainer
const componentContainer = new ComponentSubscriptionContainer_1.ComponentSubscriptionContainer(componentInstance, new Set(subs));
// Create an instance of the componentSubscriptionContainer in the Component.. to be able to unsubscribe it later (see unsubscribe)
const componentSubscriptionContainer = new internal_1.ComponentSubscriptionContainer(componentInstance, new Set(subs));
this.componentSubs.add(componentSubscriptionContainer);
// To have an instance of a SubscriptionContainer in the Component (needed to unsubscribe component later)
if (componentInstance.componentSubscriptionContainer)
componentInstance.componentSubscriptionContainer = componentContainer;
// Add to components
this.components.add(componentContainer);
// Set Ready
componentInstance.componentSubscriptionContainer = componentSubscriptionContainer;
// Set to ready if not waiting for component to mount
if (!this.agileInstance().config.waitForMount)
componentContainer.ready = true;
componentSubscriptionContainer.ready = true;
// Logging
if (this.agileInstance().config.logJobs)
console.log("Agile: Registered Component ", componentContainer);
return componentContainer;
console.log("Agile: Registered Component based Subscription ", componentSubscriptionContainer);
return componentSubscriptionContainer;
}

@@ -135,17 +145,30 @@ //=========================================================================================================

/**
* Registers Callback Subscription
* Note: Helper Function
* @internal
* Registers Callback based Subscription
* @param callbackFunction - Callback Function that causes rerender on Component which got subscribed by Observer/s
* @param subs - Initial Subscriptions
*/
registerCallbackSubscription(callbackFunction, subs = []) {
// Create CallbackSubscriptionContainer
const callbackContainer = new CallbackSubscriptionContainer_1.CallbackSubscriptionContainer(callbackFunction, new Set(subs));
// Add to callbacks
this.callbacks.add(callbackContainer);
// Set Ready
callbackContainer.ready = true;
const callbackSubscriptionContainer = new internal_1.CallbackSubscriptionContainer(callbackFunction, new Set(subs));
this.callbackSubs.add(callbackSubscriptionContainer);
callbackSubscriptionContainer.ready = true;
// Logging
if (this.agileInstance().config.logJobs)
console.log("Agile: Registered Callback ", callbackContainer);
return callbackContainer;
console.log("Agile: Registered Callback based Subscription ", callbackSubscriptionContainer);
return callbackSubscriptionContainer;
}
//=========================================================================================================
// Mount
//=========================================================================================================
/**
* @internal
* Mounts Component based SubscriptionContainer
* @param componentInstance - SubscriptionContainer(Component) that gets mounted
*/
mount(componentInstance) {
if (!componentInstance.componentSubscriptionContainer)
return;
componentInstance.componentSubscriptionContainer.ready = true;
}
}
exports.SubController = SubController;

@@ -1,7 +0,2 @@

import { Agile, Dep, StorageKey } from '../internal';
export declare type StateKey = string | number;
export interface PersistSettingsInterface {
isPersisted: boolean;
persistKey?: string | number;
}
import { Agile, StorageKey, StateObserver, StatePersistent, Observer, StateJobConfigInterface, StatePersistentConfigInterface } from "../internal";
export declare class State<ValueType = any> {

@@ -11,101 +6,217 @@ agileInstance: () => Agile;

valueType?: string;
dep: Dep;
isSet: boolean;
isPlaceholder: boolean;
initialStateValue: ValueType;
_value: ValueType;
previousStateValue: ValueType;
nextStateValue: ValueType;
observer: StateObserver;
sideEffects: {
[key: string]: (properties?: {
[key: string]: any;
}) => void;
};
isPersisted: boolean;
persistent: StatePersistent | undefined;
watchers: {
[key: string]: (value: any) => void;
};
sideEffects?: Function;
isSet: boolean;
persistSettings: PersistSettingsInterface;
output?: any;
isPlaceholder: boolean;
initialState: ValueType;
_value: ValueType;
previousState: ValueType;
nextState: ValueType;
constructor(agileInstance: Agile, initialState: ValueType, key?: StateKey, deps?: Array<Dep>);
/**
* @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 key - Key/Name of State
* @param deps - Initial deps of State
*/
constructor(agileInstance: Agile, initialValue: ValueType, key?: StateKey, deps?: Array<Observer>);
/**
* @public
* Set Value of State
*/
set value(value: ValueType);
/**
* @public
* Get Value of State
*/
get value(): ValueType;
/**
* @public
* Set Key/Name of State
*/
set key(value: StateKey | undefined);
/**
* @public
* Get Key/Name of State
*/
get key(): StateKey | undefined;
/**
* Directly set state to a new value
* @internal
* Set Key/Name of State
* https://github.com/microsoft/TypeScript/issues/338
* @param value - New Key/Name of State
*/
set(value: ValueType, options?: {
background?: boolean;
sideEffects?: boolean;
}): this;
setKey(value: StateKey | undefined): void;
/**
* @public
* Updates Value of State
* @param value - new State Value
* @param config - Config
*/
set(value: ValueType, config?: SetConfigInterface): this;
/**
* @internal
* Will ingest the nextState or the computedValue (rebuilds state)
* Ingests nextStateValue, computedValue into Runtime
* @param config - Config
*/
ingest(options?: {
background?: boolean;
sideEffects?: boolean;
forceRerender?: boolean;
}): void;
ingest(config?: StateJobConfigInterface): this;
/**
* This is thought for js users.. because ts users can set the type in <>
* @param type - wished type of the state
* @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')
*/
type(type: any): this;
/**
* Will set the state to the previous State
* @public
* Undoes latest State Value change
*/
undo(): void;
/**
* Will reset the state to the initial value
* @public
* Resets State to its initial Value
*/
reset(): this;
/**
* Will merge the changes into the state
* @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
*/
patch(targetWithChanges: object, options?: {
addNewProperties?: boolean;
background?: boolean;
}): this;
patch(targetWithChanges: object, config?: PatchConfigInterface): this;
/**
* Will always be called if the state changes
* @param key - The key of the watch method
* @param callback - The callback function
* @public
* Watches State and detects State changes
* @param callback - Callback Function that gets called if the State Value changes
* @return Key of Watcher
*/
watch(key: string, callback: (value: ValueType) => void): this;
watch(callback: Callback<ValueType>): string;
/**
* Removes a watcher called after the key
* @param key - the key of the watcher function
* @public
* Watches State and detects State changes
* @param key - Key of Watcher Function
* @param callback - Callback Function that gets called if the State Value changes
*/
watch(key: string, callback: Callback<ValueType>): this;
/**
* @public
* Removes Watcher at given Key
* @param key - Key of Watcher that gets removed
*/
removeWatcher(key: string): this;
/**
* Saves the state in the local storage or in a own configured storage
* @param key - the storage key (if no key passed it will take the state key)
* @public
* Checks if watcher at given Key exists
* @param key - Key of Watcher
*/
persist(key?: StorageKey): this;
hasWatcher(key: string): boolean;
/**
* Returns a fresh copy of the current value
* @public
* Stores State Value into Agile Storage permanently
* @param config - Config
*/
persist(config?: StatePersistentConfigInterface): this;
/**
* @public
* Stores State Value into Agile Storage permanently
* @param key - Storage Key (Note: not needed if State has key/name)
* @param config - Config
*/
persist(key?: StorageKey, config?: StatePersistentConfigInterface): this;
/**
* @public
* Creates fresh copy of State Value (-> No reference to State Value)
*/
copy(): ValueType;
/**
* Checks if the State exists
* @public
* Checks if State exists
*/
get exists(): boolean;
/**
* @public
* Equivalent to ===
* @param value - Value that gets checked if its equals to the State Value
*/
is(value: ValueType): boolean;
/**
* @public
* Equivalent to !==
* @param value - Value that gets checked if its not equals to the State Value
*/
isNot(value: ValueType): boolean;
/**
* @public
* Inverts State Value
* Note: Only useful with boolean based States
*/
invert(): this;
/**
* @internal
* Returns 100% the public value of a state because at some points (group) the _value contains only keys
* Adds SideEffect to State
* @param key - Key of SideEffect
* @param sideEffect - Callback Function that gets called on every State Value change
*/
getPublicValue(): ValueType;
addSideEffect(key: string, sideEffect: (properties?: {
[key: string]: any;
}) => void): this;
/**
* @internal
* Will set a new _value and handles the stuff around like storage, ..
* Removes SideEffect at given Key
* @param key - Key of the SideEffect that gets removed
*/
privateWrite(value: any): void;
removeSideEffect(key: string): this;
/**
* @internal
* Will return the perstiable Value of this state..
* some classes which extends state might have another peristiableValue than this.value (like the selector)
* Checks if sideEffect at given Key exists
* @param key - Key of SideEffect
*/
getPersistableValue(): any;
hasSideEffect(key: string): boolean;
/**
* @internal
* Checks if the 'value' has the same type as state.value
* Checks if Value has correct valueType (js)
* @param value - Value that gets checked for its correct Type
*/
private isCorrectType;
private hasCorrectType;
/**
* @internal
* Returns public Value of State
*/
getPublicValue(): ValueType;
/**
* @internal
* Returns Value that gets written into the Agile Storage
*/
getPersistableValue(): any;
}
export declare type StateKey = string | number;
/**
* @param background - If assigning a new value happens in the background (-> not causing any rerender)
* @param sideEffects - If Side Effects of State get executed
* @param storage - If State value gets saved in Agile Storage (only useful if State is persisted)
*/
export interface SetConfigInterface {
background?: boolean;
sideEffects?: boolean;
storage?: boolean;
}
/**
* @param background - If assigning new value happens in the background (-> not causing any rerender)
* @param addNewProperties - If new Properties gets added to the State Value
*/
export interface PatchConfigInterface {
addNewProperties?: boolean;
background?: boolean;
}
export declare type Callback<T = any> = (value: T) => void;

@@ -5,31 +5,53 @@ "use strict";

const internal_1 = require("../internal");
const persist_1 = require("./persist");
class State {
constructor(agileInstance, initialState, key, deps = []) {
/**
* @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 key - Key/Name of State
* @param deps - Initial deps of State
*/
constructor(agileInstance, initialValue, key, deps = []) {
this.isSet = false; // If value is not the same as initialValue
this.isPlaceholder = false;
this.sideEffects = {}; // SideEffects of State (will be executed in Runtime)
this.isPersisted = false; // If State is stored in Storage
this.watchers = {};
this.isSet = false; // Has been changed from initial value
this.isPlaceholder = false; // Defines if the state is a placeholder or not
this.agileInstance = () => agileInstance;
this.initialState = initialState;
this.dep = new internal_1.Dep(deps);
this.initialStateValue = initialValue;
this._key = key;
this._value = initialState;
this.previousState = initialState;
this.nextState = initialState;
this.persistSettings = {
isPersisted: false
};
this._value = internal_1.copy(initialValue);
this.previousStateValue = internal_1.copy(initialValue);
this.nextStateValue = internal_1.copy(initialValue);
this.observer = new internal_1.StateObserver(agileInstance, this, deps, key);
}
/**
* @public
* Set Value of State
*/
set value(value) {
this.set(value);
}
/**
* @public
* Get Value of State
*/
get value() {
// Add state to foundState (for auto tracking used states in computed functions)
if (this.agileInstance().runtime.trackState)
this.agileInstance().runtime.foundStates.add(this);
// Add State to tracked Observers (for auto tracking used observers in computed function)
if (this.agileInstance().runtime.trackObservers)
this.agileInstance().runtime.foundObservers.add(this.observer);
return this._value;
}
/**
* @public
* Set Key/Name of State
*/
set key(value) {
this._key = value;
this.setKey(value);
}
/**
* @public
* Get Key/Name of State
*/
get key() {

@@ -39,26 +61,46 @@ return this._key;

//=========================================================================================================
// Set Key
//=========================================================================================================
/**
* @internal
* Set Key/Name of State
* https://github.com/microsoft/TypeScript/issues/338
* @param value - New Key/Name of State
*/
setKey(value) {
const oldKey = this._key;
// Update State Key
this._key = value;
// Update Key in Observer
this.observer.key = value;
// Update Key in PersistManager
if (value !== undefined &&
this.persistent &&
this.persistent.key === oldKey)
this.persistent.key = value;
}
//=========================================================================================================
// Set
//=========================================================================================================
/**
* Directly set state to a new value
* @public
* Updates Value of State
* @param value - new State Value
* @param config - Config
*/
set(value, options = {}) {
// Assign defaults to options
options = internal_1.defineConfig(options, {
set(value, config = {}) {
config = internal_1.defineConfig(config, {
sideEffects: true,
background: false
background: false,
});
// Check if Type is Correct
if (this.valueType && !this.isCorrectType(value)) {
console.warn(`Agile: Incorrect type (${typeof value}) was provided. Type fixed to ${this.valueType}`);
// Check value has correct Type (js)
if (this.valueType && !this.hasCorrectType(value)) {
console.warn(`Agile: Incorrect type (${typeof value}) was provided.`);
return this;
}
// Check if something has changed (stringifying because of possible object or array)
if (JSON.stringify(this.value) === JSON.stringify(value))
// Check if value has changed
if (internal_1.equal(this.nextStateValue, value))
return this;
// Ingest updated value
this.agileInstance().runtime.ingest(this, value, {
background: options.background,
sideEffects: options.sideEffects
});
// Ingest new value into runtime
this.observer.ingest(value, config);
return this;

@@ -71,16 +113,13 @@ }

* @internal
* Will ingest the nextState or the computedValue (rebuilds state)
* Ingests nextStateValue, computedValue into Runtime
* @param config - Config
*/
ingest(options = {}) {
// Assign defaults to options
options = internal_1.defineConfig(options, {
ingest(config = {}) {
config = internal_1.defineConfig(config, {
sideEffects: true,
background: false,
forceRerender: false
forceRerender: false,
});
this.agileInstance().runtime.ingest(this, this.agileInstance().runtime.internalIngestKey, {
background: options.background,
sideEffects: options.sideEffects,
forceRerender: options.forceRerender
});
this.observer.ingest(internal_1.internalIngestKey, config);
return this;
}

@@ -91,14 +130,14 @@ //=========================================================================================================

/**
* This is thought for js users.. because ts users can set the type in <>
* @param type - wished type of the state
* @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')
*/
type(type) {
// Supported types
const supportedTypes = ['String', 'Boolean', 'Array', 'Object', 'Number'];
const supportedTypes = ["String", "Boolean", "Array", "Object", "Number"];
// Check if type is a supported Type
if (supportedTypes.findIndex(supportedType => supportedType === type.name) === -1) {
if (!supportedTypes.includes(type.name)) {
console.warn(`Agile: '${type}' is not supported! Supported types: String, Boolean, Array, Object, Number`);
return this;
}
// Set valueType
this.valueType = type.name.toLowerCase();

@@ -111,6 +150,7 @@ return this;

/**
* Will set the state to the previous State
* @public
* Undoes latest State Value change
*/
undo() {
this.set(this.previousState);
this.set(this.previousStateValue);
}

@@ -121,10 +161,9 @@ //=========================================================================================================

/**
* Will reset the state to the initial value
* @public
* Resets State to its initial Value
*/
reset() {
// Remove State from Storage (because it is than the initial State again and there is no need to save it anymore)
if (this.persistSettings.isPersisted && this.persistSettings.persistKey)
this.agileInstance().storage.remove(this.persistSettings.persistKey);
// Set State to initial State
this.set(this.initialState);
var _a;
this.set(this.initialStateValue);
(_a = this.persistent) === null || _a === void 0 ? void 0 : _a.removeValue(); // Remove State from Storage (since its the initial Value)
return this;

@@ -136,11 +175,17 @@ }

/**
* Will merge the changes into the state
* @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
*/
patch(targetWithChanges, options = {}) {
// Check if state is object.. because only objects can use the patch method
if (!internal_1.isValidObject(this.nextState)) {
console.warn("Agile: You can't use the patch method on a non object state!");
patch(targetWithChanges, config = {}) {
config = internal_1.defineConfig(config, {
addNewProperties: true,
background: false,
});
if (!internal_1.isValidObject(this.nextStateValue)) {
console.warn("Agile: You can't use the patch method on a non object States!");
return this;
}
// Check if targetWithChanges is an Object.. because you can only patch objects into the State Object
if (!internal_1.isValidObject(targetWithChanges)) {

@@ -150,34 +195,35 @@ console.warn("Agile: TargetWithChanges has to be an object!");

}
// Assign defaults to options
options = internal_1.defineConfig(options, {
addNewProperties: true,
background: false
});
// Merge targetWithChanges into next State
this.nextState = internal_1.flatMerge(this.nextState, targetWithChanges, options);
// Check if something has changed (stringifying because of possible object or array)
if (JSON.stringify(this.value) === JSON.stringify(this.nextState))
// Merge targetWithChanges into nextStateValue
this.nextStateValue = internal_1.flatMerge(this.nextStateValue, targetWithChanges, { addNewProperties: config.addNewProperties });
// Check if value has been changed
if (internal_1.equal(this.value, this.nextStateValue))
return this;
// Set State to nextState
this.ingest({ background: options.background });
this.isSet = this.nextState !== this.initialState;
// Ingest updated nextStateValue into Runtime
this.ingest({ background: config.background });
return this;
}
//=========================================================================================================
// Watch
//=========================================================================================================
/**
* Will always be called if the state changes
* @param key - The key of the watch method
* @param callback - The callback function
*/
watch(key, callback) {
// Check if callback is a function (js)
if (typeof callback !== 'function') {
console.error('Agile: A watcher callback function has to be an function!');
watch(keyOrCallback, callback) {
const generateKey = internal_1.isFunction(keyOrCallback);
let _callback;
let key;
if (generateKey) {
key = internal_1.generateId();
_callback = keyOrCallback;
}
else {
key = keyOrCallback;
_callback = callback;
}
// Check if Callback is a Function
if (!internal_1.isFunction(_callback)) {
console.error("Agile: A Watcher Callback Function has to be an function!");
return this;
}
// Add callback with key to watchers
this.watchers[key] = callback;
return this;
// Check if Callback Function already exists
if (this.watchers[key]) {
console.error(`Agile: Watcher Callback Function with the key/name ${key} already exists!`);
return this;
}
this.watchers[key] = _callback;
return generateKey ? key : this;
}

@@ -188,4 +234,5 @@ //=========================================================================================================

/**
* Removes a watcher called after the key
* @param key - the key of the watcher function
* @public
* Removes Watcher at given Key
* @param key - Key of Watcher that gets removed
*/

@@ -197,10 +244,34 @@ removeWatcher(key) {

//=========================================================================================================
// Persist
// Has Watcher
//=========================================================================================================
/**
* Saves the state in the local storage or in a own configured storage
* @param key - the storage key (if no key passed it will take the state key)
* @public
* Checks if watcher at given Key exists
* @param key - Key of Watcher
*/
persist(key) {
persist_1.persistValue(this, key);
hasWatcher(key) {
return !!this.watchers[key];
}
persist(keyOrConfig = {}, config = {}) {
let _config;
let key;
if (internal_1.isValidObject(keyOrConfig)) {
_config = keyOrConfig;
key = undefined;
}
else {
_config = config || {};
key = keyOrConfig;
}
_config = internal_1.defineConfig(_config, {
instantiate: true,
});
// Update Persistent Key
if (this.persistent) {
if (key)
this.persistent.key = key;
return this;
}
// Create persistent -> Persist Value
this.persistent = new internal_1.StatePersistent(this.agileInstance(), this, key, { instantiate: _config.instantiate });
return this;

@@ -212,3 +283,4 @@ }

/**
* Returns a fresh copy of the current value
* @public
* Creates fresh copy of State Value (-> No reference to State Value)
*/

@@ -222,43 +294,85 @@ copy() {

/**
* Checks if the State exists
* @public
* Checks if State exists
*/
get exists() {
// Check if the value is not undefined and that the state is no placeholder
return this.getPublicValue() !== undefined && !this.isPlaceholder;
}
//=========================================================================================================
// Get Public Value
// Is
//=========================================================================================================
/**
* @public
* Equivalent to ===
* @param value - Value that gets checked if its equals to the State Value
*/
is(value) {
return internal_1.equal(value, this.value);
}
//=========================================================================================================
// Is Not
//=========================================================================================================
/**
* @public
* Equivalent to !==
* @param value - Value that gets checked if its not equals to the State Value
*/
isNot(value) {
return internal_1.notEqual(value, this.value);
}
//=========================================================================================================
// Invert
//=========================================================================================================
/**
* @public
* Inverts State Value
* Note: Only useful with boolean based States
*/
invert() {
if (typeof this._value !== "boolean") {
console.warn("Agile: You can only invert boolean based States!");
return this;
}
this.set(this._value);
return this;
}
//=========================================================================================================
// Add SideEffect
//=========================================================================================================
/**
* @internal
* Returns 100% the public value of a state because at some points (group) the _value contains only keys
* Adds SideEffect to State
* @param key - Key of SideEffect
* @param sideEffect - Callback Function that gets called on every State Value change
*/
getPublicValue() {
if (this.output !== undefined)
return this.output;
return this._value;
addSideEffect(key, sideEffect) {
if (!internal_1.isFunction(sideEffect)) {
console.error("Agile: A sideEffect function has to be an function!");
return this;
}
this.sideEffects[key] = sideEffect;
return this;
}
//=========================================================================================================
// Private Write
// Remove SideEffect
//=========================================================================================================
/**
* @internal
* Will set a new _value and handles the stuff around like storage, ..
* Removes SideEffect at given Key
* @param key - Key of the SideEffect that gets removed
*/
privateWrite(value) {
this._value = internal_1.copy(value);
this.nextState = internal_1.copy(value);
// Save changes in Storage
persist_1.updateValue(this);
removeSideEffect(key) {
delete this.sideEffects[key];
return this;
}
//=========================================================================================================
// Get Persistable Value
// Has SideEffect
//=========================================================================================================
/**
* @internal
* Will return the perstiable Value of this state..
* some classes which extends state might have another peristiableValue than this.value (like the selector)
* Checks if sideEffect at given Key exists
* @param key - Key of SideEffect
*/
getPersistableValue() {
return this.value;
hasSideEffect(key) {
return !!this.sideEffects[key];
}

@@ -270,9 +384,33 @@ //=========================================================================================================

* @internal
* Checks if the 'value' has the same type as state.value
* Checks if Value has correct valueType (js)
* @param value - Value that gets checked for its correct Type
*/
isCorrectType(value) {
hasCorrectType(value) {
let type = typeof value;
return type === this.valueType;
}
//=========================================================================================================
// Get Public Value
//=========================================================================================================
/**
* @internal
* Returns public Value of State
*/
getPublicValue() {
// If State Value is used internal and output represents the real state value (for instance in Group)
if (this["output"] !== undefined)
return this["output"];
return this._value;
}
//=========================================================================================================
// Get Persistable Value
//=========================================================================================================
/**
* @internal
* Returns Value that gets written into the Agile Storage
*/
getPersistableValue() {
return this.value;
}
}
exports.State = State;

@@ -1,44 +0,78 @@

import { Agile, State, Collection } from '../internal';
export declare type StorageKey = string | number;
export interface StorageConfigInterface {
async?: boolean;
prefix?: string;
methods?: {
get: (key: string) => any;
set: (key: string, value: any) => void;
remove: (key: string) => void;
};
}
import { Persistent } from "../internal";
export declare class Storage {
agileInstance: () => Agile;
isAsync: boolean;
private storageReady;
private storageType;
private storagePrefix;
private storageConfig;
persistedStates: Set<State>;
persistedCollections: Set<Collection>;
constructor(agileInstance: Agile, storageConfig: StorageConfigInterface);
storageReady: boolean;
storageType: StorageType;
config: StorageConfigInterface;
persistentInstances: Set<Persistent>;
/**
* This instantiate the Local Storage
* @public
* Storage - Interface for storing Items permanently
* @param storageConfig - Config
*/
constructor(storageConfig: StorageConfigInterface);
/**
* @internal
* Instantiates Local Storage
*/
private instantiateLocalStorage;
/**
* This instantiate the Custom Storage
* @internal
* Instantiates Custom Storage
*/
private instantiateCustomStorage;
/**
* Gets the value provided by the key from the storage
* @public
* Gets value at provided Key
* @param key - Key of Storage property
*/
get<GetType = any>(key: StorageKey): GetType | Promise<GetType> | undefined;
/**
* Sets the value into the storage
* @internal
* Gets value at provided Key (async)
* @param key - Key of Storage property
*/
private asyncGet;
/**
* @public
* Saves/Updates value at provided Key
* @param key - Key of Storage property
* @param value - new Value that gets set
*/
set(key: StorageKey, value: any): void;
/**
* Deletes the value that is stored with the key
* @public
* Removes value at provided Key
* @param key - Key of Storage property
*/
remove(key: StorageKey): void;
/**
* @internal
* Creates Storage Key from provided key
* @param key - Key that gets converted into a Storage Key
*/
private getStorageKey;
private localStorageAvailable;
/**
* @internal
* Checks if localStorage is available in this Environment
*/
static localStorageAvailable(): boolean;
}
export declare type StorageKey = string | number;
export declare type StorageType = "localStorage" | "custom";
/**
* @param async - If its an async storage
* @param prefix - Prefix of Storage Property
* @param methods - Storage methods like (get, set, remove)
* @param methods.get - Get Method of Storage (gets items from storage)
* @param methods.set - Set Method of Storage (saves/updates items in storage)
* @param methods.remove - Remove Methods of Storage (removes items from storage)
*/
export interface StorageConfigInterface {
async?: boolean;
prefix?: string;
methods?: {
get: (key: string) => any;
set: (key: string, value: any) => void;
remove: (key: string) => void;
};
}

@@ -6,24 +6,22 @@ "use strict";

class Storage {
constructor(agileInstance, storageConfig) {
this.isAsync = false;
/**
* @public
* Storage - Interface for storing Items permanently
* @param storageConfig - Config
*/
constructor(storageConfig) {
this.storageReady = false;
this.storageType = 'localStorage';
this.storagePrefix = 'agile';
this.persistedStates = new Set();
this.persistedCollections = new Set();
this.agileInstance = () => agileInstance;
this.storageConfig = storageConfig;
// Set custom Storage prefix
if (storageConfig.prefix)
this.storagePrefix = storageConfig.prefix;
// Set custom Storage functions
this.storageType = "localStorage";
this.persistentInstances = new Set();
this.config = internal_1.defineConfig(storageConfig, {
prefix: "agile",
async: false,
});
// If Storage Methods are provided its a custom Storage
if (storageConfig.methods)
this.storageType = 'custom';
if (storageConfig.async)
this.isAsync = true;
// Instantiate Custom Storage
if (this.storageType === 'custom')
this.storageType = "custom";
// Instantiate Storage
if (this.storageType === "custom")
this.instantiateCustomStorage();
// Instantiate Local Storage
if (this.storageType === 'localStorage')
if (this.storageType === "localStorage")
this.instantiateLocalStorage();

@@ -35,15 +33,16 @@ }

/**
* This instantiate the Local Storage
* @internal
* Instantiates Local Storage
*/
instantiateLocalStorage() {
// Check if Local Storage is Available (For instance in ReactNative it doesn't exist)
if (!this.localStorageAvailable()) {
console.warn("Agile: Local Storage is here not available.. to use the Storage functionality please provide a custom Storage!");
// Check if Local Storage is Available
if (!Storage.localStorageAvailable()) {
console.warn("Agile: Local Storage is here not available, to use Storage functionalities like persist please provide a custom Storage!");
return;
}
// Set StorageMethods to LocalStorageMethods
this.storageConfig.methods = {
this.config.methods = {
get: localStorage.getItem.bind(localStorage),
set: localStorage.setItem.bind(localStorage),
remove: localStorage.removeItem.bind(localStorage)
remove: localStorage.removeItem.bind(localStorage),
};

@@ -56,24 +55,25 @@ this.storageReady = true;

/**
* This instantiate the Custom Storage
* @internal
* Instantiates Custom Storage
*/
instantiateCustomStorage() {
var _a, _b, _c, _d, _e, _f;
// Check Get Function
if (!internal_1.isFunction((_a = this.storageConfig.methods) === null || _a === void 0 ? void 0 : _a.get)) {
// Validate Functions
if (!internal_1.isFunction((_a = this.config.methods) === null || _a === void 0 ? void 0 : _a.get)) {
console.error("Agile: Your GET StorageMethod isn't valid!");
return;
}
// Check Set Function
if (!internal_1.isFunction((_b = this.storageConfig.methods) === null || _b === void 0 ? void 0 : _b.set)) {
if (!internal_1.isFunction((_b = this.config.methods) === null || _b === void 0 ? void 0 : _b.set)) {
console.error("Agile: Your SET StorageMethod isn't valid!");
return;
}
// Check Remove Function
if (!internal_1.isFunction((_c = this.storageConfig.methods) === null || _c === void 0 ? void 0 : _c.remove)) {
if (!internal_1.isFunction((_c = this.config.methods) === null || _c === void 0 ? void 0 : _c.remove)) {
console.error("Agile: Your REMOVE StorageMethod isn't valid!");
return;
}
// Check if one function is async if so set is Async to true
if (internal_1.isAsyncFunction((_d = this.storageConfig.methods) === null || _d === void 0 ? void 0 : _d.get) || internal_1.isAsyncFunction((_e = this.storageConfig.methods) === null || _e === void 0 ? void 0 : _e.set) || internal_1.isAsyncFunction((_f = this.storageConfig.methods) === null || _f === void 0 ? void 0 : _f.remove))
this.isAsync = true;
// Check if custom Storage is async
if (internal_1.isAsyncFunction((_d = this.config.methods) === null || _d === void 0 ? void 0 : _d.get) ||
internal_1.isAsyncFunction((_e = this.config.methods) === null || _e === void 0 ? void 0 : _e.set) ||
internal_1.isAsyncFunction((_f = this.config.methods) === null || _f === void 0 ? void 0 : _f.remove))
this.config.async = true;
this.storageReady = true;

@@ -85,22 +85,15 @@ }

/**
* Gets the value provided by the key from the storage
* @public
* Gets value at provided Key
* @param key - Key of Storage property
*/
get(key) {
var _a;
if (!this.storageReady || !((_a = this.storageConfig.methods) === null || _a === void 0 ? void 0 : _a.get))
if (!this.storageReady || !((_a = this.config.methods) === null || _a === void 0 ? void 0 : _a.get))
return;
// Async Get
if (this.isAsync)
return new Promise((resolve, reject) => {
var _a;
(_a = this.storageConfig.methods) === null || _a === void 0 ? void 0 : _a.get(this.getStorageKey(key)).then((res) => {
// If result is no Json
if (!internal_1.isJsonString(res))
return resolve(res);
// Format Json to Object
resolve(JSON.parse(res));
}).catch(reject);
});
if (this.config.async)
return this.asyncGet(key);
// Normal Get
const res = this.storageConfig.methods.get(this.getStorageKey(key));
const res = this.config.methods.get(this.getStorageKey(key));
if (internal_1.isJsonString(res))

@@ -111,12 +104,33 @@ return JSON.parse(res);

//=========================================================================================================
// Async Get
//=========================================================================================================
/**
* @internal
* Gets value at provided Key (async)
* @param key - Key of Storage property
*/
asyncGet(key) {
return new Promise((resolve, reject) => {
var _a;
(_a = this.config.methods) === null || _a === void 0 ? void 0 : _a.get(this.getStorageKey(key)).then((res) => {
if (internal_1.isJsonString(res))
return resolve(JSON.parse(res));
resolve(res);
}).catch(reject);
});
}
//=========================================================================================================
// Set
//=========================================================================================================
/**
* Sets the value into the storage
* @public
* Saves/Updates value at provided Key
* @param key - Key of Storage property
* @param value - new Value that gets set
*/
set(key, value) {
var _a;
if (!this.storageReady || !((_a = this.storageConfig.methods) === null || _a === void 0 ? void 0 : _a.set))
if (!this.storageReady || !((_a = this.config.methods) === null || _a === void 0 ? void 0 : _a.set))
return;
this.storageConfig.methods.set(this.getStorageKey(key), JSON.stringify(value));
this.config.methods.set(this.getStorageKey(key), JSON.stringify(value));
}

@@ -127,20 +141,34 @@ //=========================================================================================================

/**
* Deletes the value that is stored with the key
* @public
* Removes value at provided Key
* @param key - Key of Storage property
*/
remove(key) {
var _a;
if (!this.storageReady || !((_a = this.storageConfig.methods) === null || _a === void 0 ? void 0 : _a.remove))
if (!this.storageReady || !((_a = this.config.methods) === null || _a === void 0 ? void 0 : _a.remove))
return;
this.storageConfig.methods.remove(this.getStorageKey(key));
this.config.methods.remove(this.getStorageKey(key));
}
//=========================================================================================================
// Helper
// Get Storage Key
//=========================================================================================================
/**
* @internal
* Creates Storage Key from provided key
* @param key - Key that gets converted into a Storage Key
*/
getStorageKey(key) {
return `_${this.storagePrefix}_${key}`;
return `_${this.config.prefix}_${key}`;
}
localStorageAvailable() {
//=========================================================================================================
// Local Storage Available
//=========================================================================================================
/**
* @internal
* Checks if localStorage is available in this Environment
*/
static localStorageAvailable() {
try {
localStorage.setItem('_', '_');
localStorage.removeItem('_');
localStorage.setItem("_myDummyKey_", "myDummyValue");
localStorage.removeItem("_myDummyKey_");
return true;

@@ -147,0 +175,0 @@ }

@@ -1,4 +0,6 @@

import { Agile } from './internal';
import { Agile } from "./internal";
/**
* Copy an array or object.. without any dependencies
* @internal
* Creates a fresh copy of an Array/Object
* @param value - Array/Object that gets copied
*/

@@ -8,39 +10,101 @@ export declare function copy<T = any>(value: T): T;

/**
* Checks if an Object is an valid object for Agile
* @internal
* Checks if an value is a valid Object
* https://stackoverflow.com/questions/12996871/why-does-typeof-array-with-objects-return-object-and-not-array
* @param value - Value that is tested for its correctness
*/
export declare function isValidObject(value: any): boolean;
/**
* Convert item into an array
* @internal
* Transforms Item/s to an Item Array
* @param items - Item/s that gets transformed to an Array
*/
export declare function normalizeArray<DataType = any>(items?: DataType | Array<DataType>): Array<DataType>;
/**
* Tries to get AgileInstance from instance(State, Collection)
* @internal
* Tries to get an AgileInstance from provided instance
* If no agileInstance found it returns the global AgileInstance
* @param instance - Instance that might hold an AgileInstance
*/
export declare function getAgileInstance(instance: any): Agile | null;
export declare function getAgileInstance(instance: any): Agile | undefined;
/**
* Checks if func is a function
* @internal
* Checks if value is a function
* @param value - Value that gets tested if its a function
*/
export declare function isFunction(func: any): boolean;
export declare function isFunction(value: any): boolean;
/**
* Checks if func is a async function
* @internal
* Checks if value is an async function
* @param value - Value that gets tested if its an async function
*/
export declare function isAsyncFunction(func: any): boolean;
export declare function isAsyncFunction(value: any): boolean;
/**
* Checks if url is valid
* @internal
* Checks the correctness of an url
* Resource: https://stackoverflow.com/questions/5717093/check-if-a-javascript-string-is-a-url
* @param url - Url that gets tested for its correctness
*/
export declare function isValidUrl(url: string): boolean;
/**
* Checks if value is a valid JsonString
* @internal
* Checks if value is valid JsonString
* @param value - Value that gets checked
*/
export declare function isJsonString(value: any): boolean;
/**
* Will create a config (config) and merges default values (default) into this config (config)
* @internal
* Merges default values/properties into config object
* @param config - Config object that receives default values
* @param defaults - Default values object that gets merged into config object
*/
export declare function defineConfig<C>(config: C, defaults: object): C;
export declare function defineConfig<ConfigInterface = Object>(config: ConfigInterface, defaults: Object): ConfigInterface;
/**
* Merged the items flat into the object
* @internal
* @param addNewProperties - Adds new properties to source Object
*/
export declare function flatMerge<DataType = Object>(source: DataType, changes: Object, config?: {
export interface FlatMergeConfigInterface {
addNewProperties?: boolean;
}): DataType;
}
/**
* @internal
* Merges items into object, be aware that the merge will only happen at the top level of the object
* @param source - Source object
* @param changes - Changes that get merged into the source object
* @param config - Config
*/
export declare function flatMerge<DataType = Object>(source: DataType, changes: Object, config?: FlatMergeConfigInterface): DataType;
/**
* @internal
* Check if two values are equal
* @param value1 - First Value
* @param value2 - Second Value
*/
export declare function equal(value1: any, value2: any): boolean;
/**
* @internal
* Checks if two values aren't equal
* @param value1 - First Value
* @param value2 - Second Value
*/
export declare function notEqual(value1: any, value2: any): boolean;
/**
* @internal
* Generates random Id
* @param length - Length of generated Id
*/
export declare function generateId(length?: number): string;
/**
* @internal
* Clones a Class
* @param instance - Instance of Class you want to clone
*/
export declare function clone<T>(instance: T): T;
/**
* @internal
* Binds Instance Global
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis
* @param key - Key of Instance
* @param instance - Instance which becomes globally accessible (globalThis.key)
*/
export declare function globalBind(key: string, instance: any): void;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.flatMerge = exports.defineConfig = exports.isJsonString = exports.isValidUrl = exports.isAsyncFunction = exports.isFunction = exports.getAgileInstance = exports.normalizeArray = exports.isValidObject = exports.copy = void 0;
exports.globalBind = exports.clone = exports.generateId = exports.notEqual = exports.equal = exports.flatMerge = exports.defineConfig = exports.isJsonString = exports.isValidUrl = exports.isAsyncFunction = exports.isFunction = exports.getAgileInstance = exports.normalizeArray = exports.isValidObject = exports.copy = void 0;
const internal_1 = require("./internal");

@@ -17,4 +17,6 @@ function copy(value) {

/**
* Checks if an Object is an valid object for Agile
* @internal
* Checks if an value is a valid Object
* https://stackoverflow.com/questions/12996871/why-does-typeof-array-with-objects-return-object-and-not-array
* @param value - Value that is tested for its correctness
*/

@@ -27,6 +29,12 @@ function isValidObject(value) {

catch (e) {
return typeof obj === 'object' && obj.nodeType === 1 && typeof obj.style === 'object' && typeof obj.ownerDocument === 'object';
return (typeof obj === "object" &&
obj.nodeType === 1 &&
typeof obj.style === "object" &&
typeof obj.ownerDocument === "object");
}
}
return value !== null && typeof value === 'object' && !isHTMLElement(value) && !Array.isArray(value);
return (value !== null &&
typeof value === "object" &&
!isHTMLElement(value) &&
!Array.isArray(value));
}

@@ -38,6 +46,7 @@ exports.isValidObject = isValidObject;

/**
* Convert item into an array
* @internal
* Transforms Item/s to an Item Array
* @param items - Item/s that gets transformed to an Array
*/
function normalizeArray(items) {
// Return empty array if no items
if (!items)

@@ -52,7 +61,10 @@ return [];

/**
* Tries to get AgileInstance from instance(State, Collection)
* @internal
* Tries to get an AgileInstance from provided instance
* If no agileInstance found it returns the global AgileInstance
* @param instance - Instance that might hold an AgileInstance
*/
function getAgileInstance(instance) {
try {
// Return state agileInstance
// Try to find agileInstance in Instance
if (instance instanceof internal_1.State)

@@ -64,5 +76,7 @@ return instance.agileInstance();

return instance.agileInstance();
// Return the globalBind agile instance
// @ts-ignore
return globalThis.__agile;
const _instance = instance["agileInstance"];
if (_instance)
return instance;
// Return global bound agileInstance (set in first instantiation of Agile)
return globalThis.__agile__;
}

@@ -72,3 +86,3 @@ catch (e) {

}
return null;
return undefined;
}

@@ -80,6 +94,8 @@ exports.getAgileInstance = getAgileInstance;

/**
* Checks if func is a function
* @internal
* Checks if value is a function
* @param value - Value that gets tested if its a function
*/
function isFunction(func) {
return typeof func === 'function';
function isFunction(value) {
return typeof value === "function";
}

@@ -91,6 +107,8 @@ exports.isFunction = isFunction;

/**
* Checks if func is a async function
* @internal
* Checks if value is an async function
* @param value - Value that gets tested if its an async function
*/
function isAsyncFunction(func) {
return isFunction(func) && func.constructor.name === 'AsyncFunction';
function isAsyncFunction(value) {
return isFunction(value) && value.constructor.name === "AsyncFunction";
}

@@ -100,14 +118,16 @@ exports.isAsyncFunction = isAsyncFunction;

// Is Valid Url
// https://stackoverflow.com/questions/5717093/check-if-a-javascript-string-is-a-url
//=========================================================================================================
/**
* Checks if url is valid
* @internal
* Checks the correctness of an url
* Resource: https://stackoverflow.com/questions/5717093/check-if-a-javascript-string-is-a-url
* @param url - Url that gets tested for its correctness
*/
function isValidUrl(url) {
const pattern = new RegExp('^(https?:\\/\\/)?' + // protocol
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
'((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
'(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
'(\\#[-a-z\\d_]*)?$', 'i'); // fragment locator
const pattern = new RegExp("^(https?:\\/\\/)?" + // protocol
"((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name
"((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address
"(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path
"(\\?[;&a-z\\d%_.~+=-]*)?" + // query string
"(\\#[-a-z\\d_]*)?$", "i");
return pattern.test(url);

@@ -120,3 +140,5 @@ }

/**
* Checks if value is a valid JsonString
* @internal
* Checks if value is valid JsonString
* @param value - Value that gets checked
*/

@@ -137,3 +159,6 @@ function isJsonString(value) {

/**
* Will create a config (config) and merges default values (default) into this config (config)
* @internal
* Merges default values/properties into config object
* @param config - Config object that receives default values
* @param defaults - Default values object that gets merged into config object
*/

@@ -144,18 +169,19 @@ function defineConfig(config, defaults) {

exports.defineConfig = defineConfig;
//=========================================================================================================
// Flat Merge
//=========================================================================================================
/**
* Merged the items flat into the object
* @internal
* Merges items into object, be aware that the merge will only happen at the top level of the object
* @param source - Source object
* @param changes - Changes that get merged into the source object
* @param config - Config
*/
function flatMerge(source, changes, config = {}) {
// Copy Source to avoid reference
// Copy Source to avoid references
const _source = copy(source);
// Loop through changes object and merge changes into source
if (!_source)
return _source;
// Loop through source object an merge changes into it
let keys = Object.keys(changes);
keys.forEach(property => {
// @ts-ignore https://stackoverflow.com/questions/18452920/continue-in-cursor-foreach
keys.forEach((property) => {
if (!config.addNewProperties && !_source[property])
return;
// @ts-ignore
_source[property] = changes[property];

@@ -166,1 +192,80 @@ });

exports.flatMerge = flatMerge;
//=========================================================================================================
// Equals
//=========================================================================================================
/**
* @internal
* Check if two values are equal
* @param value1 - First Value
* @param value2 - Second Value
*/
function equal(value1, value2) {
return value1 === value2 || JSON.stringify(value1) === JSON.stringify(value2);
}
exports.equal = equal;
//=========================================================================================================
// Not Equals
//=========================================================================================================
/**
* @internal
* Checks if two values aren't equal
* @param value1 - First Value
* @param value2 - Second Value
*/
function notEqual(value1, value2) {
return value1 !== value2 && JSON.stringify(value1) !== JSON.stringify(value2);
}
exports.notEqual = notEqual;
//=========================================================================================================
// Generate Id
//=========================================================================================================
/**
* @internal
* Generates random Id
* @param length - Length of generated Id
*/
function generateId(length) {
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const charactersLength = characters.length;
let result = "";
if (!length)
length = 5;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
exports.generateId = generateId;
//=========================================================================================================
// Clone
//=========================================================================================================
/**
* @internal
* Clones a Class
* @param instance - Instance of Class you want to clone
*/
function clone(instance) {
const copy = Object.create(Object.getPrototypeOf(instance));
return Object.assign(copy, instance);
}
exports.clone = clone;
//=========================================================================================================
// Global Bind
//=========================================================================================================
/**
* @internal
* Binds Instance Global
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis
* @param key - Key of Instance
* @param instance - Instance which becomes globally accessible (globalThis.key)
*/
function globalBind(key, instance) {
try {
if (!globalThis[key])
globalThis[key] = instance;
}
catch (e) {
console.warn(`Agile: Failed to create global Instance called '${name}'`, instance);
}
}
exports.globalBind = globalBind;
{
"name": "@agile-ts/core",
"version": "0.0.3",
"version": "0.0.4",
"author": "BennoDev",

@@ -11,3 +11,6 @@ "license": "ISC",

"scripts": {
"build": "tsc"
"build": "tsc",
"dev-publish": "yalc publish",
"dev-push": "yalc push",
"watch": "tsc-watch --onSuccess \"npm run dev-push\""
},

@@ -14,0 +17,0 @@ "devDependencies": {

SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc