@agile-ts/core
Advanced tools
Comparing version 0.0.3 to 0.0.4
@@ -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": { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
211494
54
5223
1