diff --git a/.changeset/serious-terms-perform.md b/.changeset/serious-terms-perform.md
new file mode 100644
index 00000000..afb114d6
--- /dev/null
+++ b/.changeset/serious-terms-perform.md
@@ -0,0 +1,27 @@
+---
+'@agile-ts/api': patch
+'@agile-ts/core': patch
+'@agile-ts/event': patch
+'@agile-ts/logger': patch
+'@agile-ts/multieditor': patch
+'@agile-ts/proxytree': patch
+'@agile-ts/react': patch
+'@agile-ts/utils': patch
+'@agile-ts/vue': patch
+---
+
+#### :rocket: New Feature
+* `react`
+ * [#171](https://github.com/agile-ts/agile/pull/171) Add deps array to useAgile() hook ([@bennodev19](https://github.com/bennodev19))
+* `core`, `event`, `react`, `vue`
+ * [#166](https://github.com/agile-ts/agile/pull/166) Shared Agile Instance ([@bennodev19](https://github.com/bennodev19))
+
+#### :nail_care: Polish
+* `api`, `core`, `event`, `logger`, `multieditor`, `react`, `utils`
+ * [#168](https://github.com/agile-ts/agile/pull/168) Performance optimization ([@bennodev19](https://github.com/bennodev19))
+* `core`, `event`, `react`, `vue`
+ * [#166](https://github.com/agile-ts/agile/pull/166) Shared Agile Instance ([@bennodev19](https://github.com/bennodev19))
+
+#### Committers: 1
+- BennoDev ([@bennodev19](https://github.com/bennodev19))
+
diff --git a/README.md b/README.md
index 3a1ae771..d054994f 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-> Global, simple, spacy State and Logic Framework
+> Global State and Logic Framework
@@ -42,20 +42,20 @@
```tsx
// -- core.js ------------------------------------------
-// 1️⃣ Create Instance of AgileTs
-const App = new Agile();
-
-// 2️⃣ Create State with help of before defined Agile Instance
-const MY_FIRST_STATE = App.createState("Hello Friend!");
+// 1️⃣ Create State with the initial value "Hello Friend!"
+const MY_FIRST_STATE = createState("Hello Friend!");
// -- MyComponent.whatever ------------------------------------------
-// 3️⃣ Bind initialized State to desired UI-Component
-// And wolla, it's reactive. Everytime the State mutates the Component rerenders
-const myFirstState = useAgile(MY_FIRST_STATE); // Returns value of State ("Hello Friend!")
+// 2️⃣ Bind initialized State to the desired UI-Component.
+// And wolla, the Component is reactive.
+// Everytime the State mutates the Component re-renders.
+const myFirstState = useAgile(MY_FIRST_STATE);
+console.log(myFirstState); // Returns "Hello Friend!"
```
-Want to learn more? Check out our [Quick Start Guides](https://agile-ts.org/docs/Installation.md).
+Want to learn how to implement AgileTs in your preferred UI-Framework?
+Check out our [Quick Start Guides](https://agile-ts.org/docs/Installation.md).
### ⛳️ Sandbox
Test AgileTs yourself in a [codesandbox](https://codesandbox.io/s/agilets-first-state-f12cz).
@@ -66,7 +66,7 @@ It's only one click away. Just select your preferred Framework below.
- [Vue](https://codesandbox.io/s/agilets-first-state-i5xxs)
- Angular (coming soon)
-More examples can be found in the [Example Section](https://agile-ts.org/docs/examples).
+More examples can be found in the [Example section](https://agile-ts.org/docs/examples).
@@ -75,59 +75,59 @@ More examples can be found in the [Example Section](https://agile-ts.org/docs/ex
-AgileTs is a global, simple, well-tested State Management Framework implemented in Typescript.
+AgileTs is a global State and Logic Framework implemented in Typescript.
It offers a reimagined API that focuses on **developer experience**
-and allows you to **easily** manage your States.
-Besides States, AgileTs offers some other powerful APIs that make your life easier.
+and allows you to **easily** and **flexible** manage your application States.
+Besides [States](https://agile-ts.org/docs/core/state),
+AgileTs offers some other powerful APIs that make your life easier,
+such as [Collections](https://agile-ts.org/docs/core/collection)
+and [Computed States](https://agile-ts.org/docs/core/computed).
The philosophy behind AgileTs is simple:
### 🚅 Straightforward
Write minimalistic, boilerplate-free code that captures your intent.
```ts
-const MY_STATE = App.createState('frank'); // Create State
-MY_STATE.set('jeff'); // Update State value
-MY_STATE.undo(); // Undo latest State value change
-MY_STATE.is({hello: "jeff"}); // Check if State has the value '{hello: "jeff"}'
-MY_STATE.watch((value) => {console.log(value);}); // Watch on State changes
-```
+// Create State with inital value 'frank'
+const MY_STATE = createState('frank');
-**Some more straightforward syntax examples:**
+// Update State value from 'frank' to 'jeff'
+MY_STATE.set('jeff');
-- Store State in any Storage, like the [Local Storage](https://www.w3schools.com/html/html5_webstorage.asp)
- ```ts
- MY_STATE.persist("storage-key");
- ```
-- Create a reactive Array of States
- ```ts
- const MY_COLLECTION = App.createCollection();
- MY_COLLECTION.collect({id: 1, name: "Frank"});
- MY_COLLECTION.collect({id: 2, name: "Dieter"});
- MY_COLLECTION.update(1, {name: "Jeff"});
- ```
-- Compute State depending on other States
- ```ts
- const MY_INTRODUCTION = App.createComputed(() => {
- return `Hello I am '${MY_NAME.vale}' and I use ${MY_STATE_MANAGER.value} for State Management.`;
- });
- ```
+// Undo latest State value change
+MY_STATE.undo();
+
+// Reset State value to its initial value
+MY_STATE.reset();
+
+// Permanently store State value in an external Storage
+MY_STATE.persist("storage-key");
+```
### 🤸 Flexible
-- Works in nearly any UI-Layer. Check [here](https://agile-ts.org/docs/Frameworks) if your preferred Framework is supported too.
-- Surly behaves with the workflow which suits you best. No need for _reducers_, _actions_, ..
-- Has **0** external dependencies
+- Works in nearly any UI-Framework (currently supported are [React](https://reactjs.org/), [React-Native](https://reactnative.dev/) and [Vue](https://vuejs.org/)).
+- Surly behaves with the workflow that suits you best.
+ No need for _reducers_, _actions_, ..
+- Has **0** external dependencies.
### ⛳️ Centralize
-AgileTs is designed to take all business logic out of UI-Components and put them in a central place, often called `core`.
-The benefit of keeping logic separate to UI-Components is to make your code more decoupled, portable, scalable, and above all, easily testable.
+AgileTs is designed to take all business logic out of the UI-Components
+and put them in a central place, often called `core`.
+The advantage of keeping logic separate to UI-Components,
+is that your code is more decoupled, portable, scalable,
+and above all, easily testable.
+
+You can learn more about ways to centralize your application logic with AgileTs
+in our [Style Guides](https://agile-ts.org/docs/style-guide).
### 🎯 Easy to Use
-Learn the powerful tools of AgileTs in a short amount of time. An excellent place to start are
-our [Quick Start Guides](https://agile-ts.org/docs/Installation), or if you don't like to follow any tutorials,
-you can jump straight into our [Example](https://agile-ts.org/docs/examples/Introduction) Section.
+Learn the powerful tools of AgileTs in a short period of time.
+An excellent place to start are our [Quick Start Guides](https://agile-ts.org/docs/Installation),
+or if you don't like to follow tutorials,
+you can jump straight into the [Example section](https://agile-ts.org/docs/examples/Introduction).
@@ -136,17 +136,18 @@ you can jump straight into our [Example](https://agile-ts.org/docs/examples/Intr
-In order to properly use AgileTs, in a UI-Framework, we need to install **two** packages.
+In order to use AgileTs in a UI-Framework, we need to install **two packages**.
-- The [`core`](https://agile-ts.org/docs/core) package, which contains the State Management Logic of AgileTs
- and therefore offers powerful classes such as the [`State Class`](https://agile-ts.org/docs/core/state).
+- The [`core`](https://agile-ts.org/docs/core) package contains the State Management Logic of AgileTs
+ and therefore provides powerful classes like the [`State Class`](https://agile-ts.org/docs/core/state).
```
npm install @agile-ts/core
```
-- And on the other hand, a _fitting Integration_ for your preferred UI-Framework.
- In my case, the [React Integration](https://www.npmjs.com/package/@agile-ts/react).
- Check [here](https://agile-ts.org/docs/frameworks) if your desired Framework is supported, too.
+- A _fitting Integration_ for the UI-Framework of your choice, on the other hand,
+ is an interface to the actual UI and provides useful functionalities
+ to bind States to UI-Components for reactivity.
+ I prefer React, so let's go with the [React Integration](https://www.npmjs.com/package/@agile-ts/react) for now.
```
npm install @agile-ts/react
```
@@ -158,10 +159,39 @@ In order to properly use AgileTs, in a UI-Framework, we need to install **two**
-Sounds AgileTs interesting to you?
-Checkout our **[documentation](https://agile-ts.org/docs/introduction)**, to learn more.
-And I promise you. You will be able to use AgileTs in no time.
-If you have any further questions, don't hesitate to join our [Community Discord](https://discord.gg/T9GzreAwPH).
+Does AgileTs sound interesting to you?
+Take a look at our **[documentation](https://agile-ts.org/docs/introduction)**,
+to learn more about its functionalities and capabilities.
+If you have any further questions,
+feel free to join our [Community Discord](https://discord.gg/T9GzreAwPH).
+We will be happy to help you.
+
+- Overview
+ - [Introduction](https://agile-ts.org/docs/introduction/)
+ - [Installation](https://agile-ts.org/docs/installation)
+ - [Style Guides](https://agile-ts.org/docs/style-guide)
+ - [Supported Frameworks](https://agile-ts.org/docs/frameworks)
+ - [Contributing](https://agile-ts.org/docs/contributing)
+- Quick Start
+ - [React](https://agile-ts.org/docs/quick-start/react)
+ - [Vue](https://agile-ts.org/docs/quick-start/vue)
+ - [Angular](https://agile-ts.org/docs/quick-start/angular)
+- Packages
+ - [core](https://agile-ts.org/docs/core)
+ - [Agile Instance](https://agile-ts.org/docs/core/agile-instance)
+ - [State](https://agile-ts.org/docs/core/state)
+ - [Collection](https://agile-ts.org/docs/core/collection)
+ - [Computed](https://agile-ts.org/docs/core/computed)
+ - [Storage](https://agile-ts.org/docs/core/storage)
+ - [Integration](https://agile-ts.org/docs/core/integration)
+ - [react](https://agile-ts.org/docs/react)
+ - [React Hooks](https://agile-ts.org/docs/react/hooks)
+ - [AgileHOC](https://agile-ts.org/docs/react/AgileHOC)
+- Examples
+ - [React](https://agile-ts.org/docs/examples/react)
+ - [React-Native](https://agile-ts.org/docs/examples/react-native)
+ - [Vue](https://agile-ts.org/docs/examples/vue)
+- [Typescript Interfaces](https://agile-ts.org/docs/interfaces)
@@ -184,17 +214,17 @@ To find out more about contributing, check out the [CONTRIBUTING.md](https://git
-| Name | Latest Version | Description |
-| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
-| [@agile-ts/core](/packages/core) | [](https://www.npmjs.com/package/@agile-ts/core) | State Manager |
-| [@agile-ts/react](/packages/react) | [](https://www.npmjs.com/package/@agile-ts/react) | React Integration |
-| [@agile-ts/vue](/packages/vue) | [](https://www.npmjs.com/package/@agile-ts/vue) | Vue Integration |
-| [@agile-ts/api](/packages/api) | [](https://www.npmjs.com/package/@agile-ts/api) | Promise based API |
-| [@agile-ts/multieditor](/packages/multieditor) | [](https://www.npmjs.com/package/@agile-ts/multieditor) | Simple Form Manager |
-| [@agile-ts/event](/packages/event) | [](https://www.npmjs.com/package/@agile-ts/event) | Handy class for emitting UI Events |
-| [@agile-ts/logger](/packages/logger) | [](https://www.npmjs.com/package/@agile-ts/logger) | Manages the logging of AgileTs |
-| [@agile-ts/utils](/packages/utils) | [](https://www.npmjs.com/package/@agile-ts/utils) | Util functions of AgileTs |
-| [@agile-ts/proxytree](/packages/proxytree) | [](https://www.npmjs.com/package/@agile-ts/proxytree) | Create Proxy Tree |
+| Name | Latest Version | Description |
+| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
+| [@agile-ts/core](/packages/core) | [](https://www.npmjs.com/package/@agile-ts/core) | State Manager Logic |
+| [@agile-ts/react](/packages/react) | [](https://www.npmjs.com/package/@agile-ts/react) | React Integration |
+| [@agile-ts/vue](/packages/vue) | [](https://www.npmjs.com/package/@agile-ts/vue) | Vue Integration |
+| [@agile-ts/api](/packages/api) | [](https://www.npmjs.com/package/@agile-ts/api) | Promise based API |
+| [@agile-ts/multieditor](/packages/multieditor) | [](https://www.npmjs.com/package/@agile-ts/multieditor) | Simple Form Manager |
+| [@agile-ts/event](/packages/event) | [](https://www.npmjs.com/package/@agile-ts/event) | Handy class for emitting UI Events |
+| [@agile-ts/logger](/packages/logger) | [](https://www.npmjs.com/package/@agile-ts/logger) | Logging API of AgileTs |
+| [@agile-ts/utils](/packages/utils) | [](https://www.npmjs.com/package/@agile-ts/utils) | Utilities of AgileTs |
+| [@agile-ts/proxytree](/packages/proxytree) | [](https://www.npmjs.com/package/@agile-ts/proxytree) | Proxy Tree for tracking accessed properties | |
@@ -202,4 +232,5 @@ To find out more about contributing, check out the [CONTRIBUTING.md](https://git
-AgileTs is inspired by [MVVM Frameworks](https://de.wikipedia.org/wiki/Model_View_ViewModel) like [MobX](https://mobx.js.org/README.html) and [PulseJs](https://github.com/pulse-framework/pulse).
+AgileTs is inspired by [MVVM Frameworks](https://de.wikipedia.org/wiki/Model_View_ViewModel)
+like [MobX](https://mobx.js.org/README.html) and [PulseJs](https://github.com/pulse-framework/pulse).
diff --git a/benchmark/.env b/benchmark/.env
new file mode 100644
index 00000000..21903adb
--- /dev/null
+++ b/benchmark/.env
@@ -0,0 +1 @@
+MANUAL_BENCHMARK=false
diff --git a/benchmark/README.md b/benchmark/README.md
new file mode 100644
index 00000000..ce2e9db2
--- /dev/null
+++ b/benchmark/README.md
@@ -0,0 +1,136 @@
+# 🚀️ Benchmarks
+
+The `Benchmark Test Suites` are supposed to showcase where AgileTs is roughly located in terms of performance.
+I know a counter doesn't really show real world app performance,
+but it is better than nothing.
+
+### What do the results from benchmark js mean?
+https://stackoverflow.com/questions/28524653/what-do-the-results-from-benchmark-js-mean
+
+## Counter Benchmark
+
+```ts
+1. AgileTs.............43028 ops/se ±2.45 (63 runs sampled)
+2. PulseJs.............41086 ops/se ±2.60 (63 runs sampled)
+3. Nano Stores.........31933 ops/se ±1.27 (63 runs sampled)
+4. Zustand.............29329 ops/se ±1.30 (62 runs sampled)
+5. Redux...............28845 ops/se ±2.47 (61 runs sampled)
+6. Hookstate...........27555 ops/se ±5.00 (59 runs sampled)
+7. Mobx................27427 ops/se ±3.69 (62 runs sampled)
+8. Redux-Toolkit.......22191 ops/se ±1.06 (65 runs sampled)
+9. Jotai...............22157 ops/se ±4.10 (63 runs sampled)
+10. Valtio..............21089 ops/se ±0.77 (63 runs sampled)
+11. Recoil..............13926 ops/se ±2.12 (62 runs sampled)
+
+Fastest is AgileTs
+```
+
+## 1000 Fields
+
+```ts
+// 1 Field
+1. Agile nested State..27992 ops/se ±1.73 (64 runs sampled)
+2. Pulse Collection....25547 ops/se ±1.04 (64 runs sampled)
+3. Agile State.........23962 ops/se ±2.16 (64 runs sampled)
+4. Nano Stores.........20662 ops/se ±1.76 (65 runs sampled)
+5. Hookstate...........19430 ops/se ±1.81 (61 runs sampled)
+6. Agile Collection....18491 ops/se ±2.13 (65 runs sampled)
+7. Jotai...............16029 ops/se ±3.39 (62 runs sampled)
+8. Mobx................15631 ops/se ±3.42 (61 runs sampled)
+9. Redux...............12698 ops/se ±2.86 (61 runs sampled)
+10. Recoil..............11183 ops/se ±3.73 (61 runs sampled)
+11. Valtio..............9728 ops/se ±2.81 (62 runs sampled)
+
+Fastest is Agile nested State
+
+// 10 Fields
+1. Agile nested State..27658 ops/se ±1.99 (64 runs sampled)
+2. Pulse Collection....24839 ops/se ±1.31 (65 runs sampled)
+3. Agile State.........19853 ops/se ±2.15 (64 runs sampled)
+4. Nano Stores.........19479 ops/se ±2.12 (60 runs sampled)
+5. Hookstate...........18104 ops/se ±3.37 (60 runs sampled)
+6. Jotai...............15472 ops/se ±2.45 (62 runs sampled)
+7. Agile Collection....13352 ops/se ±3.67 (61 runs sampled)
+8. Recoil..............10522 ops/se ±3.79 (58 runs sampled)
+9. Mobx................9477 ops/se ±1.94 (62 runs sampled)
+10. Redux...............8434 ops/se ±2.67 (47 runs sampled)
+11. Valtio..............3532 ops/se ±2.27 (23 runs sampled)
+
+Fastest is Agile nested State
+
+// 100 Fields
+1. Agile nested State..24124 ops/se ±1.05 (65 runs sampled)
+2. Pulse Collection....21912 ops/se ±1.35 (66 runs sampled)
+3. Nano Stores.........15638 ops/se ±1.63 (62 runs sampled)
+4. Hookstate...........13986 ops/se ±2.28 (59 runs sampled)
+5. Jotai...............12167 ops/se ±2.78 (63 runs sampled)
+6. Agile State.........9175 ops/se ±1.56 (51 runs sampled)
+7. Recoil..............8717 ops/se ±3.51 (49 runs sampled)
+8. Agile Collection....4177 ops/se ±1.64 (61 runs sampled)
+9. Redux...............1763 ops/se ±1.06 (63 runs sampled)
+10. Mobx................1699 ops/se ±1.82 (62 runs sampled)
+11. Valtio..............432 ops/se ±2.18 (60 runs sampled)
+
+Fastest is Agile nested State
+
+// 1000 Fields
+1. Agile nested State..10756 ops/se ±1.43 (58 runs sampled)
+2. Pulse Collection....9774 ops/se ±2.39 (58 runs sampled)
+3. Hookstate...........4737 ops/se ±4.33 (58 runs sampled)
+4. Nano Stores.........4638 ops/se ±6.40 (28 runs sampled)
+5. Jotai...............3352 ops/se ±4.17 (53 runs sampled)
+6. Recoil..............3139 ops/se ±4.69 (54 runs sampled)
+7. Agile State.........1389 ops/se ±1.52 (57 runs sampled)
+8. Agile Collection....500 ops/se ±1.89 (61 runs sampled)
+9. Redux...............154 ops/se ±1.48 (57 runs sampled)
+10. Mobx................144 ops/se ±1.06 (55 runs sampled)
+11. Valtio..............37 ops/se ±4.26 (40 runs sampled)
+
+Fastest is Agile nested State
+```
+
+## Computed
+
+```ts
+1. Agile Hard Coded....32079 ops/se ±1.51 (62 runs sampled)
+2. Agile Auto Tracking.30974 ops/se ±2.21 (64 runs sampled)
+3. Nano Stores.........28821 ops/se ±1.49 (64 runs sampled)
+4. Jotai...............18922 ops/se ±2.12 (61 runs sampled)
+5. Recoil..............10103 ops/se ±2.47 (64 runs sampled)
+
+Fastest is Agile Hard Coded
+```
+
+## 🏃 Running Benchmarks
+
+The Benchmark tests run on top of the [`benchmark.js` library](https://github.com/bestiejs/benchmark.js/)
+via [Playwright](https://github.com/microsoft/playwright) in the Chrome Browser.
+
+Before starting, make sure you are in the `/benchmark` directory.
+
+### 1️⃣ Install dependencies
+
+To prepare the dependencies, run:
+```ts
+yarn install
+```
+
+### 2️⃣ Run Benchmark Test Suite
+
+Execute the benchmark located in `./benchmarks/react/counter`.
+```ts
+yarn run test:counter
+```
+
+## ⭐️ Contribute
+
+Get a part of AgileTs and start contributing. We welcome any meaningful contribution. 😀
+To find out more about contributing, check out the [CONTRIBUTING.md](https://github.com/agile-ts/agile/blob/master/CONTRIBUTING.md).
+
+
+
+
+
+## 🎉 Credits
+
+The Benchmark CLI is inspired by [`exome`](https://github.com/Marcisbee/exome).
diff --git a/benchmark/benchmarkManager.ts b/benchmark/benchmarkManager.ts
new file mode 100644
index 00000000..dd20e5a1
--- /dev/null
+++ b/benchmark/benchmarkManager.ts
@@ -0,0 +1,73 @@
+export interface CycleResultInterface {
+ name: string;
+ opsInSec: number;
+ failRate: number;
+ ranSampleCount: number;
+ ui: any;
+}
+
+export function getCycleResult(event: any): CycleResultInterface {
+ return {
+ name: event.target.name,
+ opsInSec: Math.round(event.target.hz),
+ failRate: event.target.stats.rme.toFixed(2),
+ ranSampleCount: event.target.stats.sample.length,
+ ui: {
+ count: event.target.output,
+ },
+ };
+}
+
+export function startBenchmarkLog(testSuiteName: string): void {
+ console.log(`{white Starting Benchmark "{magenta.bold ${testSuiteName}}"..}`);
+}
+
+export function cycleLog(
+ cycleResult: CycleResultInterface,
+ ...addition: any[]
+): void {
+ console.log(
+ `{gray ..Proceeded {green.bold ${cycleResult.name}} - {yellow ${cycleResult.opsInSec} ops/sec}}`,
+ ...addition
+ );
+}
+
+export function endBenchmarkLog(
+ testSuiteName: string,
+ results: CycleResultInterface[],
+ fastest: string[]
+): void {
+ console.log(`{white ..End Benchmark "{magenta.bold ${testSuiteName}}"}\n\n`);
+
+ results.sort((a, b) => {
+ if (a.opsInSec < b.opsInSec) return 1;
+ return -1;
+ });
+
+ let resultString = '';
+ for (let i = 0; i < results.length; i++) {
+ const cycleResult = results[i];
+
+ // Build Cycle Result Log
+ const cycleString = `{bold.bgGreen ${
+ i + 1
+ }.} {bold.blue ${cycleResult.name
+ .padEnd(20, '.')
+ .replace(/(\.+)$/, '{red $1}')}}{yellow ${
+ cycleResult.opsInSec
+ } ops/se} {gray ±${cycleResult.failRate}%} (${
+ cycleResult.ranSampleCount
+ } runs sampled)`;
+
+ resultString += `${cycleString}${i < results.length - 1 ? '\n' : ''}`;
+ }
+
+ // Build Leaderboard Header
+ console.log('{bgYellow.white.bold Leaderboard:}\n');
+
+ // Print Leaderboard
+ console.log(resultString);
+
+ // Print fastest
+ console.log(`\n{bold Fastest is {bold.green ${fastest}}}\n`);
+}
diff --git a/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx b/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx
new file mode 100644
index 00000000..7269c0a0
--- /dev/null
+++ b/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx
@@ -0,0 +1,67 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { createCollection, LogCodeManager } from '@agile-ts/core';
+import { useAgile, useValue } from '@agile-ts/react';
+
+LogCodeManager.getLogger().isActive = false;
+
+export default function (target: HTMLElement, fieldsCount: number) {
+ const FIELDS = createCollection({
+ initialData: Array.from(Array(fieldsCount).keys()).map((i) => ({
+ id: i,
+ name: `Field #${i + 1}`,
+ })),
+ });
+
+ let updatedFieldsCount = 0;
+ let renderFieldsCount = 0;
+
+ function Field({ index }: { index: number | string }) {
+ const ITEM = FIELDS.getItem(index);
+ const item = useAgile(ITEM);
+
+ renderFieldsCount++;
+
+ return (
+
+ Last {``} render at: {new Date().toISOString()}
+
+ {
+ ITEM?.patch({ name: e.target.value });
+
+ updatedFieldsCount++;
+
+ (document.getElementById(
+ 'updatedFieldsCount'
+ ) as any).innerText = updatedFieldsCount;
+ (document.getElementById(
+ 'renderFieldsCount'
+ ) as any).innerText = renderFieldsCount;
+ }}
+ />
+
+ );
+ }
+
+ function App() {
+ const fieldKeys = useValue(FIELDS);
+
+ return (
+
+
+ Last {`
`} render at: {new Date().toISOString()}
+
+
+ {fieldKeys.map((key, i) => (
+
+ ))}
+
+
+
+ );
+ }
+
+ ReactDom.render( , target);
+}
diff --git a/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx b/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx
new file mode 100644
index 00000000..377b6b01
--- /dev/null
+++ b/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx
@@ -0,0 +1,64 @@
+import * as React from 'react';
+import * as ReactDom from 'react-dom';
+import { createState, LogCodeManager, State } from '@agile-ts/core';
+import { useAgile } from '@agile-ts/react';
+
+LogCodeManager.getLogger().isActive = false;
+
+export default function (target: HTMLElement, fieldsCount: number) {
+ const FIELDS = createState(
+ Array.from(Array(fieldsCount).keys()).map((i) =>
+ createState(`Field #${i + 1}`)
+ )
+ );
+
+ let updatedFieldsCount = 0;
+ let renderFieldsCount = 0;
+
+ function Field({ field }: { field: State }) {
+ const name = useAgile(field);
+
+ renderFieldsCount++;
+
+ return (
+
+ Last {``} render at: {new Date().toISOString()}
+
+ {
+ field.set(e.target.value);
+
+ updatedFieldsCount++;
+
+ (document.getElementById(
+ 'updatedFieldsCount'
+ ) as any).innerText = updatedFieldsCount;
+ (document.getElementById(
+ 'renderFieldsCount'
+ ) as any).innerText = renderFieldsCount;
+ }}
+ />
+
+ );
+ }
+
+ function App() {
+ const fields = useAgile(FIELDS);
+ return (
+
+
+ Last {`
`} render at: {new Date().toISOString()}
+
+
+ {fields.map((field, index) => (
+
+ ))}
+
+
+
+ );
+ }
+
+ ReactDom.render( , target);
+}
diff --git a/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx b/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx
new file mode 100644
index 00000000..93f9798a
--- /dev/null
+++ b/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx
@@ -0,0 +1,62 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { createState, LogCodeManager } from '@agile-ts/core';
+import { useAgile } from '@agile-ts/react';
+
+LogCodeManager.getLogger().isActive = false;
+
+export default function (target: HTMLElement, fieldsCount: number) {
+ const FIELDS = createState(
+ Array.from(Array(fieldsCount).keys()).map((i) => `Field #${i + 1}`)
+ );
+
+ let updatedFieldsCount = 0;
+ let renderFieldsCount = 0;
+
+ function Field({ index }: { index: number }) {
+ const fields = useAgile(FIELDS);
+
+ renderFieldsCount++;
+
+ return (
+
+ Last {``} render at: {new Date().toISOString()}
+
+ {
+ FIELDS.nextStateValue[index] = e.target.value;
+ FIELDS.ingest();
+
+ updatedFieldsCount++;
+
+ (document.getElementById(
+ 'updatedFieldsCount'
+ ) as any).innerText = updatedFieldsCount;
+ (document.getElementById(
+ 'renderFieldsCount'
+ ) as any).innerText = renderFieldsCount;
+ }}
+ />
+
+ );
+ }
+
+ function App() {
+ return (
+
+
+ Last {`
`} render at: {new Date().toISOString()}
+
+
+ {FIELDS.value.map((field, index) => (
+
+ ))}
+
+
+
+ );
+ }
+
+ ReactDom.render( , target);
+}
diff --git a/benchmark/benchmarks/react/1000fields/bench/hookstate.tsx b/benchmark/benchmarks/react/1000fields/bench/hookstate.tsx
new file mode 100644
index 00000000..a64009fb
--- /dev/null
+++ b/benchmark/benchmarks/react/1000fields/bench/hookstate.tsx
@@ -0,0 +1,61 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { createState, useHookstate, State } from '@hookstate/core';
+
+export default function (target: HTMLElement, fieldsCount: number) {
+ const fields = createState(
+ Array.from(
+ Array.from(Array(fieldsCount).keys()).map((i) => `Field #${i + 1} value`)
+ )
+ );
+
+ let updatedFieldsCount = 0;
+ let renderFieldsCount = 0;
+
+ function Field({ field }: { field: State }) {
+ const name = useHookstate(field);
+
+ renderFieldsCount++;
+
+ return (
+
+ Last {``} render at: {new Date().toISOString()}
+
+ {
+ name.set(e.target.value);
+
+ updatedFieldsCount++;
+
+ (document.getElementById(
+ 'updatedFieldsCount'
+ ) as any).innerText = updatedFieldsCount;
+ (document.getElementById(
+ 'renderFieldsCount'
+ ) as any).innerText = renderFieldsCount;
+ }}
+ />
+
+ );
+ }
+
+ function App() {
+ const state = useHookstate(fields);
+ return (
+
+
+ Last {`
`} render at: {new Date().toISOString()}
+
+
+ {state.map((field, index) => (
+
+ ))}
+
+
+
+ );
+ }
+
+ ReactDom.render( , target);
+}
diff --git a/benchmark/benchmarks/react/1000fields/bench/jotai.tsx b/benchmark/benchmarks/react/1000fields/bench/jotai.tsx
new file mode 100644
index 00000000..3823e908
--- /dev/null
+++ b/benchmark/benchmarks/react/1000fields/bench/jotai.tsx
@@ -0,0 +1,62 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { atom, useAtom, Atom } from 'jotai';
+
+export default function (target: HTMLElement, fieldsCount: number) {
+ const fields = Array.from(Array(fieldsCount).keys()).map((i) =>
+ atom(`Field #${i + 1}`)
+ );
+
+ const fieldsStore = atom(fields);
+
+ let updatedFieldsCount = 0;
+ let renderFieldsCount = 0;
+
+ function Field({ field }: { field: Atom }) {
+ const [name, rename] = useAtom(field);
+
+ renderFieldsCount++;
+
+ return (
+
+ Last {``} render at: {new Date().toISOString()}
+
+ {
+ rename(e.target.value);
+
+ updatedFieldsCount++;
+
+ (document.getElementById(
+ 'updatedFieldsCount'
+ ) as any).innerText = updatedFieldsCount;
+ (document.getElementById(
+ 'renderFieldsCount'
+ ) as any).innerText = renderFieldsCount;
+ }}
+ />
+
+ );
+ }
+
+ function App() {
+ const [fields] = useAtom(fieldsStore);
+
+ return (
+
+
+ Last {`
`} render at: {new Date().toISOString()}
+
+
+ {fields.map((field, index) => (
+
+ ))}
+
+
+
+ );
+ }
+
+ ReactDom.render( , target);
+}
diff --git a/benchmark/benchmarks/react/1000fields/bench/mobx.tsx b/benchmark/benchmarks/react/1000fields/bench/mobx.tsx
new file mode 100644
index 00000000..d8b05198
--- /dev/null
+++ b/benchmark/benchmarks/react/1000fields/bench/mobx.tsx
@@ -0,0 +1,63 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { action, observable } from 'mobx';
+import { observer } from 'mobx-react';
+
+export default function (target: HTMLElement, fieldsCount: number) {
+ const appState = observable({
+ fields: Array.from(Array(fieldsCount).keys()).map((i) => `Field #${i + 1}`),
+ rename: action(function (value: string, index: number) {
+ // console.log(state)
+ appState.fields[index] = value;
+ }),
+ });
+
+ let updatedFieldsCount = 0;
+ let renderFieldsCount = 0;
+
+ function Field({ index }: { index: number }) {
+ const field = appState.fields[index];
+
+ renderFieldsCount++;
+
+ return (
+
+ Last {``} render at: {new Date().toISOString()}
+
+ {
+ appState.rename(e.target.value, index);
+
+ updatedFieldsCount++;
+
+ (document.getElementById(
+ 'updatedFieldsCount'
+ ) as any).innerText = updatedFieldsCount;
+ (document.getElementById(
+ 'renderFieldsCount'
+ ) as any).innerText = renderFieldsCount;
+ }}
+ />
+
+ );
+ }
+
+ const App = observer(() => {
+ return (
+
+
+ Last {`
`} render at: {new Date().toISOString()}
+
+
+ {appState.fields.map((field, index) => (
+
+ ))}
+
+
+
+ );
+ });
+
+ ReactDom.render( , target);
+}
diff --git a/benchmark/benchmarks/react/1000fields/bench/nanostores.tsx b/benchmark/benchmarks/react/1000fields/bench/nanostores.tsx
new file mode 100644
index 00000000..4fa38246
--- /dev/null
+++ b/benchmark/benchmarks/react/1000fields/bench/nanostores.tsx
@@ -0,0 +1,67 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { createStore, WritableStore } from 'nanostores';
+import { useStore } from 'nanostores/react';
+
+export default function (target: HTMLElement, fieldsCount: number) {
+ const fieldsStore = createStore[]>(() => {
+ const fields = Array.from(Array(fieldsCount).keys()).map((i) => {
+ const fieldStore = createStore(() => {
+ fieldsStore.set(`Field #${i + 1}` as any);
+ });
+ return fieldStore;
+ });
+
+ fieldsStore.set(fields);
+ });
+
+ let updatedFieldsCount = 0;
+ let renderFieldsCount = 0;
+
+ function Field({ field }: { field: WritableStore }) {
+ const name = useStore(field);
+
+ renderFieldsCount++;
+
+ return (
+
+ Last {``} render at: {new Date().toISOString()}
+
+ {
+ field.set(e.target.value);
+
+ updatedFieldsCount++;
+
+ (document.getElementById(
+ 'updatedFieldsCount'
+ ) as any).innerText = updatedFieldsCount;
+ (document.getElementById(
+ 'renderFieldsCount'
+ ) as any).innerText = renderFieldsCount;
+ }}
+ />
+
+ );
+ }
+
+ function App() {
+ const fields = useStore(fieldsStore);
+ return (
+
+
+ Last {`
`} render at: {new Date().toISOString()}
+
+
+ {fields.map((field, index) => (
+
+ ))}
+
+
+
+ );
+ }
+
+ ReactDom.render( , target);
+}
diff --git a/benchmark/benchmarks/react/1000fields/bench/pulsejs/collection.tsx b/benchmark/benchmarks/react/1000fields/bench/pulsejs/collection.tsx
new file mode 100644
index 00000000..078a7ab9
--- /dev/null
+++ b/benchmark/benchmarks/react/1000fields/bench/pulsejs/collection.tsx
@@ -0,0 +1,63 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { collection, usePulse } from '@pulsejs/react';
+
+export default function (target: HTMLElement, fieldsCount: number) {
+ const FIELDS = collection<{ id: number; name: string }>();
+ FIELDS.collect(
+ Array.from(Array(fieldsCount).keys()).map((i) => ({
+ id: i,
+ name: `Field #${i + 1}`,
+ }))
+ );
+
+ let updatedFieldsCount = 0;
+ let renderFieldsCount = 0;
+
+ function Field({ index }: { index: number | string }) {
+ const ITEM = FIELDS.getData(index);
+ const item = usePulse(ITEM);
+
+ renderFieldsCount++;
+
+ return (
+
+ Last {``} render at: {new Date().toISOString()}
+
+ {
+ ITEM?.patch({ name: e.target.value });
+
+ updatedFieldsCount++;
+
+ (document.getElementById(
+ 'updatedFieldsCount'
+ ) as any).innerText = updatedFieldsCount;
+ (document.getElementById(
+ 'renderFieldsCount'
+ ) as any).innerText = renderFieldsCount;
+ }}
+ />
+
+ );
+ }
+
+ function App() {
+ return (
+
+
+ Last {`
`} render at: {new Date().toISOString()}
+
+
+ {Object.keys(FIELDS.data).map((key, i) => (
+
+ ))}
+
+
+
+ );
+ }
+
+ ReactDom.render( , target);
+}
diff --git a/benchmark/benchmarks/react/1000fields/bench/pulsejs/nestedState.tsx b/benchmark/benchmarks/react/1000fields/bench/pulsejs/nestedState.tsx
new file mode 100644
index 00000000..d09f86f5
--- /dev/null
+++ b/benchmark/benchmarks/react/1000fields/bench/pulsejs/nestedState.tsx
@@ -0,0 +1,60 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { state, State } from '@pulsejs/core';
+import { usePulse } from '@pulsejs/react';
+
+export default function (target: HTMLElement, fieldsCount: number) {
+ const FIELDS = state(
+ Array.from(Array(fieldsCount).keys()).map((i) => state(`Field #${i + 1}`))
+ );
+
+ let updatedFieldsCount = 0;
+ let renderFieldsCount = 0;
+
+ function Field({ field }: { field: State }) {
+ const name = usePulse(field);
+
+ renderFieldsCount++;
+
+ return (
+
+ Last {``} render at: {new Date().toISOString()}
+
+ {
+ field.set(e.target.value);
+
+ updatedFieldsCount++;
+
+ (document.getElementById(
+ 'updatedFieldsCount'
+ ) as any).innerText = updatedFieldsCount;
+ (document.getElementById(
+ 'renderFieldsCount'
+ ) as any).innerText = renderFieldsCount;
+ }}
+ />
+
+ );
+ }
+
+ function App() {
+ const fields = usePulse(FIELDS);
+ return (
+
+
+ Last {`
`} render at: {new Date().toISOString()}
+
+
+ {fields.map((field, index) => (
+
+ ))}
+
+
+
+ );
+ }
+
+ ReactDom.render( , target);
+}
diff --git a/benchmark/benchmarks/react/1000fields/bench/pulsejs/state.tsx b/benchmark/benchmarks/react/1000fields/bench/pulsejs/state.tsx
new file mode 100644
index 00000000..11e55fe7
--- /dev/null
+++ b/benchmark/benchmarks/react/1000fields/bench/pulsejs/state.tsx
@@ -0,0 +1,60 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { state } from '@pulsejs/core';
+import { usePulse } from '@pulsejs/react';
+
+export default function (target: HTMLElement, fieldsCount: number) {
+ const FIELDS = state(
+ Array.from(Array(fieldsCount).keys()).map((i) => `Field #${i + 1}`)
+ );
+
+ let updatedFieldsCount = 0;
+ let renderFieldsCount = 0;
+
+ function Field({ index }: { index: number }) {
+ const fields = usePulse(FIELDS);
+
+ renderFieldsCount++;
+
+ return (
+
+ Last {``} render at: {new Date().toISOString()}
+
+ {
+ FIELDS.nextState[index] = e.target.value;
+ FIELDS.set();
+
+ updatedFieldsCount++;
+
+ (document.getElementById(
+ 'updatedFieldsCount'
+ ) as any).innerText = updatedFieldsCount;
+ (document.getElementById(
+ 'renderFieldsCount'
+ ) as any).innerText = renderFieldsCount;
+ }}
+ />
+
+ );
+ }
+
+ function App() {
+ return (
+
+
+ Last {`
`} render at: {new Date().toISOString()}
+
+
+ {FIELDS.value.map((field, index) => (
+
+ ))}
+
+
+
+ );
+ }
+
+ ReactDom.render( , target);
+}
diff --git a/benchmark/benchmarks/react/1000fields/bench/recoil.tsx b/benchmark/benchmarks/react/1000fields/bench/recoil.tsx
new file mode 100644
index 00000000..c6e194ab
--- /dev/null
+++ b/benchmark/benchmarks/react/1000fields/bench/recoil.tsx
@@ -0,0 +1,70 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { atom, RecoilRoot, RecoilState, useRecoilState } from 'recoil';
+
+export default function (target: HTMLElement, fieldsCount: number) {
+ const fields = Array.from(Array(fieldsCount).keys()).map((i) =>
+ atom({ key: `field-${i}`, default: `Field #${i + 1}` })
+ );
+
+ const fieldsStore = atom({
+ key: 'fieldsStore',
+ default: fields,
+ });
+
+ let updatedFieldsCount = 0;
+ let renderFieldsCount = 0;
+
+ function Field({ field }: { field: RecoilState }) {
+ const [name, rename] = useRecoilState(field);
+
+ renderFieldsCount++;
+
+ return (
+
+ Last {``} render at: {new Date().toISOString()}
+
+ {
+ rename(e.target.value);
+
+ updatedFieldsCount++;
+
+ (document.getElementById(
+ 'updatedFieldsCount'
+ ) as any).innerText = updatedFieldsCount;
+ (document.getElementById(
+ 'renderFieldsCount'
+ ) as any).innerText = renderFieldsCount;
+ }}
+ />
+
+ );
+ }
+
+ function App() {
+ const [fields] = useRecoilState(fieldsStore);
+
+ return (
+
+
+ Last {`
`} render at: {new Date().toISOString()}
+
+
+ {fields.map((field, index) => (
+
+ ))}
+
+
+
+ );
+ }
+
+ ReactDom.render(
+
+
+ ,
+ target
+ );
+}
diff --git a/benchmark/benchmarks/react/1000fields/bench/redux.tsx b/benchmark/benchmarks/react/1000fields/bench/redux.tsx
new file mode 100644
index 00000000..ac134f70
--- /dev/null
+++ b/benchmark/benchmarks/react/1000fields/bench/redux.tsx
@@ -0,0 +1,87 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { configureStore, createSlice } from '@reduxjs/toolkit';
+import { Provider, useDispatch, useSelector } from 'react-redux';
+
+export default function (target: HTMLElement, fieldsCount: number) {
+ const fieldsSlice = createSlice({
+ name: 'fields',
+ initialState: {
+ fields: Array.from(Array(fieldsCount).keys()).map(
+ (i) => `Field #${i + 1}`
+ ),
+ },
+ reducers: {
+ rename: (state, action) => {
+ state.fields[action.payload.index] = action.payload.value;
+ },
+ },
+ });
+
+ const store = configureStore({
+ reducer: {
+ fields: fieldsSlice.reducer,
+ },
+ });
+
+ let updatedFieldsCount = 0;
+ let renderFieldsCount = 0;
+
+ function Field({ index, field: name }: { index: number; field: string }) {
+ const dispatch = useDispatch();
+
+ renderFieldsCount++;
+
+ return (
+
+ Last {``} render at: {new Date().toISOString()}
+
+ {
+ dispatch(
+ fieldsSlice.actions.rename({ index, value: e.target.value })
+ );
+
+ updatedFieldsCount++;
+
+ (document.getElementById(
+ 'updatedFieldsCount'
+ ) as any).innerText = updatedFieldsCount;
+ (document.getElementById(
+ 'renderFieldsCount'
+ ) as any).innerText = renderFieldsCount;
+ }}
+ />
+
+ );
+ }
+
+ function App() {
+ const fields: string[] = useSelector(
+ (state: any) => state.fields.fields,
+ () => false
+ );
+
+ return (
+
+
+ Last {`
`} render at: {new Date().toISOString()}
+
+
+ {fields.map((field, index) => (
+
+ ))}
+
+
+
+ );
+ }
+
+ ReactDom.render(
+
+
+ ,
+ target
+ );
+}
diff --git a/benchmark/benchmarks/react/1000fields/bench/valtio.tsx b/benchmark/benchmarks/react/1000fields/bench/valtio.tsx
new file mode 100644
index 00000000..b27698b0
--- /dev/null
+++ b/benchmark/benchmarks/react/1000fields/bench/valtio.tsx
@@ -0,0 +1,61 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { proxy, useSnapshot } from 'valtio';
+
+export default function (target: HTMLElement, fieldsCount: number) {
+ const state = proxy({
+ fields: Array.from(Array(fieldsCount).keys()).map((i) => `Field #${i + 1}`),
+ });
+
+ let updatedFieldsCount = 0;
+ let renderFieldsCount = 0;
+
+ function Field({ index }: { index: number }) {
+ const { fields } = useSnapshot(state);
+ const name = fields[index];
+
+ renderFieldsCount++;
+
+ return (
+
+ Last {``} render at: {new Date().toISOString()}
+
+ {
+ state.fields[index] = e.target.value;
+
+ updatedFieldsCount++;
+
+ (document.getElementById(
+ 'updatedFieldsCount'
+ ) as any).innerText = updatedFieldsCount;
+ (document.getElementById(
+ 'renderFieldsCount'
+ ) as any).innerText = renderFieldsCount;
+ }}
+ />
+
+ );
+ }
+
+ function App() {
+ const { fields } = useSnapshot(state, { sync: true });
+
+ return (
+
+
+ Last {`
`} render at: {new Date().toISOString()}
+
+
+ {fields.map((field, index) => (
+
+ ))}
+
+
+
+ );
+ }
+
+ ReactDom.render( , target);
+}
diff --git a/benchmark/benchmarks/react/1000fields/index.ts b/benchmark/benchmarks/react/1000fields/index.ts
new file mode 100644
index 00000000..9b251c1d
--- /dev/null
+++ b/benchmark/benchmarks/react/1000fields/index.ts
@@ -0,0 +1,115 @@
+import ReactDOM from 'react-dom';
+import Benchmark, { Suite, Options } from 'benchmark';
+import {
+ cycleLog,
+ CycleResultInterface,
+ endBenchmarkLog,
+ getCycleResult,
+ startBenchmarkLog,
+} from '../../../benchmarkManager';
+
+// Files to run the Benchmark on
+import agileCollection from './bench/agilets/collection';
+import agileState from './bench/agilets/state';
+import agileNestedState from './bench/agilets/nestedState';
+import pulseCollection from './bench/pulsejs/collection';
+import pulseState from './bench/pulsejs/state';
+import pulseNestedState from './bench/pulsejs/nestedState';
+import hookstate from './bench/hookstate';
+import jotai from './bench/jotai';
+import mobx from './bench/mobx';
+import nanostores from './bench/nanostores';
+import recoil from './bench/recoil';
+import redux from './bench/redux';
+import valtio from './bench/valtio';
+
+// @ts-ignore
+// Benchmark.js requires an instance of itself globally
+window.Benchmark = Benchmark;
+
+const fieldsCount = 1000;
+
+// Create new Benchmark Test Suite
+const suite = new Suite(`${fieldsCount} Fields`);
+
+// Retrieve the Element to render the Benchmark Test Suite in
+const target = document.getElementById('bench')!;
+
+// Configures a single Benchmark Test
+function configTest(
+ renderElement: (target: HTMLElement, fieldsCount: number) => void
+): Options {
+ return {
+ fn() {
+ // Retrieve Input field to update
+ const fieldToUpdate = Math.floor(Math.random() * fieldsCount);
+ const input = target.querySelectorAll('input')[fieldToUpdate];
+
+ // Update retrieved Input value
+ const evt = document.createEvent('HTMLEvents');
+ evt.initEvent('input', true, true);
+ input.value = '' + Math.random();
+ (input as any)._valueTracker.setValue(Math.random());
+ input.dispatchEvent(evt);
+ },
+ onStart() {
+ // Render React Component in the target Element
+ renderElement(target, fieldsCount);
+ },
+ onComplete() {
+ (this as any).updatedFieldsCount = parseInt(
+ (document.getElementById('updatedFieldsCount') as any)?.innerText,
+ 10
+ );
+ (this as any).renderFieldsCount = parseInt(
+ (document.getElementById('renderFieldsCount') as any)?.innerText,
+ 10
+ );
+
+ // Unmount React Component
+ ReactDOM.unmountComponentAtNode(target);
+ target.innerHTML = '';
+ },
+ };
+}
+
+const results: CycleResultInterface[] = [];
+
+// Add Tests to the Benchmark Test Suite
+suite
+ .add('Agile Collection', configTest(agileCollection))
+ .add('Agile State', configTest(agileState))
+ .add('Agile nested State', configTest(agileNestedState))
+ .add('Pulse Collection', configTest(pulseCollection))
+ // .add('Pulse State', configTest(pulseState))
+ // .add('Pulse nested State', configTest(pulseNestedState))
+ .add('Hookstate', configTest(hookstate))
+ .add('Jotai', configTest(jotai))
+ .add('Mobx', configTest(mobx))
+ .add('Nano Stores', configTest(nanostores))
+ .add('Recoil', configTest(recoil))
+ .add('Redux', configTest(redux))
+ .add('Valtio', configTest(valtio))
+
+ // Add Listener
+ .on('start', function (this: any) {
+ startBenchmarkLog(this.name);
+ })
+ .on('cycle', (event: any) => {
+ const cycleResult = getCycleResult(event);
+ cycleLog(
+ cycleResult,
+ `[updatedFieldsCount: ${event.target.updatedFieldsCount}, renderFieldsCount: ${event.target.renderFieldsCount}]`
+ );
+ results.push(cycleResult);
+ })
+ .on('complete', function (this: any) {
+ endBenchmarkLog(this.name, results, this.filter('fastest').map('name'));
+
+ // @ts-ignore
+ // Notify server that the Benchmark Test Suite has ended
+ window.TEST.ended = true;
+ })
+
+ // Run Benchmark Test Suite
+ .run({ async: true });
diff --git a/benchmark/benchmarks/react/computed/bench/agilets/autoTracking.tsx b/benchmark/benchmarks/react/computed/bench/agilets/autoTracking.tsx
new file mode 100644
index 00000000..0b596405
--- /dev/null
+++ b/benchmark/benchmarks/react/computed/bench/agilets/autoTracking.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { createComputed, createState, LogCodeManager } from '@agile-ts/core';
+import { useAgile } from '@agile-ts/react';
+
+LogCodeManager.getLogger().isActive = false;
+const COUNT = createState(0);
+const COMPUTED_COUNT = createComputed(() => {
+ return COUNT.value * 5;
+});
+
+const CountView = () => {
+ const count = useAgile(COUNT);
+ return COUNT.set((state) => state + 1)}>{count} ;
+};
+
+const ComputedCountView = () => {
+ const computedCount = useAgile(COMPUTED_COUNT);
+ return {computedCount}
;
+};
+
+const App = () => {
+ return (
+
+
+
+
+ );
+};
+
+export default function (target: HTMLElement) {
+ ReactDom.render( , target);
+}
diff --git a/benchmark/benchmarks/react/computed/bench/agilets/hardCoded.tsx b/benchmark/benchmarks/react/computed/bench/agilets/hardCoded.tsx
new file mode 100644
index 00000000..59cc57a8
--- /dev/null
+++ b/benchmark/benchmarks/react/computed/bench/agilets/hardCoded.tsx
@@ -0,0 +1,36 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { createComputed, createState, LogCodeManager } from '@agile-ts/core';
+import { useAgile } from '@agile-ts/react';
+
+LogCodeManager.getLogger().isActive = false;
+const COUNT = createState(0);
+const COMPUTED_COUNT = createComputed(
+ () => {
+ return COUNT.value * 5;
+ },
+ { autodetect: false, computedDeps: [COUNT] }
+);
+
+const CountView = () => {
+ const count = useAgile(COUNT);
+ return COUNT.set((state) => state + 1)}>{count} ;
+};
+
+const ComputedCountView = () => {
+ const computedCount = useAgile(COMPUTED_COUNT);
+ return {computedCount}
;
+};
+
+const App = () => {
+ return (
+
+
+
+
+ );
+};
+
+export default function (target: HTMLElement) {
+ ReactDom.render( , target);
+}
diff --git a/benchmark/benchmarks/react/computed/bench/jotai.tsx b/benchmark/benchmarks/react/computed/bench/jotai.tsx
new file mode 100644
index 00000000..6de63920
--- /dev/null
+++ b/benchmark/benchmarks/react/computed/bench/jotai.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { atom, useAtom } from 'jotai';
+
+const countAtom = atom(0);
+const computedCountAtom = atom((get) => get(countAtom) * 5);
+
+const CountView = () => {
+ const [count, setCount] = useAtom(countAtom);
+ return setCount((v) => v + 1)}>{count} ;
+};
+
+const ComputedCountView = () => {
+ const [computedCount] = useAtom(computedCountAtom);
+ return {computedCount}
;
+};
+
+const App = () => {
+ return (
+
+
+
+
+ );
+};
+
+export default function (target: HTMLElement) {
+ ReactDom.render( , target);
+}
diff --git a/benchmark/benchmarks/react/computed/bench/nanostores.tsx b/benchmark/benchmarks/react/computed/bench/nanostores.tsx
new file mode 100644
index 00000000..5ac7c0d2
--- /dev/null
+++ b/benchmark/benchmarks/react/computed/bench/nanostores.tsx
@@ -0,0 +1,36 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { createDerived, createStore, getValue } from 'nanostores';
+import { useStore } from 'nanostores/react';
+
+const countStore = createStore(() => {
+ countStore.set(0);
+});
+const computedStore = createDerived(countStore, (count) => {
+ return count * 5;
+});
+
+const CountView = () => {
+ const count = useStore(countStore);
+ return (
+ countStore.set(getValue(countStore) + 1)}>{count}
+ );
+};
+
+const ComputedCountView = () => {
+ const computedCount = useStore(computedStore);
+ return {computedCount}
;
+};
+
+const App = () => {
+ return (
+
+
+
+
+ );
+};
+
+export default function (target: HTMLElement) {
+ ReactDom.render( , target);
+}
diff --git a/benchmark/benchmarks/react/computed/bench/recoil.tsx b/benchmark/benchmarks/react/computed/bench/recoil.tsx
new file mode 100644
index 00000000..8b275a0e
--- /dev/null
+++ b/benchmark/benchmarks/react/computed/bench/recoil.tsx
@@ -0,0 +1,49 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import {
+ atom,
+ RecoilRoot,
+ useRecoilState,
+ selector,
+ useRecoilValue,
+} from 'recoil';
+import { useAtom } from 'jotai';
+
+const countState = atom({
+ key: 'countState',
+ default: 0,
+});
+const computedCountState = selector({
+ key: 'computedCountState',
+ get: ({ get }) => {
+ return get(countState) * 5;
+ },
+});
+
+const CountView = () => {
+ const [count, setCount] = useRecoilState(countState);
+ return setCount((v) => v + 1)}>{count} ;
+};
+
+const ComputedCountView = () => {
+ const computedCount = useRecoilValue(computedCountState);
+ return {computedCount}
;
+};
+
+const App = () => {
+ return (
+
+
+
+
+ );
+};
+
+export default function (target: HTMLElement) {
+ ReactDom.render(
+
+
+ ,
+ target
+ );
+}
diff --git a/benchmark/benchmarks/react/computed/index.ts b/benchmark/benchmarks/react/computed/index.ts
new file mode 100644
index 00000000..e70b9c70
--- /dev/null
+++ b/benchmark/benchmarks/react/computed/index.ts
@@ -0,0 +1,94 @@
+import ReactDOM from 'react-dom';
+import Benchmark, { Suite, Options } from 'benchmark';
+import {
+ cycleLog,
+ CycleResultInterface,
+ endBenchmarkLog,
+ getCycleResult,
+ startBenchmarkLog,
+} from '../../../benchmarkManager';
+
+// Files to run the Benchmark on
+import agileAutoTracking from './bench/agilets/autoTracking';
+import agileHardCoded from './bench/agilets/hardCoded';
+import jotai from './bench/jotai';
+import nanostores from './bench/nanostores';
+import recoil from './bench/recoil';
+
+// @ts-ignore
+// Benchmark.js requires an instance of itself globally
+window.Benchmark = Benchmark;
+
+// Create new Benchmark Test Suite
+const suite = new Suite('Computed');
+
+// Retrieve the Element to render the Benchmark Test Suite in
+const target = document.getElementById('bench')!;
+
+// Increment Element
+let increment: HTMLHeadingElement;
+
+// Configures a single Benchmark Test
+function configTest(renderElement: (target: HTMLElement) => void): Options {
+ return {
+ fn() {
+ // Execute increment action
+ increment.click();
+ },
+ onStart() {
+ // Render React Component in the target Element
+ renderElement(target);
+
+ // Retrieve Element to execute the increment action on
+ increment = target.querySelector('h1')!;
+ },
+ onComplete() {
+ // Set 'output' in the Benchmark itself to print it later
+ (this as any).output = parseInt(
+ (target.querySelector('h1') as any)?.innerText,
+ 10
+ );
+ (this as any).computedOutput = parseInt(
+ (target.querySelector('p') as any)?.innerText,
+ 10
+ );
+
+ // Unmount React Component
+ ReactDOM.unmountComponentAtNode(target);
+ target.innerHTML = '';
+ },
+ };
+}
+
+const results: CycleResultInterface[] = [];
+
+// Add Tests to the Benchmark Test Suite
+suite
+ .add('Agile Auto Tracking', configTest(agileAutoTracking))
+ .add('Agile Hard Coded', configTest(agileHardCoded))
+ .add('Jotai', configTest(jotai))
+ .add('Nano Stores', configTest(nanostores))
+ .add('Recoil', configTest(recoil))
+
+ // Add Listener
+ .on('start', function (this: any) {
+ startBenchmarkLog(this.name);
+ })
+ .on('cycle', (event: any) => {
+ const cycleResult = getCycleResult(event);
+ cycleLog(
+ cycleResult,
+ `[Count: ${event.target.output}, ComputedCount: ${event.target.computedOutput}]`
+ );
+ results.push(cycleResult);
+ })
+ .on('complete', function (this: any) {
+ endBenchmarkLog(this.name, results, this.filter('fastest').map('name'));
+
+ // @ts-ignore
+ // Notify server that the Benchmark Test Suite has ended
+ window.TEST.ended = true;
+ })
+
+ // Run Benchmark Test Suite
+ .run({ async: true });
diff --git a/benchmark/benchmarks/react/counter/bench/agilets.tsx b/benchmark/benchmarks/react/counter/bench/agilets.tsx
new file mode 100644
index 00000000..309b2f2a
--- /dev/null
+++ b/benchmark/benchmarks/react/counter/bench/agilets.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { createState, LogCodeManager } from '@agile-ts/core';
+import { useAgile } from '@agile-ts/react';
+
+LogCodeManager.getLogger().isActive = false;
+const COUNT = createState(0);
+
+const App = () => {
+ const count = useAgile(COUNT, undefined);
+ return COUNT.set((state) => state + 1)}>{count} ;
+};
+
+export default function (target: HTMLElement) {
+ ReactDom.render( , target);
+}
diff --git a/benchmark/benchmarks/react/counter/bench/hookstate.tsx b/benchmark/benchmarks/react/counter/bench/hookstate.tsx
new file mode 100644
index 00000000..e3f12268
--- /dev/null
+++ b/benchmark/benchmarks/react/counter/bench/hookstate.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { createState, useHookstate } from '@hookstate/core';
+
+const counter = createState(0);
+
+const App = () => {
+ const state = useHookstate(counter);
+ return state.set((v) => v + 1)}>{state.get()} ;
+};
+
+export default function (target: HTMLElement) {
+ ReactDom.render( , target);
+}
diff --git a/benchmark/benchmarks/react/counter/bench/jotai.tsx b/benchmark/benchmarks/react/counter/bench/jotai.tsx
new file mode 100644
index 00000000..cb256aba
--- /dev/null
+++ b/benchmark/benchmarks/react/counter/bench/jotai.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { atom, useAtom } from 'jotai';
+
+const countAtom = atom(0);
+
+const App = () => {
+ const [count, setCount] = useAtom(countAtom);
+ return setCount((v) => v + 1)}>{count} ;
+};
+
+export default function (target: HTMLElement) {
+ ReactDom.render( , target);
+}
diff --git a/benchmark/benchmarks/react/counter/bench/mobx.tsx b/benchmark/benchmarks/react/counter/bench/mobx.tsx
new file mode 100644
index 00000000..c597bf9a
--- /dev/null
+++ b/benchmark/benchmarks/react/counter/bench/mobx.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { action, observable } from 'mobx';
+import { observer } from 'mobx-react';
+
+const appState = observable({
+ count: 0,
+ increment: action(function () {
+ appState.count += 1;
+ }),
+});
+
+const App = observer(() => {
+ return {appState.count} ;
+});
+
+export default function (target: HTMLElement) {
+ ReactDom.render( , target);
+}
diff --git a/benchmark/benchmarks/react/counter/bench/nanostores.tsx b/benchmark/benchmarks/react/counter/bench/nanostores.tsx
new file mode 100644
index 00000000..9b377c7b
--- /dev/null
+++ b/benchmark/benchmarks/react/counter/bench/nanostores.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { createStore, getValue } from 'nanostores';
+import { useStore } from 'nanostores/react';
+
+const countStore = createStore(() => {
+ countStore.set(0);
+});
+
+const App = () => {
+ const count = useStore(countStore);
+ return (
+ countStore.set(getValue(countStore) + 1)}>{count}
+ );
+};
+
+export default function (target: HTMLElement) {
+ ReactDom.render( , target);
+}
diff --git a/benchmark/benchmarks/react/counter/bench/pulsejs.tsx b/benchmark/benchmarks/react/counter/bench/pulsejs.tsx
new file mode 100644
index 00000000..f34e2d38
--- /dev/null
+++ b/benchmark/benchmarks/react/counter/bench/pulsejs.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { state } from '@pulsejs/core';
+import { usePulse } from '@pulsejs/react';
+
+const COUNT = state(0);
+
+const App = () => {
+ const count = usePulse(COUNT);
+ return COUNT.set((state) => state + 1)}>{count} ;
+};
+
+export default function (target: HTMLElement) {
+ ReactDom.render( , target);
+}
diff --git a/benchmark/benchmarks/react/counter/bench/recoil.tsx b/benchmark/benchmarks/react/counter/bench/recoil.tsx
new file mode 100644
index 00000000..d857e23d
--- /dev/null
+++ b/benchmark/benchmarks/react/counter/bench/recoil.tsx
@@ -0,0 +1,22 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { atom, RecoilRoot, useRecoilState } from 'recoil';
+
+const counterState = atom({
+ key: 'counterState',
+ default: 0,
+});
+
+const App = () => {
+ const [count, setCount] = useRecoilState(counterState);
+ return setCount((v) => v + 1)}>{count} ;
+};
+
+export default function (target: HTMLElement) {
+ ReactDom.render(
+
+
+ ,
+ target
+ );
+}
diff --git a/benchmark/benchmarks/react/counter/bench/redux-toolkit.tsx b/benchmark/benchmarks/react/counter/bench/redux-toolkit.tsx
new file mode 100644
index 00000000..1fbbedc6
--- /dev/null
+++ b/benchmark/benchmarks/react/counter/bench/redux-toolkit.tsx
@@ -0,0 +1,40 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { configureStore, createSlice } from '@reduxjs/toolkit';
+import { Provider, useDispatch, useSelector } from 'react-redux';
+
+const counterSlice = createSlice({
+ name: 'counter',
+ initialState: {
+ count: 0,
+ },
+ reducers: {
+ increment: (state) => {
+ state.count += 1;
+ },
+ },
+});
+
+const store = configureStore({
+ reducer: {
+ counter: counterSlice.reducer,
+ },
+});
+
+const App = () => {
+ const count = useSelector((state: any) => state.counter.count);
+ const dispatch = useDispatch();
+
+ return (
+ dispatch(counterSlice.actions.increment())}>{count}
+ );
+};
+
+export default function (target: HTMLElement) {
+ ReactDom.render(
+
+
+ ,
+ target
+ );
+}
diff --git a/benchmark/benchmarks/react/counter/bench/redux.tsx b/benchmark/benchmarks/react/counter/bench/redux.tsx
new file mode 100644
index 00000000..72b5c604
--- /dev/null
+++ b/benchmark/benchmarks/react/counter/bench/redux.tsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { combineReducers, createStore } from 'redux';
+import { Provider, useDispatch, useSelector } from 'react-redux';
+
+const increment = () => {
+ return {
+ type: 'INCREMENT',
+ };
+};
+
+const counter = (state = 0, action: any) => {
+ switch (action.type) {
+ case 'INCREMENT':
+ return state + 1;
+ default:
+ return state;
+ }
+};
+
+const rootReducer = combineReducers({
+ counter,
+});
+
+const store = createStore(rootReducer);
+
+const App = () => {
+ const count = useSelector((state: any) => state.counter);
+ const dispatch = useDispatch();
+
+ return dispatch(increment())}>{count} ;
+};
+
+export default function (target: HTMLElement) {
+ ReactDom.render(
+
+
+ ,
+ target
+ );
+}
diff --git a/benchmark/benchmarks/react/counter/bench/valtio.tsx b/benchmark/benchmarks/react/counter/bench/valtio.tsx
new file mode 100644
index 00000000..682a3211
--- /dev/null
+++ b/benchmark/benchmarks/react/counter/bench/valtio.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import { proxy, useSnapshot } from 'valtio';
+
+const state = proxy({ count: 0 });
+
+function App() {
+ const snapshot = useSnapshot(state, { sync: true });
+ return state.count++}>{snapshot.count} ;
+}
+
+export default function (target: HTMLElement) {
+ ReactDom.render( , target);
+}
diff --git a/benchmark/benchmarks/react/counter/bench/zustand.tsx b/benchmark/benchmarks/react/counter/bench/zustand.tsx
new file mode 100644
index 00000000..ac73a2b5
--- /dev/null
+++ b/benchmark/benchmarks/react/counter/bench/zustand.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import create from 'zustand';
+
+const useStore = create((set) => ({
+ count: 0,
+ increment: () => set((state) => ({ count: state.count + 1 })),
+}));
+
+const App = () => {
+ const count = useStore((state) => state.count);
+ const increment = useStore((state) => state.increment);
+
+ return {count} ;
+};
+
+export default function (target: HTMLElement) {
+ ReactDom.render( , target);
+}
diff --git a/benchmark/benchmarks/react/counter/index.ts b/benchmark/benchmarks/react/counter/index.ts
new file mode 100644
index 00000000..067a85de
--- /dev/null
+++ b/benchmark/benchmarks/react/counter/index.ts
@@ -0,0 +1,96 @@
+import ReactDOM from 'react-dom';
+import Benchmark, { Suite, Options } from 'benchmark';
+import {
+ cycleLog,
+ CycleResultInterface,
+ endBenchmarkLog,
+ getCycleResult,
+ startBenchmarkLog,
+} from '../../../benchmarkManager';
+
+// Files to run the Benchmark on
+import agilets from './bench/agilets';
+import hookstate from './bench/hookstate';
+import jotai from './bench/jotai';
+import mobx from './bench/mobx';
+import nanostores from './bench/nanostores';
+import pulsejs from './bench/pulsejs';
+import recoil from './bench/recoil';
+import redux from './bench/redux';
+import reduxToolkit from './bench/redux-toolkit';
+import valtio from './bench/valtio';
+import zustand from './bench/zustand';
+
+// @ts-ignore
+// Benchmark.js requires an instance of itself globally
+window.Benchmark = Benchmark;
+
+// Create new Benchmark Test Suite
+const suite = new Suite('Count');
+
+// Retrieve the Element to render the Benchmark Test Suite in
+const target = document.getElementById('bench')!;
+
+// Increment Element
+let increment: HTMLHeadingElement;
+
+// Configures a single Benchmark Test
+function configTest(renderElement: (target: HTMLElement) => void): Options {
+ return {
+ fn() {
+ // Execute increment action
+ increment.click();
+ },
+ onStart() {
+ // Render React Component in the target Element
+ renderElement(target);
+
+ // Retrieve Element to execute the increment action on
+ increment = target.querySelector('h1')!;
+ },
+ onComplete() {
+ // Set 'output' in the Benchmark itself to print it later
+ (this as any).output = parseInt(target.innerText, 10);
+
+ // Unmount React Component
+ ReactDOM.unmountComponentAtNode(target);
+ target.innerHTML = '';
+ },
+ };
+}
+
+const results: CycleResultInterface[] = [];
+
+// Add Tests to the Benchmark Test Suite
+suite
+ .add('AgileTs', configTest(agilets))
+ // .add('Hookstate', configTest(hookstate))
+ // .add('Jotai', configTest(jotai))
+ // .add('Mobx', configTest(mobx))
+ // .add('Nano Stores', configTest(nanostores))
+ // .add('PulseJs', configTest(pulsejs))
+ // .add('Recoil', configTest(recoil))
+ // .add('Redux', configTest(redux))
+ // .add('Redux-Toolkit', configTest(reduxToolkit))
+ // .add('Valtio', configTest(valtio))
+ // .add('Zustand', configTest(zustand))
+
+ // Add Listener
+ .on('start', function (this: any) {
+ startBenchmarkLog(this.name);
+ })
+ .on('cycle', (event: any) => {
+ const cycleResult = getCycleResult(event);
+ cycleLog(cycleResult, `[Count: ${event.target.output}]`);
+ results.push(cycleResult);
+ })
+ .on('complete', function (this: any) {
+ endBenchmarkLog(this.name, results, this.filter('fastest').map('name'));
+
+ // @ts-ignore
+ // Notify server that the Benchmark Test Suite has ended
+ window.TEST.ended = true;
+ })
+
+ // Run Benchmark Test Suite
+ .run({ async: true });
diff --git a/benchmark/benchmarks/typescript/defineConfig/bench/referencer.ts b/benchmark/benchmarks/typescript/defineConfig/bench/referencer.ts
new file mode 100644
index 00000000..ba6f36b6
--- /dev/null
+++ b/benchmark/benchmarks/typescript/defineConfig/bench/referencer.ts
@@ -0,0 +1,16 @@
+export function defineConfig(
+ config: any,
+ defaults: any,
+ overwriteUndefinedProperties?: boolean
+): void {
+ if (overwriteUndefinedProperties === undefined)
+ overwriteUndefinedProperties = true;
+
+ for (const defaultKey in defaults) {
+ if (
+ !Object.prototype.hasOwnProperty.call(config, defaultKey) ||
+ (overwriteUndefinedProperties && config[defaultKey] === undefined)
+ )
+ config[defaultKey] = defaults[defaultKey];
+ }
+}
diff --git a/benchmark/benchmarks/typescript/defineConfig/bench/spreader.ts b/benchmark/benchmarks/typescript/defineConfig/bench/spreader.ts
new file mode 100644
index 00000000..7fe3c013
--- /dev/null
+++ b/benchmark/benchmarks/typescript/defineConfig/bench/spreader.ts
@@ -0,0 +1,17 @@
+export function defineConfig(
+ config: any,
+ defaults: any,
+ overwriteUndefinedProperties?: boolean
+): any {
+ if (overwriteUndefinedProperties === undefined)
+ overwriteUndefinedProperties = true;
+
+ if (overwriteUndefinedProperties) {
+ const finalConfig = { ...defaults, ...config };
+ for (const key in finalConfig)
+ if (finalConfig[key] === undefined) finalConfig[key] = defaults[key];
+ return finalConfig;
+ }
+
+ return { ...defaults, ...config };
+}
diff --git a/benchmark/benchmarks/typescript/defineConfig/index.ts b/benchmark/benchmarks/typescript/defineConfig/index.ts
new file mode 100644
index 00000000..f38b2034
--- /dev/null
+++ b/benchmark/benchmarks/typescript/defineConfig/index.ts
@@ -0,0 +1,85 @@
+import Benchmark, { Suite } from 'benchmark';
+import {
+ cycleLog,
+ CycleResultInterface,
+ endBenchmarkLog,
+ getCycleResult,
+ startBenchmarkLog,
+} from '../../../benchmarkManager';
+
+// Files to run the Benchmark on
+import * as referencer from './bench/referencer';
+import * as spreader from './bench/spreader';
+
+interface ConfigInterface {
+ x1?: boolean;
+ x2?: string;
+ x3?: number;
+ x4?: boolean;
+ x5?: string;
+}
+
+const defaultConfig: ConfigInterface = { x1: true, x2: undefined };
+
+// @ts-ignore
+// Benchmark.js requires an instance of itself globally
+window.Benchmark = Benchmark;
+
+// Create new Benchmark Test Suite
+const suite = new Suite('define config');
+
+const results: CycleResultInterface[] = [];
+
+// Add Tests to the Benchmark Test Suite
+suite
+ .add('Primitiver', function () {
+ let config = defaultConfig;
+ config = {
+ x1: false,
+ x2: 'jeff',
+ x3: 10,
+ x4: false,
+ x5: 'hans',
+ ...config,
+ };
+ })
+ .add('Referencer', function () {
+ const config = defaultConfig;
+ referencer.defineConfig(config, {
+ x1: false,
+ x2: 'jeff',
+ x3: 10,
+ x4: false,
+ x5: 'hans',
+ });
+ })
+ .add('Spreader', function () {
+ let config = defaultConfig;
+ config = spreader.defineConfig(config, {
+ x1: false,
+ x2: 'jeff',
+ x3: 10,
+ x4: false,
+ x5: 'hans',
+ });
+ })
+
+ // Add Listener
+ .on('start', function (this: any) {
+ startBenchmarkLog(this.name);
+ })
+ .on('cycle', (event: any) => {
+ const cycleResult = getCycleResult(event);
+ cycleLog(cycleResult);
+ results.push(cycleResult);
+ })
+ .on('complete', function (this: any) {
+ endBenchmarkLog(this.name, results, this.filter('fastest').map('name'));
+
+ // @ts-ignore
+ // Notify server that the Benchmark Test Suite has ended
+ window.TEST.ended = true;
+ })
+
+ // Run Benchmark Test Suite
+ .run({ async: true });
diff --git a/benchmark/lodash.ts b/benchmark/lodash.ts
new file mode 100644
index 00000000..104b3b1f
--- /dev/null
+++ b/benchmark/lodash.ts
@@ -0,0 +1,4 @@
+import _ from 'lodash';
+
+// Benchmark.js requires lodash globally
+window._ = _;
diff --git a/benchmark/package.json b/benchmark/package.json
new file mode 100644
index 00000000..d7cf2247
--- /dev/null
+++ b/benchmark/package.json
@@ -0,0 +1,59 @@
+{
+ "name": "benchmark",
+ "version": "0.1.0",
+ "private": true,
+ "author": "BennoDev",
+ "license": "MIT",
+ "homepage": "https://agile-ts.org/",
+ "description": "Benchmark Tests",
+ "scripts": {
+ "test": "node -r esbuild-register run.ts",
+ "test:counter": "yarn test ./benchmarks/react/counter",
+ "test:1000fields": "yarn test ./benchmarks/react/1000fields",
+ "test:computed": "yarn test ./benchmarks/react/computed",
+ "test:defineConfig": "yarn test ./benchmarks/typescript/defineConfig",
+ "install:dev:agile": "yalc add @agile-ts/core @agile-ts/react & yarn install",
+ "install:prod:agile": "yarn add @agile-ts/core @agile-ts/react & yarn install"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/agile-ts/agile.git"
+ },
+ "dependencies": {
+ "@agile-ts/core": "file:.yalc/@agile-ts/core",
+ "@agile-ts/react": "file:.yalc/@agile-ts/react",
+ "@hookstate/core": "^3.0.8",
+ "@pulsejs/core": "^4.0.0-beta.3",
+ "@pulsejs/react": "^4.0.0-beta.3",
+ "@reduxjs/toolkit": "^1.6.0",
+ "benchmark": "^2.1.4",
+ "chalk": "^4.1.1",
+ "colorette": "^1.2.2",
+ "dotenv": "^10.0.0",
+ "esbuild": "^0.12.14",
+ "esbuild-register": "^2.6.0",
+ "jotai": "^1.1.2",
+ "lodash": "^4.17.21",
+ "mobx": "^6.3.2",
+ "mobx-react": "^7.2.0",
+ "nanostores": "^0.3.3",
+ "playwright": "^1.12.3",
+ "react": "^17.0.2",
+ "react-dom": "^17.0.2",
+ "react-redux": "^7.2.4",
+ "recoil": "^0.3.1",
+ "redux": "^4.1.0",
+ "typescript": "^4.3.5",
+ "valtio": "^1.0.6",
+ "zustand": "^3.5.5"
+ },
+ "devDependencies": {
+ "@types/benchmark": "^2.1.0",
+ "@types/node": "^16.0.0",
+ "@types/react": "^17.0.13",
+ "@types/react-dom": "^17.0.8"
+ },
+ "bugs": {
+ "url": "https://github.com/agile-ts/agile/issues"
+ }
+}
diff --git a/benchmark/public/index.html b/benchmark/public/index.html
new file mode 100644
index 00000000..93fc27b9
--- /dev/null
+++ b/benchmark/public/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+ Benchmark
+
+
+
+
+
+
+
diff --git a/benchmark/run.ts b/benchmark/run.ts
new file mode 100644
index 00000000..2bd03642
--- /dev/null
+++ b/benchmark/run.ts
@@ -0,0 +1,111 @@
+import dotenv from 'dotenv';
+import esbuild from 'esbuild';
+import playwright from 'playwright';
+import chalk from 'chalk';
+
+// Loads environment variables from the '.env' file
+dotenv.config();
+
+// https://nodejs.org/docs/latest/api/process.html#process_process_argv
+// Extract entry (at third parameter) from the executed command
+// yarn run ./path/to/entry -> './path/to/entry' is extracted
+const entry = process.argv.slice(2)[0];
+if (entry == null) {
+ throw new Error(
+ "No valid entry was provided! Valid entry example: 'yarn run ./benchmarks/react/counter'"
+ );
+}
+
+const startBenchmark = async () => {
+ console.log(chalk.blue('Starting the benchmark server..\n'));
+
+ // Bundle Benchmark Test Suite
+ // and launch the server on which the Test Suite is executed
+ const server = await esbuild.serve(
+ {
+ servedir: 'public',
+ port: 3003,
+ host: '127.0.0.1', // localhost
+ },
+ {
+ inject: ['./lodash.ts'], // https://esbuild.github.io/api/#inject
+ entryPoints: [entry], // https://esbuild.github.io/api/#entry-points
+ outfile: './public/bundle.js',
+ target: 'es2015',
+ format: 'cjs', // https://esbuild.github.io/api/#format-commonjs
+ platform: 'browser',
+ minify: true, // https://esbuild.github.io/api/#minify
+ bundle: true, // https://esbuild.github.io/api/#bundle
+ sourcemap: 'external', // https://esbuild.github.io/api/#sourcemap// https://github.com/evanw/esbuild/issues/69
+ }
+ );
+ const serverUrl = `http://${server.host}:${server.port}`;
+
+ console.log(
+ `${chalk.blue('[i]')} ${chalk.gray(
+ `Server is running at port: ${chalk.blueBright.bold(server.port)}`
+ )}`
+ );
+
+ // Launch Chrome as browser to run the Benchmark Test Suite in
+ const browser = await playwright.chromium.launch();
+ const context = await browser.newContext();
+ const page = await context.newPage();
+
+ // Option to open and test the Benchmark Test Suite in the browser manually
+ if (process.env.MANUAL_BENCHMARK === 'true') {
+ console.log(
+ `${chalk.blue('[i]')} ${chalk.gray(
+ `Benchmark is running at ${chalk.blueBright.bold(serverUrl)}`
+ )}`
+ );
+
+ await server.wait;
+ }
+
+ console.log('\n');
+
+ // Setup 'pageerror' listener to throw occurring errors in the local console
+ // https://playwright.dev/docs/api/class-page/#page-event-page-error
+ page.on('pageerror', (error) => {
+ throw error;
+ });
+
+ // Setup 'console' listener to transfer the browser logs into the local console
+ // https://playwright.dev/docs/api/class-page/#page-event-console
+ page.on('console', (...message) => {
+ const stringMessages = message.map((m) => m.text());
+ const colorMessage = stringMessages[0];
+ stringMessages.shift(); // Remove 'colorMessage' (first argument) from 'stringMessages' array
+
+ // Parse color message to work in chalck
+ // https://stackoverflow.com/questions/56526522/gulp-chalk-pass-string-template-through-method
+ const parsedColorMessage = [colorMessage];
+ // @ts-ignore
+ parsedColorMessage.raw = [colorMessage];
+
+ console.log(chalk(parsedColorMessage), ...stringMessages);
+ });
+
+ // Open the url the server is running on
+ await page.goto(serverUrl);
+
+ // Wait for tests to be executed (indicator is when 'window.TESTS.ended' is set to true)
+ // https://playwright.dev/docs/api/class-frame#frame-wait-for-function
+ await page.waitForFunction(
+ // @ts-ignore
+ () => window.TEST?.ended,
+ undefined,
+ {
+ timeout: 0,
+ polling: 100,
+ }
+ );
+
+ // Close browser and stop server
+ await browser.close();
+ server.stop();
+};
+
+// Execute the Benchmark
+startBenchmark();
diff --git a/benchmark/tsconfig.json b/benchmark/tsconfig.json
new file mode 100644
index 00000000..b36827f2
--- /dev/null
+++ b/benchmark/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react"
+ }
+}
diff --git a/benchmark/yarn.lock b/benchmark/yarn.lock
new file mode 100644
index 00000000..16d56ebc
--- /dev/null
+++ b/benchmark/yarn.lock
@@ -0,0 +1,624 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@agile-ts/core@file:.yalc/@agile-ts/core":
+ version "0.1.0"
+ dependencies:
+ "@agile-ts/utils" "^0.0.5"
+
+"@agile-ts/react@file:.yalc/@agile-ts/react":
+ version "0.1.0"
+
+"@agile-ts/utils@^0.0.5":
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/@agile-ts/utils/-/utils-0.0.5.tgz#23cc83e60eb6b15734247fac1d77f1fd629ffdb6"
+ integrity sha512-R86X9MjMty14eoQ4djulZSdHf9mIF9dPcj4g+SABqdA6AqbewS0/BQGNGR5p6gXhqc4+mT8rzkutywdPnMUNfA==
+
+"@babel/runtime@^7.12.1", "@babel/runtime@^7.9.2":
+ version "7.14.6"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d"
+ integrity sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
+"@hookstate/core@^3.0.8":
+ version "3.0.8"
+ resolved "https://registry.yarnpkg.com/@hookstate/core/-/core-3.0.8.tgz#d6838153d6d43c2f35cfca475c31248192564e62"
+ integrity sha512-blQagGIVIbNoUiNCRrvaXqFmUe7WGMY35ok/LENfl2pcIsLBjkreYIZiaSFi83tkycwq7ZOmcQz/R1nvLKhH8w==
+
+"@pulsejs/core@^4.0.0-beta.3":
+ version "4.0.0-beta.3"
+ resolved "https://registry.yarnpkg.com/@pulsejs/core/-/core-4.0.0-beta.3.tgz#a19c983093dbc516ee3dcda4dee92917c77fd4cc"
+ integrity sha512-nYSckFFTPt8/8wZpEaFqKsSc1xd+eT7t3TSB3acy6yEQjbkQ1T2CL5tyNGL4tzT3Bher6LHkipDy0tMi5PsPUg==
+
+"@pulsejs/react@^4.0.0-beta.3":
+ version "4.0.0-beta.3"
+ resolved "https://registry.yarnpkg.com/@pulsejs/react/-/react-4.0.0-beta.3.tgz#d25bf1e969b8749d0f098f6e2ac066364f8c1fe1"
+ integrity sha512-QuO/UoBTL+5ObCTje3nvrMqrifPujWtbHpVMJMnWnZGrL88hiSivaWGvqzaU0BsIE3R6WQHZTJNDvBR8sXFIEQ==
+
+"@reduxjs/toolkit@^1.6.0":
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.6.0.tgz#0a17c6941c57341f8b31e982352b495ab69d5add"
+ integrity sha512-eGL50G+Vj5AG5uD0lineb6rRtbs96M8+hxbcwkHpZ8LQcmt0Bm33WyBSnj5AweLkjQ7ZP+KFRDHiLMznljRQ3A==
+ dependencies:
+ immer "^9.0.1"
+ redux "^4.1.0"
+ redux-thunk "^2.3.0"
+ reselect "^4.0.0"
+
+"@types/benchmark@^2.1.0":
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/@types/benchmark/-/benchmark-2.1.0.tgz#157e2ef22311d3140fb33e82a938a1beb26e78e0"
+ integrity sha512-wxT2/LZn4z0NvSfZirxmBx686CU7EXp299KHkIk79acXpQtgeYHrslFzDacPGXifC0Pe3CEaLup07bgY1PnuQw==
+
+"@types/hoist-non-react-statics@^3.3.0":
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
+ integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
+ dependencies:
+ "@types/react" "*"
+ hoist-non-react-statics "^3.3.0"
+
+"@types/node@*", "@types/node@^16.0.0":
+ version "16.0.0"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.0.0.tgz#067a6c49dc7a5c2412a505628e26902ae967bf6f"
+ integrity sha512-TmCW5HoZ2o2/z2EYi109jLqIaPIi9y/lc2LmDCWzuCi35bcaQ+OtUh6nwBiFK7SOu25FAU5+YKdqFZUwtqGSdg==
+
+"@types/prop-types@*":
+ version "15.7.3"
+ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
+ integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
+
+"@types/react-dom@^17.0.8":
+ version "17.0.8"
+ resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.8.tgz#3180de6d79bf53762001ad854e3ce49f36dd71fc"
+ integrity sha512-0ohAiJAx1DAUEcY9UopnfwCE9sSMDGnY/oXjWMax6g3RpzmTt2GMyMVAXcbn0mo8XAff0SbQJl2/SBU+hjSZ1A==
+ dependencies:
+ "@types/react" "*"
+
+"@types/react-redux@^7.1.16":
+ version "7.1.16"
+ resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.16.tgz#0fbd04c2500c12105494c83d4a3e45c084e3cb21"
+ integrity sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw==
+ dependencies:
+ "@types/hoist-non-react-statics" "^3.3.0"
+ "@types/react" "*"
+ hoist-non-react-statics "^3.3.0"
+ redux "^4.0.0"
+
+"@types/react@*", "@types/react@^17.0.13":
+ version "17.0.13"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.13.tgz#6b7c9a8f2868586ad87d941c02337c6888fb874f"
+ integrity sha512-D/G3PiuqTfE3IMNjLn/DCp6umjVCSvtZTPdtAFy5+Ved6CsdRvivfKeCzw79W4AatShtU4nGqgvOv5Gro534vQ==
+ dependencies:
+ "@types/prop-types" "*"
+ "@types/scheduler" "*"
+ csstype "^3.0.2"
+
+"@types/scheduler@*":
+ version "0.16.1"
+ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275"
+ integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==
+
+"@types/yauzl@^2.9.1":
+ version "2.9.2"
+ resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.2.tgz#c48e5d56aff1444409e39fa164b0b4d4552a7b7a"
+ integrity sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==
+ dependencies:
+ "@types/node" "*"
+
+agent-base@6:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
+ integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
+ dependencies:
+ debug "4"
+
+ansi-styles@^4.1.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
+ integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+ dependencies:
+ color-convert "^2.0.1"
+
+balanced-match@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
+ integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+
+benchmark@^2.1.4:
+ version "2.1.4"
+ resolved "https://registry.yarnpkg.com/benchmark/-/benchmark-2.1.4.tgz#09f3de31c916425d498cc2ee565a0ebf3c2a5629"
+ integrity sha1-CfPeMckWQl1JjMLuVloOvzwqVik=
+ dependencies:
+ lodash "^4.17.4"
+ platform "^1.3.3"
+
+brace-expansion@^1.1.7:
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+ integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+buffer-crc32@~0.2.3:
+ version "0.2.13"
+ resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
+ integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
+
+chalk@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad"
+ integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
+color-convert@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
+ integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+ dependencies:
+ color-name "~1.1.4"
+
+color-name@~1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
+ integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+
+colorette@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
+ integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
+
+commander@^6.1.0:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
+ integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+ integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
+
+csstype@^3.0.2:
+ version "3.0.8"
+ resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340"
+ integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==
+
+debug@4, debug@^4.1.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
+ integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
+ dependencies:
+ ms "2.1.2"
+
+dotenv@^10.0.0:
+ version "10.0.0"
+ resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
+ integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
+
+end-of-stream@^1.1.0:
+ version "1.4.4"
+ resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
+ integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
+ dependencies:
+ once "^1.4.0"
+
+esbuild-register@^2.6.0:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/esbuild-register/-/esbuild-register-2.6.0.tgz#9f19a54c82be751dd87673d6a66d7b9e1cdd8498"
+ integrity sha512-2u4AtnCXP5nivtIxZryiZOUcEQkOzFS7UhAqibUEmaTAThJ48gDLYTBF/Fsz+5r0hbV1jrFE6PQvPDUrKZNt/Q==
+ dependencies:
+ esbuild "^0.12.8"
+ jsonc-parser "^3.0.0"
+
+esbuild@^0.12.14, esbuild@^0.12.8:
+ version "0.12.14"
+ resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.14.tgz#43157dbd0b36d939247d4eb4909a4886ac40f82e"
+ integrity sha512-z8p+6FGiplR7a3pPonXREbm+8IeXjBGvDpVidZmGB/AJMsJSfGCU+n7KOMCazA9AwvagadRWBhiKorC0w9WJvw==
+
+escape-string-regexp@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
+ integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
+
+extract-zip@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a"
+ integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==
+ dependencies:
+ debug "^4.1.1"
+ get-stream "^5.1.0"
+ yauzl "^2.10.0"
+ optionalDependencies:
+ "@types/yauzl" "^2.9.1"
+
+fd-slicer@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
+ integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
+ dependencies:
+ pend "~1.2.0"
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+ integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
+
+get-stream@^5.1.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
+ integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
+ dependencies:
+ pump "^3.0.0"
+
+glob@^7.1.3:
+ version "7.1.7"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
+ integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.0.4"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+graceful-fs@^4.2.4:
+ version "4.2.6"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
+ integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
+
+hamt_plus@1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/hamt_plus/-/hamt_plus-1.0.2.tgz#e21c252968c7e33b20f6a1b094cd85787a265601"
+ integrity sha1-4hwlKWjH4zsg9qGwlM2FeHomVgE=
+
+has-flag@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
+ integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
+hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
+ integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
+ dependencies:
+ react-is "^16.7.0"
+
+https-proxy-agent@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
+ integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
+ dependencies:
+ agent-base "6"
+ debug "4"
+
+immer@^9.0.1:
+ version "9.0.3"
+ resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.3.tgz#146e2ba8b84d4b1b15378143c2345559915097f4"
+ integrity sha512-mONgeNSMuyjIe0lkQPa9YhdmTv8P19IeHV0biYhcXhbd5dhdB9HSK93zBpyKjp6wersSUgT5QyU0skmejUVP2A==
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+jotai@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/jotai/-/jotai-1.1.2.tgz#3f211e0c03c74e95ea6fd7a69c1d2b65731009bf"
+ integrity sha512-dni4wtgYGG+s9YbOJN7lcfrrhxiD6bH1SN00Pnl0F2htgOXmjxqkGlFzw02OK0Rw35wGNzBfDTJVtbGD9wHOhg==
+
+jpeg-js@^0.4.2:
+ version "0.4.3"
+ resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b"
+ integrity sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q==
+
+"js-tokens@^3.0.0 || ^4.0.0":
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+ integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
+jsonc-parser@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22"
+ integrity sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==
+
+lodash@^4.17.21, lodash@^4.17.4:
+ version "4.17.21"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+ integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+
+loose-envify@^1.1.0, loose-envify@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
+ integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
+ dependencies:
+ js-tokens "^3.0.0 || ^4.0.0"
+
+mime@^2.4.6:
+ version "2.5.2"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe"
+ integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==
+
+minimatch@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
+ integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
+ dependencies:
+ brace-expansion "^1.1.7"
+
+mobx-react-lite@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-3.2.0.tgz#331d7365a6b053378dfe9c087315b4e41c5df69f"
+ integrity sha512-q5+UHIqYCOpBoFm/PElDuOhbcatvTllgRp3M1s+Hp5j0Z6XNgDbgqxawJ0ZAUEyKM8X1zs70PCuhAIzX1f4Q/g==
+
+mobx-react@^7.2.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-7.2.0.tgz#241e925e963bb83a31d269f65f9f379e37ecbaeb"
+ integrity sha512-KHUjZ3HBmZlNnPd1M82jcdVsQRDlfym38zJhZEs33VxyVQTvL77hODCArq6+C1P1k/6erEeo2R7rpE7ZeOL7dg==
+ dependencies:
+ mobx-react-lite "^3.2.0"
+
+mobx@^6.3.2:
+ version "6.3.2"
+ resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.3.2.tgz#125590961f702a572c139ab69392bea416d2e51b"
+ integrity sha512-xGPM9dIE1qkK9Nrhevp0gzpsmELKU4MFUJRORW/jqxVFIHHWIoQrjDjL8vkwoJYY3C2CeVJqgvl38hgKTalTWg==
+
+ms@2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+ integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
+nanostores@^0.3.3:
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/nanostores/-/nanostores-0.3.3.tgz#3f6a858d3e2700c70c7942301380550f66a8e9ee"
+ integrity sha512-+MemxV/HzzTPJQCvzEmwfIFMAIcDEiod37A5F1ERyKQqtm6hbEfuNokfmJfecYM2gjunlPdPHA5lQ9cwHHSYNg==
+
+object-assign@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+ integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
+
+once@^1.3.0, once@^1.3.1, once@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
+ dependencies:
+ wrappy "1"
+
+path-is-absolute@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+ integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
+
+pend@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
+ integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
+
+platform@^1.3.3:
+ version "1.3.6"
+ resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7"
+ integrity sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==
+
+playwright@^1.12.3:
+ version "1.12.3"
+ resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.12.3.tgz#113afa2cba10fb56e9a5b307377343e32a155a99"
+ integrity sha512-eyhHvZV7dMAUltqjQsgJ9CjZM8dznzN1+rcfCI6W6lfQ7IlPvTFGLuKOCcI4ETbjfbxqaS5FKIkb1WDDzq2Nww==
+ dependencies:
+ commander "^6.1.0"
+ debug "^4.1.1"
+ extract-zip "^2.0.1"
+ https-proxy-agent "^5.0.0"
+ jpeg-js "^0.4.2"
+ mime "^2.4.6"
+ pngjs "^5.0.0"
+ progress "^2.0.3"
+ proper-lockfile "^4.1.1"
+ proxy-from-env "^1.1.0"
+ rimraf "^3.0.2"
+ stack-utils "^2.0.3"
+ ws "^7.4.6"
+ yazl "^2.5.1"
+
+pngjs@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb"
+ integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==
+
+progress@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
+ integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
+
+prop-types@^15.7.2:
+ version "15.7.2"
+ resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
+ integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
+ dependencies:
+ loose-envify "^1.4.0"
+ object-assign "^4.1.1"
+ react-is "^16.8.1"
+
+proper-lockfile@^4.1.1:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f"
+ integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==
+ dependencies:
+ graceful-fs "^4.2.4"
+ retry "^0.12.0"
+ signal-exit "^3.0.2"
+
+proxy-compare@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/proxy-compare/-/proxy-compare-2.0.0.tgz#36f41114a25fcf359037308d12529183a9dc182c"
+ integrity sha512-xhJF1+vPCnu93QYva3Weii5ho1AeX5dsR/P5O7pzy9QLxeOgMSQNC8zDo0bGg9vtn61Pu5Qn+5w/Y8OSU5k+8g==
+
+proxy-from-env@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
+ integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
+
+pump@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
+ integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
+ dependencies:
+ end-of-stream "^1.1.0"
+ once "^1.3.1"
+
+react-dom@^17.0.2:
+ version "17.0.2"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
+ integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
+ dependencies:
+ loose-envify "^1.1.0"
+ object-assign "^4.1.1"
+ scheduler "^0.20.2"
+
+react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1:
+ version "16.13.1"
+ resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
+ integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
+
+react-redux@^7.2.4:
+ version "7.2.4"
+ resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.4.tgz#1ebb474032b72d806de2e0519cd07761e222e225"
+ integrity sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==
+ dependencies:
+ "@babel/runtime" "^7.12.1"
+ "@types/react-redux" "^7.1.16"
+ hoist-non-react-statics "^3.3.2"
+ loose-envify "^1.4.0"
+ prop-types "^15.7.2"
+ react-is "^16.13.1"
+
+react@^17.0.2:
+ version "17.0.2"
+ resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
+ integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
+ dependencies:
+ loose-envify "^1.1.0"
+ object-assign "^4.1.1"
+
+recoil@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/recoil/-/recoil-0.3.1.tgz#40ef544160d19d76e25de8929d7e512eace13b90"
+ integrity sha512-KNA3DRqgxX4rRC8E7fc6uIw7BACmMPuraIYy+ejhE8tsw7w32CetMm8w7AMZa34wzanKKkev3vl3H7Z4s0QSiA==
+ dependencies:
+ hamt_plus "1.0.2"
+
+redux-thunk@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
+ integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
+
+redux@^4.0.0, redux@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.0.tgz#eb049679f2f523c379f1aff345c8612f294c88d4"
+ integrity sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g==
+ dependencies:
+ "@babel/runtime" "^7.9.2"
+
+regenerator-runtime@^0.13.4:
+ version "0.13.7"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
+ integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
+
+reselect@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
+ integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==
+
+retry@^0.12.0:
+ version "0.12.0"
+ resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
+ integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=
+
+rimraf@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
+ integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
+ dependencies:
+ glob "^7.1.3"
+
+scheduler@^0.20.2:
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
+ integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==
+ dependencies:
+ loose-envify "^1.1.0"
+ object-assign "^4.1.1"
+
+signal-exit@^3.0.2:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
+ integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
+
+stack-utils@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277"
+ integrity sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==
+ dependencies:
+ escape-string-regexp "^2.0.0"
+
+supports-color@^7.1.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
+ integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+ dependencies:
+ has-flag "^4.0.0"
+
+typescript@^4.3.5:
+ version "4.3.5"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4"
+ integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==
+
+valtio@^1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/valtio/-/valtio-1.0.6.tgz#b316deea5537d254a141e2e5af1692d9eae2f60f"
+ integrity sha512-ylCis9IkcE7b92XjMb3ebdJgLvJEFJ2NjfuD01QNr98pVOhRa5WsW4LSykFgbO4W7ftrZtO8jN4svZL0XlD77w==
+ dependencies:
+ proxy-compare "2.0.0"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+ integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
+
+ws@^7.4.6:
+ version "7.5.1"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.1.tgz#44fc000d87edb1d9c53e51fbc69a0ac1f6871d66"
+ integrity sha512-2c6faOUH/nhoQN6abwMloF7Iyl0ZS2E9HGtsiLrWn0zOOMWlhtDmdf/uihDt6jnuCxgtwGBNy6Onsoy2s2O2Ow==
+
+yauzl@^2.10.0:
+ version "2.10.0"
+ resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
+ integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
+ dependencies:
+ buffer-crc32 "~0.2.3"
+ fd-slicer "~1.1.0"
+
+yazl@^2.5.1:
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35"
+ integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==
+ dependencies:
+ buffer-crc32 "~0.2.3"
+
+zustand@^3.5.5:
+ version "3.5.5"
+ resolved "https://registry.yarnpkg.com/zustand/-/zustand-3.5.5.tgz#628458ad70621ddc2a17dbee49be963e5c0dccb5"
+ integrity sha512-iTiJoxzYFtiD7DhscgwK2P4Kft1JcZEI2U7mG8IxiOFM4KpBAiJZfFop3r/3wbCuyltXI6ph1Fx90e4j/S43XA==
diff --git a/examples/react/develop/functional-component-ts/package.json b/examples/react/develop/functional-component-ts/package.json
index 08b5324c..4972a343 100644
--- a/examples/react/develop/functional-component-ts/package.json
+++ b/examples/react/develop/functional-component-ts/package.json
@@ -5,10 +5,11 @@
"dependencies": {
"@agile-ts/api": "file:.yalc/@agile-ts/api",
"@agile-ts/core": "file:.yalc/@agile-ts/core",
- "@agile-ts/multieditor": "file:.yalc/@agile-ts/multieditor",
- "@agile-ts/react": "file:.yalc/@agile-ts/react",
"@agile-ts/event": "file:.yalc/@agile-ts/event",
+ "@agile-ts/logger": "file:.yalc/@agile-ts/logger",
+ "@agile-ts/multieditor": "file:.yalc/@agile-ts/multieditor",
"@agile-ts/proxytree": "file:.yalc/@agile-ts/proxytree",
+ "@agile-ts/react": "file:.yalc/@agile-ts/react",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "^5.2.0",
@@ -30,7 +31,8 @@
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
- "install:agile": "yalc add @agile-ts/core @agile-ts/react @agile-ts/api @agile-ts/multieditor @agile-ts/event @agile-ts/proxytree & yarn install"
+ "install:dev:agile": "yalc add @agile-ts/core @agile-ts/react @agile-ts/api @agile-ts/multieditor @agile-ts/event @agile-ts/proxytree @agile-ts/logger & yarn install",
+ "install:prod:agile": "yarn add @agile-ts/core @agile-ts/react @agile-ts/api @agile-ts/multieditor @agile-ts/event @agile-ts/proxytree @agile-ts/logger & yarn install"
},
"eslintConfig": {
"extends": "react-app"
diff --git a/examples/react/develop/functional-component-ts/src/core/index.ts b/examples/react/develop/functional-component-ts/src/core/index.ts
index cf448f46..c98da0af 100644
--- a/examples/react/develop/functional-component-ts/src/core/index.ts
+++ b/examples/react/develop/functional-component-ts/src/core/index.ts
@@ -1,10 +1,11 @@
-import { Agile, clone, Item, Logger } from '@agile-ts/core';
+import { Agile, clone, Item } from '@agile-ts/core';
import Event from '@agile-ts/event';
+import { assignSharedAgileLoggerConfig, Logger } from '@agile-ts/logger';
export const myStorage: any = {};
+assignSharedAgileLoggerConfig({ level: Logger.level.DEBUG });
export const App = new Agile({
- logConfig: { level: Logger.level.DEBUG },
localStorage: true,
});
diff --git a/examples/react/develop/functional-component-ts/yarn.lock b/examples/react/develop/functional-component-ts/yarn.lock
index 21b151e8..b44f5283 100644
--- a/examples/react/develop/functional-component-ts/yarn.lock
+++ b/examples/react/develop/functional-component-ts/yarn.lock
@@ -3,46 +3,36 @@
"@agile-ts/api@file:.yalc/@agile-ts/api":
- version "0.0.18"
+ version "0.0.19"
dependencies:
- "@agile-ts/utils" "^0.0.4"
+ "@agile-ts/utils" "^0.0.5"
"@agile-ts/core@file:.yalc/@agile-ts/core":
- version "0.0.17"
+ version "0.1.0"
dependencies:
- "@agile-ts/logger" "^0.0.4"
- "@agile-ts/utils" "^0.0.4"
+ "@agile-ts/utils" "^0.0.5"
"@agile-ts/event@file:.yalc/@agile-ts/event":
- version "0.0.7"
+ version "0.0.8"
-"@agile-ts/logger@^0.0.4":
- version "0.0.4"
- resolved "https://registry.yarnpkg.com/@agile-ts/logger/-/logger-0.0.4.tgz#7f4d82ef8f03b13089af0878c360575c43f0962d"
- integrity sha512-qm0obAKqJMaPKM+c76gktRXyw3OL1v39AnhMZ0FBGwJqHWU+fLRkCzlQwjaROCr3F1XP01Lc/Ls3efF0WzyEPw==
+"@agile-ts/logger@file:.yalc/@agile-ts/logger":
+ version "0.0.5"
dependencies:
- "@agile-ts/utils" "^0.0.4"
+ "@agile-ts/utils" "^0.0.5"
"@agile-ts/multieditor@file:.yalc/@agile-ts/multieditor":
- version "0.0.17"
-
-"@agile-ts/proxytree@^0.0.3":
- version "0.0.3"
- resolved "https://registry.yarnpkg.com/@agile-ts/proxytree/-/proxytree-0.0.3.tgz#e3dacab123a311f2f0d4a0369793fe90fdab7569"
- integrity sha512-auO6trCo7ivLJYuLjxrnK4xuUTangVPTq8UuOMTlGbJFjmb8PLEkaXuRoVGSzv9jsT2FeS7KsP7Fs+yvv0WPdg==
+ version "0.0.18"
"@agile-ts/proxytree@file:.yalc/@agile-ts/proxytree":
- version "0.0.3"
+ version "0.0.4"
"@agile-ts/react@file:.yalc/@agile-ts/react":
- version "0.0.18"
- dependencies:
- "@agile-ts/proxytree" "^0.0.3"
+ version "0.1.0"
-"@agile-ts/utils@^0.0.4":
- version "0.0.4"
- resolved "https://registry.yarnpkg.com/@agile-ts/utils/-/utils-0.0.4.tgz#66e9536e561796489a37155da6b74ce2dc482697"
- integrity sha512-GiZyTYmCm4j2N57oDjeMuPpfQdgn9clb0Cxpfuwi2Bq5T/KPXlaROLsVGwHLjwwT+NX7xxr5qNJH8pZTnHnYRQ==
+"@agile-ts/utils@^0.0.5":
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/@agile-ts/utils/-/utils-0.0.5.tgz#23cc83e60eb6b15734247fac1d77f1fd629ffdb6"
+ integrity sha512-R86X9MjMty14eoQ4djulZSdHf9mIF9dPcj4g+SABqdA6AqbewS0/BQGNGR5p6gXhqc4+mT8rzkutywdPnMUNfA==
"@babel/code-frame@7.8.3":
version "7.8.3"
diff --git a/examples/react/release/boxes/package.json b/examples/react/release/boxes/package.json
index 44579fee..8fdd52f6 100644
--- a/examples/react/release/boxes/package.json
+++ b/examples/react/release/boxes/package.json
@@ -3,10 +3,8 @@
"version": "0.1.0",
"private": true,
"dependencies": {
- "@agile-ts/api": "file:.yalc/@agile-ts/api",
"@agile-ts/core": "file:.yalc/@agile-ts/core",
- "@agile-ts/event": "file:.yalc/@agile-ts/event",
- "@agile-ts/multieditor": "file:.yalc/@agile-ts/multieditor",
+ "@agile-ts/logger": "file:.yalc/@agile-ts/logger",
"@agile-ts/proxytree": "file:.yalc/@agile-ts/proxytree",
"@agile-ts/react": "file:.yalc/@agile-ts/react",
"@chakra-ui/react": "^1.6.3",
@@ -35,7 +33,8 @@
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
- "install:agile": "yalc add @agile-ts/core @agile-ts/react @agile-ts/api @agile-ts/multieditor @agile-ts/event @agile-ts/proxytree & yarn install"
+ "install:dev:agile": "yalc add @agile-ts/core @agile-ts/react @agile-ts/proxytree @agile-ts/logger & yarn install",
+ "install:prod:agile": "yarn add @agile-ts/core @agile-ts/react @agile-ts/proxytree @agile-ts/logger & yarn install"
},
"eslintConfig": {
"extends": [
diff --git a/examples/react/release/boxes/src/api.ts b/examples/react/release/boxes/src/api.ts
index 17d0a2a6..8e01fedc 100644
--- a/examples/react/release/boxes/src/api.ts
+++ b/examples/react/release/boxes/src/api.ts
@@ -1,26 +1,26 @@
-import queryString, {ParsedUrlQueryInput} from 'querystring'
+import queryString, { ParsedUrlQueryInput } from 'querystring';
type RequestOptions = {
- queryParams?: ParsedUrlQueryInput
- method?: 'GET' | 'POST'
- body?: object | string
-}
+ queryParams?: ParsedUrlQueryInput;
+ method?: 'GET' | 'POST';
+ body?: object | string;
+};
export const apiUrl = (lambda: string, queryParams?: ParsedUrlQueryInput) => {
- let url = `https://f10adraov8.execute-api.us-east-1.amazonaws.com/dev/${lambda}`
- if (queryParams) url += '?' + queryString.stringify(queryParams)
+ let url = `https://f10adraov8.execute-api.us-east-1.amazonaws.com/dev/${lambda}`;
+ if (queryParams) url += '?' + queryString.stringify(queryParams);
- return url
-}
+ return url;
+};
export const callApi = (lambda: string, options?: RequestOptions) => {
- const {queryParams, body, method} = options || {}
- const url = apiUrl(lambda, queryParams)
+ const { queryParams, body, method } = options || {};
+ const url = apiUrl(lambda, queryParams);
- let bodyString = body
- if (typeof bodyString === 'object') {
- bodyString = JSON.stringify(body)
- }
+ let bodyString = body;
+ if (typeof bodyString === 'object') {
+ bodyString = JSON.stringify(body);
+ }
- return fetch(url, {body: bodyString, method}).then((res) => res.json())
-}
+ return fetch(url, { body: bodyString, method }).then((res) => res.json());
+};
diff --git a/examples/react/release/boxes/src/components/EditProperties/index.tsx b/examples/react/release/boxes/src/components/EditProperties/index.tsx
index 62c3fd51..3729f759 100644
--- a/examples/react/release/boxes/src/components/EditProperties/index.tsx
+++ b/examples/react/release/boxes/src/components/EditProperties/index.tsx
@@ -11,16 +11,17 @@ import _ from 'lodash';
import { ImageInfo, ImageInfoFallback } from './components/ImageInfo';
import { useProxy, useSelector } from '@agile-ts/react';
import core from '../../core';
+import { ElementImageInterface } from '../../core/entities/ui/ui.interfaces';
export const EditProperties = () => {
const selectedElementId = useSelector(
core.ui.SELECTED_ELEMENT,
(value) => value?.id
- );
+ ) as number | string;
const selectedElementImage = useSelector(
core.ui.SELECTED_ELEMENT,
(value) => value?.image
- );
+ ) as ElementImageInterface;
// TODO useProxy doesn't work here as expected because the selected Elements
// doesn't exist on the creation of the Subscription Container
@@ -135,7 +136,8 @@ const Property = ({
id: number | string;
}) => {
const ELEMENT = core.ui.ELEMENTS.getItem(id);
- const element = useProxy(ELEMENT, { componentId: 'Property' });
+ const element = useProxy(ELEMENT, { componentId: 'Property', deps: [id] });
+
return (
= (
setIsLoading(true);
core.ui.getImageDimensions(element.image.src).then((response) => {
setIsLoading(false);
- ELEMENT?.patch({
- style: { ...{ size: response }, ...ELEMENT?.value.style },
- });
+ if (ELEMENT != null) {
+ ELEMENT.nextStateValue.style.size = response;
+ ELEMENT?.ingest();
+ }
});
} else {
setIsLoading(false);
diff --git a/examples/react/release/boxes/src/components/Rectangle/index.tsx b/examples/react/release/boxes/src/components/Rectangle/index.tsx
index c4a34aee..5dee9586 100644
--- a/examples/react/release/boxes/src/components/Rectangle/index.tsx
+++ b/examples/react/release/boxes/src/components/Rectangle/index.tsx
@@ -3,7 +3,7 @@ import { Drag } from '../actionComponents/Drag';
import { Resize } from '../actionComponents/Resize';
import { RectangleContainer } from './components/RectangleContainer';
import { RectangleInner } from './components/RectangleInner';
-import { useAgile, useProxy } from '@agile-ts/react';
+import { useAgile } from '@agile-ts/react';
import core from '../../core';
import { SELECTED_ELEMENT } from '../../core/entities/ui/ui.controller';
import { ElementStyleInterface } from '../../core/entities/ui/ui.interfaces';
@@ -24,7 +24,7 @@ export const Rectangle: React.FC = (props) => {
{
componentId: 'Rectangle',
}
- );
+ ) as string | number;
if (element == null) return null;
diff --git a/examples/react/release/boxes/src/components/actionComponents/Resize/components/Handle.tsx b/examples/react/release/boxes/src/components/actionComponents/Resize/components/Handle.tsx
index dbe0a795..3b86fe96 100644
--- a/examples/react/release/boxes/src/components/actionComponents/Resize/components/Handle.tsx
+++ b/examples/react/release/boxes/src/components/actionComponents/Resize/components/Handle.tsx
@@ -10,13 +10,14 @@ type Position = {
bottom?: number | string;
};
-interface HandlePropsInterface {
+export interface HandlePropsInterface {
placement: ResizeHandle;
visible: boolean;
+ innerRef?: any;
}
export const Handle: React.FC = (props) => {
- const { placement, visible } = props;
+ const { placement, visible, innerRef } = props;
const size = 10;
const position: Position = {};
@@ -34,6 +35,7 @@ export const Handle: React.FC = (props) => {
return (
= (props) => {
lockAspectRatio,
} = props;
+ // https://github.com/react-grid-layout/react-resizable#custom-react-component
+ const WrappedHandle = React.forwardRef((props: HandlePropsInterface, ref) => (
+
+ ));
+
return (
= (props) => {
}}
resizeHandles={handlePlacements}
handle={(placement) => (
-
-
-
+
)}
lockAspectRatio={lockAspectRatio}>
{children}
diff --git a/examples/react/release/boxes/src/core/app.ts b/examples/react/release/boxes/src/core/app.ts
index 8cf510e6..89d7df2e 100644
--- a/examples/react/release/boxes/src/core/app.ts
+++ b/examples/react/release/boxes/src/core/app.ts
@@ -1,6 +1,6 @@
-import { Agile, Logger } from '@agile-ts/core';
-import reactIntegration from '@agile-ts/react';
+import { Agile, assignSharedAgileInstance } from '@agile-ts/core';
+import { assignSharedAgileLoggerConfig, Logger } from '@agile-ts/logger';
-export const App = new Agile({
- logConfig: { level: Logger.level.WARN },
-}).integrate(reactIntegration);
+export const App = new Agile();
+assignSharedAgileInstance(App);
+assignSharedAgileLoggerConfig({ level: Logger.level.WARN });
diff --git a/examples/react/release/boxes/yarn.lock b/examples/react/release/boxes/yarn.lock
index bd82c231..7fff41aa 100644
--- a/examples/react/release/boxes/yarn.lock
+++ b/examples/react/release/boxes/yarn.lock
@@ -2,42 +2,26 @@
# yarn lockfile v1
-"@agile-ts/api@file:.yalc/@agile-ts/api":
- version "0.0.18"
- dependencies:
- "@agile-ts/utils" "^0.0.4"
-
"@agile-ts/core@file:.yalc/@agile-ts/core":
- version "0.0.17"
+ version "0.1.0"
dependencies:
- "@agile-ts/logger" "^0.0.4"
- "@agile-ts/utils" "^0.0.4"
+ "@agile-ts/utils" "^0.0.5"
-"@agile-ts/event@file:.yalc/@agile-ts/event":
- version "0.0.7"
-
-"@agile-ts/logger@^0.0.4":
- version "0.0.4"
- resolved "https://registry.yarnpkg.com/@agile-ts/logger/-/logger-0.0.4.tgz#7f4d82ef8f03b13089af0878c360575c43f0962d"
- integrity sha512-qm0obAKqJMaPKM+c76gktRXyw3OL1v39AnhMZ0FBGwJqHWU+fLRkCzlQwjaROCr3F1XP01Lc/Ls3efF0WzyEPw==
+"@agile-ts/logger@file:.yalc/@agile-ts/logger":
+ version "0.0.5"
dependencies:
- "@agile-ts/utils" "^0.0.4"
+ "@agile-ts/utils" "^0.0.5"
-"@agile-ts/multieditor@file:.yalc/@agile-ts/multieditor":
- version "0.0.17"
-
-"@agile-ts/proxytree@^0.0.3", "@agile-ts/proxytree@file:.yalc/@agile-ts/proxytree":
- version "0.0.3"
+"@agile-ts/proxytree@file:.yalc/@agile-ts/proxytree":
+ version "0.0.4"
"@agile-ts/react@file:.yalc/@agile-ts/react":
- version "0.0.18"
- dependencies:
- "@agile-ts/proxytree" "^0.0.3"
+ version "0.1.0"
-"@agile-ts/utils@^0.0.4":
- version "0.0.4"
- resolved "https://registry.yarnpkg.com/@agile-ts/utils/-/utils-0.0.4.tgz#66e9536e561796489a37155da6b74ce2dc482697"
- integrity sha512-GiZyTYmCm4j2N57oDjeMuPpfQdgn9clb0Cxpfuwi2Bq5T/KPXlaROLsVGwHLjwwT+NX7xxr5qNJH8pZTnHnYRQ==
+"@agile-ts/utils@^0.0.5":
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/@agile-ts/utils/-/utils-0.0.5.tgz#23cc83e60eb6b15734247fac1d77f1fd629ffdb6"
+ integrity sha512-R86X9MjMty14eoQ4djulZSdHf9mIF9dPcj4g+SABqdA6AqbewS0/BQGNGR5p6gXhqc4+mT8rzkutywdPnMUNfA==
"@babel/code-frame@7.10.4":
version "7.10.4"
diff --git a/examples/vue/develop/my-project/babel.config.js b/examples/vue/develop/my-project/babel.config.js
index e9558405..078c0056 100644
--- a/examples/vue/develop/my-project/babel.config.js
+++ b/examples/vue/develop/my-project/babel.config.js
@@ -1,5 +1,3 @@
module.exports = {
- presets: [
- '@vue/cli-plugin-babel/preset'
- ]
-}
+ presets: ['@vue/cli-plugin-babel/preset'],
+};
diff --git a/examples/vue/develop/my-project/package.json b/examples/vue/develop/my-project/package.json
index 47b1f72d..ce01a6b2 100644
--- a/examples/vue/develop/my-project/package.json
+++ b/examples/vue/develop/my-project/package.json
@@ -6,10 +6,12 @@
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
- "install:agile": "yalc add @agile-ts/core @agile-ts/vue & yarn install"
+ "install:dev:agile": "yalc add @agile-ts/core @agile-ts/vue @agile-ts/logger & yarn install",
+ "install:prod:agile": "yarn add @agile-ts/core @agile-ts/vue @agile-ts/logger & yarn install"
},
"dependencies": {
"@agile-ts/core": "file:.yalc/@agile-ts/core",
+ "@agile-ts/logger": "file:.yalc/@agile-ts/logger",
"@agile-ts/vue": "file:.yalc/@agile-ts/vue",
"core-js": "^3.6.5",
"vue": "^2.6.11"
diff --git a/examples/vue/develop/my-project/src/core.js b/examples/vue/develop/my-project/src/core.js
index a8999d9f..0512ec9b 100644
--- a/examples/vue/develop/my-project/src/core.js
+++ b/examples/vue/develop/my-project/src/core.js
@@ -1,19 +1,22 @@
-import { Agile, globalBind, Logger } from '@agile-ts/core';
-import vueIntegration from '@agile-ts/vue';
+import { Agile, assignSharedAgileInstance, globalBind } from '@agile-ts/core';
+import { Logger, assignSharedAgileLoggerConfig } from '@agile-ts/logger';
+import '@agile-ts/vue';
+
+assignSharedAgileLoggerConfig({ level: Logger.level.DEBUG });
// Create Agile Instance
-export const App = new Agile({
- logConfig: { level: Logger.level.DEBUG },
-}).integrate(vueIntegration);
+export const App = new Agile({ localStorage: true });
+assignSharedAgileInstance(App);
// console.debug('hi'); // Doesn't work here idk why
// Create State
export const MY_STATE = App.createState('World', {
key: 'my-state',
-}).computeValue((v) => {
- return `Hello ${v}`;
-});
+})
+ .computeValue((v) => {
+ return `Hello ${v}`;
+ });
export const MY_COMPUTED = App.createComputed(
async () => {
diff --git a/examples/vue/develop/my-project/yarn.lock b/examples/vue/develop/my-project/yarn.lock
index 04c336c0..5997fe57 100644
--- a/examples/vue/develop/my-project/yarn.lock
+++ b/examples/vue/develop/my-project/yarn.lock
@@ -3,25 +3,22 @@
"@agile-ts/core@file:.yalc/@agile-ts/core":
- version "0.0.17"
+ version "0.1.0"
dependencies:
- "@agile-ts/logger" "^0.0.4"
- "@agile-ts/utils" "^0.0.4"
+ "@agile-ts/utils" "^0.0.5"
-"@agile-ts/logger@^0.0.4":
- version "0.0.4"
- resolved "https://registry.yarnpkg.com/@agile-ts/logger/-/logger-0.0.4.tgz#7f4d82ef8f03b13089af0878c360575c43f0962d"
- integrity sha512-qm0obAKqJMaPKM+c76gktRXyw3OL1v39AnhMZ0FBGwJqHWU+fLRkCzlQwjaROCr3F1XP01Lc/Ls3efF0WzyEPw==
+"@agile-ts/logger@file:.yalc/@agile-ts/logger":
+ version "0.0.5"
dependencies:
- "@agile-ts/utils" "^0.0.4"
+ "@agile-ts/utils" "^0.0.5"
-"@agile-ts/utils@^0.0.4":
- version "0.0.4"
- resolved "https://registry.yarnpkg.com/@agile-ts/utils/-/utils-0.0.4.tgz#66e9536e561796489a37155da6b74ce2dc482697"
- integrity sha512-GiZyTYmCm4j2N57oDjeMuPpfQdgn9clb0Cxpfuwi2Bq5T/KPXlaROLsVGwHLjwwT+NX7xxr5qNJH8pZTnHnYRQ==
+"@agile-ts/utils@^0.0.5":
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/@agile-ts/utils/-/utils-0.0.5.tgz#23cc83e60eb6b15734247fac1d77f1fd629ffdb6"
+ integrity sha512-R86X9MjMty14eoQ4djulZSdHf9mIF9dPcj4g+SABqdA6AqbewS0/BQGNGR5p6gXhqc4+mT8rzkutywdPnMUNfA==
"@agile-ts/vue@file:.yalc/@agile-ts/vue":
- version "0.0.5"
+ version "0.1.0"
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13":
version "7.12.13"
diff --git a/jest.base.config.js b/jest.base.config.js
index 33c9a7b1..9c42b9e0 100644
--- a/jest.base.config.js
+++ b/jest.base.config.js
@@ -15,5 +15,6 @@ module.exports = {
'ts-jest': {
tsconfig: '/packages/tsconfig.default.json',
},
+ __DEV__: true,
},
};
diff --git a/package.json b/package.json
index b4ed3355..2e7e2bce 100644
--- a/package.json
+++ b/package.json
@@ -1,4 +1,5 @@
{
+ "name": "agile",
"private": true,
"author": "BennoDev",
"license": "MIT",
@@ -73,6 +74,5 @@
},
"workspaces": [
"packages/*"
- ],
- "name": "agile"
+ ]
}
diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts
index 9c7880ec..e8de6610 100644
--- a/packages/api/src/index.ts
+++ b/packages/api/src/index.ts
@@ -1,4 +1,4 @@
-import { clone, copy, defineConfig, isValidObject } from '@agile-ts/utils';
+import { clone, copy, isValidObject } from '@agile-ts/utils';
export default class API {
public config: ApiConfig;
@@ -113,7 +113,7 @@ export default class API {
// Configure request Options
const config = copy(this.config);
- config.options = defineConfig(options, config.options || {});
+ config.options = { ...config.options, ...options };
config.options.method = method;
if (!config.options.headers) config.options.headers = {};
diff --git a/packages/core/.size-limit.js b/packages/core/.size-limit.js
index 1b8247f6..b3971b86 100644
--- a/packages/core/.size-limit.js
+++ b/packages/core/.size-limit.js
@@ -1,6 +1,6 @@
module.exports = [
{
path: 'dist/*',
- limit: '35 kB',
+ limit: '20 kB',
},
];
diff --git a/packages/core/package.json b/packages/core/package.json
index 1463bafd..4d10af6e 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -44,9 +44,16 @@
"@agile-ts/utils": "file:../utils"
},
"dependencies": {
- "@agile-ts/utils": "^0.0.5",
+ "@agile-ts/utils": "^0.0.5"
+ },
+ "peerDependencies": {
"@agile-ts/logger": "^0.0.5"
},
+ "peerDependenciesMeta": {
+ "@agile-ts/logger": {
+ "optional": true
+ }
+ },
"publishConfig": {
"access": "public"
},
diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts
index adf0419c..85c03544 100644
--- a/packages/core/src/agile.ts
+++ b/packages/core/src/agile.ts
@@ -13,20 +13,25 @@ import {
Storages,
CreateStorageConfigInterface,
RegisterConfigInterface,
- defineConfig,
- Logger,
- CreateLoggerConfigInterface,
StateConfigInterface,
flatMerge,
LogCodeManager,
DependableAgileInstancesType,
CreateComputedConfigInterface,
ComputeFunctionType,
+ createStorage,
+ createState,
+ createCollection,
+ createComputed,
+ IntegrationsConfigInterface,
} from './internal';
export class Agile {
public config: AgileConfigInterface;
+ // Key/Name identifier of Agile Instance
+ public key?: AgileKey;
+
// Queues and executes incoming Observer-based Jobs
public runtime: Runtime;
// Manages and simplifies the subscription to UI-Components
@@ -34,18 +39,8 @@ export class Agile {
// Handles the permanent persistence of Agile Classes
public storages: Storages;
- // Integrations (UI-Frameworks) that are integrated into AgileTs
+ // Integrations (UI-Frameworks) that are integrated into the Agile Instance
public integrations: Integrations;
- // External added Integrations that are to integrate into AgileTs when it is instantiated
- static initialIntegrations: Integration[] = [];
-
- // Static AgileTs Logger with the default config
- // (-> is overwritten by the last created Agile Instance)
- static logger = new Logger({
- prefix: 'Agile',
- active: true,
- level: Logger.level.WARN,
- });
// Identifier used to bind an Agile Instance globally
static globalKey = '__agile__';
@@ -77,39 +72,37 @@ export class Agile {
* @param config - Configuration object
*/
constructor(config: CreateAgileConfigInterface = {}) {
- config = defineConfig(config, {
- localStorage: true,
+ config = {
+ localStorage: false,
waitForMount: true,
- logConfig: {},
bindGlobal: false,
- });
- config.logConfig = defineConfig(config.logConfig, {
- prefix: 'Agile',
- active: true,
- level: Logger.level.WARN,
- canUseCustomStyles: true,
- allowedTags: ['runtime', 'storage', 'subscription', 'multieditor'],
- });
+ autoIntegrate: true,
+ bucket: true,
+ ...config,
+ };
this.config = {
waitForMount: config.waitForMount as any,
+ bucket: config.bucket as any,
};
- this.integrations = new Integrations(this);
+ this.key = config.key;
+ this.integrations = new Integrations(this, {
+ autoIntegrate: config.autoIntegrate,
+ });
this.runtime = new Runtime(this);
this.subController = new SubController(this);
this.storages = new Storages(this, {
localStorage: config.localStorage,
});
- // Assign customized Logger config to the static Logger
- Agile.logger = new Logger(config.logConfig);
-
- LogCodeManager.log('10:00:00', [], this, Agile.logger);
+ LogCodeManager.log('10:00:00', [], this);
// Create a global instance of the Agile Instance.
// Why? 'getAgileInstance()' returns the global Agile Instance
// if it couldn't find any Agile Instance in the specified Instance.
if (config.bindGlobal)
- if (!globalBind(Agile.globalKey, this)) LogCodeManager.log('10:02:00');
+ if (!globalBind(Agile.globalKey, this)) {
+ LogCodeManager.log('10:02:00');
+ }
}
/**
@@ -128,7 +121,7 @@ export class Agile {
* @param config - Configuration object
*/
public createStorage(config: CreateStorageConfigInterface): Storage {
- return new Storage(config);
+ return createStorage(config);
}
/**
@@ -150,7 +143,10 @@ export class Agile {
initialValue: ValueType,
config: StateConfigInterface = {}
): State {
- return new State(this, initialValue, config);
+ return createState(initialValue, {
+ ...config,
+ ...{ agileInstance: this },
+ });
}
/**
@@ -174,7 +170,7 @@ export class Agile {
public createCollection(
config?: CollectionConfig
): Collection {
- return new Collection(this, config);
+ return createCollection(config, this);
}
/**
@@ -232,12 +228,14 @@ export class Agile {
if (Array.isArray(configOrDeps)) {
_config = flatMerge(_config, {
computedDeps: configOrDeps,
+ agileInstance: this,
});
} else {
- if (configOrDeps) _config = configOrDeps;
+ if (configOrDeps)
+ _config = { ...configOrDeps, ...{ agileInstance: this } };
}
- return new Computed(this, computeFunction, _config);
+ return createComputed(computeFunction, _config);
}
/**
@@ -303,18 +301,10 @@ export class Agile {
}
}
-export interface CreateAgileConfigInterface {
- /**
- * Configures the logging behaviour of AgileTs.
- * @default {
- prefix: 'Agile',
- active: true,
- level: Logger.level.WARN,
- canUseCustomStyles: true,
- allowedTags: ['runtime', 'storage', 'subscription', 'multieditor'],
- }
- */
- logConfig?: CreateLoggerConfigInterface;
+export type AgileKey = string | number;
+
+export interface CreateAgileConfigInterface
+ extends IntegrationsConfigInterface {
/**
* Whether the Subscription Container shouldn't be ready
* until the UI-Component it represents has been mounted.
@@ -323,7 +313,7 @@ export interface CreateAgileConfigInterface {
waitForMount?: boolean;
/**
* Whether the Local Storage should be registered as a Agile Storage by default.
- * @default true
+ * @default false
*/
localStorage?: boolean;
/**
@@ -332,6 +322,20 @@ export interface CreateAgileConfigInterface {
* @default false
*/
bindGlobal?: boolean;
+ /**
+ * Key/Name identifier of the Agile Instance.
+ * @default undefined
+ */
+ key?: AgileKey;
+ /**
+ * Whether to put render events into "The bucket" of the browser,
+ * where all events are first put in wait for the UI thread
+ * to be done with whatever it's doing.
+ *
+ * [Learn more..](https://stackoverflow.com/questions/9083594/call-settimeout-without-delay)
+ * @default true
+ */
+ bucket?: boolean;
}
export interface AgileConfigInterface {
@@ -341,4 +345,13 @@ export interface AgileConfigInterface {
* @default true
*/
waitForMount: boolean;
+ /**
+ * Whether to put render events into "The bucket" of the browser,
+ * where all events are first put in wait for the UI thread
+ * to be done with whatever it's doing.
+ *
+ * [Learn more..](https://stackoverflow.com/questions/9083594/call-settimeout-without-delay)
+ * @default true
+ */
+ bucket: boolean;
}
diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts
index 86b049a3..0289620b 100644
--- a/packages/core/src/collection/collection.persistent.ts
+++ b/packages/core/src/collection/collection.persistent.ts
@@ -3,7 +3,6 @@ import {
CollectionKey,
CreatePersistentConfigInterface,
DefaultItem,
- defineConfig,
Group,
GroupKey,
ItemKey,
@@ -37,11 +36,12 @@ export class CollectionPersistent<
super(collection.agileInstance(), {
instantiate: false,
});
- config = defineConfig(config, {
+ config = {
instantiate: true,
storageKeys: [],
- defaultStorageKey: null,
- });
+ defaultStorageKey: null as any,
+ ...config,
+ };
this.collection = () => collection;
this.instantiatePersistent({
key: config.key,
@@ -145,7 +145,7 @@ export class CollectionPersistent<
// that it was loaded completely and exists at all
dummyItem?.persist(itemStorageKey, {
loadValue: false,
- defaultStorageKey: this.config.defaultStorageKey || undefined,
+ defaultStorageKey: this.config.defaultStorageKey as any,
storageKeys: this.storageKeys,
followCollectionPersistKeyPattern: false, // Because of the dynamic 'storageItemKey', the key is already formatted above
});
diff --git a/packages/core/src/collection/group/group.observer.ts b/packages/core/src/collection/group/group.observer.ts
index d298f653..c9da7654 100644
--- a/packages/core/src/collection/group/group.observer.ts
+++ b/packages/core/src/collection/group/group.observer.ts
@@ -3,7 +3,6 @@ import {
Group,
CreateObserverConfigInterface,
copy,
- defineConfig,
equal,
generateId,
RuntimeJob,
@@ -68,7 +67,7 @@ export class GroupObserver extends Observer {
config: GroupIngestConfigInterface = {}
): void {
const group = this.group();
- config = defineConfig(config, {
+ config = {
perform: true,
background: false,
sideEffects: {
@@ -77,7 +76,8 @@ export class GroupObserver extends Observer {
},
force: false,
maxTriesToUpdate: 3,
- });
+ ...config,
+ };
// Force overwriting the Group value if it is a placeholder.
// After assigning a value to the Group, the Group is supposed to be no placeholder anymore.
diff --git a/packages/core/src/collection/group/index.ts b/packages/core/src/collection/group/index.ts
index af25f737..03f1d413 100644
--- a/packages/core/src/collection/group/index.ts
+++ b/packages/core/src/collection/group/index.ts
@@ -3,7 +3,6 @@ import {
Collection,
DefaultItem,
ItemKey,
- defineConfig,
normalizeArray,
Item,
copy,
@@ -184,10 +183,11 @@ export class Group<
const notExistingItemKeysInCollection: Array = [];
const existingItemKeys: Array = [];
let newGroupValue = copy(this.nextStateValue);
- config = defineConfig(config, {
+ config = {
method: 'push',
overwrite: false,
- });
+ ...config,
+ };
// Add itemKeys to Group
_itemKeys.forEach((itemKey) => {
@@ -304,12 +304,13 @@ export class Group<
key = keyOrConfig as PersistentKey;
}
- _config = defineConfig(_config, {
+ _config = {
loadValue: true,
followCollectionPersistKeyPattern: true,
storageKeys: [],
- defaultStorageKey: null,
- });
+ defaultStorageKey: null as any,
+ ..._config,
+ };
// Create storageItemKey based on Collection key/name identifier
if (_config.followCollectionPersistKeyPattern) {
diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts
index 32272973..14d707cb 100644
--- a/packages/core/src/collection/index.ts
+++ b/packages/core/src/collection/index.ts
@@ -7,7 +7,6 @@ import {
SelectorKey,
StorageKey,
GroupConfigInterface,
- defineConfig,
isValidObject,
normalizeArray,
copy,
@@ -72,12 +71,13 @@ export class Collection<
constructor(agileInstance: Agile, config: CollectionConfig = {}) {
this.agileInstance = () => agileInstance;
let _config = typeof config === 'function' ? config(this) : config;
- _config = defineConfig(_config, {
+ _config = {
primaryKey: 'id',
groups: {},
selectors: {},
defaultGroupKey: 'default',
- });
+ ..._config,
+ };
this._key = _config.key;
this.config = {
defaultGroupKey: _config.defaultGroupKey as any,
@@ -304,12 +304,13 @@ export class Collection<
const _groupKeys = normalizeArray(groupKeys);
const defaultGroupKey = this.config.defaultGroupKey;
const primaryKey = this.config.primaryKey;
- config = defineConfig(config, {
+ config = {
method: 'push',
background: false,
patch: false,
select: false,
- });
+ ...config,
+ };
// Add default groupKey, since all Items are added to the default Group
if (!_groupKeys.includes(defaultGroupKey)) _groupKeys.push(defaultGroupKey);
@@ -373,10 +374,11 @@ export class Collection<
): Item | undefined {
const item = this.getItem(itemKey, { notExisting: true });
const primaryKey = this.config.primaryKey;
- config = defineConfig(config, {
+ config = {
patch: true,
background: false,
- });
+ ...config,
+ };
// Check if the given conditions are suitable for a update action
if (item == null) {
@@ -405,9 +407,10 @@ export class Collection<
let patchConfig: { addNewProperties?: boolean } =
typeof config.patch === 'object' ? config.patch : {};
- patchConfig = defineConfig(patchConfig, {
+ patchConfig = {
addNewProperties: true,
- });
+ ...patchConfig,
+ };
item.patch(changes as any, {
background: config.background,
@@ -495,9 +498,10 @@ export class Collection<
groupKey: GroupKey | undefined | null,
config: HasConfigInterface = {}
): Group | undefined {
- config = defineConfig(config, {
+ config = {
notExisting: false,
- });
+ ...config,
+ };
// Retrieve Group
const group = groupKey ? this.groups[groupKey] : undefined;
@@ -668,9 +672,10 @@ export class Collection<
selectorKey: SelectorKey | undefined | null,
config: HasConfigInterface = {}
): Selector | undefined {
- config = defineConfig(config, {
+ config = {
notExisting: false,
- });
+ ...config,
+ };
// Get Selector
const selector = selectorKey ? this.selectors[selectorKey] : undefined;
@@ -775,9 +780,10 @@ export class Collection<
itemKey: ItemKey | undefined | null,
config: HasConfigInterface = {}
): Item | undefined {
- config = defineConfig(config, {
+ config = {
notExisting: false,
- });
+ ...config,
+ };
// Get Item
const item = itemKey != null ? this.data[itemKey] : undefined;
@@ -879,9 +885,10 @@ export class Collection<
* @param config - Configuration object
*/
public getAllItems(config: HasConfigInterface = {}): Array- > {
- config = defineConfig(config, {
+ config = {
notExisting: false,
- });
+ ...config,
+ };
const defaultGroup = this.getDefaultGroup();
let items: Array
- > = [];
@@ -962,11 +969,12 @@ export class Collection<
key = keyOrConfig as StorageKey;
}
- _config = defineConfig(_config, {
+ _config = {
loadValue: true,
storageKeys: [],
- defaultStorageKey: null,
- });
+ defaultStorageKey: null as any,
+ ..._config,
+ };
// Check if Collection is already persisted
if (this.persistent != null && this.isPersisted) return this;
@@ -1105,9 +1113,10 @@ export class Collection<
config: UpdateItemKeyConfigInterface = {}
): boolean {
const item = this.getItem(oldItemKey, { notExisting: true });
- config = defineConfig(config, {
+ config = {
background: false,
- });
+ ...config,
+ };
if (item == null || oldItemKey === newItemKey) return false;
@@ -1277,10 +1286,11 @@ export class Collection<
itemKeys: ItemKey | Array
,
config: RemoveItemsConfigInterface = {}
): this {
- config = defineConfig(config, {
+ config = {
notExisting: false,
removeSelector: false,
- });
+ ...config,
+ };
const _itemKeys = normalizeArray(itemKeys);
_itemKeys.forEach((itemKey) => {
@@ -1339,10 +1349,11 @@ export class Collection<
data: DataType,
config: AssignDataConfigInterface = {}
): boolean {
- config = defineConfig(config, {
+ config = {
patch: false,
background: false,
- });
+ ...config,
+ };
const _data = copy(data); // Copy data object to get rid of reference
const primaryKey = this.config.primaryKey;
@@ -1397,10 +1408,11 @@ export class Collection<
item: Item,
config: AssignItemConfigInterface = {}
): boolean {
- config = defineConfig(config, {
+ config = {
overwrite: false,
background: false,
- });
+ ...config,
+ };
const primaryKey = this.config.primaryKey;
let itemKey = item._value[primaryKey];
let increaseCollectionSize = true;
@@ -1457,13 +1469,14 @@ export class Collection<
itemKey: ItemKey,
config: RebuildGroupsThatIncludeItemKeyConfigInterface = {}
): void {
- config = defineConfig(config, {
+ config = {
background: false,
sideEffects: {
enabled: true,
exclude: [],
},
- });
+ ...config,
+ };
// Rebuild Groups that include itemKey
for (const groupKey in this.groups) {
diff --git a/packages/core/src/collection/item.ts b/packages/core/src/collection/item.ts
index 5375b143..7797e042 100644
--- a/packages/core/src/collection/item.ts
+++ b/packages/core/src/collection/item.ts
@@ -3,7 +3,6 @@ import {
Collection,
StateKey,
StateRuntimeJobConfigInterface,
- defineConfig,
SelectorKey,
PersistentKey,
isValidObject,
@@ -63,7 +62,7 @@ export class Item extends State<
config: StateRuntimeJobConfigInterface = {}
): this {
super.setKey(value);
- config = defineConfig(config, {
+ config = {
sideEffects: {
enabled: true,
exclude: [],
@@ -72,7 +71,8 @@ export class Item extends State<
force: false,
storage: true,
overwrite: false,
- });
+ ...config,
+ };
if (value == null) return this;
// Update 'rebuildGroupsThatIncludeItemKey' side effect to the new itemKey
@@ -129,12 +129,13 @@ export class Item extends State<
key = keyOrConfig as PersistentKey;
}
- _config = defineConfig(_config, {
+ _config = {
loadValue: true,
followCollectionPersistKeyPattern: true,
storageKeys: [],
- defaultStorageKey: null,
- });
+ defaultStorageKey: null as any,
+ ..._config,
+ };
// Create storageItemKey based on Collection key/name identifier
if (_config.followCollectionPersistKeyPattern) {
diff --git a/packages/core/src/collection/selector.ts b/packages/core/src/collection/selector.ts
index 5e484e9d..7937dd66 100644
--- a/packages/core/src/collection/selector.ts
+++ b/packages/core/src/collection/selector.ts
@@ -1,7 +1,6 @@
import {
Collection,
DefaultItem,
- defineConfig,
Item,
ItemKey,
State,
@@ -41,9 +40,10 @@ export class Selector<
itemKey: ItemKey | null,
config: SelectorConfigInterface = {}
) {
- config = defineConfig(config, {
+ config = {
isPlaceholder: false,
- });
+ ...config,
+ };
super(collection.agileInstance(), null, config);
this.collection = () => collection;
this._item = null;
@@ -116,7 +116,7 @@ export class Selector<
itemKey: ItemKey | null,
config: StateRuntimeJobConfigInterface = {}
): this {
- config = defineConfig(config, {
+ config = {
background: false,
sideEffects: {
enabled: true,
@@ -125,7 +125,8 @@ export class Selector<
force: false,
overwrite: this._item?.isPlaceholder ?? false,
storage: true,
- });
+ ...config,
+ };
// Don't select Item if Collection is not correctly instantiated yet
// (because only after a successful instantiation the Collection
@@ -227,10 +228,7 @@ export class Selector<
item.selectedBy.delete(this._key as any);
item.removeSideEffect(Selector.rebuildSelectorSideEffectKey);
item.removeSideEffect(Selector.rebuildItemSideEffectKey);
- if (
- item.isPlaceholder &&
- this._itemKey != null
- )
+ if (item.isPlaceholder && this._itemKey != null)
delete this.collection().data[this._itemKey];
}
diff --git a/packages/core/src/computed/index.ts b/packages/core/src/computed/index.ts
index b166deb1..efc19b5e 100644
--- a/packages/core/src/computed/index.ts
+++ b/packages/core/src/computed/index.ts
@@ -1,12 +1,10 @@
import {
State,
Agile,
- defineConfig,
Observer,
StateConfigInterface,
ComputedTracker,
Collection,
- extractObservers,
StateIngestConfigInterface,
removeProperties,
LogCodeManager,
@@ -52,10 +50,11 @@ export class Computed extends State<
key: config.key,
dependents: config.dependents,
});
- config = defineConfig(config, {
+ config = {
computedDeps: [],
autodetect: !isAsyncFunction(computeFunction),
- });
+ ...config,
+ };
this.agileInstance = () => agileInstance;
this.computeFunction = computeFunction;
this.config = {
@@ -87,9 +86,10 @@ export class Computed extends State<
* @param config - Configuration object
*/
public recompute(config: RecomputeConfigInterface = {}): this {
- config = defineConfig(config, {
+ config = {
autodetect: false,
- });
+ ...config,
+ };
this.compute({ autodetect: config.autodetect }).then((result) => {
this.observers['value'].ingestValue(
result,
@@ -120,9 +120,10 @@ export class Computed extends State<
deps: Array = [],
config: RecomputeConfigInterface = {}
): this {
- config = defineConfig(config, {
+ config = {
autodetect: this.config.autodetect,
- });
+ ...config,
+ };
// Make this Observer no longer depend on the old dep Observers
this.deps.forEach((observer) => {
@@ -160,9 +161,10 @@ export class Computed extends State<
public async compute(
config: ComputeConfigInterface = {}
): Promise {
- config = defineConfig(config, {
+ config = {
autodetect: this.config.autodetect,
- });
+ ...config,
+ };
// Start auto tracking of Observers on which the computeFunction might depend
if (config.autodetect) ComputedTracker.track();
diff --git a/packages/core/src/integrations/index.ts b/packages/core/src/integrations/index.ts
index 038166db..a7421b18 100644
--- a/packages/core/src/integrations/index.ts
+++ b/packages/core/src/integrations/index.ts
@@ -1,5 +1,9 @@
import { Agile, Integration, LogCodeManager } from '../internal';
+const onRegisterInitialIntegrationCallbacks: ((
+ integration: Integration
+) => void)[] = [];
+
export class Integrations {
// Agile Instance the Integrations belongs to
public agileInstance: () => Agile;
@@ -7,6 +11,42 @@ export class Integrations {
// Registered Integrations
public integrations: Set = new Set();
+ // External added Integrations
+ // that are to integrate into not yet existing Agile Instances
+ static initialIntegrations: Integration[] = [];
+
+ /**
+ * Registers the specified Integration in each existing or not-yet created Agile Instance.
+ *
+ * @public
+ * @param integration - Integration to be registered in each Agile Instance.
+ */
+ static addInitialIntegration(integration: Integration): void {
+ if (integration instanceof Integration) {
+ // Executed external registered Integration callbacks
+ onRegisterInitialIntegrationCallbacks.forEach((callback) =>
+ callback(integration)
+ );
+
+ Integrations.initialIntegrations.push(integration);
+ }
+ }
+
+ /**
+ * Fires on each external added Integration.
+ *
+ * @public
+ * @param callback - Callback to be fired when an Integration was externally added.
+ */
+ static onRegisterInitialIntegration(
+ callback: (integration: Integration) => void
+ ): void {
+ onRegisterInitialIntegrationCallbacks.push(callback);
+ Integrations.initialIntegrations.forEach((integration) => {
+ callback(integration);
+ });
+ }
+
/**
* The Integrations Class manages all Integrations for an Agile Instance
* and provides an interface to easily update
@@ -14,14 +54,21 @@ export class Integrations {
*
* @internal
* @param agileInstance - Instance of Agile the Integrations belongs to.
+ * @param config - Configuration object
*/
- constructor(agileInstance: Agile) {
+ constructor(agileInstance: Agile, config: IntegrationsConfigInterface = {}) {
+ config = {
+ autoIntegrate: true,
+ ...config,
+ };
this.agileInstance = () => agileInstance;
- // Integrate initial Integrations which were statically set externally
- Agile.initialIntegrations.forEach((integration) =>
- this.integrate(integration)
- );
+ if (config.autoIntegrate) {
+ // Setup listener to be notified when an external registered Integration was added
+ Integrations.onRegisterInitialIntegration((integration) => {
+ this.integrate(integration);
+ });
+ }
}
/**
@@ -33,8 +80,12 @@ export class Integrations {
*/
public async integrate(integration: Integration): Promise {
// Check if Integration is valid
- if (!integration._key) {
- LogCodeManager.log('18:03:00', [integration._key], integration);
+ if (integration._key == null) {
+ LogCodeManager.log(
+ '18:03:00',
+ [integration._key, this.agileInstance().key],
+ integration
+ );
return false;
}
@@ -47,7 +98,11 @@ export class Integrations {
this.integrations.add(integration);
integration.integrated = true;
- LogCodeManager.log('18:00:00', [integration._key], integration);
+ LogCodeManager.log(
+ '18:00:00',
+ [integration._key, this.agileInstance().key],
+ integration
+ );
return true;
}
@@ -84,3 +139,14 @@ export class Integrations {
return this.integrations.size > 0;
}
}
+
+export interface IntegrationsConfigInterface {
+ /**
+ * Whether external added Integrations
+ * are to integrate automatically into the Integrations Class.
+ * For example, when the package '@agile-ts/react' was installed,
+ * whether to automatically integrate the 'reactIntegration'.
+ * @default true
+ */
+ autoIntegrate?: boolean;
+}
diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts
index 6e403e5a..b9356568 100644
--- a/packages/core/src/internal.ts
+++ b/packages/core/src/internal.ts
@@ -4,17 +4,20 @@
// !! All internal Agile modules must be imported from here!!
-// Logger
-export * from '@agile-ts/logger';
-export * from './logCodeManager';
-
// Utils
export * from './utils';
export * from '@agile-ts/utils';
+// Logger
+export * from './logCodeManager';
+
// Agile
export * from './agile';
+// Integrations
+export * from './integrations';
+export * from './integrations/integration';
+
// Runtime
export * from './runtime';
export * from './runtime/observer';
@@ -47,6 +50,5 @@ export * from './collection/item';
export * from './collection/selector';
export * from './collection/collection.persistent';
-// Integrations
-export * from './integrations';
-export * from './integrations/integration';
+// Shared
+export * from './shared';
diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts
index 36a1eba0..f7a6788b 100644
--- a/packages/core/src/logCodeManager.ts
+++ b/packages/core/src/logCodeManager.ts
@@ -1,4 +1,10 @@
-import { Agile } from './agile';
+// TODO https://stackoverflow.com/questions/68148235/require-module-inside-a-function-doesnt-work
+export let loggerPackage: any = null;
+try {
+ loggerPackage = require('@agile-ts/logger');
+} catch (e) {
+ // empty catch block
+}
// The Log Code Manager keeps track
// and manages all important Logs of AgileTs.
@@ -33,7 +39,7 @@ const logCodeMessages = {
// Storages
'11:02:00':
- "The 'Local Storage' is not available in your current environment." +
+ "The 'Local Storage' is not available in your current environment. " +
"To use the '.persist()' functionality, please provide a custom Storage!",
'11:02:01':
'The first allocated Storage for AgileTs must be set as the default Storage!',
@@ -59,6 +65,7 @@ const logCodeMessages = {
"The Storage with the key/name '${1}' doesn't exists!`",
// Storage
+ '13:00:00': "Registered new Storage '${0}'.",
'13:01:00': "GET value at key '${1}' from Storage '${0}'.",
'13:01:01': "SET value at key '${1}' in Storage '${0}'.",
'13:01:02': "REMOVE value at key '${1}' from Storage '${0}'.",
@@ -96,10 +103,10 @@ const logCodeMessages = {
"The 'perform()' method isn't set in Observer but need to be set! Observer is no stand alone class.",
// Integrations
- '18:00:00': "Integrated '${0}' into AgileTs",
+ '18:00:00': "Integrated '${0}' into AgileTs '${1}'",
'18:02:00':
"Can't call the 'update()' method on a not ready Integration '${0}'!",
- '18:03:00': "Failed to integrate Framework '${0}'!",
+ '18:03:00': "Failed to integrate Framework '${0}' into AgileTs '${1}'!",
// Computed
'19:03:00':
@@ -180,9 +187,9 @@ function getLog>(
): string {
let result = logCodeMessages[logCode] ?? `'${logCode}' is a unknown logCode!`;
- for (const i in replacers) {
- // https://stackoverflow.com/questions/41438656/why-do-i-get-cannot-read-property-tostring-of-undefined
- result = result.split('${' + i + '}').join(replacers[i] + '');
+ // Replace '${x}' with the specified replacer instances
+ for (let i = 0; i < replacers.length; i++) {
+ result = result.replace('${' + i + '}', replacers[i]);
}
return result;
@@ -190,7 +197,7 @@ function getLog>(
/**
* Logs the log message according to the specified log code
- * with the Agile Logger.
+ * with the Agile Logger if installed or the normal console.
*
* @internal
* @param logCode - Log code of the message to be returned.
@@ -203,9 +210,52 @@ function log>(
replacers: any[] = [],
...data: any[]
): void {
- const codes = logCode.split(':');
- if (codes.length === 3)
- Agile.logger[logCodeTypes[codes[1]]](getLog(logCode, replacers), ...data);
+ const logger = LogCodeManager.getLogger();
+ if (!logger?.isActive) return;
+ const logType = logCodeTypes[logCode.substr(3, 2)];
+ if (typeof logType !== 'string') return;
+
+ // Handle logging without Logger
+ if (logger == null) {
+ if (logType === 'error' || logType === 'warn')
+ console[logType](getLog(logCode, replacers));
+ return;
+ }
+
+ // Handle logging with Logger
+ logger[logType](getLog(logCode, replacers), ...data);
+}
+
+/**
+ * Logs the log message according to the specified log code
+ * with the Agile Logger if installed and the provided tags are active.
+ *
+ * @internal
+ * @param tags - Tags to be active to log the logCode.
+ * @param logCode - Log code of the message to be returned.
+ * @param replacers - Instances that replace these '${x}' placeholders based on the index
+ * For example: 'replacers[0]' replaces '${0}', 'replacers[1]' replaces '${1}', ..
+ * @param data - Data to be attached to the end of the log message.
+ */
+function logIfTags>(
+ tags: string[],
+ logCode: T,
+ replacers: any[] = [],
+ ...data: any[]
+): void {
+ const logger = LogCodeManager.getLogger();
+ if (!logger?.isActive) return;
+ const logType = logCodeTypes[logCode.substr(3, 2)];
+ if (typeof logType !== 'string') return;
+
+ // Handle logging without Logger
+ if (logger == null) {
+ // Log nothing since if a log has a tag it is probably not so important
+ return;
+ }
+
+ // Handle logging with Logger
+ logger.if.tag(tags)[logType](getLog(logCode, replacers), ...data);
}
/**
@@ -219,6 +269,12 @@ export const LogCodeManager = {
log,
logCodeLogTypes: logCodeTypes,
logCodeMessages: logCodeMessages,
+ // Not doing 'logger: loggerPackage?.sharedAgileLogger'
+ // because only by calling a function (now 'getLogger()') the 'sharedLogger' is refetched
+ getLogger: () => {
+ return loggerPackage?.sharedAgileLogger ?? null;
+ },
+ logIfTags,
};
export type LogCodesArrayType = {
diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts
index f5578699..f5222a3b 100644
--- a/packages/core/src/runtime/index.ts
+++ b/packages/core/src/runtime/index.ts
@@ -4,7 +4,6 @@ import {
RuntimeJob,
CallbackSubscriptionContainer,
ComponentSubscriptionContainer,
- defineConfig,
notEqual,
LogCodeManager,
} from '../internal';
@@ -28,6 +27,9 @@ export class Runtime {
// Whether the `jobQueue` is currently being actively processed
public isPerformingJobs = false;
+ // Current 'bucket' timeout 'scheduled' for updating the Subscribers (UI-Components)
+ public bucketTimeout: NodeJS.Timeout | null = null;
+
/**
* The Runtime queues and executes incoming Observer-based Jobs
* to prevent [race conditions](https://en.wikipedia.org/wiki/Race_condition#:~:text=A%20race%20condition%20or%20race,the%20possible%20behaviors%20is%20undesirable.)
@@ -64,16 +66,15 @@ export class Runtime {
* @param config - Configuration object
*/
public ingest(job: RuntimeJob, config: IngestConfigInterface = {}): void {
- config = defineConfig(config, {
+ config = {
perform: !this.isPerformingJobs,
- });
+ ...config,
+ };
// Add specified Job to the queue
this.jobQueue.push(job);
- Agile.logger.if
- .tag(['runtime'])
- .info(LogCodeManager.getLog('16:01:00', [job._key]), job);
+ LogCodeManager.logIfTags(['runtime'], '16:01:00', [job._key], job);
// Run first Job from the queue
if (config.perform) {
@@ -110,9 +111,7 @@ export class Runtime {
if (job.rerender) this.jobsToRerender.push(job);
this.currentJob = null;
- Agile.logger.if
- .tag(['runtime'])
- .info(LogCodeManager.getLog('16:01:01', [job._key]), job);
+ LogCodeManager.logIfTags(['runtime'], '16:01:01', [job._key], job);
// Perform Jobs as long as Jobs are left in the queue.
// If no Job is left start updating (re-rendering) Subscription Container (UI-Components)
@@ -123,10 +122,18 @@ export class Runtime {
} else {
this.isPerformingJobs = false;
if (this.jobsToRerender.length > 0) {
- // https://stackoverflow.com/questions/9083594/call-settimeout-without-delay
- setTimeout(() => {
- this.updateSubscribers();
- });
+ if (this.agileInstance().config.bucket) {
+ // Check if an bucket timeout is active, if so don't call a new one,
+ // since if the active timeout is called it will also proceed Jobs
+ // that were not added before the call
+ if (this.bucketTimeout == null) {
+ // https://stackoverflow.com/questions/9083594/call-settimeout-without-delay
+ this.bucketTimeout = setTimeout(() => {
+ this.bucketTimeout = null;
+ this.updateSubscribers();
+ });
+ }
+ } else this.updateSubscribers();
}
}
}
@@ -174,9 +181,13 @@ export class Runtime {
public extractToUpdateSubscriptionContainer(
jobs: Array
): Array {
+ // https://medium.com/@bretcameron/how-to-make-your-code-faster-using-javascript-sets-b432457a4a77
const subscriptionsToUpdate = new Set();
- jobs.forEach((job) => {
+ // Using for loop for performance optimization
+ // https://stackoverflow.com/questions/43821759/why-array-foreach-is-slower-than-for-loop-in-javascript
+ for (let i = 0; i < jobs.length; i++) {
+ const job = jobs[i];
job.subscriptionContainersToUpdate.forEach((subscriptionContainer) => {
let updateSubscriptionContainer = true;
@@ -228,7 +239,7 @@ export class Runtime {
job.subscriptionContainersToUpdate.delete(subscriptionContainer);
});
- });
+ }
return Array.from(subscriptionsToUpdate);
}
@@ -245,7 +256,11 @@ export class Runtime {
public updateSubscriptionContainer(
subscriptionsToUpdate: Array
): void {
- subscriptionsToUpdate.forEach((subscriptionContainer) => {
+ // Using for loop for performance optimization
+ // https://stackoverflow.com/questions/43821759/why-array-foreach-is-slower-than-for-loop-in-javascript
+ for (let i = 0; i < subscriptionsToUpdate.length; i++) {
+ const subscriptionContainer = subscriptionsToUpdate[i];
+
// Call 'callback function' if Callback based Subscription
if (subscriptionContainer instanceof CallbackSubscriptionContainer)
subscriptionContainer.callback();
@@ -258,11 +273,14 @@ export class Runtime {
);
subscriptionContainer.updatedSubscribers.clear();
- });
+ }
- Agile.logger.if
- .tag(['runtime'])
- .info(LogCodeManager.getLog('16:01:02'), subscriptionsToUpdate);
+ LogCodeManager.logIfTags(
+ ['runtime'],
+ '16:01:02',
+ [],
+ subscriptionsToUpdate
+ );
}
/**
diff --git a/packages/core/src/runtime/observer.ts b/packages/core/src/runtime/observer.ts
index bce9e5fe..4897a059 100644
--- a/packages/core/src/runtime/observer.ts
+++ b/packages/core/src/runtime/observer.ts
@@ -3,7 +3,6 @@ import {
StateKey,
RuntimeJob,
SubscriptionContainer,
- defineConfig,
IngestConfigInterface,
CreateRuntimeJobConfigInterface,
LogCodeManager,
@@ -60,10 +59,11 @@ export class Observer {
agileInstance: Agile,
config: CreateObserverConfigInterface = {}
) {
- config = defineConfig(config, {
+ config = {
dependents: [],
subs: [],
- });
+ ...config,
+ };
this.agileInstance = () => agileInstance;
this._key = config.key;
this.value = config.value;
@@ -104,7 +104,7 @@ export class Observer {
* @param config - Configuration object
*/
public ingest(config: ObserverIngestConfigInterface = {}): void {
- config = defineConfig(config, {
+ config = {
perform: true,
background: false,
sideEffects: {
@@ -112,7 +112,8 @@ export class Observer {
exclude: [],
},
force: false,
- });
+ ...config,
+ };
// Create Runtime-Job
const job = new RuntimeJob(this, {
diff --git a/packages/core/src/runtime/runtime.job.ts b/packages/core/src/runtime/runtime.job.ts
index d3eea3f2..84fbb5f1 100644
--- a/packages/core/src/runtime/runtime.job.ts
+++ b/packages/core/src/runtime/runtime.job.ts
@@ -1,4 +1,4 @@
-import { Observer, defineConfig, SubscriptionContainer } from '../internal';
+import { Observer, SubscriptionContainer } from '../internal';
export class RuntimeJob {
public config: RuntimeJobConfigInterface;
@@ -32,7 +32,7 @@ export class RuntimeJob {
observer: ObserverType,
config: CreateRuntimeJobConfigInterface = {}
) {
- config = defineConfig(config, {
+ config = {
background: false,
sideEffects: {
enabled: true,
@@ -40,7 +40,8 @@ export class RuntimeJob {
},
force: false,
maxTriesToUpdate: 3,
- });
+ ...config,
+ };
this.config = {
background: config.background,
force: config.force,
diff --git a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts
index 4c0dcb17..34aca13e 100644
--- a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts
+++ b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts
@@ -1,9 +1,4 @@
-import {
- defineConfig,
- generateId,
- isValidObject,
- Observer,
-} from '../../../internal';
+import { generateId, isValidObject, Observer } from '../../../internal';
export class SubscriptionContainer {
/**
@@ -110,11 +105,10 @@ export class SubscriptionContainer {
subs: Array | { [key: string]: Observer },
config: SubscriptionContainerConfigInterface = {}
) {
- config = defineConfig(config, {
- proxyWeakMap: new WeakMap(),
- selectorWeakMap: new WeakMap(),
+ config = {
key: generateId(),
- });
+ ...config,
+ };
this.subscribers = new Set();
this.key = config.key;
diff --git a/packages/core/src/runtime/subscription/sub.controller.ts b/packages/core/src/runtime/subscription/sub.controller.ts
index d738d5b5..3f6d22ea 100644
--- a/packages/core/src/runtime/subscription/sub.controller.ts
+++ b/packages/core/src/runtime/subscription/sub.controller.ts
@@ -6,7 +6,6 @@ import {
CallbackSubscriptionContainer,
isFunction,
SubscriptionContainerConfigInterface,
- defineConfig,
removeProperties,
LogCodeManager,
} from '../../internal';
@@ -113,9 +112,10 @@ export class SubController {
subscriptionContainer: SubscriptionContainer;
props: { [key: string]: Observer['value'] };
} {
- config = defineConfig(config, {
+ config = {
waitForMount: this.agileInstance().config.waitForMount,
- });
+ ...config,
+ };
// Create Subscription Container based on specified 'integrationInstance'
const subscriptionContainer = isFunction(integrationInstance)
@@ -165,9 +165,12 @@ export class SubController {
if (subscriptionInstance instanceof CallbackSubscriptionContainer) {
unsub(subscriptionInstance);
this.callbackSubs.delete(subscriptionInstance);
- Agile.logger.if
- .tag(['runtime', 'subscription'])
- .info(LogCodeManager.getLog('15:01:00'), subscriptionInstance);
+ LogCodeManager.logIfTags(
+ ['subscription'],
+ '15:01:00',
+ [],
+ subscriptionInstance
+ );
return;
}
@@ -175,9 +178,12 @@ export class SubController {
if (subscriptionInstance instanceof ComponentSubscriptionContainer) {
unsub(subscriptionInstance);
this.componentSubs.delete(subscriptionInstance);
- Agile.logger.if
- .tag(['runtime', 'subscription'])
- .info(LogCodeManager.getLog('15:01:01'), subscriptionInstance);
+ LogCodeManager.logIfTags(
+ ['subscription'],
+ '15:01:01',
+ [],
+ subscriptionInstance
+ );
return;
}
@@ -191,9 +197,12 @@ export class SubController {
(subContainer) => {
unsub(subContainer as ComponentSubscriptionContainer);
this.componentSubs.delete(subContainer);
- Agile.logger.if
- .tag(['runtime', 'subscription'])
- .info(LogCodeManager.getLog('15:01:01'), subscriptionInstance);
+ LogCodeManager.logIfTags(
+ ['subscription'],
+ '15:01:01',
+ [],
+ subscriptionInstance
+ );
}
);
return;
@@ -241,9 +250,12 @@ export class SubController {
componentSubscriptionContainer,
];
- Agile.logger.if
- .tag(['runtime', 'subscription'])
- .info(LogCodeManager.getLog('15:01:02'), componentSubscriptionContainer);
+ LogCodeManager.logIfTags(
+ ['subscription'],
+ '15:01:02',
+ [],
+ componentSubscriptionContainer
+ );
return componentSubscriptionContainer;
}
@@ -270,9 +282,12 @@ export class SubController {
this.callbackSubs.add(callbackSubscriptionContainer);
callbackSubscriptionContainer.ready = true;
- Agile.logger.if
- .tag(['runtime', 'subscription'])
- .info(LogCodeManager.getLog('15:01:03'), callbackSubscriptionContainer);
+ LogCodeManager.logIfTags(
+ ['subscription'],
+ '15:01:03',
+ [],
+ callbackSubscriptionContainer
+ );
return callbackSubscriptionContainer;
}
diff --git a/packages/core/src/shared.ts b/packages/core/src/shared.ts
new file mode 100644
index 00000000..79d792d3
--- /dev/null
+++ b/packages/core/src/shared.ts
@@ -0,0 +1,199 @@
+import {
+ Agile,
+ Collection,
+ CollectionConfig,
+ Computed,
+ ComputeFunctionType,
+ CreateComputedConfigInterface,
+ CreateStorageConfigInterface,
+ DefaultItem,
+ DependableAgileInstancesType,
+ flatMerge,
+ removeProperties,
+ runsOnServer,
+ State,
+ StateConfigInterface,
+ Storage,
+} from './internal';
+
+/**
+ * Shared Agile Instance that is used when no Agile Instance was specified.
+ */
+let sharedAgileInstance = new Agile({
+ key: 'shared',
+ localStorage: !runsOnServer(),
+});
+export { sharedAgileInstance as shared };
+
+/**
+ * Assigns the specified Agile Instance as the shared Agile Instance.
+ *
+ * @param agileInstance - Agile Instance to become the new shared Agile Instance.
+ */
+// https://stackoverflow.com/questions/32558514/javascript-es6-export-const-vs-export-let
+export function assignSharedAgileInstance(agileInstance: Agile): void {
+ sharedAgileInstance = agileInstance;
+}
+
+/**
+ * Returns a newly created Storage.
+ *
+ * A Storage Class serves as an interface to external storages,
+ * such as the [Async Storage](https://github.com/react-native-async-storage/async-storage) or
+ * [Local Storage](https://www.w3schools.com/html/html5_webstorage.asp).
+ *
+ * It creates the foundation to easily [`persist()`](https://agile-ts.org/docs/core/state/methods#persist) [Agile Sub Instances](https://agile-ts.org/docs/introduction/#agile-sub-instance)
+ * (like States or Collections) in nearly any external storage.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createstorage)
+ *
+ * @public
+ * @param config - Configuration object
+ */
+export function createStorage(config: CreateStorageConfigInterface): Storage {
+ return new Storage(config);
+}
+
+/**
+ * Returns a newly created State.
+ *
+ * A State manages a piece of Information
+ * that we need to remember globally at a later point in time.
+ * While providing a toolkit to use and mutate this piece of Information.
+ *
+ * You can create as many global States as you need.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createstate)
+ *
+ * @public
+ * @param initialValue - Initial value of the State.
+ * @param config - Configuration object
+ */
+export function createState(
+ initialValue: ValueType,
+ config: CreateStateConfigInterfaceWithAgile = {}
+): State {
+ config = {
+ agileInstance: sharedAgileInstance,
+ ...config,
+ };
+ return new State(
+ config.agileInstance as any,
+ initialValue,
+ removeProperties(config, ['agileInstance'])
+ );
+}
+
+/**
+ * Returns a newly created Collection.
+ *
+ * A Collection manages a reactive set of Information
+ * that we need to remember globally at a later point in time.
+ * While providing a toolkit to use and mutate this set of Information.
+ *
+ * It is designed for arrays of data objects following the same pattern.
+ *
+ * Each of these data object must have a unique `primaryKey` to be correctly identified later.
+ *
+ * You can create as many global Collections as you need.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createcollection)
+ *
+ * @public
+ * @param config - Configuration object
+ * @param agileInstance - Instance of Agile the Collection belongs to.
+ */
+export function createCollection(
+ config?: CollectionConfig,
+ agileInstance: Agile = sharedAgileInstance
+): Collection {
+ return new Collection(agileInstance, config);
+}
+
+/**
+ * Returns a newly created Computed.
+ *
+ * A Computed is an extension of the State Class
+ * that computes its value based on a specified compute function.
+ *
+ * The computed value will be cached to avoid unnecessary recomputes
+ * and is only recomputed when one of its direct dependencies changes.
+ *
+ * Direct dependencies can be States and Collections.
+ * So when, for example, a dependent State value changes, the computed value is recomputed.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createstate)
+ *
+ * @public
+ * @param computeFunction - Function to compute the computed value.
+ * @param config - Configuration object
+ */
+export function createComputed(
+ computeFunction: ComputeFunctionType,
+ config?: CreateComputedConfigInterfaceWithAgile
+): Computed;
+/**
+ * Returns a newly created Computed.
+ *
+ * A Computed is an extension of the State Class
+ * that computes its value based on a specified compute function.
+ *
+ * The computed value will be cached to avoid unnecessary recomputes
+ * and is only recomputed when one of its direct dependencies changes.
+ *
+ * Direct dependencies can be States and Collections.
+ * So when, for example, a dependent State value changes, the computed value is recomputed.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createcomputed)
+ *
+ * @public
+ * @param computeFunction - Function to compute the computed value.
+ * @param deps - Hard-coded dependencies on which the Computed Class should depend.
+ */
+export function createComputed(
+ computeFunction: ComputeFunctionType,
+ deps?: Array
+): Computed;
+export function createComputed(
+ computeFunction: ComputeFunctionType,
+ configOrDeps?:
+ | CreateComputedConfigInterface
+ | Array
+): Computed {
+ let _config: CreateComputedConfigInterfaceWithAgile = {};
+
+ if (Array.isArray(configOrDeps)) {
+ _config = flatMerge(_config, {
+ computedDeps: configOrDeps,
+ });
+ } else {
+ if (configOrDeps) _config = configOrDeps;
+ }
+
+ _config = {
+ agileInstance: sharedAgileInstance,
+ ..._config,
+ };
+
+ return new Computed(
+ _config.agileInstance as any,
+ computeFunction,
+ removeProperties(_config, ['agileInstance'])
+ );
+}
+
+export interface CreateAgileSubInstanceInterface {
+ /**
+ * Instance of Agile the Instance belongs to.
+ * @default sharedAgileInstance
+ */
+ agileInstance?: Agile;
+}
+
+export interface CreateStateConfigInterfaceWithAgile
+ extends CreateAgileSubInstanceInterface,
+ StateConfigInterface {}
+
+export interface CreateComputedConfigInterfaceWithAgile
+ extends CreateAgileSubInstanceInterface,
+ CreateComputedConfigInterface {}
diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts
index b70a3a70..6c3cf779 100644
--- a/packages/core/src/state/index.ts
+++ b/packages/core/src/state/index.ts
@@ -2,7 +2,6 @@ import {
Agile,
StorageKey,
copy,
- defineConfig,
flatMerge,
isValidObject,
StateObserver,
@@ -84,10 +83,11 @@ export class State {
initialValue: ValueType,
config: StateConfigInterface = {}
) {
- config = defineConfig(config, {
+ config = {
dependents: [],
isPlaceholder: false,
- });
+ ...config,
+ };
this.agileInstance = () => agileInstance;
this._key = config.key;
this.observers['value'] = new StateObserver(this, {
@@ -196,9 +196,10 @@ export class State {
value: ValueType | ((value: ValueType) => ValueType),
config: StateIngestConfigInterface = {}
): this {
- config = defineConfig(config, {
+ config = {
force: false,
- });
+ ...config,
+ };
const _value = isFunction(value)
? (value as any)(copy(this._value))
: value;
@@ -299,9 +300,10 @@ export class State {
targetWithChanges: Object,
config: PatchConfigInterface = {}
): this {
- config = defineConfig(config, {
+ config = {
addNewProperties: true,
- });
+ ...config,
+ };
// Check if the given conditions are suitable for a patch action
if (!isValidObject(this.nextStateValue, true)) {
@@ -467,11 +469,12 @@ export class State {
key = keyOrConfig as PersistentKey;
}
- _config = defineConfig(_config, {
+ _config = {
loadValue: true,
storageKeys: [],
- defaultStorageKey: null,
- });
+ defaultStorageKey: null as any,
+ ..._config,
+ };
// Check if State is already persisted
if (this.persistent != null && this.isPersisted) return this;
@@ -697,9 +700,10 @@ export class State {
callback: SideEffectFunctionType,
config: AddSideEffectConfigInterface = {}
): this {
- config = defineConfig(config, {
+ config = {
weight: 10,
- });
+ ...config,
+ };
if (!isFunction(callback)) {
LogCodeManager.log('00:03:01', ['Side Effect Callback', 'function']);
return this;
diff --git a/packages/core/src/state/state.observer.ts b/packages/core/src/state/state.observer.ts
index 85598d79..2b874757 100644
--- a/packages/core/src/state/state.observer.ts
+++ b/packages/core/src/state/state.observer.ts
@@ -3,7 +3,6 @@ import {
State,
Computed,
copy,
- defineConfig,
equal,
notEqual,
isFunction,
@@ -81,7 +80,7 @@ export class StateObserver extends Observer {
config: StateIngestConfigInterface = {}
): void {
const state = this.state();
- config = defineConfig(config, {
+ config = {
perform: true,
background: false,
sideEffects: {
@@ -92,7 +91,8 @@ export class StateObserver extends Observer {
storage: true,
overwrite: false,
maxTriesToUpdate: 3,
- });
+ ...config,
+ };
// Force overwriting the State value if it is a placeholder.
// After assigning a value to the State, the State is supposed to be no placeholder anymore.
diff --git a/packages/core/src/state/state.persistent.ts b/packages/core/src/state/state.persistent.ts
index 03029ff0..4b544388 100644
--- a/packages/core/src/state/state.persistent.ts
+++ b/packages/core/src/state/state.persistent.ts
@@ -1,6 +1,5 @@
import {
CreatePersistentConfigInterface,
- defineConfig,
Persistent,
PersistentKey,
State,
@@ -26,11 +25,12 @@ export class StatePersistent extends Persistent {
super(state.agileInstance(), {
instantiate: false,
});
- config = defineConfig(config, {
+ config = {
instantiate: true,
storageKeys: [],
- defaultStorageKey: null,
- });
+ defaultStorageKey: null as any,
+ ...config,
+ };
this.state = () => state;
this.instantiatePersistent({
key: config.key,
diff --git a/packages/core/src/state/state.runtime.job.ts b/packages/core/src/state/state.runtime.job.ts
index 16e1628a..3faf958d 100644
--- a/packages/core/src/state/state.runtime.job.ts
+++ b/packages/core/src/state/state.runtime.job.ts
@@ -1,5 +1,4 @@
import {
- defineConfig,
RuntimeJob,
RuntimeJobConfigInterface,
RuntimeJobKey,
@@ -25,7 +24,7 @@ export class StateRuntimeJob extends RuntimeJob {
config: CreateStateRuntimeJobConfigInterface = {}
) {
super(observer, config);
- config = defineConfig(config, {
+ config = {
background: false,
sideEffects: {
enabled: true,
@@ -35,7 +34,8 @@ export class StateRuntimeJob extends RuntimeJob {
storage: true,
overwrite: false,
maxTriesToUpdate: 3,
- });
+ ...config,
+ };
this.config = {
background: config.background,
diff --git a/packages/core/src/storages/index.ts b/packages/core/src/storages/index.ts
index 552a96b7..75eddab5 100644
--- a/packages/core/src/storages/index.ts
+++ b/packages/core/src/storages/index.ts
@@ -1,7 +1,6 @@
import {
Agile,
Storage,
- defineConfig,
Persistent,
StorageKey,
StorageItemKey,
@@ -34,10 +33,11 @@ export class Storages {
config: CreateStoragesConfigInterface = {}
) {
this.agileInstance = () => agileInstance;
- config = defineConfig(config, {
+ config = {
localStorage: false,
- defaultStorageKey: null,
- });
+ defaultStorageKey: null as any,
+ ...config,
+ };
this.config = { defaultStorageKey: config.defaultStorageKey as any };
if (config.localStorage) this.instantiateLocalStorage();
}
@@ -115,6 +115,8 @@ export class Storages {
}
});
+ LogCodeManager.log('13:00:00', [storage.key], storage);
+
return true;
}
diff --git a/packages/core/src/storages/persistent.ts b/packages/core/src/storages/persistent.ts
index a335d982..abeaab17 100644
--- a/packages/core/src/storages/persistent.ts
+++ b/packages/core/src/storages/persistent.ts
@@ -1,10 +1,4 @@
-import {
- Agile,
- copy,
- defineConfig,
- LogCodeManager,
- StorageKey,
-} from '../internal';
+import { Agile, copy, LogCodeManager, StorageKey } from '../internal';
export class Persistent {
// Agile Instance the Persistent belongs to
@@ -44,11 +38,12 @@ export class Persistent {
) {
this.agileInstance = () => agileInstance;
this._key = Persistent.placeHolderKey;
- config = defineConfig(config, {
+ config = {
instantiate: true,
storageKeys: [],
- defaultStorageKey: null,
- });
+ defaultStorageKey: null as any,
+ ...config,
+ };
this.agileInstance().storages.persistentInstances.add(this);
this.config = { defaultStorageKey: config.defaultStorageKey as any };
diff --git a/packages/core/src/storages/storage.ts b/packages/core/src/storages/storage.ts
index 894a2a6d..01100e4e 100644
--- a/packages/core/src/storages/storage.ts
+++ b/packages/core/src/storages/storage.ts
@@ -1,9 +1,7 @@
import {
isJsonString,
- defineConfig,
isAsyncFunction,
isFunction,
- Agile,
LogCodeManager,
} from '../internal';
@@ -28,10 +26,11 @@ export class Storage {
* @param config - Configuration object
*/
constructor(config: CreateStorageConfigInterface) {
- config = defineConfig(config, {
+ config = {
prefix: 'agile',
async: false,
- });
+ ...config,
+ };
this.key = config.key;
this.methods = config.methods;
this.config = {
@@ -89,12 +88,12 @@ export class Storage {
const res = this.methods.get(this.getStorageKey(key));
const _res = isJsonString(res) ? JSON.parse(res) : res;
- Agile.logger.if
- .tag(['storage'])
- .info(
- LogCodeManager.getLog('13:01:00', [this.key, this.getStorageKey(key)]),
- _res
- );
+ LogCodeManager.logIfTags(
+ ['storage'],
+ '13:01:00',
+ [this.key, this.getStorageKey(key)],
+ _res
+ );
return _res;
}
@@ -122,15 +121,12 @@ export class Storage {
.then((res: any) => {
const _res = isJsonString(res) ? JSON.parse(res) : res;
- Agile.logger.if
- .tag(['storage'])
- .info(
- LogCodeManager.getLog('13:01:00', [
- this.key,
- this.getStorageKey(key),
- ]),
- _res
- );
+ LogCodeManager.logIfTags(
+ ['storage'],
+ '13:01:00',
+ [this.key, this.getStorageKey(key)],
+ _res
+ );
resolve(_res);
})
@@ -149,12 +145,10 @@ export class Storage {
public set(key: StorageItemKey, value: any): void {
if (!this.ready || !this.methods.set) return;
- Agile.logger.if
- .tag(['storage'])
- .info(
- LogCodeManager.getLog('13:01:01', [this.key, this.getStorageKey(key)]),
- value
- );
+ LogCodeManager.logIfTags(['storage'], '13:01:01', [
+ this.key,
+ this.getStorageKey(key),
+ ]);
this.methods.set(this.getStorageKey(key), JSON.stringify(value));
}
@@ -169,11 +163,10 @@ export class Storage {
public remove(key: StorageItemKey): void {
if (!this.ready || !this.methods.remove) return;
- Agile.logger.if
- .tag(['storage'])
- .info(
- LogCodeManager.getLog('13:01:02', [this.key, this.getStorageKey(key)])
- );
+ LogCodeManager.logIfTags(['storage'], '13:01:02', [
+ this.key,
+ this.getStorageKey(key),
+ ]);
this.methods.remove(this.getStorageKey(key));
}
diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts
index 56af1728..8748f28e 100644
--- a/packages/core/src/utils.ts
+++ b/packages/core/src/utils.ts
@@ -5,6 +5,7 @@ import {
normalizeArray,
isFunction,
LogCodeManager,
+ shared,
} from './internal';
/**
@@ -25,6 +26,11 @@ export function getAgileInstance(instance: any): Agile | undefined {
if (_agileInstance) return _agileInstance;
}
+ // Try to get shared Agile Instance
+ if (shared instanceof Agile) {
+ return shared;
+ }
+
// Return global bound Agile Instance
return globalThis[Agile.globalKey];
} catch (e) {
@@ -254,3 +260,16 @@ export function globalBind(
}
return false;
}
+
+/**
+ * Returns a boolean indicating whether AgileTs is currently running on a server.
+ *
+ * @public
+ */
+export const runsOnServer = (): boolean => {
+ return !(
+ typeof window !== 'undefined' &&
+ typeof window.document !== 'undefined' &&
+ typeof window.document.createElement !== 'undefined'
+ );
+};
diff --git a/packages/core/tests/helper/logMock.ts b/packages/core/tests/helper/logMock.ts
index 49c4c4dc..b431beb8 100644
--- a/packages/core/tests/helper/logMock.ts
+++ b/packages/core/tests/helper/logMock.ts
@@ -22,7 +22,7 @@ const logTypes = {
};
function mockLogs(mockArg?: LogTypes[]): void {
- const _mockArg = mockArg ?? ['warn', 'error'];
+ const _mockArg = mockArg ?? ['warn', 'error', 'log'];
mockConsole(_mockArg);
}
diff --git a/packages/core/tests/integration/collection.persistent.integration.test.ts b/packages/core/tests/integration/collection.persistent.integration.test.ts
index 203fbfd4..e1d151ee 100644
--- a/packages/core/tests/integration/collection.persistent.integration.test.ts
+++ b/packages/core/tests/integration/collection.persistent.integration.test.ts
@@ -17,16 +17,7 @@ describe('Collection Persist Function Tests', () => {
delete myStorage[key];
}),
};
-
- // Define Agile with Storage
- const App = new Agile({ localStorage: false });
- App.registerStorage(
- App.createStorage({
- key: 'testStorage',
- prefix: 'test',
- methods: storageMethods,
- })
- );
+ let App: Agile;
interface User {
id: number;
@@ -36,6 +27,15 @@ describe('Collection Persist Function Tests', () => {
beforeEach(() => {
LogMock.mockLogs();
jest.clearAllMocks();
+
+ App = new Agile({ localStorage: false });
+ App.registerStorage(
+ App.createStorage({
+ key: 'testStorage',
+ prefix: 'test',
+ methods: storageMethods,
+ })
+ );
});
describe('Collection', () => {
diff --git a/packages/core/tests/unit/agile.test.ts b/packages/core/tests/unit/agile.test.ts
index f1bdec49..2d0dc29e 100644
--- a/packages/core/tests/unit/agile.test.ts
+++ b/packages/core/tests/unit/agile.test.ts
@@ -7,37 +7,52 @@ import {
Storage,
Computed,
Collection,
- Logger,
Storages,
} from '../../src';
import testIntegration from '../helper/test.integration';
import { LogMock } from '../helper/logMock';
+import * as Shared from '../../src/shared';
-jest.mock('../../src/runtime/index');
-jest.mock('../../src/runtime/subscription/sub.controller');
-jest.mock('../../src/storages/index');
-jest.mock('../../src/integrations/index');
-jest.mock('../../src/storages/storage');
-jest.mock('../../src/collection/index');
-jest.mock('../../src/computed/index');
-/* Can't mock Logger because I somehow can't overwrite a static get method
-jest.mock("../../src/logger/index", () => {
- return class {
- static get level() {
+// https://github.com/facebook/jest/issues/5023
+jest.mock('../../src/runtime', () => {
+ return {
+ // https://jestjs.io/docs/mock-function-api#mockfnmockimplementationfn
+ Runtime: jest.fn().mockImplementation(() => {
return {
- TRACE: 1,
- DEBUG: 2,
- LOG: 5,
- TABLE: 5,
- INFO: 10,
- WARN: 20,
- ERROR: 50,
+ ingest: jest.fn(),
};
- }
+ }),
+ };
+});
+jest.mock('../../src/runtime/subscription/sub.controller', () => {
+ return {
+ SubController: jest.fn(),
+ };
+});
+jest.mock('../../src/storages', () => {
+ return {
+ Storages: jest.fn(),
+ };
+});
+
+// https://gist.github.com/virgs/d9c50e878fc69832c01f8085f2953f12
+// https://medium.com/@masonlgoetz/mock-static-class-methods-in-jest-1ceda967b47f
+jest.mock('../../src/integrations', () => {
+ const mockedInstances = {
+ // https://jestjs.io/docs/mock-function-api#mockfnmockimplementationfn
+ Integrations: jest.fn().mockImplementation(() => {
+ return {
+ integrate: jest.fn(),
+ hasIntegration: jest.fn(),
+ };
+ }),
};
+ // @ts-ignore
+ mockedInstances.Integrations.onRegisteredExternalIntegration = jest.fn();
+ // @ts-ignore
+ mockedInstances.Integrations.initialIntegrations = [];
+ return mockedInstances;
});
- */
-// jest.mock("../../src/state/index"); // Can't mock State because mocks get instantiated before everything else -> I got the good old not loaded Object error https://github.com/kentcdodds/how-jest-mocking-works
describe('Agile Tests', () => {
const RuntimeMock = Runtime as jest.MockedClass;
@@ -50,131 +65,114 @@ describe('Agile Tests', () => {
>;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
+ // Clear specified mocks
RuntimeMock.mockClear();
SubControllerMock.mockClear();
StoragesMock.mockClear();
IntegrationsMock.mockClear();
- // Reset Global This
+ // Reset globalThis
globalThis[Agile.globalKey] = undefined;
+
+ jest.spyOn(Agile.prototype, 'integrate');
+
+ jest.clearAllMocks();
});
it('should instantiate Agile (default config)', () => {
const agile = new Agile();
- // Check if Agile properties got instantiated properly
expect(agile.config).toStrictEqual({
waitForMount: true,
+ bucket: true,
});
- expect(IntegrationsMock).toHaveBeenCalledWith(agile);
- expect(agile.integrations).toBeInstanceOf(Integrations);
+ expect(agile.key).toBeUndefined();
+ expect(IntegrationsMock).toHaveBeenCalledWith(agile, {
+ autoIntegrate: true,
+ });
+ // expect(agile.integrations).toBeInstanceOf(Integrations); // Because 'Integrations' is completely overwritten with a mock (mockImplementation)
expect(RuntimeMock).toHaveBeenCalledWith(agile);
- expect(agile.runtime).toBeInstanceOf(Runtime);
+ // expect(agile.runtime).toBeInstanceOf(Runtime); // Because 'Runtime' is completely overwritten with a mock (mockImplementation)
expect(SubControllerMock).toHaveBeenCalledWith(agile);
expect(agile.subController).toBeInstanceOf(SubController);
expect(StoragesMock).toHaveBeenCalledWith(agile, {
- localStorage: true,
+ localStorage: false,
});
expect(agile.storages).toBeInstanceOf(Storages);
- // Check if Static Logger has correct config
- expect(Agile.logger.config).toStrictEqual({
- prefix: 'Agile',
- level: Logger.level.WARN,
- canUseCustomStyles: true,
- timestamp: false,
- });
- expect(Agile.logger.allowedTags).toStrictEqual([
- 'runtime',
- 'storage',
- 'subscription',
- 'multieditor',
- ]);
- expect(Agile.logger.isActive).toBeTruthy();
-
- // Check if global Agile Instance got created
+ // Check if Agile Instance got bound globally
expect(globalThis[Agile.globalKey]).toBeUndefined();
});
- it('should instantiate Agile with (specific config)', () => {
+ it('should instantiate Agile (specific config)', () => {
const agile = new Agile({
waitForMount: false,
- localStorage: false,
- logConfig: {
- level: Logger.level.DEBUG,
- active: false,
- prefix: 'Jeff',
- timestamp: true,
- },
+ bucket: false,
+ localStorage: true,
bindGlobal: true,
+ key: 'jeff',
+ autoIntegrate: false,
});
- // Check if Agile properties got instantiated properly
expect(agile.config).toStrictEqual({
waitForMount: false,
+ bucket: false,
+ });
+ expect(agile.key).toBe('jeff');
+ expect(IntegrationsMock).toHaveBeenCalledWith(agile, {
+ autoIntegrate: false,
});
- expect(IntegrationsMock).toHaveBeenCalledWith(agile);
- expect(agile.integrations).toBeInstanceOf(Integrations);
+ // expect(agile.integrations).toBeInstanceOf(Integrations); // Because 'Integrations' is completely overwritten with a mock (mockImplementation)
expect(RuntimeMock).toHaveBeenCalledWith(agile);
- expect(agile.runtime).toBeInstanceOf(Runtime);
+ // expect(agile.runtime).toBeInstanceOf(Runtime); // Because 'Runtime' is completely overwritten with a mock (mockImplementation)
expect(SubControllerMock).toHaveBeenCalledWith(agile);
expect(agile.subController).toBeInstanceOf(SubController);
expect(StoragesMock).toHaveBeenCalledWith(agile, {
- localStorage: false,
+ localStorage: true,
});
expect(agile.storages).toBeInstanceOf(Storages);
- // Check if Static Logger has correct config
- expect(Agile.logger.config).toStrictEqual({
- prefix: 'Jeff',
- level: Logger.level.DEBUG,
- canUseCustomStyles: true,
- timestamp: true,
- });
- expect(Agile.logger.allowedTags).toStrictEqual([
- 'runtime',
- 'storage',
- 'subscription',
- 'multieditor',
- ]);
- expect(Agile.logger.isActive).toBeFalsy();
-
- // Check if global Agile Instance got created
+ // Check if Agile Instance got bound globally
expect(globalThis[Agile.globalKey]).toBe(agile);
});
- it('should instantiate second Agile Instance and print warning if config.bindGlobal is set both times to true', () => {
- const agile1 = new Agile({
- bindGlobal: true,
- });
+ it(
+ 'should instantiate second Agile Instance ' +
+ 'and print warning when an attempt is made to set the second Agile Instance globally ' +
+ 'although the previously defined Agile Instance is already globally set',
+ () => {
+ const agile1 = new Agile({
+ bindGlobal: true,
+ });
- const agile2 = new Agile({
- bindGlobal: true,
- });
+ const agile2 = new Agile({
+ bindGlobal: true,
+ });
- expect(globalThis[Agile.globalKey]).toBe(agile1);
- LogMock.hasLoggedCode('10:02:00');
- });
+ expect(agile1).toBeInstanceOf(Agile);
+ expect(agile2).toBeInstanceOf(Agile);
+
+ expect(globalThis[Agile.globalKey]).toBe(agile1);
+ LogMock.hasLoggedCode('10:02:00');
+ }
+ );
describe('Agile Function Tests', () => {
let agile: Agile;
beforeEach(() => {
agile = new Agile();
- jest.clearAllMocks(); // Because creating Agile executes some mocks
+ jest.clearAllMocks(); // Because creating the Agile Instance calls some mocks
});
describe('createStorage function tests', () => {
- const StorageMock = Storage as jest.MockedClass;
-
beforeEach(() => {
- StorageMock.mockClear();
+ jest.spyOn(Shared, 'createStorage');
});
- it('should create Storage', () => {
+ it('should call createStorage', () => {
const storageConfig = {
prefix: 'test',
methods: {
@@ -190,31 +188,36 @@ describe('Agile Tests', () => {
},
key: 'myTestStorage',
};
- const storage = agile.createStorage(storageConfig);
- expect(storage).toBeInstanceOf(Storage);
- expect(StorageMock).toHaveBeenCalledWith(storageConfig);
+ const response = agile.createStorage(storageConfig);
+
+ expect(response).toBeInstanceOf(Storage);
+ expect(Shared.createStorage).toHaveBeenCalledWith(storageConfig);
});
});
- describe('state function tests', () => {
- it('should create State', () => {
- const state = agile.createState('testValue', {
- key: 'myCoolState',
- });
+ describe('createState function tests', () => {
+ beforeEach(() => {
+ jest.spyOn(Shared, 'createState');
+ });
+
+ it('should call createState with the Agile Instance it was called on', () => {
+ const response = agile.createState('jeff', { key: 'jeffState' });
- expect(state).toBeInstanceOf(State);
+ expect(response).toBeInstanceOf(State);
+ expect(Shared.createState).toHaveBeenCalledWith('jeff', {
+ key: 'jeffState',
+ agileInstance: agile,
+ });
});
});
describe('createCollection function tests', () => {
- const CollectionMock = Collection as jest.MockedClass;
-
beforeEach(() => {
- CollectionMock.mockClear();
+ jest.spyOn(Shared, 'createCollection');
});
- it('should create Collection', () => {
+ it('should call createCollection with the Agile Instance it was called on', () => {
const collectionConfig = {
selectors: ['test', 'test1'],
groups: ['test2', 'test10'],
@@ -222,48 +225,53 @@ describe('Agile Tests', () => {
key: 'myCoolCollection',
};
- const collection = agile.createCollection(collectionConfig);
+ const response = agile.createCollection(collectionConfig);
- expect(collection).toBeInstanceOf(Collection);
- expect(CollectionMock).toHaveBeenCalledWith(agile, collectionConfig);
+ expect(response).toBeInstanceOf(Collection);
+ expect(Shared.createCollection).toHaveBeenCalledWith(
+ collectionConfig,
+ agile
+ );
});
});
describe('createComputed function tests', () => {
- const ComputedMock = Computed as jest.MockedClass;
const computedFunction = () => {
- // console.log("Hello Jeff");
+ // empty
};
beforeEach(() => {
- ComputedMock.mockClear();
+ jest.spyOn(Shared, 'createComputed');
});
- it('should create Computed', () => {
- const computed = agile.createComputed(computedFunction, [
+ it('should call createComputed with the Agile Instance it was called on (default config)', () => {
+ const response = agile.createComputed(computedFunction, [
'dummyDep' as any,
]);
- expect(computed).toBeInstanceOf(Computed);
- expect(ComputedMock).toHaveBeenCalledWith(agile, computedFunction, {
+ expect(response).toBeInstanceOf(Computed);
+ expect(Shared.createComputed).toHaveBeenCalledWith(computedFunction, {
computedDeps: ['dummyDep' as any],
+ agileInstance: agile,
});
});
- it('should create Computed with config', () => {
- const computed = agile.createComputed(computedFunction, {
+ it('should call createComputed with the Agile Instance it was called on (specific config)', () => {
+ const computedConfig = {
key: 'jeff',
isPlaceholder: false,
computedDeps: ['dummyDep' as any],
autodetect: true,
- });
+ };
- expect(computed).toBeInstanceOf(Computed);
- expect(ComputedMock).toHaveBeenCalledWith(agile, computedFunction, {
- key: 'jeff',
- isPlaceholder: false,
- computedDeps: ['dummyDep' as any],
- autodetect: true,
+ const response = agile.createComputed(computedFunction, computedConfig);
+
+ expect(response).toBeInstanceOf(Computed);
+ expect(Shared.createComputed).toHaveBeenCalledWith(computedFunction, {
+ ...computedConfig,
+ ...{
+ agileInstance: agile,
+ },
});
});
});
@@ -280,6 +288,10 @@ describe('Agile Tests', () => {
});
describe('registerStorage function tests', () => {
+ beforeEach(() => {
+ agile.storages.register = jest.fn();
+ });
+
it('should register provided Storage', () => {
const dummyStorage = new Storage({
prefix: 'test',
@@ -317,6 +329,10 @@ describe('Agile Tests', () => {
});
describe('hasStorage function tests', () => {
+ beforeEach(() => {
+ agile.storages.hasStorage = jest.fn();
+ });
+
it('should check if Agile has any registered Storage', () => {
agile.hasStorage();
diff --git a/packages/core/tests/unit/collection/collection.persistent.test.ts b/packages/core/tests/unit/collection/collection.persistent.test.ts
index 3ebbc97c..0c746a27 100644
--- a/packages/core/tests/unit/collection/collection.persistent.test.ts
+++ b/packages/core/tests/unit/collection/collection.persistent.test.ts
@@ -20,7 +20,6 @@ describe('CollectionPersistent Tests', () => {
let dummyCollection: Collection;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyAgile = new Agile({ localStorage: false });
@@ -30,6 +29,8 @@ describe('CollectionPersistent Tests', () => {
jest.spyOn(CollectionPersistent.prototype, 'instantiatePersistent');
jest.spyOn(CollectionPersistent.prototype, 'initialLoading');
+
+ jest.clearAllMocks();
});
it('should create CollectionPersistent and should call initialLoading if Persistent is ready (default config)', () => {
diff --git a/packages/core/tests/unit/collection/collection.test.ts b/packages/core/tests/unit/collection/collection.test.ts
index 47db19a4..e5b0e686 100644
--- a/packages/core/tests/unit/collection/collection.test.ts
+++ b/packages/core/tests/unit/collection/collection.test.ts
@@ -22,7 +22,6 @@ describe('Collection Tests', () => {
let dummyAgile: Agile;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyAgile = new Agile({ localStorage: false });
@@ -30,6 +29,8 @@ describe('Collection Tests', () => {
jest.spyOn(Collection.prototype, 'initSelectors');
jest.spyOn(Collection.prototype, 'initGroups');
jest.spyOn(Collection.prototype, 'collect');
+
+ jest.clearAllMocks();
});
it('should create Collection (default config)', () => {
diff --git a/packages/core/tests/unit/collection/group/group.observer.test.ts b/packages/core/tests/unit/collection/group/group.observer.test.ts
index f24e14d6..a295262e 100644
--- a/packages/core/tests/unit/collection/group/group.observer.test.ts
+++ b/packages/core/tests/unit/collection/group/group.observer.test.ts
@@ -24,7 +24,6 @@ describe('GroupObserver Tests', () => {
let dummyItem2: Item;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyAgile = new Agile({ localStorage: false });
@@ -40,6 +39,8 @@ describe('GroupObserver Tests', () => {
id: 'dummyItem2Key',
name: 'jeff',
});
+
+ jest.clearAllMocks();
});
it('should create Group Observer (default config)', () => {
diff --git a/packages/core/tests/unit/collection/group/group.test.ts b/packages/core/tests/unit/collection/group/group.test.ts
index 7aaceb4f..f9d48a74 100644
--- a/packages/core/tests/unit/collection/group/group.test.ts
+++ b/packages/core/tests/unit/collection/group/group.test.ts
@@ -21,7 +21,6 @@ describe('Group Tests', () => {
let dummyCollection: Collection;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyAgile = new Agile({ localStorage: false });
@@ -31,6 +30,8 @@ describe('Group Tests', () => {
jest.spyOn(Group.prototype, 'rebuild');
jest.spyOn(Group.prototype, 'addSideEffect');
+
+ jest.clearAllMocks();
});
it('should create Group with no initialItems (default config)', () => {
diff --git a/packages/core/tests/unit/collection/item.test.ts b/packages/core/tests/unit/collection/item.test.ts
index b50b620a..374352b8 100644
--- a/packages/core/tests/unit/collection/item.test.ts
+++ b/packages/core/tests/unit/collection/item.test.ts
@@ -18,13 +18,14 @@ describe('Item Tests', () => {
let dummyCollection: Collection;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyAgile = new Agile({ localStorage: false });
dummyCollection = new Collection(dummyAgile);
jest.spyOn(Item.prototype, 'addRebuildGroupThatIncludeItemKeySideEffect');
+
+ jest.clearAllMocks();
});
it('should create Item (default config)', () => {
diff --git a/packages/core/tests/unit/collection/selector.test.ts b/packages/core/tests/unit/collection/selector.test.ts
index 092a0d4c..11080f9f 100644
--- a/packages/core/tests/unit/collection/selector.test.ts
+++ b/packages/core/tests/unit/collection/selector.test.ts
@@ -11,13 +11,14 @@ describe('Selector Tests', () => {
let dummyCollection: Collection;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyAgile = new Agile({ localStorage: false });
dummyCollection = new Collection(dummyAgile);
jest.spyOn(Selector.prototype, 'select');
+
+ jest.clearAllMocks();
});
it('should create Selector and call initial select (default config)', () => {
diff --git a/packages/core/tests/unit/computed/computed.test.ts b/packages/core/tests/unit/computed/computed.test.ts
index 7f4c58fe..ef88decc 100644
--- a/packages/core/tests/unit/computed/computed.test.ts
+++ b/packages/core/tests/unit/computed/computed.test.ts
@@ -14,13 +14,14 @@ describe('Computed Tests', () => {
let dummyAgile: Agile;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyAgile = new Agile({ localStorage: false });
jest.spyOn(Computed.prototype, 'recompute');
jest.spyOn(Utils, 'extractRelevantObservers');
+
+ jest.clearAllMocks();
});
it('should create Computed with a not async compute method (default config)', () => {
diff --git a/packages/core/tests/unit/computed/computed.tracker.test.ts b/packages/core/tests/unit/computed/computed.tracker.test.ts
index 81483956..20e09dc1 100644
--- a/packages/core/tests/unit/computed/computed.tracker.test.ts
+++ b/packages/core/tests/unit/computed/computed.tracker.test.ts
@@ -5,7 +5,6 @@ describe('ComputedTracker Tests', () => {
let dummyAgile: Agile;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyAgile = new Agile({ localStorage: false });
@@ -13,6 +12,8 @@ describe('ComputedTracker Tests', () => {
// Reset ComputedTracker (because it works static)
ComputedTracker.isTracking = false;
ComputedTracker.trackedObservers = new Set();
+
+ jest.clearAllMocks();
});
describe('ComputedTracker Function Tests', () => {
diff --git a/packages/core/tests/unit/integrations/integration.test.ts b/packages/core/tests/unit/integrations/integration.test.ts
index f9cac640..a73cc5c9 100644
--- a/packages/core/tests/unit/integrations/integration.test.ts
+++ b/packages/core/tests/unit/integrations/integration.test.ts
@@ -3,8 +3,8 @@ import { LogMock } from '../../helper/logMock';
describe('Integration Tests', () => {
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
+ jest.clearAllMocks();
});
it('should create Integration', () => {
diff --git a/packages/core/tests/unit/integrations/integrations.test.ts b/packages/core/tests/unit/integrations/integrations.test.ts
index d4baf660..a7598d61 100644
--- a/packages/core/tests/unit/integrations/integrations.test.ts
+++ b/packages/core/tests/unit/integrations/integrations.test.ts
@@ -3,56 +3,124 @@ import { LogMock } from '../../helper/logMock';
describe('Integrations Tests', () => {
let dummyAgile: Agile;
+ let dummyIntegration1: Integration;
+ let dummyIntegration2: Integration;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyAgile = new Agile({ localStorage: false });
- Agile.initialIntegrations = [];
+ dummyIntegration1 = new Integration({
+ key: 'dummyIntegration1',
+ });
+ dummyIntegration2 = new Integration({
+ key: 'dummyIntegration2',
+ });
- jest.spyOn(Integrations.prototype, 'integrate');
- });
+ Integrations.initialIntegrations = [];
- it('should create Integrations', () => {
- const integrations = new Integrations(dummyAgile);
+ jest.spyOn(Integrations.prototype, 'integrate');
+ jest.spyOn(Integrations, 'onRegisterInitialIntegration');
- expect(integrations.integrations.size).toBe(0);
+ jest.clearAllMocks();
});
- it('should create Integrations and integrate Agile initialIntegrations', async () => {
- const dummyIntegration1 = new Integration({
- key: 'initialIntegration1',
- });
- const dummyIntegration2 = new Integration({
- key: 'initialIntegration2',
- });
- Agile.initialIntegrations.push(dummyIntegration1);
- Agile.initialIntegrations.push(dummyIntegration2);
+ it('should create Integrations with the before specified initial Integrations (default config)', () => {
+ Integrations.initialIntegrations = [dummyIntegration1, dummyIntegration2];
const integrations = new Integrations(dummyAgile);
- expect(integrations.integrations.size).toBe(2);
- expect(integrations.integrations.has(dummyIntegration1)).toBeTruthy();
- expect(integrations.integrations.has(dummyIntegration2)).toBeTruthy();
+ expect(Array.from(integrations.integrations)).toStrictEqual([
+ dummyIntegration1,
+ dummyIntegration2,
+ ]);
+ expect(Integrations.onRegisterInitialIntegration).toHaveBeenCalledWith(
+ expect.any(Function)
+ );
+ expect(integrations.integrate).toHaveBeenCalledTimes(2);
expect(integrations.integrate).toHaveBeenCalledWith(dummyIntegration1);
expect(integrations.integrate).toHaveBeenCalledWith(dummyIntegration2);
});
+ it('should create Integrations without the before specified initial Integrations (autoIntegrate = false)', () => {
+ Integrations.initialIntegrations = [dummyIntegration1, dummyIntegration2];
+
+ const integrations = new Integrations(dummyAgile, { autoIntegrate: false });
+
+ expect(Array.from(integrations.integrations)).toStrictEqual([]);
+
+ expect(Integrations.onRegisterInitialIntegration).not.toHaveBeenCalled();
+ expect(integrations.integrate).not.toHaveBeenCalled();
+ });
+
describe('Integrations Function Tests', () => {
let integrations: Integrations;
- let dummyIntegration1: Integration;
- let dummyIntegration2: Integration;
beforeEach(() => {
integrations = new Integrations(dummyAgile);
- dummyIntegration1 = new Integration({
- key: 'dummyIntegration1',
+ });
+
+ describe('onRegisterInitialIntegration function tests', () => {
+ let callback;
+ beforeEach(() => {
+ callback = jest.fn();
});
- dummyIntegration2 = new Integration({
- key: 'dummyIntegration2',
+
+ it(
+ 'should register specified onRegisterInitialIntegration callback ' +
+ 'and call it for each tracked initial Integrations',
+ () => {
+ Integrations.initialIntegrations = [
+ dummyIntegration1,
+ dummyIntegration2,
+ ];
+
+ Integrations.onRegisterInitialIntegration(callback);
+
+ expect(callback).toHaveBeenCalledTimes(2);
+ expect(callback).toHaveBeenCalledWith(dummyIntegration1);
+ expect(callback).toHaveBeenCalledWith(dummyIntegration2);
+ }
+ );
+ });
+
+ describe('addInitialIntegration function tests', () => {
+ const callback1 = jest.fn();
+ const callback2 = jest.fn();
+
+ beforeEach(() => {
+ Integrations.onRegisterInitialIntegration(callback1);
+ Integrations.onRegisterInitialIntegration(callback2);
});
+
+ it(
+ 'should add valid Integration to the initialIntegrations array ' +
+ 'and fire the onRegisterInitialIntegration callbacks',
+ () => {
+ Integrations.addInitialIntegration(dummyIntegration1);
+
+ expect(callback1).toHaveBeenCalledTimes(1);
+ expect(callback1).toHaveBeenCalledWith(dummyIntegration1);
+ expect(callback2).toHaveBeenCalledTimes(1);
+ expect(callback2).toHaveBeenCalledWith(dummyIntegration1);
+ expect(Integrations.initialIntegrations).toStrictEqual([
+ dummyIntegration1,
+ ]);
+ }
+ );
+
+ it(
+ "shouldn't add invalid Integration to the initialIntegrations array " +
+ "and shouldn't fire the onRegisterInitialIntegration callbacks",
+ () => {
+ Integrations.addInitialIntegration(undefined as any);
+
+ expect(callback1).not.toHaveBeenCalled();
+ expect(callback2).not.toHaveBeenCalled();
+ expect(Integrations.initialIntegrations).toStrictEqual([]);
+ }
+ );
});
describe('integrate function tests', () => {
@@ -101,7 +169,7 @@ describe('Integrations Tests', () => {
LogMock.hasLoggedCode(
'18:03:00',
- [dummyIntegration1._key],
+ [dummyIntegration1._key, dummyAgile.key],
dummyIntegration1
);
});
diff --git a/packages/core/tests/unit/runtime/observer.test.ts b/packages/core/tests/unit/runtime/observer.test.ts
index 59889d40..5810c138 100644
--- a/packages/core/tests/unit/runtime/observer.test.ts
+++ b/packages/core/tests/unit/runtime/observer.test.ts
@@ -15,7 +15,6 @@ describe('Observer Tests', () => {
let dummySubscription2: SubscriptionContainer;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyAgile = new Agile();
@@ -26,6 +25,8 @@ describe('Observer Tests', () => {
jest.spyOn(dummySubscription1, 'addSubscription');
jest.spyOn(dummySubscription2, 'addSubscription');
+
+ jest.clearAllMocks();
});
it('should create Observer (default config)', () => {
diff --git a/packages/core/tests/unit/runtime/runtime.job.test.ts b/packages/core/tests/unit/runtime/runtime.job.test.ts
index 4793df21..b01c3765 100644
--- a/packages/core/tests/unit/runtime/runtime.job.test.ts
+++ b/packages/core/tests/unit/runtime/runtime.job.test.ts
@@ -7,7 +7,6 @@ describe('RuntimeJob Tests', () => {
let dummyObserver: Observer;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyAgile = new Agile({ localStorage: false });
@@ -15,6 +14,8 @@ describe('RuntimeJob Tests', () => {
key: 'myIntegration',
});
dummyObserver = new Observer(dummyAgile);
+
+ jest.clearAllMocks();
});
it(
diff --git a/packages/core/tests/unit/runtime/runtime.test.ts b/packages/core/tests/unit/runtime/runtime.test.ts
index 2906607d..5bcc3014 100644
--- a/packages/core/tests/unit/runtime/runtime.test.ts
+++ b/packages/core/tests/unit/runtime/runtime.test.ts
@@ -9,15 +9,17 @@ import {
} from '../../../src';
import * as Utils from '@agile-ts/utils';
import { LogMock } from '../../helper/logMock';
+import waitForExpect from 'wait-for-expect';
describe('Runtime Tests', () => {
let dummyAgile: Agile;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyAgile = new Agile({ localStorage: false });
+
+ jest.clearAllMocks();
});
it('should create Runtime', () => {
@@ -29,6 +31,7 @@ describe('Runtime Tests', () => {
expect(runtime.jobsToRerender).toStrictEqual([]);
expect(Array.from(runtime.notReadyJobsToRerender)).toStrictEqual([]);
expect(runtime.isPerformingJobs).toBeFalsy();
+ expect(runtime.bucketTimeout).toBeNull();
});
describe('Runtime Function Tests', () => {
@@ -110,8 +113,11 @@ describe('Runtime Tests', () => {
it(
"should perform specified Job and all remaining Jobs in the 'jobQueue' " +
- "and call 'updateSubscribers' if at least one performed Job needs to rerender",
+ "and call 'updateSubscribers' in a setTimeout (bucket) " +
+ 'if at least one performed Job needs to rerender (config.bucket = true)',
async () => {
+ runtime.agileInstance().config.bucket = true;
+ runtime.bucketTimeout = null;
runtime.jobQueue.push(dummyJob2);
runtime.jobQueue.push(dummyJob3);
@@ -127,14 +133,51 @@ describe('Runtime Tests', () => {
expect(runtime.isPerformingJobs).toBeFalsy(); // because Jobs were performed
expect(runtime.jobQueue).toStrictEqual([]);
expect(runtime.jobsToRerender).toStrictEqual([dummyJob1, dummyJob2]);
+ expect(runtime.bucketTimeout).not.toBeNull();
+
+ // Because 'updateSubscribers' is called in a timeout
+ await waitForExpect(() => {
+ expect(runtime.updateSubscribers).toHaveBeenCalledTimes(1);
+ expect(runtime.bucketTimeout).toBeNull();
+ });
+ }
+ );
+
+ it(
+ "should perform specified Job and all remaining Jobs in the 'jobQueue' " +
+ "and call 'updateSubscribers' " +
+ 'if at least one performed Job needs to rerender (config.bucket = false)',
+ async () => {
+ runtime.agileInstance().config.bucket = false;
+ runtime.bucketTimeout = null;
+ runtime.jobQueue.push(dummyJob2);
+ runtime.jobQueue.push(dummyJob3);
+
+ runtime.perform(dummyJob1);
- // Sleep 5ms because updateSubscribers is called in a timeout
- await new Promise((resolve) => setTimeout(resolve, 5));
+ expect(runtime.bucketTimeout).toBeNull();
expect(runtime.updateSubscribers).toHaveBeenCalledTimes(1);
}
);
+ it(
+ "should perform specified Job and all remaining Jobs in the 'jobQueue' " +
+ "and shouldn't call 'updateSubscribers' although at least one performed Job needs to rerender" +
+ 'if a bucket timeout is already active (config.bucket = true)',
+ async () => {
+ runtime.agileInstance().config.bucket = true;
+ runtime.bucketTimeout = 'notNull' as any;
+ runtime.jobQueue.push(dummyJob2);
+ runtime.jobQueue.push(dummyJob3);
+
+ runtime.perform(dummyJob1);
+
+ expect(runtime.bucketTimeout).toBe('notNull');
+ expect(runtime.updateSubscribers).not.toHaveBeenCalled();
+ }
+ );
+
it('should perform specified Job and ingest its dependents into the runtime', async () => {
dummyJob1.observer.dependents.add(dummyObserver2);
dummyJob1.observer.dependents.add(dummyObserver1);
@@ -150,7 +193,8 @@ describe('Runtime Tests', () => {
it(
"should perform specified Job and all remaining Jobs in the 'jobQueue' " +
- "and shouldn't call 'updateSubscribes' if no performed Job needs to rerender",
+ "and shouldn't call 'updateSubscribes' " +
+ 'if no performed Job needs to rerender',
async () => {
dummyJob1.rerender = false;
runtime.jobQueue.push(dummyJob3);
@@ -165,9 +209,7 @@ describe('Runtime Tests', () => {
expect(runtime.isPerformingJobs).toBeFalsy(); // because Jobs were performed
expect(runtime.jobQueue).toStrictEqual([]);
expect(runtime.jobsToRerender).toStrictEqual([]);
-
- // Sleep 5ms because updateSubscribers is called in a timeout
- await new Promise((resolve) => setTimeout(resolve, 5));
+ expect(runtime.bucketTimeout).toBeNull();
expect(runtime.updateSubscribers).not.toHaveBeenCalled();
}
diff --git a/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts b/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts
index e8bea19e..7f342dc9 100644
--- a/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts
+++ b/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts
@@ -15,7 +15,6 @@ describe('CallbackSubscriptionContainer Tests', () => {
let dummyProxyWeakMap: ProxyWeakMapType;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyAgile = new Agile();
@@ -23,6 +22,8 @@ describe('CallbackSubscriptionContainer Tests', () => {
dummyObserver2 = new Observer(dummyAgile, { key: 'dummyObserver2' });
dummySelectorWeakMap = new WeakMap();
dummyProxyWeakMap = new WeakMap();
+
+ jest.clearAllMocks();
});
it('should create CallbackSubscriptionContainer', () => {
diff --git a/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts b/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts
index 4401cf88..9efe4262 100644
--- a/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts
+++ b/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts
@@ -15,7 +15,6 @@ describe('ComponentSubscriptionContainer Tests', () => {
let dummyProxyWeakMap: ProxyWeakMapType;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyAgile = new Agile();
@@ -23,6 +22,8 @@ describe('ComponentSubscriptionContainer Tests', () => {
dummyObserver2 = new Observer(dummyAgile, { key: 'dummyObserver2' });
dummySelectorWeakMap = new WeakMap();
dummyProxyWeakMap = new WeakMap();
+
+ jest.clearAllMocks();
});
it('should create ComponentSubscriptionContainer', () => {
diff --git a/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts b/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts
index c3aa012d..ce983e95 100644
--- a/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts
+++ b/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts
@@ -16,7 +16,6 @@ describe('SubscriptionContainer Tests', () => {
let dummyProxyWeakMap: ProxyWeakMapType;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyAgile = new Agile();
@@ -24,6 +23,8 @@ describe('SubscriptionContainer Tests', () => {
dummyObserver2 = new Observer(dummyAgile, { key: 'dummyObserver2' });
dummySelectorWeakMap = new WeakMap();
dummyProxyWeakMap = new WeakMap();
+
+ jest.clearAllMocks();
});
it('should create SubscriptionContainer with passed subs array (default config)', () => {
diff --git a/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts b/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts
index e52e81a1..6d73d851 100644
--- a/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts
+++ b/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts
@@ -12,10 +12,11 @@ describe('SubController Tests', () => {
let dummyAgile: Agile;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyAgile = new Agile({ localStorage: false });
+
+ jest.clearAllMocks();
});
it('should create SubController', () => {
diff --git a/packages/core/tests/unit/shared.test.ts b/packages/core/tests/unit/shared.test.ts
new file mode 100644
index 00000000..fc81e6e8
--- /dev/null
+++ b/packages/core/tests/unit/shared.test.ts
@@ -0,0 +1,212 @@
+import {
+ Agile,
+ Collection,
+ Computed,
+ shared,
+ State,
+ Storage,
+ createStorage,
+ createState,
+ createCollection,
+ createComputed,
+ assignSharedAgileInstance,
+} from '../../src';
+import { LogMock } from '../helper/logMock';
+
+jest.mock('../../src/storages/storage');
+jest.mock('../../src/collection');
+jest.mock('../../src/computed');
+
+// https://github.com/facebook/jest/issues/5023
+jest.mock('../../src/state', () => {
+ return {
+ State: jest.fn(),
+ };
+});
+
+describe('Shared Tests', () => {
+ let sharedAgileInstance: Agile;
+
+ beforeEach(() => {
+ LogMock.mockLogs();
+
+ sharedAgileInstance = new Agile();
+ assignSharedAgileInstance(sharedAgileInstance);
+
+ jest.clearAllMocks();
+ });
+
+ describe('assignSharedAgileInstance function tests', () => {
+ it('should assign the specified Agile Instance as new shared Agile Instance', () => {
+ const newAgileInstance = new Agile({ key: 'notShared' });
+
+ assignSharedAgileInstance(newAgileInstance);
+
+ expect(shared).toBe(newAgileInstance);
+ });
+ });
+
+ describe('createStorage function tests', () => {
+ const StorageMock = Storage as jest.MockedClass;
+
+ beforeEach(() => {
+ StorageMock.mockClear();
+ });
+
+ it('should create Storage', () => {
+ const storageConfig = {
+ prefix: 'test',
+ methods: {
+ get: () => {
+ /* empty function */
+ },
+ set: () => {
+ /* empty function */
+ },
+ remove: () => {
+ /* empty function */
+ },
+ },
+ key: 'myTestStorage',
+ };
+
+ const storage = createStorage(storageConfig);
+
+ expect(storage).toBeInstanceOf(Storage);
+ expect(StorageMock).toHaveBeenCalledWith(storageConfig);
+ });
+ });
+
+ describe('createState function tests', () => {
+ const StateMock = State as jest.MockedClass;
+
+ it('should create State with the shared Agile Instance', () => {
+ const state = createState('testValue', {
+ key: 'myCoolState',
+ });
+
+ expect(state).toBeInstanceOf(State);
+ expect(StateMock).toHaveBeenCalledWith(sharedAgileInstance, 'testValue', {
+ key: 'myCoolState',
+ });
+ });
+
+ it('should create State with a specified Agile Instance', () => {
+ const agile = new Agile();
+
+ const state = createState('testValue', {
+ key: 'myCoolState',
+ agileInstance: agile,
+ });
+
+ expect(state).toBeInstanceOf(State);
+ expect(StateMock).toHaveBeenCalledWith(agile, 'testValue', {
+ key: 'myCoolState',
+ });
+ });
+ });
+
+ describe('createCollection function tests', () => {
+ const CollectionMock = Collection as jest.MockedClass;
+
+ beforeEach(() => {
+ CollectionMock.mockClear();
+ });
+
+ it('should create Collection with the shared Agile Instance', () => {
+ const collectionConfig = {
+ selectors: ['test', 'test1'],
+ groups: ['test2', 'test10'],
+ defaultGroupKey: 'frank',
+ key: 'myCoolCollection',
+ };
+
+ const collection = createCollection(collectionConfig);
+
+ expect(collection).toBeInstanceOf(Collection);
+ expect(CollectionMock).toHaveBeenCalledWith(
+ sharedAgileInstance,
+ collectionConfig
+ );
+ });
+
+ it('should create Collection with a specified Agile Instance', () => {
+ const agile = new Agile();
+ const collectionConfig = {
+ selectors: ['test', 'test1'],
+ groups: ['test2', 'test10'],
+ defaultGroupKey: 'frank',
+ key: 'myCoolCollection',
+ };
+
+ const collection = createCollection(collectionConfig, agile);
+
+ expect(collection).toBeInstanceOf(Collection);
+ expect(CollectionMock).toHaveBeenCalledWith(agile, collectionConfig);
+ });
+ });
+
+ describe('createComputed function tests', () => {
+ const ComputedMock = Computed as jest.MockedClass;
+ const computedFunction = () => {
+ // empty
+ };
+
+ beforeEach(() => {
+ ComputedMock.mockClear();
+ });
+
+ it('should create Computed with the shared Agile Instance (default config)', () => {
+ const response = createComputed(computedFunction, ['dummyDep' as any]);
+
+ expect(response).toBeInstanceOf(Computed);
+ expect(ComputedMock).toHaveBeenCalledWith(
+ sharedAgileInstance,
+ computedFunction,
+ {
+ computedDeps: ['dummyDep' as any],
+ }
+ );
+ });
+
+ it('should create Computed with the shared Agile Instance (specific config)', () => {
+ const computedConfig = {
+ key: 'jeff',
+ isPlaceholder: false,
+ computedDeps: ['dummyDep' as any],
+ autodetect: true,
+ };
+
+ const response = createComputed(computedFunction, computedConfig);
+
+ expect(response).toBeInstanceOf(Computed);
+ expect(ComputedMock).toHaveBeenCalledWith(
+ sharedAgileInstance,
+ computedFunction,
+ computedConfig
+ );
+ });
+
+ it('should create Computed with a specified Agile Instance (specific config)', () => {
+ const agile = new Agile();
+ const computedConfig = {
+ key: 'jeff',
+ isPlaceholder: false,
+ computedDeps: ['dummyDep' as any],
+ autodetect: true,
+ };
+
+ const response = createComputed(computedFunction, {
+ ...computedConfig,
+ ...{ agileInstance: agile },
+ });
+
+ expect(response).toBeInstanceOf(Computed);
+ expect(ComputedMock).toHaveBeenCalledWith(
+ agile,
+ computedFunction,
+ computedConfig
+ );
+ });
+ });
+});
diff --git a/packages/core/tests/unit/state/state.observer.test.ts b/packages/core/tests/unit/state/state.observer.test.ts
index 32a99109..035ec4a5 100644
--- a/packages/core/tests/unit/state/state.observer.test.ts
+++ b/packages/core/tests/unit/state/state.observer.test.ts
@@ -17,11 +17,12 @@ describe('StateObserver Tests', () => {
let dummyState: State;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyAgile = new Agile({ localStorage: false });
dummyState = new State(dummyAgile, 'dummyValue', { key: 'dummyState' });
+
+ jest.clearAllMocks();
});
it('should create State Observer (default config)', () => {
diff --git a/packages/core/tests/unit/state/state.persistent.test.ts b/packages/core/tests/unit/state/state.persistent.test.ts
index 1fc7d061..65603523 100644
--- a/packages/core/tests/unit/state/state.persistent.test.ts
+++ b/packages/core/tests/unit/state/state.persistent.test.ts
@@ -12,7 +12,6 @@ describe('StatePersistent Tests', () => {
let dummyState: State;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyAgile = new Agile({ localStorage: false });
@@ -20,6 +19,8 @@ describe('StatePersistent Tests', () => {
jest.spyOn(StatePersistent.prototype, 'instantiatePersistent');
jest.spyOn(StatePersistent.prototype, 'initialLoading');
+
+ jest.clearAllMocks();
});
it("should create StatePersistent and shouldn't call initialLoading if Persistent isn't ready (default config)", () => {
diff --git a/packages/core/tests/unit/state/state.runtime.job.test.ts b/packages/core/tests/unit/state/state.runtime.job.test.ts
index 58906f56..85df6e56 100644
--- a/packages/core/tests/unit/state/state.runtime.job.test.ts
+++ b/packages/core/tests/unit/state/state.runtime.job.test.ts
@@ -16,7 +16,6 @@ describe('RuntimeJob Tests', () => {
let dummyObserver: StateObserver;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyAgile = new Agile({ localStorage: false });
@@ -25,6 +24,8 @@ describe('RuntimeJob Tests', () => {
});
dummyState = new State(dummyAgile, 'dummyValue');
dummyObserver = new StateObserver(dummyState);
+
+ jest.clearAllMocks();
});
it(
diff --git a/packages/core/tests/unit/state/state.test.ts b/packages/core/tests/unit/state/state.test.ts
index 3bfe253f..78b3afe9 100644
--- a/packages/core/tests/unit/state/state.test.ts
+++ b/packages/core/tests/unit/state/state.test.ts
@@ -15,12 +15,13 @@ describe('State Tests', () => {
let dummyAgile: Agile;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyAgile = new Agile({ localStorage: false });
jest.spyOn(State.prototype, 'set');
+
+ jest.clearAllMocks();
});
it('should create State and should call initial set (default config)', () => {
diff --git a/packages/core/tests/unit/storages/persistent.test.ts b/packages/core/tests/unit/storages/persistent.test.ts
index 79ca7ca2..b038fff3 100644
--- a/packages/core/tests/unit/storages/persistent.test.ts
+++ b/packages/core/tests/unit/storages/persistent.test.ts
@@ -5,12 +5,13 @@ describe('Persistent Tests', () => {
let dummyAgile: Agile;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyAgile = new Agile({ localStorage: false });
jest.spyOn(Persistent.prototype, 'instantiatePersistent');
+
+ jest.clearAllMocks();
});
it('should create Persistent (default config)', () => {
diff --git a/packages/core/tests/unit/storages/storage.test.ts b/packages/core/tests/unit/storages/storage.test.ts
index 1d1442c8..bfe220c0 100644
--- a/packages/core/tests/unit/storages/storage.test.ts
+++ b/packages/core/tests/unit/storages/storage.test.ts
@@ -5,7 +5,6 @@ describe('Storage Tests', () => {
let dummyStorageMethods;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyStorageMethods = {
@@ -16,6 +15,8 @@ describe('Storage Tests', () => {
// https://codewithhugo.com/jest-stub-mock-spy-set-clear/
jest.spyOn(Storage.prototype, 'validate');
+
+ jest.clearAllMocks();
});
it('should create not async Storage with normal Storage Methods (default config)', () => {
diff --git a/packages/core/tests/unit/storages/storages.test.ts b/packages/core/tests/unit/storages/storages.test.ts
index efdb57a3..139a7e29 100644
--- a/packages/core/tests/unit/storages/storages.test.ts
+++ b/packages/core/tests/unit/storages/storages.test.ts
@@ -5,12 +5,13 @@ describe('Storages Tests', () => {
let dummyAgile;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyAgile = new Agile({ localStorage: false });
jest.spyOn(Storages.prototype, 'instantiateLocalStorage');
+
+ jest.clearAllMocks();
});
it('should create Storages (default config)', () => {
diff --git a/packages/core/tests/unit/utils.test.ts b/packages/core/tests/unit/utils.test.ts
index db4bd7e3..96decb42 100644
--- a/packages/core/tests/unit/utils.test.ts
+++ b/packages/core/tests/unit/utils.test.ts
@@ -1,12 +1,11 @@
import {
- globalBind,
- getAgileInstance,
Agile,
State,
Observer,
Collection,
StateObserver,
GroupObserver,
+ assignSharedAgileInstance,
} from '../../src';
import * as Utils from '../../src/utils';
import { LogMock } from '../helper/logMock';
@@ -15,47 +14,68 @@ describe('Utils Tests', () => {
let dummyAgile: Agile;
beforeEach(() => {
- jest.clearAllMocks();
LogMock.mockLogs();
dummyAgile = new Agile({ localStorage: false });
// @ts-ignore | Reset globalThis
globalThis = {};
+
+ jest.clearAllMocks();
});
describe('getAgileInstance function tests', () => {
beforeEach(() => {
+ assignSharedAgileInstance(dummyAgile);
globalThis[Agile.globalKey] = dummyAgile;
});
- it('should get agileInstance from State', () => {
+ it('should return Agile Instance from State', () => {
const dummyState = new State(dummyAgile, 'dummyValue');
- expect(getAgileInstance(dummyState)).toBe(dummyAgile);
+ expect(Utils.getAgileInstance(dummyState)).toBe(dummyAgile);
});
- it('should get agileInstance from Collection', () => {
+ it('should return Agile Instance from Collection', () => {
const dummyCollection = new Collection(dummyAgile);
- expect(getAgileInstance(dummyCollection)).toBe(dummyAgile);
+ expect(Utils.getAgileInstance(dummyCollection)).toBe(dummyAgile);
});
- it('should get agileInstance from Observer', () => {
+ it('should return Agile Instance from Observer', () => {
const dummyObserver = new Observer(dummyAgile);
- expect(getAgileInstance(dummyObserver)).toBe(dummyAgile);
+ expect(Utils.getAgileInstance(dummyObserver)).toBe(dummyAgile);
});
- it('should get agileInstance from globalThis if passed instance holds no agileInstance', () => {
- expect(getAgileInstance('weiredInstance')).toBe(dummyAgile);
- });
-
- it('should print error if something went wrong', () => {
+ it(
+ 'should return shared Agile Instance ' +
+ 'if specified Instance contains no valid Agile Instance',
+ () => {
+ expect(Utils.getAgileInstance('weiredInstance')).toBe(dummyAgile);
+ }
+ );
+
+ it(
+ 'should return globally bound Agile Instance' +
+ 'if specified Instance contains no valid Agile Instance' +
+ 'and no shared Agile Instance is specified',
+ () => {
+ // Destroy shared Agile Instance
+ assignSharedAgileInstance(undefined as any);
+
+ expect(Utils.getAgileInstance('weiredInstance')).toBe(dummyAgile);
+ }
+ );
+
+ it('should print error if no Agile Instance could be retrieved', () => {
// @ts-ignore | Destroy globalThis
globalThis = undefined;
- const response = getAgileInstance('weiredInstance');
+ // Destroy shared Agile Instance
+ assignSharedAgileInstance(undefined as any);
+
+ const response = Utils.getAgileInstance('weiredInstance');
expect(response).toBeUndefined();
LogMock.hasLoggedCode('20:03:00', [], 'weiredInstance');
@@ -360,23 +380,23 @@ describe('Utils Tests', () => {
});
it('should bind Instance globally at the specified key (default config)', () => {
- globalBind(dummyKey, 'dummyInstance');
+ Utils.globalBind(dummyKey, 'dummyInstance');
expect(globalThis[dummyKey]).toBe('dummyInstance');
});
it("shouldn't overwrite already globally bound Instance at the same key (default config)", () => {
- globalBind(dummyKey, 'I am first!');
+ Utils.globalBind(dummyKey, 'I am first!');
- globalBind(dummyKey, 'dummyInstance');
+ Utils.globalBind(dummyKey, 'dummyInstance');
expect(globalThis[dummyKey]).toBe('I am first!');
});
it('should overwrite already globally bound Instance at the same key (overwrite = true)', () => {
- globalBind(dummyKey, 'I am first!');
+ Utils.globalBind(dummyKey, 'I am first!');
- globalBind(dummyKey, 'dummyInstance', true);
+ Utils.globalBind(dummyKey, 'dummyInstance', true);
expect(globalThis[dummyKey]).toBe('dummyInstance');
});
@@ -385,9 +405,27 @@ describe('Utils Tests', () => {
// @ts-ignore | Destroy globalThis
globalThis = undefined;
- globalBind(dummyKey, 'dummyInstance');
+ Utils.globalBind(dummyKey, 'dummyInstance');
LogMock.hasLoggedCode('20:03:01', [dummyKey]);
});
});
+
+ describe('runsOnServer function tests', () => {
+ it("should return 'false' if the current environment isn't a server", () => {
+ global.window = {
+ document: {
+ createElement: 'isSet' as any,
+ } as any,
+ } as any;
+
+ expect(Utils.runsOnServer()).toBeFalsy();
+ });
+
+ it("should return 'true' if the current environment is a server", () => {
+ global.window = undefined as any;
+
+ expect(Utils.runsOnServer()).toBeTruthy();
+ });
+ });
});
diff --git a/packages/cra-template-agile-typescript/README.md b/packages/cra-template-agile-typescript/README.md
index 7ff1b678..4b973574 100644
--- a/packages/cra-template-agile-typescript/README.md
+++ b/packages/cra-template-agile-typescript/README.md
@@ -24,4 +24,4 @@ npx create-react-app my-app --template agile-typescript
## Credits
-https://github.com/facebook/create-react-app/blob/master/packages/cra-template-typescript
\ No newline at end of file
+https://github.com/facebook/create-react-app/blob/master/packages/cra-template-typescript
diff --git a/packages/event/README.md b/packages/event/README.md
index 18503089..5b52052c 100644
--- a/packages/event/README.md
+++ b/packages/event/README.md
@@ -16,8 +16,7 @@
## ⏰ Short Example
```ts
-const App = new Agile();
-const MY_EVENT = new Event(App);
+const MY_EVENT = createEvent();
MY_EVENT.on((data) => {console.log("hello there " + data.name)}); // Print 'hello there jeff' if Event gets triggered
MY_EVENT.trigger({name: "jeff"}); // Trigger Event
```
@@ -39,4 +38,4 @@ _Other Versions aren't supported anymore_
## 📄 Documentation
-The Agile Event Docs are located [here](https://agile-ts.org/docs/)
\ No newline at end of file
+The Agile Event Docs are located [here](https://agile-ts.org/docs/)
diff --git a/packages/event/src/event.ts b/packages/event/src/event.ts
index 3e4a1c55..8291b1a6 100644
--- a/packages/event/src/event.ts
+++ b/packages/event/src/event.ts
@@ -1,8 +1,8 @@
import {
Agile,
- defineConfig,
generateId,
isFunction,
+ LogCodeManager,
Observer,
} from '@agile-ts/core';
import { EventObserver, EventJob } from './internal';
@@ -33,14 +33,15 @@ export class Event {
*/
constructor(agileInstance: Agile, config: CreateEventConfigInterface = {}) {
this.agileInstance = () => agileInstance;
- config = defineConfig(config, {
+ config = {
enabled: true,
rerender: false,
maxUses: undefined,
delay: undefined,
overlap: false,
dependents: [],
- });
+ ...config,
+ };
this._key = config.key;
this.observer = new EventObserver(this, {
key: config.key,
@@ -122,7 +123,7 @@ export class Event {
// Check if Callback is a Function
if (!isFunction(_callback)) {
- Agile.logger.error(
+ LogCodeManager.getLogger()?.error(
'A Event Callback Function has to be typeof Function!'
);
return this;
@@ -130,7 +131,7 @@ export class Event {
// Check if Callback Function already exists
if (this.callbacks[key]) {
- Agile.logger.error(
+ LogCodeManager.getLogger()?.error(
`Event Callback Function with the key/name '${key}' already exists!`
);
return this;
diff --git a/packages/event/src/hooks/useEvent.ts b/packages/event/src/hooks/useEvent.ts
index 905d6413..5a1d3168 100644
--- a/packages/event/src/hooks/useEvent.ts
+++ b/packages/event/src/hooks/useEvent.ts
@@ -2,6 +2,7 @@ import React from 'react';
import {
Agile,
getAgileInstance,
+ LogCodeManager,
SubscriptionContainerKeyType,
} from '@agile-ts/core';
import { Event, EventCallbackFunction } from '../internal';
@@ -22,7 +23,10 @@ export function useEvent>(
// Get Agile Instance
if (!agileInstance) agileInstance = getAgileInstance(event);
if (!agileInstance || !agileInstance.subController) {
- Agile.logger.error('Failed to subscribe Component with deps', event);
+ LogCodeManager.getLogger()?.error(
+ 'Failed to subscribe Component with deps',
+ event
+ );
return;
}
diff --git a/packages/event/src/internal.ts b/packages/event/src/internal.ts
index e63a1ffb..695b0fbb 100644
--- a/packages/event/src/internal.ts
+++ b/packages/event/src/internal.ts
@@ -4,6 +4,10 @@
// !! All internal Agile Editor modules must be imported from here!!
+// Event
export * from './event.job';
export * from './event.observer';
export * from './event';
+
+// Shared
+export * from './shared';
diff --git a/packages/event/src/shared.ts b/packages/event/src/shared.ts
new file mode 100644
index 00000000..7aadc8a8
--- /dev/null
+++ b/packages/event/src/shared.ts
@@ -0,0 +1,27 @@
+import {
+ CreateAgileSubInstanceInterface,
+ removeProperties,
+ shared,
+} from '@agile-ts/core';
+import {
+ Event,
+ CreateEventConfigInterface,
+ DefaultEventPayload,
+} from './internal';
+
+export function createEvent(
+ config: CreateEventConfigInterfaceWithAgile = {}
+): Event {
+ config = {
+ agileInstance: shared,
+ ...config,
+ };
+ return new Event(
+ config.agileInstance as any,
+ removeProperties(config, ['agileInstance'])
+ );
+}
+
+export interface CreateEventConfigInterfaceWithAgile
+ extends CreateEventConfigInterface,
+ CreateAgileSubInstanceInterface {}
diff --git a/packages/event/tests/unit/event.job.test.ts b/packages/event/tests/unit/event.job.test.ts
index 7c475015..90397943 100644
--- a/packages/event/tests/unit/event.job.test.ts
+++ b/packages/event/tests/unit/event.job.test.ts
@@ -1,10 +1,10 @@
import { EventJob } from '../../src';
-import mockConsole from 'jest-mock-console';
+import { LogMock } from '../../../core/tests/helper/logMock';
describe('EventJob Tests', () => {
beforeEach(() => {
+ LogMock.mockLogs();
jest.clearAllMocks();
- mockConsole(['error', 'warn']);
});
it('should create EventJob (without keys)', () => {
diff --git a/packages/event/tests/unit/event.observer.test.ts b/packages/event/tests/unit/event.observer.test.ts
index 221bd2b9..b700a72f 100644
--- a/packages/event/tests/unit/event.observer.test.ts
+++ b/packages/event/tests/unit/event.observer.test.ts
@@ -1,17 +1,18 @@
import { EventObserver, Event } from '../../src';
import { Agile, Observer, SubscriptionContainer } from '@agile-ts/core';
-import mockConsole from 'jest-mock-console';
+import { LogMock } from '../../../core/tests/helper/logMock';
describe('EventObserver Tests', () => {
let dummyAgile: Agile;
let dummyEvent: Event;
beforeEach(() => {
- jest.clearAllMocks();
- mockConsole(['error', 'warn']);
+ LogMock.mockLogs();
dummyAgile = new Agile({ localStorage: false });
dummyEvent = new Event(dummyAgile);
+
+ jest.clearAllMocks();
});
it('should create EventObserver (default config)', () => {
diff --git a/packages/event/tests/unit/event.test.ts b/packages/event/tests/unit/event.test.ts
index f2bdd61f..bd369952 100644
--- a/packages/event/tests/unit/event.test.ts
+++ b/packages/event/tests/unit/event.test.ts
@@ -1,16 +1,17 @@
import { Event, EventObserver } from '../../src';
import { Agile, Observer } from '@agile-ts/core';
import * as Utils from '@agile-ts/utils';
-import mockConsole from 'jest-mock-console';
+import { LogMock } from '../../../core/tests/helper/logMock';
describe('Event Tests', () => {
let dummyAgile: Agile;
beforeEach(() => {
- jest.clearAllMocks();
- mockConsole(['error', 'warn']);
+ LogMock.mockLogs();
dummyAgile = new Agile({ localStorage: false });
+
+ jest.clearAllMocks();
});
it('should create Event (default config)', () => {
diff --git a/packages/event/tests/unit/shared.test.ts b/packages/event/tests/unit/shared.test.ts
new file mode 100644
index 00000000..7b5719a0
--- /dev/null
+++ b/packages/event/tests/unit/shared.test.ts
@@ -0,0 +1,51 @@
+import { Agile, assignSharedAgileInstance } from '@agile-ts/core';
+import { Event, createEvent } from '../../src';
+import { LogMock } from '../../../core/tests/helper/logMock';
+
+jest.mock('../../src/event');
+
+describe('Shared Tests', () => {
+ let sharedAgileInstance: Agile;
+
+ beforeEach(() => {
+ LogMock.mockLogs();
+
+ sharedAgileInstance = new Agile();
+ assignSharedAgileInstance(sharedAgileInstance);
+
+ jest.clearAllMocks();
+ });
+
+ describe('createEvent function tests', () => {
+ const EventMock = Event as jest.MockedClass;
+
+ it('should create Event with the shared Agile Instance', () => {
+ const event = createEvent({
+ key: 'myCoolEvent',
+ delay: 10,
+ });
+
+ expect(event).toBeInstanceOf(Event);
+ expect(EventMock).toHaveBeenCalledWith(sharedAgileInstance, {
+ key: 'myCoolEvent',
+ delay: 10,
+ });
+ });
+
+ it('should create Event with a specified Agile Instance', () => {
+ const agile = new Agile();
+
+ const event = createEvent({
+ key: 'myCoolEvent',
+ delay: 10,
+ agileInstance: agile,
+ });
+
+ expect(event).toBeInstanceOf(Event);
+ expect(EventMock).toHaveBeenCalledWith(agile, {
+ key: 'myCoolEvent',
+ delay: 10,
+ });
+ });
+ });
+});
diff --git a/packages/logger/src/index.ts b/packages/logger/src/index.ts
index e3a2a889..2012864f 100644
--- a/packages/logger/src/index.ts
+++ b/packages/logger/src/index.ts
@@ -1,491 +1,32 @@
-import {
- defineConfig,
- generateId,
- includesArray,
- isFunction,
- isValidObject,
-} from '@agile-ts/utils';
+import { CreateLoggerConfigInterface, Logger } from './logger';
-export class Logger {
- public key?: LoggerKey;
-
- public isActive: boolean;
- public config: LoggerConfigInterface;
- public allowedTags: string[] = [];
- public loggerCategories: { [key: string]: LoggerCategoryInterface } = {}; // Holds all registered Logger Categories
- public watchers: {
- [key: string]: LoggerWatcherConfigInterface;
- } = {};
-
- /**
- * @public
- * Logger - Handy Class for handling console.logs
- */
- constructor(config: LoggerConfig = {}) {
- let _config = typeof config === 'function' ? config(this) : config;
- _config = defineConfig(_config, {
- prefix: '',
- allowedTags: [],
- canUseCustomStyles: true,
- active: true,
- level: 0,
- timestamp: false,
- });
- this.isActive = _config.active as any;
- this.allowedTags = _config.allowedTags as any;
- this.config = {
- timestamp: _config.timestamp as any,
- prefix: _config.prefix as any,
- canUseCustomStyles: _config.canUseCustomStyles as any,
- level: _config.level as any,
- };
- this.addDefaultLoggerCategories();
- }
-
- /**
- * @public
- * Adds Conditions to Logs
- */
- public get if() {
- return {
- tag: (tags: string[]) => this.tag(tags),
- };
- }
-
- /**
- * @public
- * Default Levels of Logger
- */
- static get level() {
- return {
- TRACE: 1,
- DEBUG: 2,
- LOG: 5,
- TABLE: 5,
- INFO: 10,
- SUCCESS: 15,
- WARN: 20,
- ERROR: 50,
- };
- }
-
- //=========================================================================================================
- // Add Default Logger Categories
- //=========================================================================================================
- /**
- * @internal
- * Adds Default Logger Categories
- */
- private addDefaultLoggerCategories() {
- this.createLoggerCategory({
- key: 'log',
- level: Logger.level.LOG,
- });
- this.createLoggerCategory({
- key: 'debug',
- customStyle: 'color: #656565;',
- prefix: 'Debug',
- level: Logger.level.DEBUG,
- });
- this.createLoggerCategory({
- key: 'info',
- customStyle: 'color: #6c69a0;',
- prefix: 'Info',
- level: Logger.level.INFO,
- });
- this.createLoggerCategory({
- key: 'success',
- customStyle: 'color: #00b300;',
- prefix: 'Success',
- level: Logger.level.SUCCESS,
- });
- this.createLoggerCategory({
- key: 'warn',
- prefix: 'Warn',
- level: Logger.level.WARN,
- });
- this.createLoggerCategory({
- key: 'error',
- prefix: 'Error',
- level: Logger.level.ERROR,
- });
- this.createLoggerCategory({
- key: 'trace',
- prefix: 'Trace',
- level: Logger.level.TRACE,
- });
- this.createLoggerCategory({
- key: 'table',
- level: Logger.level.TABLE,
- });
- }
-
- //=========================================================================================================
- // Tag
- //=========================================================================================================
- /**
- * @internal
- * Only executes following 'command' if all given tags are included in allowedTags
- * @param tags - Tags
- */
- private tag(tags: string[]) {
- if (includesArray(this.allowedTags, tags)) {
- return {
- log: (...data: any[]) => this.log(...data),
- debug: (...data: any[]) => this.debug(...data),
- info: (...data: any[]) => this.info(...data),
- success: (...data: any[]) => this.success(...data),
- warn: (...data: any[]) => this.warn(...data),
- error: (...data: any[]) => this.error(...data),
- trace: (...data: any[]) => this.trace(...data),
- table: (...data: any[]) => this.table(...data),
- };
- }
- return {
- log: () => {
- /* do nothing */
- },
- debug: () => {
- /* do nothing */
- },
- info: () => {
- /* do nothing */
- },
- success: () => {
- /* do nothing */
- },
- warn: () => {
- /* do nothing */
- },
- error: () => {
- /* do nothing */
- },
- trace: () => {
- /* do nothing */
- },
- table: () => {
- /* do nothing */
- },
- };
- }
-
- public log(...data: any[]) {
- this.invokeConsole(data, 'log', 'log');
- }
-
- public debug(...data: any[]) {
- this.invokeConsole(
- data,
- 'debug',
- typeof console.debug !== 'undefined' ? 'debug' : 'log'
- );
- }
-
- public info(...data: any[]) {
- this.invokeConsole(
- data,
- 'info',
- typeof console.info !== 'undefined' ? 'info' : 'log'
- );
- }
-
- public success(...data: any[]) {
- this.invokeConsole(data, 'success', 'log');
- }
-
- public warn(...data: any[]) {
- this.invokeConsole(
- data,
- 'warn',
- typeof console.warn !== 'undefined' ? 'warn' : 'log'
- );
- }
-
- public error(...data: any[]) {
- this.invokeConsole(
- data,
- 'error',
- typeof console.error !== 'undefined' ? 'error' : 'log'
- );
- }
-
- public trace(...data: any[]) {
- this.invokeConsole(
- data,
- 'trace',
- typeof console.trace !== 'undefined' ? 'trace' : 'log'
- );
- }
-
- public table(...data: any[]) {
- this.invokeConsole(
- data,
- 'table',
- typeof console.table !== 'undefined' ? 'table' : 'log'
- );
- }
-
- public custom(loggerCategory: string, ...data: any[]) {
- this.invokeConsole(data, loggerCategory, 'log');
- }
-
- //=========================================================================================================
- // Invoke Console
- //=========================================================================================================
- /**
- * @internal
- * Logs data in Console
- * @param data - Data
- * @param loggerCategoryKey - Key/Name of Logger Category
- * @param consoleLogType - console[consoleLogProperty]
- */
- private invokeConsole(
- data: any[],
- loggerCategoryKey: LoggerCategoryKey,
- consoleLogType: ConsoleLogType
- ) {
- const loggerCategory = this.getLoggerCategory(loggerCategoryKey);
-
- // Check if Logger Category is allowed
- if (!this.isActive || loggerCategory.level < this.config.level) return;
-
- // Build Prefix of Log
- const buildPrefix = (): string => {
- let prefix = '';
- if (this.config.timestamp)
- prefix = prefix.concat(`[${Date.now().toString()}] `);
- if (this.config.prefix) prefix = prefix.concat(this.config.prefix);
- if (loggerCategory.prefix)
- prefix = prefix.concat(' ' + loggerCategory.prefix);
- if (this.config.prefix || loggerCategory.prefix)
- prefix = prefix.concat(':');
- return prefix;
- };
-
- // Add built Prefix
- if (typeof data[0] === 'string')
- data[0] = buildPrefix().concat(' ').concat(data[0]);
- else data.unshift(buildPrefix());
-
- // Call Watcher Callbacks
- for (const key in this.watchers) {
- const watcher = this.watchers[key];
- if (loggerCategory.level >= (watcher.level || 0)) {
- watcher.callback(loggerCategory, data);
- }
- }
-
- // Init Custom Style
- if (this.config.canUseCustomStyles && loggerCategory.customStyle) {
- const newLogs: any[] = [];
- let hasStyledString = false; // NOTE: Only one style can be used for one String block!
- for (const log of data) {
- if (!hasStyledString && typeof log === 'string') {
- newLogs.push(`%c${log}`);
- newLogs.push(loggerCategory.customStyle);
- hasStyledString = true;
- } else {
- newLogs.push(log);
- }
- }
- data = newLogs;
- }
-
- // Handle Console Table Log
- if (consoleLogType === 'table') {
- if (typeof data[0] === 'string') {
- console.log(data[0]);
- console.table(data.filter((d) => typeof d !== 'string' && 'number'));
- }
- return;
- }
-
- // Normal Log
- console[consoleLogType](...data);
- }
-
- //=========================================================================================================
- // Create Logger Category
- //=========================================================================================================
- /**
- * @public
- * Creates new Logger Category
- * @param loggerCategory - Logger Category
- */
- public createLoggerCategory(loggerCategory: LoggerCategoryInterface) {
- loggerCategory = defineConfig(loggerCategory, {
- prefix: '',
- level: 0,
- });
- this.loggerCategories[loggerCategory.key] = loggerCategory;
- }
-
- //=========================================================================================================
- // Get Logger Category
- //=========================================================================================================
- /**
- * @public
- * Get Logger Category
- * @param key - Key/Name of Logger Category
- */
- public getLoggerCategory(key: LoggerCategoryKey) {
- return this.loggerCategories[key];
- }
-
- //=========================================================================================================
- // Watch
- //=========================================================================================================
- /**
- * @public
- * Watches Logger and detects Logs
- * @param config - Config
- * @return Key of Watcher Function
- */
- public watch(config: LoggerWatcherConfigInterface): string;
- /**
- * @public
- * Watches Logger and detects Logs
- * @param key - Key of Watcher Function
- * @param config - Config
- */
- public watch(key: string, config: LoggerWatcherConfigInterface): this;
- public watch(
- keyOrConfig: string | LoggerWatcherConfigInterface,
- config?: LoggerWatcherConfigInterface
- ): this | string {
- const generateKey = isValidObject(keyOrConfig);
- let _config: LoggerWatcherConfigInterface;
- let key: string;
-
- if (generateKey) {
- key = generateId();
- _config = keyOrConfig as LoggerWatcherConfigInterface;
- } else {
- key = keyOrConfig as string;
- _config = config as LoggerWatcherConfigInterface;
- }
-
- _config = defineConfig(_config, {
- level: 0,
- });
-
- // Check if Callback is a Function
- if (!isFunction(_config.callback)) {
- console.error(
- 'Agile: A Watcher Callback Function has to be an function!'
- );
- 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] = _config;
- return generateKey ? key : this;
- }
-
- //=========================================================================================================
- // Remove Watcher
- //=========================================================================================================
- /**
- * @public
- * Removes Watcher at given Key
- * @param key - Key of Watcher that gets removed
- */
- public removeWatcher(key: string): this {
- delete this.watchers[key];
- return this;
- }
-
- //=========================================================================================================
- // Set Level
- //=========================================================================================================
- /**
- * @public
- * Assigns new Level to Logger
- * NOTE: Default Levels can be found in 'Logger.level.x'
- * @param level - Level
- */
- public setLevel(level: number): this {
- this.config.level = level;
- return this;
- }
-}
-
-export type LoggerCategoryKey = string | number;
-export type LoggerKey = string | number;
+const defaultLogConfig = {
+ prefix: 'Agile',
+ active: true,
+ level: Logger.level.WARN,
+ canUseCustomStyles: true,
+ allowedTags: ['runtime', 'storage', 'subscription', 'multieditor'],
+};
/**
- * @param key - Key/Name of Logger Category
- * @param customStyle - Css Styles that get applied to the Logs
- * @param prefix - Prefix that gets written before each Log of this Category
- * @param level - Until which Level this Logger Category gets logged
+ * Shared Agile Logger.
*/
-export interface LoggerCategoryInterface {
- key: LoggerCategoryKey;
- customStyle?: string;
- prefix?: string;
- level: number;
-}
-
-/**
- * @param prefix - Prefix that gets written before each log of this Logger
- * @param canUseCustomStyles - If custom Styles can be applied to the Logs
- * @param level - Handles which Logger Categories can be Logged
- * @param timestamp - Timestamp that ges written before each log of this Logger
- */
-export interface LoggerConfigInterface {
- prefix: string;
- canUseCustomStyles: boolean;
- level: number;
- timestamp: boolean;
-}
+let sharedAgileLogger = new Logger(defaultLogConfig);
/**
- * @param prefix - Prefix that gets written before each log of this Logger
- * @param allowedTags - Only Logs that, contains the allowed Tags or have no Tag get logged
- * @param canUseCustomStyles - If custom Styles can be applied to the Logs
- * @param active - If Logger is active
- * @param level - Handles which Logger Categories can be Logged
- * @param timestamp - Timestamp that ges written before each log of this Logger
+ * Assigns the specified configuration object to the shared Agile Logger.
+ *
+ * @param config - Configuration object
*/
-export interface CreateLoggerConfigInterface {
- prefix?: string;
- allowedTags?: LoggerKey[];
- canUseCustomStyles?: boolean;
- active?: boolean;
- level?: number;
- timestamp?: boolean;
+// https://stackoverflow.com/questions/32558514/javascript-es6-export-const-vs-export-let
+function assignSharedAgileLoggerConfig(
+ config: CreateLoggerConfigInterface = {}
+): Logger {
+ config = { ...defaultLogConfig, ...config };
+ sharedAgileLogger = new Logger(config);
+ return sharedAgileLogger;
}
-export type LoggerConfig =
- | CreateLoggerConfigInterface
- | ((logger: Logger) => CreateLoggerConfigInterface);
-
-export type ConsoleLogType =
- | 'log'
- | 'warn'
- | 'error'
- | 'trace'
- | 'table'
- | 'info'
- | 'debug';
-
-export type LoggerWatcherCallback = (
- loggerCategory: LoggerCategoryInterface,
- data: any[]
-) => void;
-
-/**
- * @param callback - Callback Function that gets called if something gets Logged
- * @param level - At which level the watcher is called
- */
-export interface LoggerWatcherConfigInterface {
- callback: LoggerWatcherCallback;
- level?: number;
-}
+export { sharedAgileLogger, assignSharedAgileLoggerConfig };
+export * from './logger';
+export default Logger;
diff --git a/packages/logger/src/logger.ts b/packages/logger/src/logger.ts
new file mode 100644
index 00000000..b6756030
--- /dev/null
+++ b/packages/logger/src/logger.ts
@@ -0,0 +1,494 @@
+import {
+ generateId,
+ includesArray,
+ isFunction,
+ isValidObject,
+} from '@agile-ts/utils';
+
+export class Logger {
+ public key?: LoggerKey;
+
+ public isActive: boolean;
+ public config: LoggerConfigInterface;
+ public allowedTags: string[] = [];
+ public loggerCategories: { [key: string]: LoggerCategoryInterface } = {}; // Holds all registered Logger Categories
+ public watchers: {
+ [key: string]: LoggerWatcherConfigInterface;
+ } = {};
+
+ /**
+ * @public
+ * Logger - Handy Class for handling console.logs
+ */
+ constructor(config: LoggerConfig = {}) {
+ let _config = typeof config === 'function' ? config(this) : config;
+ _config = {
+ prefix: '',
+ allowedTags: [],
+ canUseCustomStyles: true,
+ active: true,
+ level: 0,
+ timestamp: false,
+ ..._config,
+ };
+ this.isActive = _config.active as any;
+ this.allowedTags = _config.allowedTags as any;
+ this.config = {
+ timestamp: _config.timestamp as any,
+ prefix: _config.prefix as any,
+ canUseCustomStyles: _config.canUseCustomStyles as any,
+ level: _config.level as any,
+ };
+ this.addDefaultLoggerCategories();
+ }
+
+ /**
+ * @public
+ * Adds Conditions to Logs
+ */
+ public get if() {
+ return {
+ tag: (tags: string[]) => this.tag(tags),
+ };
+ }
+
+ /**
+ * @public
+ * Default Levels of Logger
+ */
+ static get level() {
+ return {
+ TRACE: 1,
+ DEBUG: 2,
+ LOG: 5,
+ TABLE: 5,
+ INFO: 10,
+ SUCCESS: 15,
+ WARN: 20,
+ ERROR: 50,
+ };
+ }
+
+ //=========================================================================================================
+ // Add Default Logger Categories
+ //=========================================================================================================
+ /**
+ * @internal
+ * Adds Default Logger Categories
+ */
+ private addDefaultLoggerCategories() {
+ this.createLoggerCategory({
+ key: 'log',
+ level: Logger.level.LOG,
+ });
+ this.createLoggerCategory({
+ key: 'debug',
+ customStyle: 'color: #656565;',
+ prefix: 'Debug',
+ level: Logger.level.DEBUG,
+ });
+ this.createLoggerCategory({
+ key: 'info',
+ customStyle: 'color: #6c69a0;',
+ prefix: 'Info',
+ level: Logger.level.INFO,
+ });
+ this.createLoggerCategory({
+ key: 'success',
+ customStyle: 'color: #00b300;',
+ prefix: 'Success',
+ level: Logger.level.SUCCESS,
+ });
+ this.createLoggerCategory({
+ key: 'warn',
+ prefix: 'Warn',
+ level: Logger.level.WARN,
+ });
+ this.createLoggerCategory({
+ key: 'error',
+ prefix: 'Error',
+ level: Logger.level.ERROR,
+ });
+ this.createLoggerCategory({
+ key: 'trace',
+ prefix: 'Trace',
+ level: Logger.level.TRACE,
+ });
+ this.createLoggerCategory({
+ key: 'table',
+ level: Logger.level.TABLE,
+ });
+ }
+
+ //=========================================================================================================
+ // Tag
+ //=========================================================================================================
+ /**
+ * @internal
+ * Only executes following 'command' if all given tags are included in allowedTags
+ * @param tags - Tags
+ */
+ private tag(tags: string[]) {
+ if (includesArray(this.allowedTags, tags)) {
+ return {
+ log: (...data: any[]) => this.log(...data),
+ debug: (...data: any[]) => this.debug(...data),
+ info: (...data: any[]) => this.info(...data),
+ success: (...data: any[]) => this.success(...data),
+ warn: (...data: any[]) => this.warn(...data),
+ error: (...data: any[]) => this.error(...data),
+ trace: (...data: any[]) => this.trace(...data),
+ table: (...data: any[]) => this.table(...data),
+ };
+ }
+ return {
+ log: () => {
+ /* do nothing */
+ },
+ debug: () => {
+ /* do nothing */
+ },
+ info: () => {
+ /* do nothing */
+ },
+ success: () => {
+ /* do nothing */
+ },
+ warn: () => {
+ /* do nothing */
+ },
+ error: () => {
+ /* do nothing */
+ },
+ trace: () => {
+ /* do nothing */
+ },
+ table: () => {
+ /* do nothing */
+ },
+ };
+ }
+
+ public log(...data: any[]) {
+ this.invokeConsole(data, 'log', 'log');
+ }
+
+ public debug(...data: any[]) {
+ this.invokeConsole(
+ data,
+ 'debug',
+ typeof console.debug !== 'undefined' ? 'debug' : 'log'
+ );
+ }
+
+ public info(...data: any[]) {
+ this.invokeConsole(
+ data,
+ 'info',
+ typeof console.info !== 'undefined' ? 'info' : 'log'
+ );
+ }
+
+ public success(...data: any[]) {
+ this.invokeConsole(data, 'success', 'log');
+ }
+
+ public warn(...data: any[]) {
+ this.invokeConsole(
+ data,
+ 'warn',
+ typeof console.warn !== 'undefined' ? 'warn' : 'log'
+ );
+ }
+
+ public error(...data: any[]) {
+ this.invokeConsole(
+ data,
+ 'error',
+ typeof console.error !== 'undefined' ? 'error' : 'log'
+ );
+ }
+
+ public trace(...data: any[]) {
+ this.invokeConsole(
+ data,
+ 'trace',
+ typeof console.trace !== 'undefined' ? 'trace' : 'log'
+ );
+ }
+
+ public table(...data: any[]) {
+ this.invokeConsole(
+ data,
+ 'table',
+ typeof console.table !== 'undefined' ? 'table' : 'log'
+ );
+ }
+
+ public custom(loggerCategory: string, ...data: any[]) {
+ this.invokeConsole(data, loggerCategory, 'log');
+ }
+
+ //=========================================================================================================
+ // Invoke Console
+ //=========================================================================================================
+ /**
+ * @internal
+ * Logs data in Console
+ * @param data - Data
+ * @param loggerCategoryKey - Key/Name of Logger Category
+ * @param consoleLogType - console[consoleLogProperty]
+ */
+ private invokeConsole(
+ data: any[],
+ loggerCategoryKey: LoggerCategoryKey,
+ consoleLogType: ConsoleLogType
+ ) {
+ const loggerCategory = this.getLoggerCategory(loggerCategoryKey);
+
+ // Check if Logger Category is allowed
+ if (!this.isActive || loggerCategory.level < this.config.level) return;
+
+ // Build Prefix of Log
+ const buildPrefix = (): string => {
+ let prefix = '';
+ if (this.config.timestamp)
+ prefix = prefix.concat(`[${Date.now().toString()}] `);
+ if (this.config.prefix) prefix = prefix.concat(this.config.prefix);
+ if (loggerCategory.prefix)
+ prefix = prefix.concat(' ' + loggerCategory.prefix);
+ if (this.config.prefix || loggerCategory.prefix)
+ prefix = prefix.concat(':');
+ return prefix;
+ };
+
+ // Add built Prefix
+ if (typeof data[0] === 'string')
+ data[0] = buildPrefix().concat(' ').concat(data[0]);
+ else data.unshift(buildPrefix());
+
+ // Call Watcher Callbacks
+ for (const key in this.watchers) {
+ const watcher = this.watchers[key];
+ if (loggerCategory.level >= (watcher.level || 0)) {
+ watcher.callback(loggerCategory, data);
+ }
+ }
+
+ // Init Custom Style
+ if (this.config.canUseCustomStyles && loggerCategory.customStyle) {
+ const newLogs: any[] = [];
+ let hasStyledString = false; // NOTE: Only one style can be used for one String block!
+ for (const log of data) {
+ if (!hasStyledString && typeof log === 'string') {
+ newLogs.push(`%c${log}`);
+ newLogs.push(loggerCategory.customStyle);
+ hasStyledString = true;
+ } else {
+ newLogs.push(log);
+ }
+ }
+ data = newLogs;
+ }
+
+ // Handle Console Table Log
+ if (consoleLogType === 'table') {
+ if (typeof data[0] === 'string') {
+ console.log(data[0]);
+ console.table(data.filter((d) => typeof d !== 'string' && 'number'));
+ }
+ return;
+ }
+
+ // Normal Log
+ console[consoleLogType](...data);
+ }
+
+ //=========================================================================================================
+ // Create Logger Category
+ //=========================================================================================================
+ /**
+ * @public
+ * Creates new Logger Category
+ * @param loggerCategory - Logger Category
+ */
+ public createLoggerCategory(loggerCategory: LoggerCategoryInterface) {
+ loggerCategory = {
+ prefix: '',
+ // @ts-ignore
+ level: 0,
+ ...loggerCategory,
+ };
+ this.loggerCategories[loggerCategory.key] = loggerCategory;
+ }
+
+ //=========================================================================================================
+ // Get Logger Category
+ //=========================================================================================================
+ /**
+ * @public
+ * Get Logger Category
+ * @param key - Key/Name of Logger Category
+ */
+ public getLoggerCategory(key: LoggerCategoryKey) {
+ return this.loggerCategories[key];
+ }
+
+ //=========================================================================================================
+ // Watch
+ //=========================================================================================================
+ /**
+ * @public
+ * Watches Logger and detects Logs
+ * @param config - Config
+ * @return Key of Watcher Function
+ */
+ public watch(config: LoggerWatcherConfigInterface): string;
+ /**
+ * @public
+ * Watches Logger and detects Logs
+ * @param key - Key of Watcher Function
+ * @param config - Config
+ */
+ public watch(key: string, config: LoggerWatcherConfigInterface): this;
+ public watch(
+ keyOrConfig: string | LoggerWatcherConfigInterface,
+ config?: LoggerWatcherConfigInterface
+ ): this | string {
+ const generateKey = isValidObject(keyOrConfig);
+ let _config: LoggerWatcherConfigInterface;
+ let key: string;
+
+ if (generateKey) {
+ key = generateId();
+ _config = keyOrConfig as LoggerWatcherConfigInterface;
+ } else {
+ key = keyOrConfig as string;
+ _config = config as LoggerWatcherConfigInterface;
+ }
+
+ _config = {
+ level: 0,
+ ..._config,
+ };
+
+ // Check if Callback is a Function
+ if (!isFunction(_config.callback)) {
+ console.error(
+ 'Agile: A Watcher Callback Function has to be an function!'
+ );
+ return this;
+ }
+
+ // Check if Callback Function already exists
+ if (this.watchers[key] != null) {
+ console.error(
+ `Agile: Watcher Callback Function with the key/name ${key} already exists!`
+ );
+ return this;
+ }
+
+ this.watchers[key] = _config;
+ return generateKey ? key : this;
+ }
+
+ //=========================================================================================================
+ // Remove Watcher
+ //=========================================================================================================
+ /**
+ * @public
+ * Removes Watcher at given Key
+ * @param key - Key of Watcher that gets removed
+ */
+ public removeWatcher(key: string): this {
+ delete this.watchers[key];
+ return this;
+ }
+
+ //=========================================================================================================
+ // Set Level
+ //=========================================================================================================
+ /**
+ * @public
+ * Assigns new Level to Logger
+ * NOTE: Default Levels can be found in 'Logger.level.x'
+ * @param level - Level
+ */
+ public setLevel(level: number): this {
+ this.config.level = level;
+ return this;
+ }
+}
+
+export type LoggerCategoryKey = string | number;
+export type LoggerKey = string | number;
+
+/**
+ * @param key - Key/Name of Logger Category
+ * @param customStyle - Css Styles that get applied to the Logs
+ * @param prefix - Prefix that gets written before each Log of this Category
+ * @param level - Until which Level this Logger Category gets logged
+ */
+export interface LoggerCategoryInterface {
+ key: LoggerCategoryKey;
+ customStyle?: string;
+ prefix?: string;
+ level: number;
+}
+
+/**
+ * @param prefix - Prefix that gets written before each log of this Logger
+ * @param canUseCustomStyles - If custom Styles can be applied to the Logs
+ * @param level - Handles which Logger Categories can be Logged
+ * @param timestamp - Timestamp that ges written before each log of this Logger
+ */
+export interface LoggerConfigInterface {
+ prefix: string;
+ canUseCustomStyles: boolean;
+ level: number;
+ timestamp: boolean;
+}
+
+/**
+ * @param prefix - Prefix that gets written before each log of this Logger
+ * @param allowedTags - Only Logs that, contains the allowed Tags or have no Tag get logged
+ * @param canUseCustomStyles - If custom Styles can be applied to the Logs
+ * @param active - If Logger is active
+ * @param level - Handles which Logger Categories can be Logged
+ * @param timestamp - Timestamp that ges written before each log of this Logger
+ */
+export interface CreateLoggerConfigInterface {
+ prefix?: string;
+ allowedTags?: LoggerKey[];
+ canUseCustomStyles?: boolean;
+ active?: boolean;
+ level?: number;
+ timestamp?: boolean;
+}
+
+export type LoggerConfig =
+ | CreateLoggerConfigInterface
+ | ((logger: Logger) => CreateLoggerConfigInterface);
+
+export type ConsoleLogType =
+ | 'log'
+ | 'warn'
+ | 'error'
+ | 'trace'
+ | 'table'
+ | 'info'
+ | 'debug';
+
+export type LoggerWatcherCallback = (
+ loggerCategory: LoggerCategoryInterface,
+ data: any[]
+) => void;
+
+/**
+ * @param callback - Callback Function that gets called if something gets Logged
+ * @param level - At which level the watcher is called
+ */
+export interface LoggerWatcherConfigInterface {
+ callback: LoggerWatcherCallback;
+ level?: number;
+}
diff --git a/packages/multieditor/src/item.ts b/packages/multieditor/src/item.ts
index 738eece2..5d1458aa 100644
--- a/packages/multieditor/src/item.ts
+++ b/packages/multieditor/src/item.ts
@@ -1,8 +1,4 @@
-import {
- defineConfig,
- State,
- StateRuntimeJobConfigInterface,
-} from '@agile-ts/core';
+import { State, StateRuntimeJobConfigInterface } from '@agile-ts/core';
import { MultiEditor, Validator, Status, ItemKey } from './internal';
export class Item extends State {
@@ -31,9 +27,10 @@ export class Item extends State {
super(editor.agileInstance(), data, {
key: key,
});
- config = defineConfig(config, {
+ config = {
canBeEdited: true,
- });
+ ...config,
+ };
this.editor = () => editor;
this.validator = editor.getValidator(key);
this.config = config;
diff --git a/packages/multieditor/src/multieditor.ts b/packages/multieditor/src/multieditor.ts
index 5a08d585..440e2549 100644
--- a/packages/multieditor/src/multieditor.ts
+++ b/packages/multieditor/src/multieditor.ts
@@ -2,8 +2,8 @@ import {
Agile,
ComputeValueMethod,
copy,
- defineConfig,
getAgileInstance,
+ LogCodeManager,
Observer,
} from '@agile-ts/core';
import {
@@ -52,19 +52,20 @@ export class MultiEditor<
) {
if (!agileInstance) agileInstance = getAgileInstance(null);
if (!agileInstance)
- Agile.logger.error(
+ LogCodeManager.getLogger()?.error(
'No Global agileInstance found! Please pass an agileInstance into the MultiEditor!'
);
this.agileInstance = () => agileInstance as any;
let _config = typeof config === 'function' ? config(this) : config;
- _config = defineConfig(_config, {
+ _config = {
fixedProperties: [],
editableProperties: Object.keys(_config.data),
validateMethods: {},
computeMethods: {},
reValidateMode: 'onSubmit',
validate: 'editable',
- });
+ ..._config,
+ };
this._key = _config?.key;
this.onSubmit = _config.onSubmit as any;
this.fixedProperties = _config.fixedProperties as any;
@@ -166,9 +167,10 @@ export class MultiEditor<
): this {
const item = this.getItemById(key);
if (!item) return this;
- config = defineConfig(config, {
+ config = {
background: true,
- });
+ ...config,
+ };
// Apply changes to Item
item.set(value, config);
@@ -193,10 +195,11 @@ export class MultiEditor<
): this {
const item = this.getItemById(key);
if (!item) return this;
- config = defineConfig(config, {
+ config = {
background: false,
reset: true,
- });
+ ...config,
+ };
// Update initial Value
item.initialStateValue = copy(value);
@@ -227,10 +230,11 @@ export class MultiEditor<
config: SubmitConfigInterface = {}
): Promise {
const preparedData: DataObject = {};
- config = defineConfig(config, {
+ config = {
assignToInitial: true,
onSubmitConfig: undefined,
- });
+ ...config,
+ };
// Assign Statuses to Items
for (const key in this.data) {
@@ -247,9 +251,9 @@ export class MultiEditor<
this.submitted = true;
// Logging
- Agile.logger.if
- .tag(['multieditor'])
- .info(`Submit MultiEditor '${this.key}'`, this.isValid);
+ // Agile.logger.if
+ // .tag(['multieditor'])
+ // .info(`Submit MultiEditor '${this.key}'`, this.isValid);
// Check if Editor is Valid
if (!this.isValid) return false;
@@ -354,7 +358,9 @@ export class MultiEditor<
*/
public getItemById(key: ItemKey): Item | undefined {
if (!Object.prototype.hasOwnProperty.call(this.data, key)) {
- Agile.logger.error(`Editor Item '${key}' does not exists!`);
+ LogCodeManager.getLogger()?.error(
+ `Editor Item '${key}' does not exists!`
+ );
return undefined;
}
return this.data[key];
diff --git a/packages/multieditor/src/status/status.observer.ts b/packages/multieditor/src/status/status.observer.ts
index 4805ac20..dd39089b 100644
--- a/packages/multieditor/src/status/status.observer.ts
+++ b/packages/multieditor/src/status/status.observer.ts
@@ -2,7 +2,6 @@ import { Status, StatusInterface } from '../internal';
import {
Agile,
copy,
- defineConfig,
equal,
IngestConfigInterface,
Observer,
@@ -45,7 +44,7 @@ export class StatusObserver extends Observer {
* @param config - Config
*/
public assign(config: StatusIngestConfigInterface = {}): void {
- config = defineConfig(config, {
+ config = {
perform: true,
background: false,
sideEffects: {
@@ -53,7 +52,8 @@ export class StatusObserver extends Observer {
exclude: [],
},
force: false,
- });
+ ...config,
+ };
// Set Next Status Value
this.nextValue = copy(this.status().nextValue);
diff --git a/packages/multieditor/src/validator/index.ts b/packages/multieditor/src/validator/index.ts
index 0e3625f9..bcc9fe7d 100644
--- a/packages/multieditor/src/validator/index.ts
+++ b/packages/multieditor/src/validator/index.ts
@@ -1,10 +1,4 @@
-import {
- Agile,
- copy,
- defineConfig,
- generateId,
- isFunction,
-} from '@agile-ts/core';
+import { copy, generateId, isFunction, LogCodeManager } from '@agile-ts/core';
import {
DataObject,
MultiEditor,
@@ -24,9 +18,10 @@ export class Validator {
* @param config - Config
*/
constructor(config: ValidatorConfigInterface = {}) {
- this.config = defineConfig(config, {
+ this.config = {
prefix: 'default',
- });
+ ...config,
+ };
this._key = this.config.key;
}
@@ -125,13 +120,15 @@ export class Validator {
// Check if Validation Method is a Function
if (!isFunction(_method)) {
- Agile.logger.error('A Validation Method has to be a function!');
+ LogCodeManager.getLogger()?.error(
+ 'A Validation Method has to be a function!'
+ );
return this;
}
// Check if Validation Method already exists
if (this.validationMethods[key]) {
- Agile.logger.error(
+ LogCodeManager.getLogger()?.error(
`Validation Method with the key/name '${key}' already exists!`
);
return this;
diff --git a/packages/react/.size-limit.js b/packages/react/.size-limit.js
new file mode 100644
index 00000000..addba251
--- /dev/null
+++ b/packages/react/.size-limit.js
@@ -0,0 +1,6 @@
+module.exports = [
+ {
+ path: 'dist/*',
+ limit: '2 kB',
+ },
+];
diff --git a/packages/react/README.md b/packages/react/README.md
index e02ab0d9..fa074c38 100644
--- a/packages/react/README.md
+++ b/packages/react/README.md
@@ -75,8 +75,7 @@ Therefore, we have created a table that shows which versions fit together withou
| @agile-ts/react | @agile-ts/core | NPM Version | Supported React versions | Supports hook based components |
| ---------------- | ----------------------- | ------------------------ | -------------------------|---------------------------------- |
-| v0.0.15+ | v0.0.16+ | v6+ | 16.8+ | Yes |
-| v0.0.7 - v0.0.14 | v0.0.7 - v0.0.15 | v6+ | 16.8+ | Yes |
+| v0.1.1+ | v0.1.1+ | v6+ | 16.8+ | Yes |
_Older Versions aren't supported anymore_
diff --git a/packages/react/package.json b/packages/react/package.json
index 922ce4ec..376b08b2 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -35,7 +35,8 @@
"preview": "npm pack",
"test": "jest",
"test:coverage": "jest --coverage",
- "lint": "eslint src/**/*"
+ "lint": "eslint src/**/*",
+ "size": "yarn run build && size-limit"
},
"devDependencies": {
"@agile-ts/core": "file:../core",
diff --git a/packages/react/src/hocs/AgileHOC.ts b/packages/react/src/hocs/AgileHOC.ts
index 0a4261f2..4832162c 100644
--- a/packages/react/src/hocs/AgileHOC.ts
+++ b/packages/react/src/hocs/AgileHOC.ts
@@ -10,6 +10,7 @@ import {
flatMerge,
extractRelevantObservers,
normalizeArray,
+ LogCodeManager,
} from '@agile-ts/core';
/**
@@ -57,7 +58,10 @@ export function AgileHOC(
}
}
if (!agileInstance || !agileInstance.subController) {
- Agile.logger.error('Failed to subscribe Component with deps', deps);
+ LogCodeManager.getLogger()?.error(
+ 'Failed to subscribe Component with deps',
+ deps
+ );
return reactComponent;
}
diff --git a/packages/react/src/hooks/useAgile.ts b/packages/react/src/hooks/useAgile.ts
index dead032f..97fb30d0 100644
--- a/packages/react/src/hooks/useAgile.ts
+++ b/packages/react/src/hooks/useAgile.ts
@@ -6,7 +6,6 @@ import {
Observer,
State,
SubscriptionContainerKeyType,
- defineConfig,
isValidObject,
generateId,
ProxyWeakMapType,
@@ -14,9 +13,10 @@ import {
extractRelevantObservers,
SelectorWeakMapType,
SelectorMethodType,
+ LogCodeManager,
+ normalizeArray,
} from '@agile-ts/core';
import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect';
-import { normalizeArray } from '@agile-ts/utils';
import { AgileOutputHookArrayType, AgileOutputHookType } from './useOutput';
// TODO https://stackoverflow.com/questions/68148235/require-module-inside-a-function-doesnt-work
@@ -66,13 +66,15 @@ export function useAgile<
deps: X | Y,
config: AgileHookConfigInterface = {}
): AgileOutputHookArrayType | AgileOutputHookType {
- config = defineConfig(config, {
+ config = {
key: generateId(),
proxyBased: false,
- agileInstance: null,
+ agileInstance: null as any,
componentId: undefined,
observerType: undefined,
- });
+ deps: [],
+ ...config,
+ };
const depsArray = extractRelevantObservers(
normalizeArray(deps),
config.observerType
@@ -142,7 +144,7 @@ export function useAgile<
// Try to extract Agile Instance from the specified Instance/s
if (!agileInstance) agileInstance = getAgileInstance(observers[0]);
if (!agileInstance || !agileInstance.subController) {
- Agile.logger.error(
+ LogCodeManager.getLogger()?.error(
'Failed to subscribe Component with deps because of missing valid Agile Instance.',
deps
);
@@ -160,8 +162,9 @@ export function useAgile<
// Building the Path WeakMap in the 'useIsomorphicLayoutEffect'
// because the 'useIsomorphicLayoutEffect' is called after the rerender.
// -> All used paths in the UI-Component were successfully tracked.
- const proxyWeakMap: ProxyWeakMapType = new WeakMap();
+ let proxyWeakMap: ProxyWeakMapType | undefined = undefined;
if (config.proxyBased && proxyPackage != null) {
+ proxyWeakMap = new WeakMap();
for (const observer of observers) {
const proxyTree = proxyTreeWeakMap.get(observer);
if (proxyTree != null) {
@@ -173,8 +176,9 @@ export function useAgile<
}
// Build Selector WeakMap based on the specified selector method
- const selectorWeakMap: SelectorWeakMapType = new WeakMap();
+ let selectorWeakMap: SelectorWeakMapType | undefined = undefined;
if (config.selector != null) {
+ selectorWeakMap = new WeakMap();
for (const observer of observers) {
selectorWeakMap.set(observer, { methods: [config.selector] });
}
@@ -191,7 +195,7 @@ export function useAgile<
proxyWeakMap,
waitForMount: false,
componentId: config.componentId,
- selectorWeakMap: selectorWeakMap,
+ selectorWeakMap,
}
);
@@ -199,7 +203,7 @@ export function useAgile<
return () => {
agileInstance?.subController.unsubscribe(subscriptionContainer);
};
- }, []);
+ }, config.deps);
return getReturnValue(depsArray);
}
@@ -258,4 +262,13 @@ export interface AgileHookConfigInterface {
* @default undefined
*/
observerType?: string;
+ /**
+ * Dependencies that determine, in addition to unmounting and remounting the React-Component,
+ * when the specified Agile Sub Instances should be re-subscribed to the React-Component.
+ *
+ * [Github issue](https://github.com/agile-ts/agile/issues/170)
+ *
+ * @default []
+ */
+ deps?: any[];
}
diff --git a/packages/react/src/hooks/useIsomorphicLayoutEffect.ts b/packages/react/src/hooks/useIsomorphicLayoutEffect.ts
index 9e397c06..c76c0393 100644
--- a/packages/react/src/hooks/useIsomorphicLayoutEffect.ts
+++ b/packages/react/src/hooks/useIsomorphicLayoutEffect.ts
@@ -1,4 +1,5 @@
import { useEffect, useLayoutEffect } from 'react';
+import { runsOnServer } from '@agile-ts/core';
// React currently throws a warning when using useLayoutEffect on the server.
// To get around it, we can conditionally useEffect on the server (no-op) and
@@ -9,9 +10,6 @@ import { useEffect, useLayoutEffect } from 'react';
// is created synchronously, otherwise a store update may occur before the
// subscription is created and an inconsistent state may be observed
-export const useIsomorphicLayoutEffect =
- typeof window !== 'undefined' &&
- typeof window.document !== 'undefined' &&
- typeof window.document.createElement !== 'undefined'
- ? useLayoutEffect
- : useEffect;
+export const useIsomorphicLayoutEffect = !runsOnServer()
+ ? useLayoutEffect
+ : useEffect;
diff --git a/packages/react/src/react.integration.ts b/packages/react/src/react.integration.ts
index a0bf8774..2ba63ec8 100644
--- a/packages/react/src/react.integration.ts
+++ b/packages/react/src/react.integration.ts
@@ -1,4 +1,4 @@
-import { Agile, flatMerge, Integration } from '@agile-ts/core';
+import { flatMerge, Integration, Integrations } from '@agile-ts/core';
import { AgileReactComponent } from './hocs/AgileHOC';
import React from 'react';
@@ -24,6 +24,6 @@ const reactIntegration = new Integration({
}
},
});
-Agile.initialIntegrations.push(reactIntegration);
+Integrations.addInitialIntegration(reactIntegration);
export default reactIntegration;
diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts
index e9104543..736c82f2 100644
--- a/packages/utils/src/index.ts
+++ b/packages/utils/src/index.ts
@@ -8,19 +8,21 @@
* @param value - Array/Object that gets copied
*/
export function copy(value: T): T {
- // Extra checking '!value' because 'typeof null === object'
- if (!value) return value;
+ // Extra checking 'value == null' because 'typeof null === object'
+ if (value == null || typeof value !== 'object') return value;
- // Ignore everything that is no object or array
- const valConstructorName = Object.getPrototypeOf(value).constructor.name;
- if (!['object', 'array'].includes(valConstructorName.toLowerCase()))
+ // Ignore everything that is no object or array but has the type of an object (e.g. classes)
+ const valConstructorName = Object.getPrototypeOf(
+ value
+ ).constructor.name.toLowerCase();
+ if (valConstructorName !== 'object' && valConstructorName !== 'array')
return value;
let temp;
const newObject: any = Array.isArray(value) ? [] : {};
for (const property in value) {
temp = value[property];
- newObject[property] = typeof temp === 'object' ? copy(temp) : temp;
+ newObject[property] = copy(temp);
}
return newObject as T;
}
@@ -86,9 +88,10 @@ export function normalizeArray(
items?: DataType | Array,
config: { createUndefinedArray?: boolean } = {}
): Array {
- config = defineConfig(config, {
+ config = {
createUndefinedArray: false, // If it should return [] or [undefined] if the passed Item is undefined
- });
+ ...config,
+ };
if (items == null && !config.createUndefinedArray) return [];
return Array.isArray(items) ? items : [items as DataType];
}
@@ -140,34 +143,6 @@ export function isJsonString(value: any): boolean {
return true;
}
-//=========================================================================================================
-// Define 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
- * @param overwriteUndefinedProperties - If undefined Properties in config gets overwritten by the default value
- */
-export function defineConfig(
- config: ConfigInterface,
- defaults: Object,
- overwriteUndefinedProperties?: boolean
-): ConfigInterface {
- if (overwriteUndefinedProperties === undefined)
- overwriteUndefinedProperties = true;
-
- if (overwriteUndefinedProperties) {
- const finalConfig = { ...defaults, ...config };
- for (const key in finalConfig)
- if (finalConfig[key] === undefined) finalConfig[key] = defaults[key];
- return finalConfig;
- }
-
- return { ...defaults, ...config };
-}
-
//=========================================================================================================
// Flat Merge
//=========================================================================================================
@@ -192,9 +167,10 @@ export function flatMerge(
changes: Object,
config: FlatMergeConfigInterface = {}
): DataType {
- config = defineConfig(config, {
+ config = {
addNewProperties: true,
- });
+ ...config,
+ };
// Copy Source to avoid References
const _source = copy(source);
@@ -223,7 +199,14 @@ export function flatMerge(
* @param value2 - Second Value
*/
export function equal(value1: any, value2: any): boolean {
- return value1 === value2 || JSON.stringify(value1) === JSON.stringify(value2);
+ return (
+ value1 === value2 ||
+ // Checking if 'value1' and 'value2' is typeof object before
+ // using the JSON.stringify comparison to optimize the performance
+ (typeof value1 === 'object' &&
+ typeof value2 === 'object' &&
+ JSON.stringify(value1) === JSON.stringify(value2))
+ );
}
//=========================================================================================================
diff --git a/packages/utils/tests/unit/utils.test.ts b/packages/utils/tests/unit/utils.test.ts
index 8206d311..cefdff3c 100644
--- a/packages/utils/tests/unit/utils.test.ts
+++ b/packages/utils/tests/unit/utils.test.ts
@@ -1,7 +1,6 @@
import {
clone,
copy,
- defineConfig,
equal,
flatMerge,
generateId,
@@ -15,12 +14,12 @@ import {
createArrayFromObject,
removeProperties,
} from '../../src';
-import mockConsole from 'jest-mock-console';
+import { LogMock } from '../../../core/tests/helper/logMock';
describe('Utils Tests', () => {
beforeEach(() => {
+ LogMock.mockLogs();
jest.clearAllMocks();
- mockConsole(['error', 'warn']);
});
describe('copy function tests', () => {
@@ -259,58 +258,6 @@ describe('Utils Tests', () => {
});
});
- describe('defineConfig function tests', () => {
- it('should merge defaults into config and overwrite undefined properties (default config)', () => {
- const config = {
- allowLogging: true,
- loops: 10,
- isHuman: undefined,
- };
- expect(
- defineConfig(config, {
- allowLogging: false,
- loops: 15,
- isHuman: true,
- isRobot: false,
- name: 'jeff',
- })
- ).toStrictEqual({
- allowLogging: true,
- loops: 10,
- isHuman: true,
- isRobot: false,
- name: 'jeff',
- });
- });
-
- it("should merge defaults into config and shouldn't overwrite undefined properties (overwriteUndefinedProperties = false)", () => {
- const config = {
- allowLogging: true,
- loops: 10,
- isHuman: undefined,
- };
- expect(
- defineConfig(
- config,
- {
- allowLogging: false,
- loops: 15,
- isHuman: true,
- isRobot: false,
- name: 'jeff',
- },
- false
- )
- ).toStrictEqual({
- allowLogging: true,
- loops: 10,
- isHuman: undefined,
- isRobot: false,
- name: 'jeff',
- });
- });
- });
-
describe('flatMerge function tests', () => {
it('should merge Changes Object into Source Object', () => {
const source = {
diff --git a/packages/vue/README.md b/packages/vue/README.md
index a82cbad0..f446a9d7 100644
--- a/packages/vue/README.md
+++ b/packages/vue/README.md
@@ -43,7 +43,7 @@ Therefore, we have created a table that shows which versions fit together withou
| @agile-ts/vue | @agile-ts/core | NPM Version | Supported Vue versions |
| ---------------- | ----------------------- | ------------------------ | -------------------------|
-| v0.0.01+ | v0.0.16+ | v6+ | 2.x (3.x not tested) |
+| v0.1.1+ | v0.1.1+ | v6+ | 2.x (3.x not tested) |
_Older Versions aren't supported anymore_
diff --git a/packages/vue/src/vue.integration.ts b/packages/vue/src/vue.integration.ts
index cf2268b9..d6a367ec 100644
--- a/packages/vue/src/vue.integration.ts
+++ b/packages/vue/src/vue.integration.ts
@@ -1,4 +1,4 @@
-import Agile, { Integration } from '@agile-ts/core';
+import Agile, { Integration, Integrations } from '@agile-ts/core';
import Vue from 'vue';
import { bindAgileInstances, DepsType } from './bindAgileInstances';
@@ -80,6 +80,6 @@ const vueIntegration = new Integration({
return Promise.resolve(true);
},
});
-Agile.initialIntegrations.push(vueIntegration);
+Integrations.addInitialIntegration(vueIntegration);
export default vueIntegration;
diff --git a/yarn.lock b/yarn.lock
index f1bbca79..d3795c28 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3,13 +3,17 @@
"@agile-ts/core@file:packages/core":
- version "0.0.17"
+ version "0.1.0"
dependencies:
- "@agile-ts/logger" "^0.0.4"
- "@agile-ts/utils" "^0.0.4"
+ "@agile-ts/utils" "^0.0.5"
+
+"@agile-ts/logger@file:packages/logger":
+ version "0.0.5"
+ dependencies:
+ "@agile-ts/utils" "^0.0.5"
"@agile-ts/proxytree@file:packages/proxytree":
- version "0.0.3"
+ version "0.0.4"
"@akryum/winattr@^3.0.0":
version "3.0.0"