+
+
+
+
+
+
+
+);
diff --git a/examples/react/develop/simple-counter/yarn.lock b/examples/react/develop/simple-counter/yarn.lock
index e0c5ba2c..ebaf211d 100644
--- a/examples/react/develop/simple-counter/yarn.lock
+++ b/examples/react/develop/simple-counter/yarn.lock
@@ -2,37 +2,23 @@
# yarn lockfile v1
-"@agile-ts/core@^0.0.17":
- version "0.0.17"
- resolved "https://registry.yarnpkg.com/@agile-ts/core/-/core-0.0.17.tgz#410ab31ff6279567ff0266a5a55818744598f2c2"
- integrity sha512-EVWqf6PkBwDS/gdTTVwo9juyGPrnnKlq8dna3diXGZdDpwEzMc09nGCmLThYM5sEkDQGzir6enn3Oo2l+7Zp2Q==
+"@agile-ts/core@file:.yalc/@agile-ts/core":
+ version "0.2.0-alpha.3"
dependencies:
- "@agile-ts/logger" "^0.0.4"
- "@agile-ts/utils" "^0.0.4"
+ "@agile-ts/utils" "^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.7"
dependencies:
- "@agile-ts/utils" "^0.0.4"
-
-"@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==
+ "@agile-ts/utils" "^0.0.7"
-"@agile-ts/react@^0.0.18":
- version "0.0.18"
- resolved "https://registry.yarnpkg.com/@agile-ts/react/-/react-0.0.18.tgz#db1a617ad535f7a70254d62980d97350d4a85718"
- integrity sha512-K2FO3Odqaw/XkU3DO/mWSLkxLn45W7pXk/UlZl5E/CQPFFWlWsjuxtH/C/kfK+E6rnaNoToTjGscmcoeN/bLjQ==
- dependencies:
- "@agile-ts/proxytree" "^0.0.3"
+"@agile-ts/react@file:.yalc/@agile-ts/react":
+ version "0.1.2"
-"@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.7":
+ version "0.0.7"
+ resolved "https://registry.yarnpkg.com/@agile-ts/utils/-/utils-0.0.7.tgz#3dd1add6b9f63d0a5bf35e71f54ac46448ae047f"
+ integrity sha512-OviTDC+ZbfyiUx8Gy8veS6YymC/tT6UeP23nT8V0EQV4F2MmuWqZ2yiKk+AYxZx8h74Ey8BVEUX6/ntpxhSNPw==
"@babel/code-frame@7.10.4":
version "7.10.4"
@@ -1664,6 +1650,13 @@
dependencies:
regenerator-runtime "^0.13.4"
+"@babel/runtime@^7.12.1", "@babel/runtime@^7.9.2":
+ version "7.14.8"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.8.tgz#7119a56f421018852694290b9f9148097391b446"
+ integrity sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
"@babel/template@^7.10.4", "@babel/template@^7.12.13", "@babel/template@^7.3.3":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327"
@@ -2018,6 +2011,16 @@
schema-utils "^2.6.5"
source-map "^0.7.3"
+"@reduxjs/toolkit@^1.6.1":
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.6.1.tgz#7bc83b47352a663bf28db01e79d17ba54b98ade9"
+ integrity sha512-pa3nqclCJaZPAyBhruQtiRwtTjottRrVJqziVZcWzI73i6L3miLTtUyWfauwv08HWtiXLx1xGyGt+yLFfW/d0A==
+ dependencies:
+ immer "^9.0.1"
+ redux "^4.1.0"
+ redux-thunk "^2.3.0"
+ reselect "^4.0.0"
+
"@rollup/plugin-node-resolve@^7.1.1":
version "7.1.3"
resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz#80de384edfbd7bfc9101164910f86078151a3eca"
@@ -2242,6 +2245,14 @@
dependencies:
"@types/node" "*"
+"@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/html-minifier-terser@^5.0.0":
version "5.1.1"
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#3c9ee980f1a10d6021ae6632ca3e79ca2ec4fb50"
@@ -2301,11 +2312,35 @@
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.2.1.tgz#374e31645d58cb18a07b3ecd8e9dede4deb2cccd"
integrity sha512-DxZZbyMAM9GWEzXL+BMZROWz9oo6A9EilwwOMET2UVu2uZTqMWS5S69KVtuVKaRjCUpcrOXRalet86/OpG4kqw==
+"@types/prop-types@*":
+ version "15.7.4"
+ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
+ integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
+
"@types/q@^1.5.1":
version "1.5.4"
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24"
integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==
+"@types/react-redux@^7.1.16":
+ version "7.1.18"
+ resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.18.tgz#2bf8fd56ebaae679a90ebffe48ff73717c438e04"
+ integrity sha512-9iwAsPyJ9DLTRH+OFeIrm9cAbIj1i2ANL3sKQFATqnPWRbg+jEFXyZOKHiQK/N86pNRXbb4HRxAxo0SIX1XwzQ==
+ dependencies:
+ "@types/hoist-non-react-statics" "^3.3.0"
+ "@types/react" "*"
+ hoist-non-react-statics "^3.3.0"
+ redux "^4.0.0"
+
+"@types/react@*":
+ version "17.0.15"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.15.tgz#c7533dc38025677e312606502df7656a6ea626d0"
+ integrity sha512-uTKHDK9STXFHLaKv6IMnwp52fm0hwU+N89w/p9grdUqcFA6WuqDyPhaWopbNyE1k/VhgzmHl8pu1L4wITtmlLw==
+ dependencies:
+ "@types/prop-types" "*"
+ "@types/scheduler" "*"
+ csstype "^3.0.2"
+
"@types/resolve@0.0.8":
version "0.0.8"
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194"
@@ -2313,6 +2348,11 @@
dependencies:
"@types/node" "*"
+"@types/scheduler@*":
+ version "0.16.2"
+ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
+ integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==
+
"@types/source-list-map@*":
version "0.1.2"
resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9"
@@ -4396,6 +4436,11 @@ cssstyle@^2.2.0:
dependencies:
cssom "~0.3.6"
+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==
+
cyclist@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
@@ -5897,6 +5942,11 @@ gzip-size@^6.0.0:
dependencies:
duplexer "^0.1.2"
+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=
+
handle-thing@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e"
@@ -6009,6 +6059,13 @@ hmac-drbg@^1.0.1:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
+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"
+
hoopy@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d"
@@ -6227,6 +6284,11 @@ immer@8.0.1:
resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.1.tgz#9c73db683e2b3975c424fb0572af5889877ae656"
integrity sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA==
+immer@^9.0.1:
+ version "9.0.5"
+ resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.5.tgz#a7154f34fe7064f15f00554cc94c66cc0bf453ec"
+ integrity sha512-2WuIehr2y4lmYz9gaQzetPR2ECniCifk4ORaQbU3g5EalLt+0IVTosEPJ5BoYl/75ky2mivzdRzV8wWgQGOSYQ==
+
import-cwd@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
@@ -7192,6 +7254,11 @@ jest@26.6.0:
import-local "^3.0.2"
jest-cli "^26.6.0"
+jotai@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/jotai/-/jotai-1.2.2.tgz#631fd7ad44e9ac26cdf9874d52282c1cfe032807"
+ integrity sha512-iqkkUdWsH2Mk4HY1biba/8kA77+8liVBy8E0d8Nce29qow4h9mzdDhpTasAruuFYPycw6JvfZgL5RB0JJuIZjw==
+
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -7907,6 +7974,11 @@ nanomatch@^1.2.9:
snapdragon "^0.8.1"
to-regex "^3.0.1"
+nanostores@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/nanostores/-/nanostores-0.4.1.tgz#61c7a4aadca063bd1992e80f9a0e8b20d55dee0f"
+ integrity sha512-GfrWjngWVTBa3YSPijFGrxyYYEE017ONA/t6d6X6G99ccfED+eZEIciH+KKCqbvZhvRbqGH0aHDhRYNBwFw8hg==
+
native-url@^0.2.6:
version "0.2.6"
resolved "https://registry.yarnpkg.com/native-url/-/native-url-0.2.6.tgz#ca1258f5ace169c716ff44eccbddb674e10399ae"
@@ -9570,7 +9642,7 @@ react-error-overlay@^6.0.9:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
-react-is@^16.8.1:
+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==
@@ -9580,6 +9652,18 @@ react-is@^17.0.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
+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-refresh@^0.8.3:
version "0.8.3"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
@@ -9732,6 +9816,13 @@ readdirp@~3.5.0:
dependencies:
picomatch "^2.2.1"
+recoil@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/recoil/-/recoil-0.4.0.tgz#2a933ba7c043cbf6f50f52da0828a879c7cd7d69"
+ integrity sha512-FZ2ljI4ldZU820V0APbKOtS4bPwPJHvpDBQEl+Cf47DMaM35wuLXl2u37E0TSgdvKAZBOUsIwcBnzE+ncODRxQ==
+ dependencies:
+ hamt_plus "1.0.2"
+
recursive-readdir@2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f"
@@ -9739,6 +9830,18 @@ recursive-readdir@2.2.2:
dependencies:
minimatch "3.0.4"
+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.1"
+ resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.1.tgz#76f1c439bb42043f985fbd9bf21990e60bd67f47"
+ integrity sha512-hZQZdDEM25UY2P493kPYuKqviVwZ58lEmGQNeQ+gXa+U0gYPUBf7NKYazbe3m+bs/DzM/ahN12DbF+NG8i0CWw==
+ dependencies:
+ "@babel/runtime" "^7.9.2"
+
regenerate-unicode-properties@^8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
@@ -9911,6 +10014,11 @@ requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
+reselect@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
+ integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==
+
resolve-cwd@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
diff --git a/examples/react/release/boxes/package.json b/examples/react/release/boxes/package.json
index ff3c03f1..479c96d1 100644
--- a/examples/react/release/boxes/package.json
+++ b/examples/react/release/boxes/package.json
@@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
- "@agile-ts/core": "^0.1.2",
+ "@agile-ts/core": "^0.1.3",
"@agile-ts/logger": "^0.0.7",
"@agile-ts/proxytree": "^0.0.5",
"@agile-ts/react": "^0.1.2",
diff --git a/package.json b/package.json
index 7511c703..7a77e31f 100644
--- a/package.json
+++ b/package.json
@@ -35,12 +35,12 @@
"release": "lerna run release && changeset publish",
"prettier": "prettier --config .prettierrc --write \"**/*.{js,ts}\"",
"lint": "eslint --cache \"**/*.{js,jsx,ts,tsx}\"",
- "pack": "lerna run prepare && lerna run preview",
- "pack:core": "cd packages/core && yarn run prepare && yarn run preview",
- "pack:react": "cd packages/react && yarn run prepare && yarn run preview",
- "pack:multieditor": "cd packages/multieditor && yarn run prepare && yarn run preview",
- "pack:api": "cd packages/api && yarn run prepare && yarn run preview",
- "pack:event": "cd packages/event && yarn run prepare && yarn run preview"
+ "pack": "lerna run prepare && lerna run pack",
+ "pack:core": "cd packages/core && yarn run prepare && yarn run pack",
+ "pack:react": "cd packages/react && yarn run prepare && yarn run pack",
+ "pack:multieditor": "cd packages/multieditor && yarn run prepare && yarn run pack",
+ "pack:api": "cd packages/api && yarn run prepare && yarn run pack",
+ "pack:event": "cd packages/event && yarn run prepare && yarn run pack"
},
"repository": {
"type": "git",
diff --git a/packages/api/package.json b/packages/api/package.json
index 8fcfd3eb..c223a02a 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -13,16 +13,20 @@
"agile-ts"
],
"main": "dist/index.js",
+ "module": "dist/esm/index.js",
"types": "dist/index.d.ts",
"scripts": {
- "build": "tsc",
- "prepare": "tsc && tsc -p ./tsconfig.production.json",
+ "build": "yarn run build:esm && yarn run build:cjs",
+ "prepare": "yarn run build",
+ "build:esm": "tsc -p ./tsconfig.esm.json",
+ "build:cjs": "tsc -p ./tsconfig.json && tsc -p ./tsconfig.production.json",
"dev:publish": "yalc publish",
"dev:push": "yalc push",
"watch:push": "tsc-watch --onSuccess \"yarn run dev:push\"",
"watch": "tsc -w",
"release": "yarn run prepare",
- "preview": "npm pack",
+ "release:manual": "yarn run prepare && yarn run release && npm publish",
+ "pack": "npm pack",
"test": "jest",
"test:coverage": "jest --coverage",
"lint": "eslint src/**/*"
@@ -48,5 +52,6 @@
"LICENSE",
"README.md",
"CHANGELOG.md"
- ]
+ ],
+ "sideEffects": false
}
diff --git a/packages/api/tsconfig.esm.json b/packages/api/tsconfig.esm.json
new file mode 100644
index 00000000..a00a08bf
--- /dev/null
+++ b/packages/api/tsconfig.esm.json
@@ -0,0 +1,9 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "module": "ES2015",
+ "outDir": "dist/esm",
+ "declaration": false, // already generated via 'tsconfig.json' in the root dist folder
+ "removeComments": true
+ }
+}
diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json
index 600cd205..791f587c 100644
--- a/packages/api/tsconfig.json
+++ b/packages/api/tsconfig.json
@@ -1,10 +1,11 @@
{
"extends": "../tsconfig.default.json",
"compilerOptions": {
+ "module": "commonjs",
"rootDir": "src",
"outDir": "dist"
},
"include": [
"./src/**/*" // Only include what is in src (-> dist, tests, .. will be excluded)
]
-}
\ No newline at end of file
+}
diff --git a/packages/api/tsconfig.production.json b/packages/api/tsconfig.production.json
index 4b5c4d12..b8ec8c38 100644
--- a/packages/api/tsconfig.production.json
+++ b/packages/api/tsconfig.production.json
@@ -1,7 +1,9 @@
+// Use File: Overwrites already generated js files with new js files that have no comments
+// Not doing in main 'tsconfig.json' because then the typescript declarations would have no comments too
{
"extends": "./tsconfig.json",
"compilerOptions": {
- "declaration": false,
+ "declaration": false, // '.d.ts' files should have been generated before
"removeComments": true
}
-}
\ No newline at end of file
+}
diff --git a/packages/core/package.json b/packages/core/package.json
index 64e19bd3..a0b5c23e 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@agile-ts/core",
- "version": "0.1.2",
+ "version": "0.2.0-alpha.3",
"author": "BennoDev",
"license": "MIT",
"homepage": "https://agile-ts.org/",
@@ -24,16 +24,20 @@
"agile-ts"
],
"main": "dist/index.js",
+ "module": "dist/esm/index.js",
"types": "dist/index.d.ts",
"scripts": {
- "build": "tsc",
- "prepare": "tsc && tsc -p ./tsconfig.production.json",
+ "build": "yarn run build:esm && yarn run build:cjs",
+ "prepare": "yarn run build",
+ "build:esm": "tsc -p ./tsconfig.esm.json",
+ "build:cjs": "tsc -p ./tsconfig.json && tsc -p ./tsconfig.production.json",
"dev:publish": "yalc publish",
"dev:push": "yalc push",
"watch:push": "tsc-watch --onSuccess \"yarn run dev:push\"",
"watch": "tsc -w",
"release": "node ./scripts/prepublish.js && yarn run prepare",
- "preview": "npm pack",
+ "release:manual": "yarn run prepare && yarn run release && npm publish && git checkout README.md",
+ "pack": "npm pack",
"test": "jest",
"test:coverage": "jest --coverage",
"lint": "eslint src/**/*",
@@ -69,5 +73,6 @@
"LICENSE",
"README.md",
"CHANGELOG.md"
- ]
+ ],
+ "sideEffects": false
}
diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts
index 6a8d34fa..23a02432 100644
--- a/packages/core/src/agile.ts
+++ b/packages/core/src/agile.ts
@@ -1,28 +1,13 @@
import {
Runtime,
Integration,
- State,
Storage,
- Collection,
- CollectionConfig,
- DefaultItem,
- Computed,
Integrations,
SubController,
globalBind,
Storages,
- CreateStorageConfigInterface,
RegisterConfigInterface,
- StateConfigInterface,
- flatMerge,
LogCodeManager,
- DependableAgileInstancesType,
- CreateComputedConfigInterface,
- ComputeFunctionType,
- createStorage,
- createState,
- createCollection,
- createComputed,
IntegrationsConfigInterface,
defineConfig,
} from './internal';
@@ -105,143 +90,6 @@ export class Agile {
}
}
- /**
- * 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
- */
- public createStorage(config: CreateStorageConfigInterface): Storage {
- return createStorage(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
- */
- public createState(
- initialValue: ValueType,
- config: StateConfigInterface = {}
- ): State {
- return createState(
- initialValue,
- defineConfig(config, {
- agileInstance: this,
- })
- );
- }
-
- /**
- * 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
- */
- public createCollection(
- config?: CollectionConfig
- ): Collection {
- return createCollection(config, this);
- }
-
- /**
- * 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
- */
- public createComputed(
- computeFunction: ComputeFunctionType,
- config?: CreateComputedConfigInterface
- ): 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.
- */
- public createComputed(
- computeFunction: ComputeFunctionType,
- deps?: Array
- ): Computed;
- public createComputed(
- computeFunction: ComputeFunctionType,
- configOrDeps?:
- | CreateComputedConfigInterface
- | Array
- ): Computed {
- let _config: CreateComputedConfigInterface = {};
-
- if (Array.isArray(configOrDeps)) {
- _config = defineConfig(_config, {
- computedDeps: configOrDeps,
- agileInstance: this,
- });
- } else {
- if (configOrDeps)
- _config = defineConfig(configOrDeps, {
- agileInstance: this,
- });
- }
-
- return createComputed(computeFunction, _config);
- }
-
/**
* Registers the specified Integration with AgileTs.
*
diff --git a/packages/core/src/collection/collection.ts b/packages/core/src/collection/collection.ts
new file mode 100644
index 00000000..31b8a107
--- /dev/null
+++ b/packages/core/src/collection/collection.ts
@@ -0,0 +1,1697 @@
+import {
+ Agile,
+ Item,
+ Group,
+ GroupKey,
+ Selector,
+ SelectorKey,
+ StorageKey,
+ GroupConfigInterface,
+ isValidObject,
+ normalizeArray,
+ copy,
+ CollectionPersistent,
+ GroupAddConfigInterface,
+ ComputedTracker,
+ generateId,
+ SideEffectConfigInterface,
+ SelectorConfigInterface,
+ removeProperties,
+ isFunction,
+ LogCodeManager,
+ PatchOptionConfigInterface,
+ defineConfig,
+} from '../internal';
+
+export class Collection<
+ DataType extends Object = DefaultItem,
+ GroupValueType = Array // To extract the Group Type Value in Integration methods like 'useAgile()'
+> {
+ // Agile Instance the Collection belongs to
+ public agileInstance: () => Agile;
+
+ public config: CollectionConfigInterface;
+ private initialConfig: CreateCollectionConfigInterface;
+
+ // Key/Name identifier of the Collection
+ public _key?: CollectionKey;
+ // Amount of the Items stored in the Collection
+ public size = 0;
+ // Items stored in the Collection
+ public data: { [key: string]: Item } = {};
+ // Whether the Collection is persisted in an external Storage
+ public isPersisted = false;
+ // Manages the permanent persistent in external Storages
+ public persistent: CollectionPersistent | undefined;
+
+ // Registered Groups of Collection
+ public groups: { [key: string]: Group } = {};
+ // Registered Selectors of Collection
+ public selectors: { [key: string]: Selector } = {};
+
+ // Whether the Collection was instantiated correctly
+ public isInstantiated = false;
+
+ // Helper property to check whether an unknown instance is a Collection,
+ // without importing the Collection itself for using 'instanceof' (Treeshaking support)
+ public isCollection = true;
+
+ /**
+ * A Collection manages a reactive set of Information
+ * that we need to remember globally at a later point in time.
+ * While providing a toolkit to use and mutate this set of Information.
+ *
+ * It is designed for arrays of data objects following the same pattern.
+ *
+ * Each of these data object must have a unique `primaryKey` to be correctly identified later.
+ *
+ * You can create as many global Collections as you need.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/)
+ *
+ * @public
+ * @param agileInstance - Instance of Agile the Collection belongs to.
+ * @param config - Configuration object
+ */
+ constructor(agileInstance: Agile, config: CollectionConfig = {}) {
+ this.agileInstance = () => agileInstance;
+ let _config = typeof config === 'function' ? config(this) : config;
+ _config = defineConfig(_config, {
+ primaryKey: 'id',
+ groups: {},
+ selectors: {},
+ defaultGroupKey: 'default',
+ });
+ this._key = _config.key;
+ this.config = {
+ defaultGroupKey: _config.defaultGroupKey as any,
+ primaryKey: _config.primaryKey as any,
+ };
+ this.initialConfig = _config;
+
+ this.initGroups(_config.groups as any);
+ this.initSelectors(_config.selectors as any);
+
+ this.isInstantiated = true;
+
+ // Add 'initialData' to Collection
+ // (after 'isInstantiated' to add them properly to the Collection)
+ if (_config.initialData) this.collect(_config.initialData);
+
+ // Reselect Selector Items
+ // Necessary because the selection of an Item
+ // hasn't worked with a not correctly 'instantiated' Collection before
+ for (const key in this.selectors) this.selectors[key].reselect();
+
+ // Rebuild of Groups
+ // Not necessary because if Items are added to the Collection,
+ // (after 'isInstantiated = true')
+ // the Groups which contain these added Items are rebuilt.
+ // for (const key in this.groups) this.groups[key].rebuild();
+ }
+
+ /**
+ * Updates the key/name identifier of the Collection.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/properties#key)
+ *
+ * @public
+ * @param value - New key/name identifier.
+ */
+ public set key(value: CollectionKey | undefined) {
+ this.setKey(value);
+ }
+
+ /**
+ * Returns the key/name identifier of the Collection.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/properties#key)
+ *
+ * @public
+ */
+ public get key(): CollectionKey | undefined {
+ return this._key;
+ }
+
+ /**
+ * Updates the key/name identifier of the Collection.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#setkey)
+ *
+ * @public
+ * @param value - New key/name identifier.
+ */
+ public setKey(value: CollectionKey | undefined) {
+ const oldKey = this._key;
+
+ // Update Collection key
+ this._key = value;
+
+ // Update key in Persistent (only if oldKey is equal to persistentKey
+ // because otherwise the persistentKey is detached from the Collection key
+ // -> not managed by Collection anymore)
+ if (value != null && this.persistent?._key === oldKey)
+ this.persistent?.setKey(value);
+
+ return this;
+ }
+
+ /**
+ * Creates a new Group without associating it to the Collection.
+ *
+ * This way of creating a Group is intended for use in the Collection configuration object,
+ * where the `constructor()` takes care of the binding.
+ *
+ * After a successful initiation of the Collection we recommend using `createGroup()`,
+ * because it automatically connects the Group to the Collection.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#group)
+ *
+ * @public
+ * @param initialItems - Key/Name identifiers of the Items to be clustered by the Group.
+ * @param config - Configuration object
+ */
+ public Group(
+ initialItems?: Array,
+ config: GroupConfigInterface = {}
+ ): Group {
+ if (this.isInstantiated) {
+ const key = config.key ?? generateId();
+ LogCodeManager.log('1B:02:00');
+ return this.createGroup(key, initialItems);
+ }
+
+ return new Group(this, initialItems, config);
+ }
+
+ /**
+ * Creates a new Selector without associating it to the Collection.
+ *
+ * This way of creating a Selector is intended for use in the Collection configuration object,
+ * where the `constructor()` takes care of the binding.
+ *
+ * After a successful initiation of the Collection we recommend using `createSelector()`,
+ * because it automatically connects the Group to the Collection.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#selector)
+ *
+ * @public
+ * @param initialKey - Key/Name identifier of the Item to be represented by the Selector.
+ * @param config - Configuration object
+ */
+ public Selector(
+ initialKey: ItemKey | null,
+ config: SelectorConfigInterface = {}
+ ): Selector {
+ if (this.isInstantiated) {
+ const key = config.key ?? generateId();
+ LogCodeManager.log('1B:02:01');
+ return this.createSelector(key, initialKey);
+ }
+
+ return new Selector(this, initialKey, config);
+ }
+
+ /**
+ * Sets up the specified Groups or Group keys
+ * and assigns them to the Collection if they are valid.
+ *
+ * It also instantiates and assigns the default Group to the Collection.
+ * The default Group reflects the default pattern of the Collection.
+ *
+ * @internal
+ * @param groups - Entire Groups or Group keys to be set up.
+ */
+ public initGroups(groups: { [key: string]: Group } | string[]): void {
+ if (!groups) return;
+ let groupsObject: { [key: string]: Group } = {};
+
+ // If groups is Array of Group keys/names, create the Groups based on these keys
+ if (Array.isArray(groups)) {
+ groups.forEach((groupKey) => {
+ groupsObject[groupKey] = new Group(this, [], {
+ key: groupKey,
+ });
+ });
+ } else groupsObject = groups;
+
+ // Add default Group
+ groupsObject[this.config.defaultGroupKey] = new Group(this, [], {
+ key: this.config.defaultGroupKey,
+ });
+
+ // Assign missing key/name to Group based on the property key
+ for (const key in groupsObject)
+ if (groupsObject[key]._key == null) groupsObject[key].setKey(key);
+
+ this.groups = groupsObject;
+ }
+
+ /**
+ * Sets up the specified Selectors or Selector keys
+ * and assigns them to the Collection if they are valid.
+ *
+ * @internal
+ * @param selectors - Entire Selectors or Selector keys to be set up.
+ */
+ public initSelectors(selectors: { [key: string]: Selector } | string[]) {
+ if (!selectors) return;
+ let selectorsObject: { [key: string]: Selector } = {};
+
+ // If selectors is Array of Selector keys/names, create the Selectors based on these keys
+ if (Array.isArray(selectors)) {
+ selectors.forEach((selectorKey) => {
+ selectorsObject[selectorKey] = new Selector(
+ this,
+ selectorKey,
+ {
+ key: selectorKey,
+ }
+ );
+ });
+ } else selectorsObject = selectors;
+
+ // Assign missing key/name to Selector based on the property key
+ for (const key in selectorsObject)
+ if (selectorsObject[key]._key == null) selectorsObject[key].setKey(key);
+
+ this.selectors = selectorsObject;
+ }
+
+ /**
+ * Appends new data objects following the same pattern to the end of the Collection.
+ *
+ * Each collected `data object` requires a unique identifier at the primaryKey property (by default 'id')
+ * to be correctly identified later.
+ *
+ * For example, if we collect some kind of user object,
+ * it must contain such unique identifier at 'id'
+ * to be added to the Collection.
+ * ```
+ * MY_COLLECTION.collect({id: '1', name: 'jeff'}); // valid
+ * MY_COLLECTION.collect({name: 'frank'}); // invalid
+ * ```
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#collect)
+ *
+ * @public
+ * @param data - Data objects or entire Items to be added.
+ * @param groupKeys - Group/s to which the specified data objects or Items are to be added.
+ * @param config - Configuration object
+ */
+ public collect(
+ data: DataType | Item | Array>,
+ groupKeys?: GroupKey | Array,
+ config: CollectConfigInterface = {}
+ ): this {
+ const _data = normalizeArray>(data);
+ const _groupKeys = normalizeArray(groupKeys);
+ const defaultGroupKey = this.config.defaultGroupKey;
+ const primaryKey = this.config.primaryKey;
+ config = defineConfig(config, {
+ method: 'push',
+ background: false,
+ patch: false,
+ select: false,
+ });
+
+ // Add default groupKey, since all Items are added to the default Group
+ if (!_groupKeys.includes(defaultGroupKey)) _groupKeys.push(defaultGroupKey);
+
+ // Create not existing Groups
+ _groupKeys.forEach(
+ (key) => this.groups[key] == null && this.createGroup(key)
+ );
+
+ _data.forEach((data, index) => {
+ let itemKey;
+ let success = false;
+
+ // Assign Data or Item to Collection
+ if (data instanceof Item) {
+ success = this.assignItem(data, {
+ background: config.background,
+ });
+ itemKey = data._key;
+ } else {
+ success = this.assignData(data, {
+ patch: config.patch,
+ background: config.background,
+ });
+ itemKey = data[primaryKey];
+ }
+
+ // Add itemKey to provided Groups and create corresponding Selector
+ if (success) {
+ _groupKeys.forEach((groupKey) => {
+ this.getGroup(groupKey)?.add(itemKey, {
+ method: config.method,
+ background: config.background,
+ });
+ });
+
+ if (config.select) this.createSelector(itemKey, itemKey);
+ }
+
+ if (config.forEachItem) config.forEachItem(data, itemKey, success, index);
+ });
+
+ return this;
+ }
+
+ /**
+ * Updates the Item `data object` with the specified `object with changes`, if the Item exists.
+ * By default the `object with changes` is merged into the Item `data object` at top level.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#update)
+ *
+ * @public
+ * @param itemKey - Key/Name identifier of the Item to be updated.
+ * @param changes - Object with changes to be merged into the Item data object.
+ * @param config - Configuration object
+ */
+ public update(
+ itemKey: ItemKey,
+ changes: DefaultItem | DataType,
+ config: UpdateConfigInterface = {}
+ ): Item | undefined {
+ const item = this.getItem(itemKey, { notExisting: true });
+ const primaryKey = this.config.primaryKey;
+ config = defineConfig(config, {
+ patch: true,
+ background: false,
+ });
+
+ // Check if the given conditions are suitable for a update action
+ if (item == null) {
+ LogCodeManager.log('1B:03:00', [itemKey, this._key]);
+ return undefined;
+ }
+ if (!isValidObject(changes)) {
+ LogCodeManager.log('1B:03:01', [itemKey, this._key]);
+ return undefined;
+ }
+
+ const oldItemKey = item._value[primaryKey];
+ const newItemKey = changes[primaryKey] || oldItemKey;
+
+ // Update itemKey if the new itemKey differs from the old one
+ if (oldItemKey !== newItemKey)
+ this.updateItemKey(oldItemKey, newItemKey, {
+ background: config.background,
+ });
+
+ // Patch changes into Item data object
+ if (config.patch) {
+ // Delete primaryKey property from 'changes object' because if it has changed,
+ // it is correctly updated in the above called 'updateItemKey()' method
+ if (changes[primaryKey]) delete changes[primaryKey];
+
+ let patchConfig: { addNewProperties?: boolean } =
+ typeof config.patch === 'object' ? config.patch : {};
+ patchConfig = {
+ addNewProperties: true,
+ ...patchConfig,
+ };
+
+ item.patch(changes as any, {
+ background: config.background,
+ addNewProperties: patchConfig.addNewProperties,
+ });
+ }
+ // Apply changes to Item data object
+ else {
+ // Ensure that the current Item identifier isn't different from the 'changes object' itemKey
+ if (changes[this.config.primaryKey] !== itemKey) {
+ changes[this.config.primaryKey] = itemKey;
+ LogCodeManager.log('1B:02:02', [], changes);
+ }
+
+ item.set(changes as any, {
+ background: config.background,
+ });
+ }
+
+ return item;
+ }
+
+ /**
+ * Creates a new Group and associates it to the Collection.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#createGroup)
+ *
+ * @public
+ * @param groupKey - Unique identifier of the Group to be created.
+ * @param initialItems - Key/Name identifiers of the Items to be clustered by the Group.
+ */
+ public createGroup(
+ groupKey: GroupKey,
+ initialItems: Array = []
+ ): Group {
+ let group = this.getGroup(groupKey, { notExisting: true });
+ if (!this.isInstantiated) LogCodeManager.log('1B:02:03');
+
+ // Check if Group already exists
+ if (group != null) {
+ if (!group.isPlaceholder) {
+ LogCodeManager.log('1B:03:02', [groupKey]);
+ return group;
+ }
+ group.set(initialItems, { overwrite: true });
+ return group;
+ }
+
+ // Create new Group
+ group = new Group(this, initialItems, { key: groupKey });
+ this.groups[groupKey] = group;
+
+ return group;
+ }
+
+ /**
+ * Returns a boolean indicating whether a Group with the specified `groupKey`
+ * exists in the Collection or not.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#hasgroup)
+ *
+ * @public
+ * @param groupKey - Key/Name identifier of the Group to be checked for existence.
+ * @param config - Configuration object
+ */
+ public hasGroup(
+ groupKey: GroupKey | undefined,
+ config: HasConfigInterface = {}
+ ): boolean {
+ return !!this.getGroup(groupKey, config);
+ }
+
+ /**
+ * Retrieves a single Group with the specified key/name identifier from the Collection.
+ *
+ * If the to retrieve Group doesn't exist, `undefined` is returned.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroup)
+ *
+ * @public
+ * @param groupKey - Key/Name identifier of the Group.
+ * @param config - Configuration object
+ */
+ public getGroup(
+ groupKey: GroupKey | undefined | null,
+ config: HasConfigInterface = {}
+ ): Group | undefined {
+ config = defineConfig(config, {
+ notExisting: false,
+ });
+
+ // Retrieve Group
+ const group = groupKey ? this.groups[groupKey] : undefined;
+
+ // Check if retrieved Group exists
+ if (group == null || (!config.notExisting && !group.exists))
+ return undefined;
+
+ ComputedTracker.tracked(group.observers['value']);
+ return group;
+ }
+
+ /**
+ * Retrieves the default Group from the Collection.
+ *
+ * Every Collection should have a default Group,
+ * which represents the default pattern of the Collection.
+ *
+ * If the default Group, for what ever reason, doesn't exist, `undefined` is returned.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getdefaultgroup)
+ *
+ * @public
+ */
+ public getDefaultGroup(): Group | undefined {
+ return this.getGroup(this.config.defaultGroupKey);
+ }
+
+ /**
+ * Retrieves a single Group with the specified key/name identifier from the Collection.
+ *
+ * If the to retrieve Group doesn't exist, a reference Group is returned.
+ * This has the advantage that Components that have the reference Group bound to themselves
+ * are rerenderd when the original Group is created.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroupwithreference)
+ *
+ * @public
+ * @param groupKey - Key/Name identifier of the Group.
+ */
+ public getGroupWithReference(groupKey: GroupKey): Group {
+ let group = this.getGroup(groupKey, { notExisting: true });
+
+ // Create dummy Group to hold reference
+ if (group == null) {
+ group = new Group(this, [], {
+ key: groupKey,
+ isPlaceholder: true,
+ });
+ this.groups[groupKey] = group;
+ }
+
+ ComputedTracker.tracked(group.observers['value']);
+ return group;
+ }
+
+ /**
+ * Removes a Group with the specified key/name identifier from the Collection,
+ * if it exists in the Collection.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removegroup)
+ *
+ * @public
+ * @param groupKey - Key/Name identifier of the Group to be removed.
+ */
+ public removeGroup(groupKey: GroupKey): this {
+ if (this.groups[groupKey] != null) delete this.groups[groupKey];
+ return this;
+ }
+
+ /**
+ * Returns the count of registered Groups in the Collection.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroupcount)
+ *
+ * @public
+ */
+ public getGroupCount(): number {
+ let size = 0;
+ Object.keys(this.groups).map(() => size++);
+ return size;
+ }
+
+ /**
+ * Creates a new Selector and associates it to the Collection.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#createSelector)
+ *
+ * @public
+ * @param selectorKey - Unique identifier of the Selector to be created.
+ * @param itemKey - Key/Name identifier of the Item to be represented by the Selector.
+ */
+ public createSelector(
+ selectorKey: SelectorKey,
+ itemKey: ItemKey | null
+ ): Selector {
+ let selector = this.getSelector(selectorKey, { notExisting: true });
+ if (!this.isInstantiated) LogCodeManager.log('1B:02:04');
+
+ // Check if Selector already exists
+ if (selector != null) {
+ if (!selector.isPlaceholder) {
+ LogCodeManager.log('1B:03:03', [selectorKey]);
+ return selector;
+ }
+ selector.select(itemKey, { overwrite: true });
+ return selector;
+ }
+
+ // Create new Selector
+ selector = new Selector(this, itemKey, {
+ key: selectorKey,
+ });
+ this.selectors[selectorKey] = selector;
+
+ return selector;
+ }
+
+ /**
+ * Creates a new Selector and associates it to the Collection.
+ *
+ * The specified `itemKey` is used as the unique identifier key of the new Selector.
+ * ```
+ * MY_COLLECTION.select('1');
+ * // is equivalent to
+ * MY_COLLECTION.createSelector('1', '1');
+ * ```
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#select)
+ *
+ * @public
+ * @param itemKey - Key/Name identifier of the Item to be represented by the Selector
+ * and used as unique identifier of the Selector.
+ */
+ public select(itemKey: ItemKey): Selector {
+ return this.createSelector(itemKey, itemKey);
+ }
+
+ /**
+ * Returns a boolean indicating whether a Selector with the specified `selectorKey`
+ * exists in the Collection or not.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#hasselector)
+ *
+ * @public
+ * @param selectorKey - Key/Name identifier of the Selector to be checked for existence.
+ * @param config - Configuration object
+ */
+ public hasSelector(
+ selectorKey: SelectorKey | undefined,
+ config: HasConfigInterface = {}
+ ): boolean {
+ return !!this.getSelector(selectorKey, config);
+ }
+
+ /**
+ * Retrieves a single Selector with the specified key/name identifier from the Collection.
+ *
+ * If the to retrieve Selector doesn't exist, `undefined` is returned.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getselector)
+ *
+ * @public
+ * @param selectorKey - Key/Name identifier of the Selector.
+ * @param config - Configuration object
+ */
+ public getSelector(
+ selectorKey: SelectorKey | undefined | null,
+ config: HasConfigInterface = {}
+ ): Selector | undefined {
+ config = defineConfig(config, {
+ notExisting: false,
+ });
+
+ // Get Selector
+ const selector = selectorKey ? this.selectors[selectorKey] : undefined;
+
+ // Check if Selector exists
+ if (selector == null || (!config.notExisting && !selector.exists))
+ return undefined;
+
+ ComputedTracker.tracked(selector.observers['value']);
+ return selector;
+ }
+
+ /**
+ * Retrieves a single Selector with the specified key/name identifier from the Collection.
+ *
+ * If the to retrieve Selector doesn't exist, a reference Selector is returned.
+ * This has the advantage that Components that have the reference Selector bound to themselves
+ * are rerenderd when the original Selector is created.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getselectorwithreference)
+ *
+ * @public
+ * @param selectorKey - Key/Name identifier of the Selector.
+ */
+ public getSelectorWithReference(
+ selectorKey: SelectorKey
+ ): Selector {
+ let selector = this.getSelector(selectorKey, { notExisting: true });
+
+ // Create dummy Selector to hold reference
+ if (selector == null) {
+ selector = new Selector(this, null, {
+ key: selectorKey,
+ isPlaceholder: true,
+ });
+ this.selectors[selectorKey] = selector;
+ }
+
+ ComputedTracker.tracked(selector.observers['value']);
+ return selector;
+ }
+
+ /**
+ * Removes a Selector with the specified key/name identifier from the Collection,
+ * if it exists in the Collection.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removeselector)
+ *
+ * @public
+ * @param selectorKey - Key/Name identifier of the Selector to be removed.
+ */
+ public removeSelector(selectorKey: SelectorKey): this {
+ if (this.selectors[selectorKey] != null) {
+ this.selectors[selectorKey].unselect();
+ delete this.selectors[selectorKey];
+ }
+ return this;
+ }
+
+ /**
+ * Returns the count of registered Selectors in the Collection.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getselectorcount)
+ *
+ * @public
+ */
+ public getSelectorCount(): number {
+ let size = 0;
+ Object.keys(this.selectors).map(() => size++);
+ return size;
+ }
+
+ /**
+ * Returns a boolean indicating whether a Item with the specified `itemKey`
+ * exists in the Collection or not.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#hasitem)
+ *
+ * @public
+ * @param itemKey - Key/Name identifier of the Item.
+ * @param config - Configuration object
+ */
+ public hasItem(
+ itemKey: ItemKey | undefined,
+ config: HasConfigInterface = {}
+ ): boolean {
+ return !!this.getItem(itemKey, config);
+ }
+
+ /**
+ * Retrieves a single Item with the specified key/name identifier from the Collection.
+ *
+ * If the to retrieve Item doesn't exist, `undefined` is returned.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getitem)
+ *
+ * @public
+ * @param itemKey - Key/Name identifier of the Item.
+ * @param config - Configuration object
+ */
+ public getItem(
+ itemKey: ItemKey | undefined | null,
+ config: HasConfigInterface = {}
+ ): Item | undefined {
+ config = defineConfig(config, {
+ notExisting: false,
+ });
+
+ // Get Item
+ const item = itemKey != null ? this.data[itemKey] : undefined;
+
+ // Check if Item exists
+ if (item == null || (!config.notExisting && !item.exists)) return undefined;
+
+ ComputedTracker.tracked(item.observers['value']);
+ return item;
+ }
+
+ /**
+ * Retrieves a single Item with the specified key/name identifier from the Collection.
+ *
+ * If the to retrieve Item doesn't exist, a reference Item is returned.
+ * This has the advantage that Components that have the reference Item bound to themselves
+ * are rerenderd when the original Item is created.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getitemwithreference)
+ *
+ * @public
+ * @param itemKey - Key/Name identifier of the Item.
+ */
+ public getItemWithReference(itemKey: ItemKey): Item {
+ let item = this.getItem(itemKey, { notExisting: true });
+
+ // Create dummy Item to hold reference
+ if (item == null) item = this.createPlaceholderItem(itemKey, true);
+
+ ComputedTracker.tracked(item.observers['value']);
+ return item;
+ }
+
+ /**
+ * Creates a placeholder Item
+ * that can be used to hold a reference to a not existing Item.
+ *
+ * @internal
+ * @param itemKey - Unique identifier of the to create placeholder Item.
+ * @param addToCollection - Whether to add the Item to be created to the Collection.
+ */
+ public createPlaceholderItem(
+ itemKey: ItemKey,
+ addToCollection = false
+ ): Item {
+ // Create placeholder Item
+ const item = new Item(
+ this,
+ {
+ [this.config.primaryKey]: itemKey, // Setting primaryKey of the Item to passed itemKey
+ dummy: 'item',
+ } as any,
+ { isPlaceholder: true }
+ );
+
+ // Add placeholder Item to Collection
+ if (
+ addToCollection &&
+ !Object.prototype.hasOwnProperty.call(this.data, itemKey)
+ )
+ this.data[itemKey] = item;
+
+ ComputedTracker.tracked(item.observers['value']);
+ return item;
+ }
+
+ /**
+ * Retrieves the value (data object) of a single Item
+ * with the specified key/name identifier from the Collection.
+ *
+ * If the to retrieve Item containing the value doesn't exist, `undefined` is returned.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getitemvalue)
+ *
+ * @public
+ * @param itemKey - Key/Name identifier of the Item.
+ * @param config - Configuration object
+ */
+ public getItemValue(
+ itemKey: ItemKey | undefined,
+ config: HasConfigInterface = {}
+ ): DataType | undefined {
+ const item = this.getItem(itemKey, config);
+ if (item == null) return undefined;
+ return item.value;
+ }
+
+ /**
+ * Retrieves all Items from the Collection.
+ * ```
+ * MY_COLLECTION.getAllItems();
+ * // is equivalent to
+ * MY_COLLECTION.getDefaultGroup().items;
+ * ```
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getallitems)
+ *
+ * @public
+ * @param config - Configuration object
+ */
+ public getAllItems(config: HasConfigInterface = {}): Array> {
+ config = defineConfig(config, {
+ notExisting: false,
+ });
+
+ const defaultGroup = this.getDefaultGroup();
+ let items: Array> = [];
+
+ // If config.notExisting transform the data object into array since it contains all Items,
+ // otherwise return the default Group Items
+ if (config.notExisting) {
+ for (const key in this.data) items.push(this.data[key]);
+ } else {
+ // Why default Group Items and not all '.exists === true' Items?
+ // Because the default Group keeps track of all existing Items.
+ // It also does control the Collection output in binding methods like 'useAgile()'
+ // and therefore should do it here too.
+ items = defaultGroup?.getItems() || [];
+ }
+
+ return items;
+ }
+
+ /**
+ * Retrieves the values (data objects) of all Items from the Collection.
+ * ```
+ * MY_COLLECTION.getAllItemValues();
+ * // is equivalent to
+ * MY_COLLECTION.getDefaultGroup().output;
+ * ```
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getallitemvalues)
+ *
+ * @public
+ * @param config - Configuration object
+ */
+ public getAllItemValues(config: HasConfigInterface = {}): Array {
+ const items = this.getAllItems(config);
+ return items.map((item) => item.value);
+ }
+
+ /**
+ * Preserves the Collection `value` in the corresponding external Storage.
+ *
+ * The Collection key/name is used as the unique identifier for the Persistent.
+ * If that is not desired or the Collection has no unique identifier,
+ * please specify a separate unique identifier for the Persistent.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#persist)
+ *
+ * @public
+ * @param config - Configuration object
+ */
+ public persist(config?: CollectionPersistentConfigInterface): this;
+ /**
+ * Preserves the Collection `value` in the corresponding external Storage.
+ *
+ * The specified key is used as the unique identifier for the Persistent.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#persist)
+ *
+ * @public
+ * @param key - Key/Name identifier of Persistent.
+ * @param config - Configuration object
+ */
+ public persist(
+ key?: StorageKey,
+ config?: CollectionPersistentConfigInterface
+ ): this;
+ public persist(
+ keyOrConfig: StorageKey | CollectionPersistentConfigInterface = {},
+ config: CollectionPersistentConfigInterface = {}
+ ): this {
+ let _config: CollectionPersistentConfigInterface;
+ let key: StorageKey | undefined;
+
+ if (isValidObject(keyOrConfig)) {
+ _config = keyOrConfig as CollectionPersistentConfigInterface;
+ key = this._key;
+ } else {
+ _config = config || {};
+ key = keyOrConfig as StorageKey;
+ }
+
+ _config = defineConfig(_config, {
+ loadValue: true,
+ storageKeys: [],
+ defaultStorageKey: null as any,
+ });
+
+ // Check if Collection is already persisted
+ if (this.persistent != null && this.isPersisted) return this;
+
+ // Create Persistent (-> persist value)
+ this.persistent = new CollectionPersistent(this, {
+ instantiate: _config.loadValue,
+ storageKeys: _config.storageKeys,
+ key: key,
+ defaultStorageKey: _config.defaultStorageKey,
+ });
+
+ return this;
+ }
+
+ /**
+ * Fires immediately after the persisted `value`
+ * is loaded into the Collection from a corresponding external Storage.
+ *
+ * Registering such callback function makes only sense
+ * when the Collection is [persisted](https://agile-ts.org/docs/core/collection/methods/#persist) in an external Storage.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#onload)
+ *
+ * @public
+ * @param callback - A function to be executed after the externally persisted `value` was loaded into the Collection.
+ */
+ public onLoad(callback: (success: boolean) => void): this {
+ if (!this.persistent) return this;
+ if (!isFunction(callback)) {
+ LogCodeManager.log('00:03:01', ['OnLoad Callback', 'function']);
+ return this;
+ }
+
+ // Register specified callback
+ this.persistent.onLoad = callback;
+
+ // If Collection is already persisted ('isPersisted') fire specified callback immediately
+ if (this.isPersisted) callback(true);
+
+ return this;
+ }
+
+ /**
+ * Removes all Items from the Collection
+ * and resets all Groups and Selectors of the Collection.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#reset)
+ *
+ * @public
+ */
+ public reset(): this {
+ // Reset data
+ this.data = {};
+ this.size = 0;
+
+ // Reset Groups
+ for (const key in this.groups) this.getGroup(key)?.reset();
+
+ // Reset Selectors
+ for (const key in this.selectors) this.getSelector(key)?.reset();
+
+ return this;
+ }
+
+ /**
+ * Puts `itemKeys/s` into Group/s.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#put)
+ *
+ * @public
+ * @param itemKeys - `itemKey/s` to be put into the specified Group/s.
+ * @param groupKeys - Key/Name Identifier/s of the Group/s the specified `itemKey/s` are to put in.
+ * @param config - Configuration object
+ */
+ public put(
+ itemKeys: ItemKey | Array,
+ groupKeys: GroupKey | Array,
+ config: GroupAddConfigInterface = {}
+ ): this {
+ const _itemKeys = normalizeArray(itemKeys);
+ const _groupKeys = normalizeArray(groupKeys);
+
+ // Assign itemKeys to Groups
+ _groupKeys.forEach((groupKey) => {
+ this.getGroup(groupKey)?.add(_itemKeys, config);
+ });
+
+ return this;
+ }
+
+ /**
+ * Moves specified `itemKey/s` from one Group to another Group.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#move)
+ *
+ * @public
+ * @param itemKeys - `itemKey/s` to be moved.
+ * @param oldGroupKey - Key/Name Identifier of the Group the `itemKey/s` are moved from.
+ * @param newGroupKey - Key/Name Identifier of the Group the `itemKey/s` are moved in.
+ * @param config - Configuration object
+ */
+ public move(
+ itemKeys: ItemKey | Array,
+ oldGroupKey: GroupKey,
+ newGroupKey: GroupKey,
+ config: GroupAddConfigInterface = {}
+ ): this {
+ const _itemKeys = normalizeArray(itemKeys);
+
+ // Remove itemKeys from old Group
+ this.getGroup(oldGroupKey)?.remove(
+ _itemKeys,
+ removeProperties(config, ['method', 'overwrite'])
+ );
+
+ // Assign itemKeys to new Group
+ this.getGroup(newGroupKey)?.add(_itemKeys, config);
+
+ return this;
+ }
+
+ /**
+ * Updates the key/name identifier of the Item
+ * and returns a boolean indicating
+ * whether the Item identifier was updated successfully.
+ *
+ * @internal
+ * @param oldItemKey - Old key/name Item identifier.
+ * @param newItemKey - New key/name Item identifier.
+ * @param config - Configuration object
+ */
+ public updateItemKey(
+ oldItemKey: ItemKey,
+ newItemKey: ItemKey,
+ config: UpdateItemKeyConfigInterface = {}
+ ): boolean {
+ const item = this.getItem(oldItemKey, { notExisting: true });
+ config = defineConfig(config, {
+ background: false,
+ });
+
+ if (item == null || oldItemKey === newItemKey) return false;
+
+ // Check if Item with newItemKey already exists
+ if (this.hasItem(newItemKey)) {
+ LogCodeManager.log('1B:03:04', [oldItemKey, newItemKey, this._key]);
+ return false;
+ }
+
+ // Update itemKey in data object
+ delete this.data[oldItemKey];
+ this.data[newItemKey] = item;
+
+ // Update key/name of the Item
+ item.setKey(newItemKey, {
+ background: config.background,
+ });
+
+ // Update Persistent key of the Item if it follows the Item Storage Key pattern
+ // and therefore differs from the actual Item key
+ // (-> isn't automatically updated when the Item key is updated)
+ if (
+ item.persistent != null &&
+ item.persistent._key ===
+ CollectionPersistent.getItemStorageKey(oldItemKey, this._key)
+ )
+ item.persistent?.setKey(
+ CollectionPersistent.getItemStorageKey(newItemKey, this._key)
+ );
+
+ // Update itemKey in Groups
+ for (const groupKey in this.groups) {
+ const group = this.getGroup(groupKey, { notExisting: true });
+ if (group == null || !group.has(oldItemKey)) continue;
+ group.replace(oldItemKey, newItemKey, { background: config.background });
+ }
+
+ // Update itemKey in Selectors
+ for (const selectorKey in this.selectors) {
+ const selector = this.getSelector(selectorKey, { notExisting: true });
+ if (selector == null) continue;
+
+ // Reselect Item in Selector that has selected the newItemKey.
+ // Necessary because potential reference placeholder Item got overwritten
+ // with the new (renamed) Item
+ // -> has to find the new Item at selected itemKey
+ // since the placeholder Item got overwritten
+ if (selector.hasSelected(newItemKey, false)) {
+ selector.reselect({
+ force: true, // Because itemKeys are the same (but not the Items at this itemKey anymore)
+ background: config.background,
+ });
+ }
+
+ // Select newItemKey in Selector that has selected the oldItemKey
+ if (selector.hasSelected(oldItemKey, false))
+ selector.select(newItemKey, {
+ background: config.background,
+ });
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns all key/name identifiers of the Group/s containing the specified `itemKey`.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroupkeysthathaveitemkey)
+ *
+ * @public
+ * @param itemKey - `itemKey` to be contained in Group/s.
+ */
+ public getGroupKeysThatHaveItemKey(itemKey: ItemKey): Array {
+ const groupKeys: Array = [];
+ for (const groupKey in this.groups) {
+ const group = this.groups[groupKey];
+ if (group?.has(itemKey)) groupKeys.push(groupKey);
+ }
+ return groupKeys;
+ }
+
+ /**
+ * Removes Item/s from:
+ *
+ * - `.everywhere()`:
+ * Removes Item/s from the entire Collection and all its Groups and Selectors (i.e. from everywhere)
+ * ```
+ * MY_COLLECTION.remove('1').everywhere();
+ * // is equivalent to
+ * MY_COLLECTION.removeItems('1');
+ * ```
+ * - `.fromGroups()`:
+ * Removes Item/s only from specified Groups.
+ * ```
+ * MY_COLLECTION.remove('1').fromGroups(['1', '2']);
+ * // is equivalent to
+ * MY_COLLECTION.removeFromGroups('1', ['1', '2']);
+ * ```
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#remove)
+ *
+ * @public
+ * @param itemKeys - Item/s with identifier/s to be removed.
+ */
+ public remove(
+ itemKeys: ItemKey | Array
+ ): {
+ fromGroups: (groups: Array | ItemKey) => Collection;
+ everywhere: (config?: RemoveItemsConfigInterface) => Collection;
+ } {
+ return {
+ fromGroups: (groups: Array | ItemKey) =>
+ this.removeFromGroups(itemKeys, groups),
+ everywhere: (config) => this.removeItems(itemKeys, config || {}),
+ };
+ }
+
+ /**
+ * Remove Item/s from specified Group/s.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removefromgroups)
+ *
+ * @public
+ * @param itemKeys - Key/Name Identifier/s of the Item/s to be removed from the Group/s.
+ * @param groupKeys - Key/Name Identifier/s of the Group/s the Item/s are to remove from.
+ */
+ public removeFromGroups(
+ itemKeys: ItemKey | Array,
+ groupKeys: GroupKey | Array
+ ): this {
+ const _itemKeys = normalizeArray(itemKeys);
+ const _groupKeys = normalizeArray(groupKeys);
+
+ _itemKeys.forEach((itemKey) => {
+ let removedFromGroupsCount = 0;
+
+ // Remove itemKey from the Groups
+ _groupKeys.forEach((groupKey) => {
+ const group = this.getGroup(groupKey, { notExisting: true });
+ if (!group?.has(itemKey)) return;
+ group.remove(itemKey);
+ removedFromGroupsCount++;
+ });
+
+ // If the Item was removed from each Group representing the Item,
+ // remove it completely
+ if (
+ removedFromGroupsCount >=
+ this.getGroupKeysThatHaveItemKey(itemKey).length
+ )
+ this.removeItems(itemKey);
+ });
+
+ return this;
+ }
+
+ /**
+ * Removes Item/s from the entire Collection and all the Collection's Groups and Selectors.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removeitems)
+ *
+ * @public
+ * @param itemKeys - Key/Name identifier/s of the Item/s to be removed from the entire Collection.
+ * @param config - Configuration object
+ */
+ public removeItems(
+ itemKeys: ItemKey | Array,
+ config: RemoveItemsConfigInterface = {}
+ ): this {
+ config = defineConfig(config, {
+ notExisting: false,
+ removeSelector: false,
+ });
+ const _itemKeys = normalizeArray(itemKeys);
+
+ _itemKeys.forEach((itemKey) => {
+ const item = this.getItem(itemKey, { notExisting: config.notExisting });
+ if (item == null) return;
+ const wasPlaceholder = item.isPlaceholder;
+
+ // Remove Item from the Groups
+ for (const groupKey in this.groups) {
+ const group = this.getGroup(groupKey, { notExisting: true });
+ if (group?.has(itemKey)) group?.remove(itemKey);
+ }
+
+ // Remove Item from Storage
+ item.persistent?.removePersistedValue();
+
+ // Remove Item from Collection
+ delete this.data[itemKey];
+
+ // Reselect or remove Selectors which have represented the removed Item
+ for (const selectorKey in this.selectors) {
+ const selector = this.getSelector(selectorKey, { notExisting: true });
+ if (selector != null && selector.hasSelected(itemKey, false)) {
+ if (config.removeSelector) {
+ // Remove Selector
+ this.removeSelector(selector._key ?? 'unknown');
+ } else {
+ // Reselect Item in Selector
+ // in order to create a new dummyItem
+ // to hold a reference to the now not existing Item
+ selector.reselect({ force: true });
+ }
+ }
+ }
+
+ if (!wasPlaceholder) this.size--;
+ });
+
+ return this;
+ }
+
+ /**
+ * Assigns the provided `data` object to an already existing Item
+ * with specified key/name identifier found in the `data` object.
+ * If the Item doesn't exist yet, a new Item with the `data` object as value
+ * is created and assigned to the Collection.
+ *
+ * Returns a boolean indicating
+ * whether the `data` object was assigned/updated successfully.
+ *
+ * @internal
+ * @param data - Data object
+ * @param config - Configuration object
+ */
+ public assignData(
+ data: DataType,
+ config: AssignDataConfigInterface = {}
+ ): boolean {
+ config = defineConfig(config, {
+ patch: false,
+ background: false,
+ });
+ const _data = copy(data); // Copy data object to get rid of reference
+ const primaryKey = this.config.primaryKey;
+
+ if (!isValidObject(_data)) {
+ LogCodeManager.log('1B:03:05', [this._key]);
+ return false;
+ }
+
+ // Check if data object contains valid itemKey,
+ // otherwise add random itemKey to Item
+ if (!Object.prototype.hasOwnProperty.call(_data, primaryKey)) {
+ LogCodeManager.log('1B:02:05', [this._key, primaryKey]);
+ _data[primaryKey] = generateId();
+ }
+
+ const itemKey = _data[primaryKey];
+ const item = this.getItem(itemKey, { notExisting: true });
+ const wasPlaceholder = item?.isPlaceholder || false;
+
+ // Create new Item or update existing Item
+ if (item != null) {
+ if (config.patch) {
+ item.patch(_data, { background: config.background });
+ } else {
+ item.set(_data, { background: config.background });
+ }
+ } else {
+ this.assignItem(new Item(this, _data), {
+ background: config.background,
+ });
+ }
+
+ // Increase size of Collection if Item was previously a placeholder
+ // (-> hasn't officially existed in Collection before)
+ if (wasPlaceholder) this.size++;
+
+ return true;
+ }
+
+ /**
+ * Assigns the specified Item to the Collection
+ * at the key/name identifier of the Item.
+ *
+ * And returns a boolean indicating
+ * whether the Item was assigned successfully.
+ *
+ * @internal
+ * @param item - Item to be added.
+ * @param config - Configuration object
+ */
+ public assignItem(
+ item: Item,
+ config: AssignItemConfigInterface = {}
+ ): boolean {
+ config = defineConfig(config, {
+ overwrite: false,
+ background: false,
+ });
+ const primaryKey = this.config.primaryKey;
+ let itemKey = item._value[primaryKey];
+ let increaseCollectionSize = true;
+
+ // Check if Item has valid itemKey,
+ // otherwise add random itemKey to Item
+ if (!Object.prototype.hasOwnProperty.call(item._value, primaryKey)) {
+ LogCodeManager.log('1B:02:05', [this._key, primaryKey]);
+ itemKey = generateId();
+ item.patch(
+ { [this.config.primaryKey]: itemKey },
+ { background: config.background }
+ );
+ item._key = itemKey;
+ }
+
+ // Check if Item belongs to this Collection
+ if (item.collection() !== this) {
+ LogCodeManager.log('1B:03:06', [this._key, item.collection()._key]);
+ return false;
+ }
+
+ // Check if Item already exists
+ if (this.getItem(itemKey, { notExisting: true }) != null) {
+ if (!config.overwrite) {
+ this.assignData(item._value);
+ return true;
+ } else increaseCollectionSize = false;
+ }
+
+ // Assign/add Item to Collection
+ this.data[itemKey] = item;
+
+ // Rebuild Groups that include itemKey
+ // after adding Item with itemKey to the Collection
+ // (because otherwise it can't find the Item as it isn't added yet)
+ this.rebuildGroupsThatIncludeItemKey(itemKey, {
+ background: config.background,
+ });
+
+ if (increaseCollectionSize) this.size++;
+
+ return true;
+ }
+
+ /**
+ * Rebuilds all Groups that contain the specified `itemKey`.
+ *
+ * @internal
+ * @itemKey - `itemKey` Groups must contain to be rebuilt.
+ * @config - Configuration object
+ */
+ public rebuildGroupsThatIncludeItemKey(
+ itemKey: ItemKey,
+ config: RebuildGroupsThatIncludeItemKeyConfigInterface = {}
+ ): void {
+ config = defineConfig(config, {
+ background: false,
+ sideEffects: {
+ enabled: true,
+ exclude: [],
+ },
+ });
+
+ // Rebuild Groups that include itemKey
+ for (const groupKey in this.groups) {
+ const group = this.getGroup(groupKey);
+ if (group?.has(itemKey)) {
+ // Not necessary because a sideEffect of ingesting the Group
+ // into the runtime is to rebuilt itself
+ // group.rebuild();
+
+ group?.rebuild({
+ background: config?.background,
+ sideEffects: config?.sideEffects,
+ storage: false,
+ });
+ }
+ }
+ }
+}
+
+export type DefaultItem = Record; // same as { [key: string]: any };
+export type CollectionKey = string | number;
+export type ItemKey = string | number;
+
+export interface CreateCollectionConfigInterface {
+ /**
+ * Initial Groups of the Collection.
+ * @default []
+ */
+ groups?: { [key: string]: Group } | string[];
+ /**
+ * Initial Selectors of the Collection
+ * @default []
+ */
+ selectors?: { [key: string]: Selector } | string[];
+ /**
+ * Key/Name identifier of the Collection.
+ * @default undefined
+ */
+ key?: CollectionKey;
+ /**
+ * Key/Name of the property
+ * which represents the unique Item identifier
+ * in collected data objects.
+ * @default 'id'
+ */
+ primaryKey?: string;
+ /**
+ * Key/Name identifier of the default Group that is created shortly after instantiation.
+ * The default Group represents the default pattern of the Collection.
+ * @default 'default'
+ */
+ defaultGroupKey?: GroupKey;
+ /**
+ * Initial data objects of the Collection.
+ * @default []
+ */
+ initialData?: Array;
+}
+
+export type CollectionConfig =
+ | CreateCollectionConfigInterface
+ | ((
+ collection: Collection
+ ) => CreateCollectionConfigInterface);
+
+export interface CollectionConfigInterface {
+ /**
+ * Key/Name of the property
+ * which represents the unique Item identifier
+ * in collected data objects.
+ * @default 'id'
+ */
+ primaryKey: string;
+ /**
+ * Key/Name identifier of the default Group that is created shortly after instantiation.
+ * The default Group represents the default pattern of the Collection.
+ * @default 'default'
+ */
+ defaultGroupKey: ItemKey;
+}
+
+export interface CollectConfigInterface
+ extends AssignDataConfigInterface {
+ /**
+ * In which way the collected data should be added to the Collection.
+ * - 'push' = at the end
+ * - 'unshift' = at the beginning
+ * https://www.tutorialspoint.com/what-are-the-differences-between-unshift-and-push-methods-in-javascript
+ * @default 'push'
+ */
+ method?: 'push' | 'unshift';
+ /**
+ * Performs the specified action for each collected data object.
+ * @default undefined
+ */
+ forEachItem?: (
+ data: DataType | Item,
+ key: ItemKey,
+ success: boolean,
+ index: number
+ ) => void;
+ /**
+ * Whether to create a Selector for each collected data object.
+ * @default false
+ */
+ select?: boolean;
+}
+
+export interface UpdateConfigInterface {
+ /**
+ * Whether to merge the data object with changes into the existing Item data object
+ * or overwrite the existing Item data object entirely.
+ * @default true
+ */
+ patch?: boolean | PatchOptionConfigInterface;
+ /**
+ * Whether to update the data object in background.
+ * So that the UI isn't notified of these changes and thus doesn't rerender.
+ * @default false
+ */
+ background?: boolean;
+}
+
+export interface UpdateItemKeyConfigInterface {
+ /**
+ * Whether to update the Item key/name identifier in background
+ * So that the UI isn't notified of these changes and thus doesn't rerender.
+ * @default false
+ */
+ background?: boolean;
+}
+
+export interface RebuildGroupsThatIncludeItemKeyConfigInterface {
+ /**
+ * Whether to rebuilt the Group in background.
+ * So that the UI isn't notified of these changes and thus doesn't rerender.
+ * @default false
+ */
+ background?: boolean;
+ /**
+ * Whether to execute the defined side effects.
+ * @default true
+ */
+ sideEffects?: SideEffectConfigInterface;
+}
+
+export interface HasConfigInterface {
+ /**
+ * Whether Items that do not officially exist,
+ * such as placeholder Items, can be found
+ * @default true
+ */
+ notExisting?: boolean;
+}
+
+export interface CollectionPersistentConfigInterface {
+ /**
+ * Whether the Persistent should automatically load
+ * the persisted value into the Collection after its instantiation.
+ * @default true
+ */
+ loadValue?: boolean;
+ /**
+ * Key/Name identifier of Storages
+ * in which the Collection value should be or is persisted.
+ * @default [`defaultStorageKey`]
+ */
+ storageKeys?: StorageKey[];
+ /**
+ * Key/Name identifier of the default Storage of the specified Storage keys.
+ *
+ * The Collection value is loaded from the default Storage by default
+ * and is only loaded from the remaining Storages (`storageKeys`)
+ * if the loading from the default Storage failed.
+ *
+ * @default first index of the specified Storage keys or the AgileTs default Storage key
+ */
+ defaultStorageKey?: StorageKey;
+}
+
+export interface RemoveItemsConfigInterface {
+ /**
+ * Whether to remove not officially existing Items (such as placeholder Items).
+ * Keep in mind that sometimes it won't remove an Item entirely
+ * as another Instance (like a Selector) might need to keep reference to it.
+ * https://github.com/agile-ts/agile/pull/152
+ * @default false
+ */
+ notExisting?: boolean;
+ /**
+ * Whether to remove Selectors that have selected an Item to be removed.
+ * @default false
+ */
+ removeSelector?: boolean;
+}
+
+export interface AssignDataConfigInterface {
+ /**
+ * When the Item identifier of the to assign data object already exists in the Collection,
+ * whether to merge the newly assigned data into the existing one
+ * or overwrite the existing one entirely.
+ * @default true
+ */
+ patch?: boolean;
+ /**
+ * Whether to assign the data object to the Collection in background.
+ * So that the UI isn't notified of these changes and thus doesn't rerender.
+ * @default false
+ */
+ background?: boolean;
+}
+
+export interface AssignItemConfigInterface {
+ /**
+ * If an Item with the Item identifier already exists,
+ * whether to overwrite it entirely with the new one.
+ * @default false
+ */
+ overwrite?: boolean;
+ /**
+ * Whether to assign the Item to the Collection in background.
+ * So that the UI isn't notified of these changes and thus doesn't rerender.
+ * @default false
+ */
+ background?: boolean;
+}
diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts
index 83b644fa..b386d611 100644
--- a/packages/core/src/collection/index.ts
+++ b/packages/core/src/collection/index.ts
@@ -1,1693 +1,40 @@
import {
+ Collection,
+ CollectionConfig,
+ DefaultItem,
Agile,
- Item,
- Group,
- GroupKey,
- Selector,
- SelectorKey,
- StorageKey,
- GroupConfigInterface,
- isValidObject,
- normalizeArray,
- copy,
- CollectionPersistent,
- GroupAddConfigInterface,
- ComputedTracker,
- generateId,
- SideEffectConfigInterface,
- SelectorConfigInterface,
- removeProperties,
- isFunction,
- LogCodeManager,
- PatchOptionConfigInterface,
- defineConfig,
+ shared,
} from '../internal';
-export class Collection<
- DataType extends Object = DefaultItem,
- GroupValueType = Array // To extract the Group Type Value in Integration methods like 'useAgile()'
-> {
- // Agile Instance the Collection belongs to
- public agileInstance: () => Agile;
-
- public config: CollectionConfigInterface;
- private initialConfig: CreateCollectionConfigInterface;
-
- // Key/Name identifier of the Collection
- public _key?: CollectionKey;
- // Amount of the Items stored in the Collection
- public size = 0;
- // Items stored in the Collection
- public data: { [key: string]: Item } = {};
- // Whether the Collection is persisted in an external Storage
- public isPersisted = false;
- // Manages the permanent persistent in external Storages
- public persistent: CollectionPersistent | undefined;
-
- // Registered Groups of Collection
- public groups: { [key: string]: Group } = {};
- // Registered Selectors of Collection
- public selectors: { [key: string]: Selector } = {};
-
- // Whether the Collection was instantiated correctly
- public isInstantiated = false;
-
- /**
- * A Collection manages a reactive set of Information
- * that we need to remember globally at a later point in time.
- * While providing a toolkit to use and mutate this set of Information.
- *
- * It is designed for arrays of data objects following the same pattern.
- *
- * Each of these data object must have a unique `primaryKey` to be correctly identified later.
- *
- * You can create as many global Collections as you need.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/)
- *
- * @public
- * @param agileInstance - Instance of Agile the Collection belongs to.
- * @param config - Configuration object
- */
- constructor(agileInstance: Agile, config: CollectionConfig = {}) {
- this.agileInstance = () => agileInstance;
- let _config = typeof config === 'function' ? config(this) : config;
- _config = defineConfig(_config, {
- primaryKey: 'id',
- groups: {},
- selectors: {},
- defaultGroupKey: 'default',
- });
- this._key = _config.key;
- this.config = {
- defaultGroupKey: _config.defaultGroupKey as any,
- primaryKey: _config.primaryKey as any,
- };
- this.initialConfig = _config;
-
- this.initGroups(_config.groups as any);
- this.initSelectors(_config.selectors as any);
-
- this.isInstantiated = true;
-
- // Add 'initialData' to Collection
- // (after 'isInstantiated' to add them properly to the Collection)
- if (_config.initialData) this.collect(_config.initialData);
-
- // Reselect Selector Items
- // Necessary because the selection of an Item
- // hasn't worked with a not correctly 'instantiated' Collection before
- for (const key in this.selectors) this.selectors[key].reselect();
-
- // Rebuild of Groups
- // Not necessary because if Items are added to the Collection,
- // (after 'isInstantiated = true')
- // the Groups which contain these added Items are rebuilt.
- // for (const key in this.groups) this.groups[key].rebuild();
- }
-
- /**
- * Updates the key/name identifier of the Collection.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/properties#key)
- *
- * @public
- * @param value - New key/name identifier.
- */
- public set key(value: CollectionKey | undefined) {
- this.setKey(value);
- }
-
- /**
- * Returns the key/name identifier of the Collection.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/properties#key)
- *
- * @public
- */
- public get key(): CollectionKey | undefined {
- return this._key;
- }
-
- /**
- * Updates the key/name identifier of the Collection.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#setkey)
- *
- * @public
- * @param value - New key/name identifier.
- */
- public setKey(value: CollectionKey | undefined) {
- const oldKey = this._key;
-
- // Update Collection key
- this._key = value;
-
- // Update key in Persistent (only if oldKey is equal to persistentKey
- // because otherwise the persistentKey is detached from the Collection key
- // -> not managed by Collection anymore)
- if (value != null && this.persistent?._key === oldKey)
- this.persistent?.setKey(value);
-
- return this;
- }
-
- /**
- * Creates a new Group without associating it to the Collection.
- *
- * This way of creating a Group is intended for use in the Collection configuration object,
- * where the `constructor()` takes care of the binding.
- *
- * After a successful initiation of the Collection we recommend using `createGroup()`,
- * because it automatically connects the Group to the Collection.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#group)
- *
- * @public
- * @param initialItems - Key/Name identifiers of the Items to be clustered by the Group.
- * @param config - Configuration object
- */
- public Group(
- initialItems?: Array,
- config: GroupConfigInterface = {}
- ): Group {
- if (this.isInstantiated) {
- const key = config.key ?? generateId();
- LogCodeManager.log('1B:02:00');
- return this.createGroup(key, initialItems);
- }
-
- return new Group(this, initialItems, config);
- }
-
- /**
- * Creates a new Selector without associating it to the Collection.
- *
- * This way of creating a Selector is intended for use in the Collection configuration object,
- * where the `constructor()` takes care of the binding.
- *
- * After a successful initiation of the Collection we recommend using `createSelector()`,
- * because it automatically connects the Group to the Collection.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#selector)
- *
- * @public
- * @param initialKey - Key/Name identifier of the Item to be represented by the Selector.
- * @param config - Configuration object
- */
- public Selector(
- initialKey: ItemKey | null,
- config: SelectorConfigInterface = {}
- ): Selector {
- if (this.isInstantiated) {
- const key = config.key ?? generateId();
- LogCodeManager.log('1B:02:01');
- return this.createSelector(key, initialKey);
- }
-
- return new Selector(this, initialKey, config);
- }
-
- /**
- * Sets up the specified Groups or Group keys
- * and assigns them to the Collection if they are valid.
- *
- * It also instantiates and assigns the default Group to the Collection.
- * The default Group reflects the default pattern of the Collection.
- *
- * @internal
- * @param groups - Entire Groups or Group keys to be set up.
- */
- public initGroups(groups: { [key: string]: Group } | string[]): void {
- if (!groups) return;
- let groupsObject: { [key: string]: Group } = {};
-
- // If groups is Array of Group keys/names, create the Groups based on these keys
- if (Array.isArray(groups)) {
- groups.forEach((groupKey) => {
- groupsObject[groupKey] = new Group(this, [], {
- key: groupKey,
- });
- });
- } else groupsObject = groups;
-
- // Add default Group
- groupsObject[this.config.defaultGroupKey] = new Group(this, [], {
- key: this.config.defaultGroupKey,
- });
-
- // Assign missing key/name to Group based on the property key
- for (const key in groupsObject)
- if (groupsObject[key]._key == null) groupsObject[key].setKey(key);
-
- this.groups = groupsObject;
- }
-
- /**
- * Sets up the specified Selectors or Selector keys
- * and assigns them to the Collection if they are valid.
- *
- * @internal
- * @param selectors - Entire Selectors or Selector keys to be set up.
- */
- public initSelectors(selectors: { [key: string]: Selector } | string[]) {
- if (!selectors) return;
- let selectorsObject: { [key: string]: Selector } = {};
-
- // If selectors is Array of Selector keys/names, create the Selectors based on these keys
- if (Array.isArray(selectors)) {
- selectors.forEach((selectorKey) => {
- selectorsObject[selectorKey] = new Selector(
- this,
- selectorKey,
- {
- key: selectorKey,
- }
- );
- });
- } else selectorsObject = selectors;
-
- // Assign missing key/name to Selector based on the property key
- for (const key in selectorsObject)
- if (selectorsObject[key]._key == null) selectorsObject[key].setKey(key);
-
- this.selectors = selectorsObject;
- }
-
- /**
- * Appends new data objects following the same pattern to the end of the Collection.
- *
- * Each collected `data object` requires a unique identifier at the primaryKey property (by default 'id')
- * to be correctly identified later.
- *
- * For example, if we collect some kind of user object,
- * it must contain such unique identifier at 'id'
- * to be added to the Collection.
- * ```
- * MY_COLLECTION.collect({id: '1', name: 'jeff'}); // valid
- * MY_COLLECTION.collect({name: 'frank'}); // invalid
- * ```
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#collect)
- *
- * @public
- * @param data - Data objects or entire Items to be added.
- * @param groupKeys - Group/s to which the specified data objects or Items are to be added.
- * @param config - Configuration object
- */
- public collect(
- data: DataType | Item | Array>,
- groupKeys?: GroupKey | Array,
- config: CollectConfigInterface = {}
- ): this {
- const _data = normalizeArray>(data);
- const _groupKeys = normalizeArray(groupKeys);
- const defaultGroupKey = this.config.defaultGroupKey;
- const primaryKey = this.config.primaryKey;
- config = defineConfig(config, {
- method: 'push',
- background: false,
- patch: false,
- select: false,
- });
-
- // Add default groupKey, since all Items are added to the default Group
- if (!_groupKeys.includes(defaultGroupKey)) _groupKeys.push(defaultGroupKey);
-
- // Create not existing Groups
- _groupKeys.forEach(
- (key) => this.groups[key] == null && this.createGroup(key)
- );
-
- _data.forEach((data, index) => {
- let itemKey;
- let success = false;
-
- // Assign Data or Item to Collection
- if (data instanceof Item) {
- success = this.assignItem(data, {
- background: config.background,
- });
- itemKey = data._key;
- } else {
- success = this.assignData(data, {
- patch: config.patch,
- background: config.background,
- });
- itemKey = data[primaryKey];
- }
-
- // Add itemKey to provided Groups and create corresponding Selector
- if (success) {
- _groupKeys.forEach((groupKey) => {
- this.getGroup(groupKey)?.add(itemKey, {
- method: config.method,
- background: config.background,
- });
- });
-
- if (config.select) this.createSelector(itemKey, itemKey);
- }
-
- if (config.forEachItem) config.forEachItem(data, itemKey, success, index);
- });
-
- return this;
- }
-
- /**
- * Updates the Item `data object` with the specified `object with changes`, if the Item exists.
- * By default the `object with changes` is merged into the Item `data object` at top level.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#update)
- *
- * @public
- * @param itemKey - Key/Name identifier of the Item to be updated.
- * @param changes - Object with changes to be merged into the Item data object.
- * @param config - Configuration object
- */
- public update(
- itemKey: ItemKey,
- changes: DefaultItem | DataType,
- config: UpdateConfigInterface = {}
- ): Item | undefined {
- const item = this.getItem(itemKey, { notExisting: true });
- const primaryKey = this.config.primaryKey;
- config = defineConfig(config, {
- patch: true,
- background: false,
- });
-
- // Check if the given conditions are suitable for a update action
- if (item == null) {
- LogCodeManager.log('1B:03:00', [itemKey, this._key]);
- return undefined;
- }
- if (!isValidObject(changes)) {
- LogCodeManager.log('1B:03:01', [itemKey, this._key]);
- return undefined;
- }
-
- const oldItemKey = item._value[primaryKey];
- const newItemKey = changes[primaryKey] || oldItemKey;
-
- // Update itemKey if the new itemKey differs from the old one
- if (oldItemKey !== newItemKey)
- this.updateItemKey(oldItemKey, newItemKey, {
- background: config.background,
- });
-
- // Patch changes into Item data object
- if (config.patch) {
- // Delete primaryKey property from 'changes object' because if it has changed,
- // it is correctly updated in the above called 'updateItemKey()' method
- if (changes[primaryKey]) delete changes[primaryKey];
-
- let patchConfig: { addNewProperties?: boolean } =
- typeof config.patch === 'object' ? config.patch : {};
- patchConfig = {
- addNewProperties: true,
- ...patchConfig,
- };
-
- item.patch(changes as any, {
- background: config.background,
- addNewProperties: patchConfig.addNewProperties,
- });
- }
- // Apply changes to Item data object
- else {
- // Ensure that the current Item identifier isn't different from the 'changes object' itemKey
- if (changes[this.config.primaryKey] !== itemKey) {
- changes[this.config.primaryKey] = itemKey;
- LogCodeManager.log('1B:02:02', [], changes);
- }
-
- item.set(changes as any, {
- background: config.background,
- });
- }
-
- return item;
- }
-
- /**
- * Creates a new Group and associates it to the Collection.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#createGroup)
- *
- * @public
- * @param groupKey - Unique identifier of the Group to be created.
- * @param initialItems - Key/Name identifiers of the Items to be clustered by the Group.
- */
- public createGroup(
- groupKey: GroupKey,
- initialItems: Array = []
- ): Group {
- let group = this.getGroup(groupKey, { notExisting: true });
- if (!this.isInstantiated) LogCodeManager.log('1B:02:03');
-
- // Check if Group already exists
- if (group != null) {
- if (!group.isPlaceholder) {
- LogCodeManager.log('1B:03:02', [groupKey]);
- return group;
- }
- group.set(initialItems, { overwrite: true });
- return group;
- }
-
- // Create new Group
- group = new Group(this, initialItems, { key: groupKey });
- this.groups[groupKey] = group;
-
- return group;
- }
-
- /**
- * Returns a boolean indicating whether a Group with the specified `groupKey`
- * exists in the Collection or not.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#hasgroup)
- *
- * @public
- * @param groupKey - Key/Name identifier of the Group to be checked for existence.
- * @param config - Configuration object
- */
- public hasGroup(
- groupKey: GroupKey | undefined,
- config: HasConfigInterface = {}
- ): boolean {
- return !!this.getGroup(groupKey, config);
- }
-
- /**
- * Retrieves a single Group with the specified key/name identifier from the Collection.
- *
- * If the to retrieve Group doesn't exist, `undefined` is returned.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroup)
- *
- * @public
- * @param groupKey - Key/Name identifier of the Group.
- * @param config - Configuration object
- */
- public getGroup(
- groupKey: GroupKey | undefined | null,
- config: HasConfigInterface = {}
- ): Group | undefined {
- config = defineConfig(config, {
- notExisting: false,
- });
-
- // Retrieve Group
- const group = groupKey ? this.groups[groupKey] : undefined;
-
- // Check if retrieved Group exists
- if (group == null || (!config.notExisting && !group.exists))
- return undefined;
-
- ComputedTracker.tracked(group.observers['value']);
- return group;
- }
-
- /**
- * Retrieves the default Group from the Collection.
- *
- * Every Collection should have a default Group,
- * which represents the default pattern of the Collection.
- *
- * If the default Group, for what ever reason, doesn't exist, `undefined` is returned.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getdefaultgroup)
- *
- * @public
- */
- public getDefaultGroup(): Group | undefined {
- return this.getGroup(this.config.defaultGroupKey);
- }
-
- /**
- * Retrieves a single Group with the specified key/name identifier from the Collection.
- *
- * If the to retrieve Group doesn't exist, a reference Group is returned.
- * This has the advantage that Components that have the reference Group bound to themselves
- * are rerenderd when the original Group is created.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroupwithreference)
- *
- * @public
- * @param groupKey - Key/Name identifier of the Group.
- */
- public getGroupWithReference(groupKey: GroupKey): Group {
- let group = this.getGroup(groupKey, { notExisting: true });
-
- // Create dummy Group to hold reference
- if (group == null) {
- group = new Group(this, [], {
- key: groupKey,
- isPlaceholder: true,
- });
- this.groups[groupKey] = group;
- }
-
- ComputedTracker.tracked(group.observers['value']);
- return group;
- }
-
- /**
- * Removes a Group with the specified key/name identifier from the Collection,
- * if it exists in the Collection.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removegroup)
- *
- * @public
- * @param groupKey - Key/Name identifier of the Group to be removed.
- */
- public removeGroup(groupKey: GroupKey): this {
- if (this.groups[groupKey] != null) delete this.groups[groupKey];
- return this;
- }
-
- /**
- * Returns the count of registered Groups in the Collection.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroupcount)
- *
- * @public
- */
- public getGroupCount(): number {
- let size = 0;
- Object.keys(this.groups).map(() => size++);
- return size;
- }
-
- /**
- * Creates a new Selector and associates it to the Collection.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#createSelector)
- *
- * @public
- * @param selectorKey - Unique identifier of the Selector to be created.
- * @param itemKey - Key/Name identifier of the Item to be represented by the Selector.
- */
- public createSelector(
- selectorKey: SelectorKey,
- itemKey: ItemKey | null
- ): Selector {
- let selector = this.getSelector(selectorKey, { notExisting: true });
- if (!this.isInstantiated) LogCodeManager.log('1B:02:04');
-
- // Check if Selector already exists
- if (selector != null) {
- if (!selector.isPlaceholder) {
- LogCodeManager.log('1B:03:03', [selectorKey]);
- return selector;
- }
- selector.select(itemKey, { overwrite: true });
- return selector;
- }
-
- // Create new Selector
- selector = new Selector(this, itemKey, {
- key: selectorKey,
- });
- this.selectors[selectorKey] = selector;
-
- return selector;
- }
-
- /**
- * Creates a new Selector and associates it to the Collection.
- *
- * The specified `itemKey` is used as the unique identifier key of the new Selector.
- * ```
- * MY_COLLECTION.select('1');
- * // is equivalent to
- * MY_COLLECTION.createSelector('1', '1');
- * ```
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#select)
- *
- * @public
- * @param itemKey - Key/Name identifier of the Item to be represented by the Selector
- * and used as unique identifier of the Selector.
- */
- public select(itemKey: ItemKey): Selector {
- return this.createSelector(itemKey, itemKey);
- }
-
- /**
- * Returns a boolean indicating whether a Selector with the specified `selectorKey`
- * exists in the Collection or not.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#hasselector)
- *
- * @public
- * @param selectorKey - Key/Name identifier of the Selector to be checked for existence.
- * @param config - Configuration object
- */
- public hasSelector(
- selectorKey: SelectorKey | undefined,
- config: HasConfigInterface = {}
- ): boolean {
- return !!this.getSelector(selectorKey, config);
- }
-
- /**
- * Retrieves a single Selector with the specified key/name identifier from the Collection.
- *
- * If the to retrieve Selector doesn't exist, `undefined` is returned.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getselector)
- *
- * @public
- * @param selectorKey - Key/Name identifier of the Selector.
- * @param config - Configuration object
- */
- public getSelector(
- selectorKey: SelectorKey | undefined | null,
- config: HasConfigInterface = {}
- ): Selector | undefined {
- config = defineConfig(config, {
- notExisting: false,
- });
-
- // Get Selector
- const selector = selectorKey ? this.selectors[selectorKey] : undefined;
-
- // Check if Selector exists
- if (selector == null || (!config.notExisting && !selector.exists))
- return undefined;
-
- ComputedTracker.tracked(selector.observers['value']);
- return selector;
- }
-
- /**
- * Retrieves a single Selector with the specified key/name identifier from the Collection.
- *
- * If the to retrieve Selector doesn't exist, a reference Selector is returned.
- * This has the advantage that Components that have the reference Selector bound to themselves
- * are rerenderd when the original Selector is created.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getselectorwithreference)
- *
- * @public
- * @param selectorKey - Key/Name identifier of the Selector.
- */
- public getSelectorWithReference(
- selectorKey: SelectorKey
- ): Selector {
- let selector = this.getSelector(selectorKey, { notExisting: true });
-
- // Create dummy Selector to hold reference
- if (selector == null) {
- selector = new Selector(this, null, {
- key: selectorKey,
- isPlaceholder: true,
- });
- this.selectors[selectorKey] = selector;
- }
-
- ComputedTracker.tracked(selector.observers['value']);
- return selector;
- }
-
- /**
- * Removes a Selector with the specified key/name identifier from the Collection,
- * if it exists in the Collection.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removeselector)
- *
- * @public
- * @param selectorKey - Key/Name identifier of the Selector to be removed.
- */
- public removeSelector(selectorKey: SelectorKey): this {
- if (this.selectors[selectorKey] != null) {
- this.selectors[selectorKey].unselect();
- delete this.selectors[selectorKey];
- }
- return this;
- }
-
- /**
- * Returns the count of registered Selectors in the Collection.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getselectorcount)
- *
- * @public
- */
- public getSelectorCount(): number {
- let size = 0;
- Object.keys(this.selectors).map(() => size++);
- return size;
- }
-
- /**
- * Returns a boolean indicating whether a Item with the specified `itemKey`
- * exists in the Collection or not.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#hasitem)
- *
- * @public
- * @param itemKey - Key/Name identifier of the Item.
- * @param config - Configuration object
- */
- public hasItem(
- itemKey: ItemKey | undefined,
- config: HasConfigInterface = {}
- ): boolean {
- return !!this.getItem(itemKey, config);
- }
-
- /**
- * Retrieves a single Item with the specified key/name identifier from the Collection.
- *
- * If the to retrieve Item doesn't exist, `undefined` is returned.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getitem)
- *
- * @public
- * @param itemKey - Key/Name identifier of the Item.
- * @param config - Configuration object
- */
- public getItem(
- itemKey: ItemKey | undefined | null,
- config: HasConfigInterface = {}
- ): Item | undefined {
- config = defineConfig(config, {
- notExisting: false,
- });
-
- // Get Item
- const item = itemKey != null ? this.data[itemKey] : undefined;
-
- // Check if Item exists
- if (item == null || (!config.notExisting && !item.exists)) return undefined;
-
- ComputedTracker.tracked(item.observers['value']);
- return item;
- }
-
- /**
- * Retrieves a single Item with the specified key/name identifier from the Collection.
- *
- * If the to retrieve Item doesn't exist, a reference Item is returned.
- * This has the advantage that Components that have the reference Item bound to themselves
- * are rerenderd when the original Item is created.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getitemwithreference)
- *
- * @public
- * @param itemKey - Key/Name identifier of the Item.
- */
- public getItemWithReference(itemKey: ItemKey): Item {
- let item = this.getItem(itemKey, { notExisting: true });
-
- // Create dummy Item to hold reference
- if (item == null) item = this.createPlaceholderItem(itemKey, true);
-
- ComputedTracker.tracked(item.observers['value']);
- return item;
- }
-
- /**
- * Creates a placeholder Item
- * that can be used to hold a reference to a not existing Item.
- *
- * @internal
- * @param itemKey - Unique identifier of the to create placeholder Item.
- * @param addToCollection - Whether to add the Item to be created to the Collection.
- */
- public createPlaceholderItem(
- itemKey: ItemKey,
- addToCollection = false
- ): Item {
- // Create placeholder Item
- const item = new Item(
- this,
- {
- [this.config.primaryKey]: itemKey, // Setting primaryKey of the Item to passed itemKey
- dummy: 'item',
- } as any,
- { isPlaceholder: true }
- );
-
- // Add placeholder Item to Collection
- if (
- addToCollection &&
- !Object.prototype.hasOwnProperty.call(this.data, itemKey)
- )
- this.data[itemKey] = item;
-
- ComputedTracker.tracked(item.observers['value']);
- return item;
- }
-
- /**
- * Retrieves the value (data object) of a single Item
- * with the specified key/name identifier from the Collection.
- *
- * If the to retrieve Item containing the value doesn't exist, `undefined` is returned.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getitemvalue)
- *
- * @public
- * @param itemKey - Key/Name identifier of the Item.
- * @param config - Configuration object
- */
- public getItemValue(
- itemKey: ItemKey | undefined,
- config: HasConfigInterface = {}
- ): DataType | undefined {
- const item = this.getItem(itemKey, config);
- if (item == null) return undefined;
- return item.value;
- }
-
- /**
- * Retrieves all Items from the Collection.
- * ```
- * MY_COLLECTION.getAllItems();
- * // is equivalent to
- * MY_COLLECTION.getDefaultGroup().items;
- * ```
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getallitems)
- *
- * @public
- * @param config - Configuration object
- */
- public getAllItems(config: HasConfigInterface = {}): Array> {
- config = defineConfig(config, {
- notExisting: false,
- });
-
- const defaultGroup = this.getDefaultGroup();
- let items: Array> = [];
-
- // If config.notExisting transform the data object into array since it contains all Items,
- // otherwise return the default Group Items
- if (config.notExisting) {
- for (const key in this.data) items.push(this.data[key]);
- } else {
- // Why default Group Items and not all '.exists === true' Items?
- // Because the default Group keeps track of all existing Items.
- // It also does control the Collection output in binding methods like 'useAgile()'
- // and therefore should do it here too.
- items = defaultGroup?.getItems() || [];
- }
-
- return items;
- }
-
- /**
- * Retrieves the values (data objects) of all Items from the Collection.
- * ```
- * MY_COLLECTION.getAllItemValues();
- * // is equivalent to
- * MY_COLLECTION.getDefaultGroup().output;
- * ```
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getallitemvalues)
- *
- * @public
- * @param config - Configuration object
- */
- public getAllItemValues(config: HasConfigInterface = {}): Array {
- const items = this.getAllItems(config);
- return items.map((item) => item.value);
- }
-
- /**
- * Preserves the Collection `value` in the corresponding external Storage.
- *
- * The Collection key/name is used as the unique identifier for the Persistent.
- * If that is not desired or the Collection has no unique identifier,
- * please specify a separate unique identifier for the Persistent.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#persist)
- *
- * @public
- * @param config - Configuration object
- */
- public persist(config?: CollectionPersistentConfigInterface): this;
- /**
- * Preserves the Collection `value` in the corresponding external Storage.
- *
- * The specified key is used as the unique identifier for the Persistent.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#persist)
- *
- * @public
- * @param key - Key/Name identifier of Persistent.
- * @param config - Configuration object
- */
- public persist(
- key?: StorageKey,
- config?: CollectionPersistentConfigInterface
- ): this;
- public persist(
- keyOrConfig: StorageKey | CollectionPersistentConfigInterface = {},
- config: CollectionPersistentConfigInterface = {}
- ): this {
- let _config: CollectionPersistentConfigInterface;
- let key: StorageKey | undefined;
-
- if (isValidObject(keyOrConfig)) {
- _config = keyOrConfig as CollectionPersistentConfigInterface;
- key = this._key;
- } else {
- _config = config || {};
- key = keyOrConfig as StorageKey;
- }
-
- _config = defineConfig(_config, {
- loadValue: true,
- storageKeys: [],
- defaultStorageKey: null as any,
- });
-
- // Check if Collection is already persisted
- if (this.persistent != null && this.isPersisted) return this;
-
- // Create Persistent (-> persist value)
- this.persistent = new CollectionPersistent(this, {
- instantiate: _config.loadValue,
- storageKeys: _config.storageKeys,
- key: key,
- defaultStorageKey: _config.defaultStorageKey,
- });
-
- return this;
- }
-
- /**
- * Fires immediately after the persisted `value`
- * is loaded into the Collection from a corresponding external Storage.
- *
- * Registering such callback function makes only sense
- * when the Collection is [persisted](https://agile-ts.org/docs/core/collection/methods/#persist) in an external Storage.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#onload)
- *
- * @public
- * @param callback - A function to be executed after the externally persisted `value` was loaded into the Collection.
- */
- public onLoad(callback: (success: boolean) => void): this {
- if (!this.persistent) return this;
- if (!isFunction(callback)) {
- LogCodeManager.log('00:03:01', ['OnLoad Callback', 'function']);
- return this;
- }
-
- // Register specified callback
- this.persistent.onLoad = callback;
-
- // If Collection is already persisted ('isPersisted') fire specified callback immediately
- if (this.isPersisted) callback(true);
-
- return this;
- }
-
- /**
- * Removes all Items from the Collection
- * and resets all Groups and Selectors of the Collection.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#reset)
- *
- * @public
- */
- public reset(): this {
- // Reset data
- this.data = {};
- this.size = 0;
-
- // Reset Groups
- for (const key in this.groups) this.getGroup(key)?.reset();
-
- // Reset Selectors
- for (const key in this.selectors) this.getSelector(key)?.reset();
-
- return this;
- }
-
- /**
- * Puts `itemKeys/s` into Group/s.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#put)
- *
- * @public
- * @param itemKeys - `itemKey/s` to be put into the specified Group/s.
- * @param groupKeys - Key/Name Identifier/s of the Group/s the specified `itemKey/s` are to put in.
- * @param config - Configuration object
- */
- public put(
- itemKeys: ItemKey | Array,
- groupKeys: GroupKey | Array,
- config: GroupAddConfigInterface = {}
- ): this {
- const _itemKeys = normalizeArray(itemKeys);
- const _groupKeys = normalizeArray(groupKeys);
-
- // Assign itemKeys to Groups
- _groupKeys.forEach((groupKey) => {
- this.getGroup(groupKey)?.add(_itemKeys, config);
- });
-
- return this;
- }
-
- /**
- * Moves specified `itemKey/s` from one Group to another Group.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#move)
- *
- * @public
- * @param itemKeys - `itemKey/s` to be moved.
- * @param oldGroupKey - Key/Name Identifier of the Group the `itemKey/s` are moved from.
- * @param newGroupKey - Key/Name Identifier of the Group the `itemKey/s` are moved in.
- * @param config - Configuration object
- */
- public move(
- itemKeys: ItemKey | Array,
- oldGroupKey: GroupKey,
- newGroupKey: GroupKey,
- config: GroupAddConfigInterface = {}
- ): this {
- const _itemKeys = normalizeArray(itemKeys);
-
- // Remove itemKeys from old Group
- this.getGroup(oldGroupKey)?.remove(
- _itemKeys,
- removeProperties(config, ['method', 'overwrite'])
- );
-
- // Assign itemKeys to new Group
- this.getGroup(newGroupKey)?.add(_itemKeys, config);
-
- return this;
- }
-
- /**
- * Updates the key/name identifier of the Item
- * and returns a boolean indicating
- * whether the Item identifier was updated successfully.
- *
- * @internal
- * @param oldItemKey - Old key/name Item identifier.
- * @param newItemKey - New key/name Item identifier.
- * @param config - Configuration object
- */
- public updateItemKey(
- oldItemKey: ItemKey,
- newItemKey: ItemKey,
- config: UpdateItemKeyConfigInterface = {}
- ): boolean {
- const item = this.getItem(oldItemKey, { notExisting: true });
- config = defineConfig(config, {
- background: false,
- });
-
- if (item == null || oldItemKey === newItemKey) return false;
-
- // Check if Item with newItemKey already exists
- if (this.hasItem(newItemKey)) {
- LogCodeManager.log('1B:03:04', [oldItemKey, newItemKey, this._key]);
- return false;
- }
-
- // Update itemKey in data object
- delete this.data[oldItemKey];
- this.data[newItemKey] = item;
-
- // Update key/name of the Item
- item.setKey(newItemKey, {
- background: config.background,
- });
-
- // Update Persistent key of the Item if it follows the Item Storage Key pattern
- // and therefore differs from the actual Item key
- // (-> isn't automatically updated when the Item key is updated)
- if (
- item.persistent != null &&
- item.persistent._key ===
- CollectionPersistent.getItemStorageKey(oldItemKey, this._key)
- )
- item.persistent?.setKey(
- CollectionPersistent.getItemStorageKey(newItemKey, this._key)
- );
-
- // Update itemKey in Groups
- for (const groupKey in this.groups) {
- const group = this.getGroup(groupKey, { notExisting: true });
- if (group == null || !group.has(oldItemKey)) continue;
- group.replace(oldItemKey, newItemKey, { background: config.background });
- }
-
- // Update itemKey in Selectors
- for (const selectorKey in this.selectors) {
- const selector = this.getSelector(selectorKey, { notExisting: true });
- if (selector == null) continue;
-
- // Reselect Item in Selector that has selected the newItemKey.
- // Necessary because potential reference placeholder Item got overwritten
- // with the new (renamed) Item
- // -> has to find the new Item at selected itemKey
- // since the placeholder Item got overwritten
- if (selector.hasSelected(newItemKey, false)) {
- selector.reselect({
- force: true, // Because itemKeys are the same (but not the Items at this itemKey anymore)
- background: config.background,
- });
- }
-
- // Select newItemKey in Selector that has selected the oldItemKey
- if (selector.hasSelected(oldItemKey, false))
- selector.select(newItemKey, {
- background: config.background,
- });
- }
-
- return true;
- }
-
- /**
- * Returns all key/name identifiers of the Group/s containing the specified `itemKey`.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroupkeysthathaveitemkey)
- *
- * @public
- * @param itemKey - `itemKey` to be contained in Group/s.
- */
- public getGroupKeysThatHaveItemKey(itemKey: ItemKey): Array {
- const groupKeys: Array = [];
- for (const groupKey in this.groups) {
- const group = this.groups[groupKey];
- if (group?.has(itemKey)) groupKeys.push(groupKey);
- }
- return groupKeys;
- }
-
- /**
- * Removes Item/s from:
- *
- * - `.everywhere()`:
- * Removes Item/s from the entire Collection and all its Groups and Selectors (i.e. from everywhere)
- * ```
- * MY_COLLECTION.remove('1').everywhere();
- * // is equivalent to
- * MY_COLLECTION.removeItems('1');
- * ```
- * - `.fromGroups()`:
- * Removes Item/s only from specified Groups.
- * ```
- * MY_COLLECTION.remove('1').fromGroups(['1', '2']);
- * // is equivalent to
- * MY_COLLECTION.removeFromGroups('1', ['1', '2']);
- * ```
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#remove)
- *
- * @public
- * @param itemKeys - Item/s with identifier/s to be removed.
- */
- public remove(
- itemKeys: ItemKey | Array
- ): {
- fromGroups: (groups: Array | ItemKey) => Collection;
- everywhere: (config?: RemoveItemsConfigInterface) => Collection;
- } {
- return {
- fromGroups: (groups: Array | ItemKey) =>
- this.removeFromGroups(itemKeys, groups),
- everywhere: (config) => this.removeItems(itemKeys, config || {}),
- };
- }
-
- /**
- * Remove Item/s from specified Group/s.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removefromgroups)
- *
- * @public
- * @param itemKeys - Key/Name Identifier/s of the Item/s to be removed from the Group/s.
- * @param groupKeys - Key/Name Identifier/s of the Group/s the Item/s are to remove from.
- */
- public removeFromGroups(
- itemKeys: ItemKey | Array,
- groupKeys: GroupKey | Array
- ): this {
- const _itemKeys = normalizeArray(itemKeys);
- const _groupKeys = normalizeArray(groupKeys);
-
- _itemKeys.forEach((itemKey) => {
- let removedFromGroupsCount = 0;
-
- // Remove itemKey from the Groups
- _groupKeys.forEach((groupKey) => {
- const group = this.getGroup(groupKey, { notExisting: true });
- if (!group?.has(itemKey)) return;
- group.remove(itemKey);
- removedFromGroupsCount++;
- });
-
- // If the Item was removed from each Group representing the Item,
- // remove it completely
- if (
- removedFromGroupsCount >=
- this.getGroupKeysThatHaveItemKey(itemKey).length
- )
- this.removeItems(itemKey);
- });
-
- return this;
- }
-
- /**
- * Removes Item/s from the entire Collection and all the Collection's Groups and Selectors.
- *
- * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removeitems)
- *
- * @public
- * @param itemKeys - Key/Name identifier/s of the Item/s to be removed from the entire Collection.
- * @param config - Configuration object
- */
- public removeItems(
- itemKeys: ItemKey | Array,
- config: RemoveItemsConfigInterface = {}
- ): this {
- config = defineConfig(config, {
- notExisting: false,
- removeSelector: false,
- });
- const _itemKeys = normalizeArray(itemKeys);
-
- _itemKeys.forEach((itemKey) => {
- const item = this.getItem(itemKey, { notExisting: config.notExisting });
- if (item == null) return;
- const wasPlaceholder = item.isPlaceholder;
-
- // Remove Item from the Groups
- for (const groupKey in this.groups) {
- const group = this.getGroup(groupKey, { notExisting: true });
- if (group?.has(itemKey)) group?.remove(itemKey);
- }
-
- // Remove Item from Storage
- item.persistent?.removePersistedValue();
-
- // Remove Item from Collection
- delete this.data[itemKey];
-
- // Reselect or remove Selectors which have represented the removed Item
- for (const selectorKey in this.selectors) {
- const selector = this.getSelector(selectorKey, { notExisting: true });
- if (selector != null && selector.hasSelected(itemKey, false)) {
- if (config.removeSelector) {
- // Remove Selector
- this.removeSelector(selector._key ?? 'unknown');
- } else {
- // Reselect Item in Selector
- // in order to create a new dummyItem
- // to hold a reference to the now not existing Item
- selector.reselect({ force: true });
- }
- }
- }
-
- if (!wasPlaceholder) this.size--;
- });
-
- return this;
- }
-
- /**
- * Assigns the provided `data` object to an already existing Item
- * with specified key/name identifier found in the `data` object.
- * If the Item doesn't exist yet, a new Item with the `data` object as value
- * is created and assigned to the Collection.
- *
- * Returns a boolean indicating
- * whether the `data` object was assigned/updated successfully.
- *
- * @internal
- * @param data - Data object
- * @param config - Configuration object
- */
- public assignData(
- data: DataType,
- config: AssignDataConfigInterface = {}
- ): boolean {
- config = defineConfig(config, {
- patch: false,
- background: false,
- });
- const _data = copy(data); // Copy data object to get rid of reference
- const primaryKey = this.config.primaryKey;
-
- if (!isValidObject(_data)) {
- LogCodeManager.log('1B:03:05', [this._key]);
- return false;
- }
-
- // Check if data object contains valid itemKey,
- // otherwise add random itemKey to Item
- if (!Object.prototype.hasOwnProperty.call(_data, primaryKey)) {
- LogCodeManager.log('1B:02:05', [this._key, primaryKey]);
- _data[primaryKey] = generateId();
- }
-
- const itemKey = _data[primaryKey];
- const item = this.getItem(itemKey, { notExisting: true });
- const wasPlaceholder = item?.isPlaceholder || false;
-
- // Create new Item or update existing Item
- if (item != null) {
- if (config.patch) {
- item.patch(_data, { background: config.background });
- } else {
- item.set(_data, { background: config.background });
- }
- } else {
- this.assignItem(new Item(this, _data), {
- background: config.background,
- });
- }
-
- // Increase size of Collection if Item was previously a placeholder
- // (-> hasn't officially existed in Collection before)
- if (wasPlaceholder) this.size++;
-
- return true;
- }
-
- /**
- * Assigns the specified Item to the Collection
- * at the key/name identifier of the Item.
- *
- * And returns a boolean indicating
- * whether the Item was assigned successfully.
- *
- * @internal
- * @param item - Item to be added.
- * @param config - Configuration object
- */
- public assignItem(
- item: Item,
- config: AssignItemConfigInterface = {}
- ): boolean {
- config = defineConfig(config, {
- overwrite: false,
- background: false,
- });
- const primaryKey = this.config.primaryKey;
- let itemKey = item._value[primaryKey];
- let increaseCollectionSize = true;
-
- // Check if Item has valid itemKey,
- // otherwise add random itemKey to Item
- if (!Object.prototype.hasOwnProperty.call(item._value, primaryKey)) {
- LogCodeManager.log('1B:02:05', [this._key, primaryKey]);
- itemKey = generateId();
- item.patch(
- { [this.config.primaryKey]: itemKey },
- { background: config.background }
- );
- item._key = itemKey;
- }
-
- // Check if Item belongs to this Collection
- if (item.collection() !== this) {
- LogCodeManager.log('1B:03:06', [this._key, item.collection()._key]);
- return false;
- }
-
- // Check if Item already exists
- if (this.getItem(itemKey, { notExisting: true }) != null) {
- if (!config.overwrite) {
- this.assignData(item._value);
- return true;
- } else increaseCollectionSize = false;
- }
-
- // Assign/add Item to Collection
- this.data[itemKey] = item;
-
- // Rebuild Groups that include itemKey
- // after adding Item with itemKey to the Collection
- // (because otherwise it can't find the Item as it isn't added yet)
- this.rebuildGroupsThatIncludeItemKey(itemKey, {
- background: config.background,
- });
-
- if (increaseCollectionSize) this.size++;
-
- return true;
- }
-
- /**
- * Rebuilds all Groups that contain the specified `itemKey`.
- *
- * @internal
- * @itemKey - `itemKey` Groups must contain to be rebuilt.
- * @config - Configuration object
- */
- public rebuildGroupsThatIncludeItemKey(
- itemKey: ItemKey,
- config: RebuildGroupsThatIncludeItemKeyConfigInterface = {}
- ): void {
- config = defineConfig(config, {
- background: false,
- sideEffects: {
- enabled: true,
- exclude: [],
- },
- });
-
- // Rebuild Groups that include itemKey
- for (const groupKey in this.groups) {
- const group = this.getGroup(groupKey);
- if (group?.has(itemKey)) {
- // Not necessary because a sideEffect of ingesting the Group
- // into the runtime is to rebuilt itself
- // group.rebuild();
-
- group?.rebuild({
- background: config?.background,
- sideEffects: config?.sideEffects,
- storage: false,
- });
- }
- }
- }
-}
-
-export type DefaultItem = Record; // same as { [key: string]: any };
-export type CollectionKey = string | number;
-export type ItemKey = string | number;
-
-export interface CreateCollectionConfigInterface {
- /**
- * Initial Groups of the Collection.
- * @default []
- */
- groups?: { [key: string]: Group } | string[];
- /**
- * Initial Selectors of the Collection
- * @default []
- */
- selectors?: { [key: string]: Selector } | string[];
- /**
- * Key/Name identifier of the Collection.
- * @default undefined
- */
- key?: CollectionKey;
- /**
- * Key/Name of the property
- * which represents the unique Item identifier
- * in collected data objects.
- * @default 'id'
- */
- primaryKey?: string;
- /**
- * Key/Name identifier of the default Group that is created shortly after instantiation.
- * The default Group represents the default pattern of the Collection.
- * @default 'default'
- */
- defaultGroupKey?: GroupKey;
- /**
- * Initial data objects of the Collection.
- * @default []
- */
- initialData?: Array;
-}
-
-export type CollectionConfig =
- | CreateCollectionConfigInterface
- | ((
- collection: Collection
- ) => CreateCollectionConfigInterface);
-
-export interface CollectionConfigInterface {
- /**
- * Key/Name of the property
- * which represents the unique Item identifier
- * in collected data objects.
- * @default 'id'
- */
- primaryKey: string;
- /**
- * Key/Name identifier of the default Group that is created shortly after instantiation.
- * The default Group represents the default pattern of the Collection.
- * @default 'default'
- */
- defaultGroupKey: ItemKey;
-}
-
-export interface CollectConfigInterface
- extends AssignDataConfigInterface {
- /**
- * In which way the collected data should be added to the Collection.
- * - 'push' = at the end
- * - 'unshift' = at the beginning
- * https://www.tutorialspoint.com/what-are-the-differences-between-unshift-and-push-methods-in-javascript
- * @default 'push'
- */
- method?: 'push' | 'unshift';
- /**
- * Performs the specified action for each collected data object.
- * @default undefined
- */
- forEachItem?: (
- data: DataType | Item,
- key: ItemKey,
- success: boolean,
- index: number
- ) => void;
- /**
- * Whether to create a Selector for each collected data object.
- * @default false
- */
- select?: boolean;
-}
-
-export interface UpdateConfigInterface {
- /**
- * Whether to merge the data object with changes into the existing Item data object
- * or overwrite the existing Item data object entirely.
- * @default true
- */
- patch?: boolean | PatchOptionConfigInterface;
- /**
- * Whether to update the data object in background.
- * So that the UI isn't notified of these changes and thus doesn't rerender.
- * @default false
- */
- background?: boolean;
-}
-
-export interface UpdateItemKeyConfigInterface {
- /**
- * Whether to update the Item key/name identifier in background
- * So that the UI isn't notified of these changes and thus doesn't rerender.
- * @default false
- */
- background?: boolean;
-}
-
-export interface RebuildGroupsThatIncludeItemKeyConfigInterface {
- /**
- * Whether to rebuilt the Group in background.
- * So that the UI isn't notified of these changes and thus doesn't rerender.
- * @default false
- */
- background?: boolean;
- /**
- * Whether to execute the defined side effects.
- * @default true
- */
- sideEffects?: SideEffectConfigInterface;
-}
-
-export interface HasConfigInterface {
- /**
- * Whether Items that do not officially exist,
- * such as placeholder Items, can be found
- * @default true
- */
- notExisting?: boolean;
-}
-
-export interface CollectionPersistentConfigInterface {
- /**
- * Whether the Persistent should automatically load
- * the persisted value into the Collection after its instantiation.
- * @default true
- */
- loadValue?: boolean;
- /**
- * Key/Name identifier of Storages
- * in which the Collection value should be or is persisted.
- * @default [`defaultStorageKey`]
- */
- storageKeys?: StorageKey[];
- /**
- * Key/Name identifier of the default Storage of the specified Storage keys.
- *
- * The Collection value is loaded from the default Storage by default
- * and is only loaded from the remaining Storages (`storageKeys`)
- * if the loading from the default Storage failed.
- *
- * @default first index of the specified Storage keys or the AgileTs default Storage key
- */
- defaultStorageKey?: StorageKey;
-}
-
-export interface RemoveItemsConfigInterface {
- /**
- * Whether to remove not officially existing Items (such as placeholder Items).
- * Keep in mind that sometimes it won't remove an Item entirely
- * as another Instance (like a Selector) might need to keep reference to it.
- * https://github.com/agile-ts/agile/pull/152
- * @default false
- */
- notExisting?: boolean;
- /**
- * Whether to remove Selectors that have selected an Item to be removed.
- * @default false
- */
- removeSelector?: boolean;
-}
-
-export interface AssignDataConfigInterface {
- /**
- * When the Item identifier of the to assign data object already exists in the Collection,
- * whether to merge the newly assigned data into the existing one
- * or overwrite the existing one entirely.
- * @default true
- */
- patch?: boolean;
- /**
- * Whether to assign the data object to the Collection in background.
- * So that the UI isn't notified of these changes and thus doesn't rerender.
- * @default false
- */
- background?: boolean;
-}
-
-export interface AssignItemConfigInterface {
- /**
- * If an Item with the Item identifier already exists,
- * whether to overwrite it entirely with the new one.
- * @default false
- */
- overwrite?: boolean;
- /**
- * Whether to assign the Item to the Collection in background.
- * So that the UI isn't notified of these changes and thus doesn't rerender.
- * @default false
- */
- background?: boolean;
+export * from './collection';
+// export * from './collection.persistent';
+// export * from './group';
+// export * from './group/group.observer';
+// export * from './item';
+// export * from './selector';
+
+/**
+ * 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 = shared
+): Collection {
+ return new Collection(agileInstance, config);
}
diff --git a/packages/core/src/computed/computed.tracker.ts b/packages/core/src/computed/computed.tracker.ts
index e3254f90..c9c7ecc6 100644
--- a/packages/core/src/computed/computed.tracker.ts
+++ b/packages/core/src/computed/computed.tracker.ts
@@ -1,4 +1,4 @@
-import { Observer } from '../runtime/observer';
+import { Observer } from '../internal';
export class ComputedTracker {
static isTracking = false;
diff --git a/packages/core/src/computed/computed.ts b/packages/core/src/computed/computed.ts
new file mode 100644
index 00000000..416cb4db
--- /dev/null
+++ b/packages/core/src/computed/computed.ts
@@ -0,0 +1,266 @@
+import {
+ State,
+ Agile,
+ Observer,
+ StateConfigInterface,
+ ComputedTracker,
+ Collection,
+ StateIngestConfigInterface,
+ removeProperties,
+ LogCodeManager,
+ isAsyncFunction,
+ extractRelevantObservers,
+ defineConfig,
+} from '../internal';
+
+export class Computed extends State<
+ ComputedValueType
+> {
+ public config: ComputedConfigInterface;
+
+ // Function to compute the Computed Class value
+ public computeFunction: ComputeFunctionType;
+ // All dependencies the Computed Class depends on (including hardCoded and automatically detected dependencies)
+ public deps: Set = new Set();
+ // Only hardCoded dependencies the Computed Class depends on
+ public hardCodedDeps: Array = [];
+
+ // Helper property to check whether an unknown instance is a Computed,
+ // without importing the Computed itself for using 'instanceof' (Treeshaking support)
+ public isComputed = true;
+
+ /**
+ * A Computed is an extension of the State Class
+ * that computes its value based on a specified compute function.
+ *
+ * The computed value will be cached to avoid unnecessary recomputes
+ * and is only recomputed when one of its direct dependencies changes.
+ *
+ * Direct dependencies can be States and Collections.
+ * So when, for example, a dependent State value changes, the computed value is recomputed.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/computed/)
+ *
+ * @public
+ * @param agileInstance - Instance of Agile the Computed belongs to.
+ * @param computeFunction - Function to compute the computed value.
+ * @param config - Configuration object
+ */
+ constructor(
+ agileInstance: Agile,
+ computeFunction: ComputeFunctionType,
+ config: CreateComputedConfigInterface = {}
+ ) {
+ super(agileInstance, null as any, {
+ key: config.key,
+ dependents: config.dependents,
+ });
+ config = defineConfig(config, {
+ computedDeps: [],
+ autodetect: !isAsyncFunction(computeFunction),
+ });
+ this.agileInstance = () => agileInstance;
+ this.computeFunction = computeFunction;
+ this.config = {
+ autodetect: config.autodetect as any,
+ };
+
+ // Extract Observer of passed hardcoded dependency instances
+ this.hardCodedDeps = extractRelevantObservers(
+ config.computedDeps as DependableAgileInstancesType[]
+ ).filter((dep): dep is Observer => dep !== undefined);
+ this.deps = new Set(this.hardCodedDeps);
+
+ // Make this Observer depend on the specified hard coded dep Observers
+ this.deps.forEach((observer) => {
+ observer.addDependent(this.observers['value']);
+ });
+
+ // Initial recompute to assign the computed initial value to the Computed
+ // and autodetect missing dependencies
+ this.recompute({ autodetect: config.autodetect, overwrite: true });
+ }
+
+ /**
+ * Forces a recomputation of the cached value with the compute function.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/computed/methods/#recompute)
+ *
+ * @public
+ * @param config - Configuration object
+ */
+ public recompute(config: RecomputeConfigInterface = {}): this {
+ config = defineConfig(config, {
+ autodetect: false,
+ });
+ this.compute({ autodetect: config.autodetect }).then((result) => {
+ this.observers['value'].ingestValue(
+ result,
+ removeProperties(config, ['autodetect'])
+ );
+ });
+ return this;
+ }
+
+ /**
+ * Assigns a new function to the Computed Class for computing its value.
+ *
+ * The dependencies of the new compute function are automatically detected
+ * and accordingly updated.
+ *
+ * An initial computation is performed with the new function
+ * to change the obsolete cached value.
+ *
+ * [Learn more..](https://agile-ts.org/docs/core/computed/methods/#updatecomputefunction)
+ *
+ * @public
+ * @param computeFunction - New function to compute the value of the Computed Class.
+ * @param deps - Hard coded dependencies on which the Computed Class depends.
+ * @param config - Configuration object
+ */
+ public updateComputeFunction(
+ computeFunction: () => ComputedValueType,
+ deps: Array = [],
+ config: RecomputeConfigInterface = {}
+ ): this {
+ config = defineConfig(config, {
+ autodetect: this.config.autodetect,
+ });
+
+ // Make this Observer no longer depend on the old dep Observers
+ this.deps.forEach((observer) => {
+ observer.removeDependent(this.observers['value']);
+ });
+
+ // Update dependencies of Computed
+ this.hardCodedDeps = extractRelevantObservers(deps).filter(
+ (dep): dep is Observer => dep !== undefined
+ );
+ this.deps = new Set(this.hardCodedDeps);
+
+ // Make this Observer depend on the new hard coded dep Observers
+ this.deps.forEach((observer) => {
+ observer.addDependent(this.observers['value']);
+ });
+
+ // Update computeFunction
+ this.computeFunction = computeFunction;
+
+ // Recompute to assign the new computed value to the Computed
+ // and autodetect missing dependencies
+ this.recompute(removeProperties(config, ['overwriteDeps']));
+
+ return this;
+ }
+
+ /**
+ * Computes and returns the new value of the Computed Class
+ * and autodetects used dependencies in the compute function.
+ *
+ * @internal
+ * @param config - Configuration object
+ */
+ public async compute(
+ config: ComputeConfigInterface = {}
+ ): Promise {
+ config = defineConfig(config, {
+ autodetect: this.config.autodetect,
+ });
+
+ // Start auto tracking of Observers on which the computeFunction might depend
+ if (config.autodetect) ComputedTracker.track();
+
+ const computedValue = this.computeFunction();
+
+ // Handle auto tracked Observers
+ if (config.autodetect) {
+ const foundDeps = ComputedTracker.getTrackedObservers();
+
+ // Clean up old dependencies
+ this.deps.forEach((observer) => {
+ if (
+ !foundDeps.includes(observer) &&
+ !this.hardCodedDeps.includes(observer)
+ ) {
+ this.deps.delete(observer);
+ observer.removeDependent(this.observers['value']);
+ }
+ });
+
+ // Make this Observer depend on the newly found dep Observers
+ foundDeps.forEach((observer) => {
+ if (!this.deps.has(observer)) {
+ this.deps.add(observer);
+ observer.addDependent(this.observers['value']);
+ }
+ });
+ }
+
+ return computedValue;
+ }
+
+ /**
+ * Not usable in Computed Class.
+ */
+ public persist(): this {
+ LogCodeManager.log('19:03:00');
+ return this;
+ }
+}
+
+export type ComputeFunctionType = () =>
+ | ComputedValueType
+ | Promise;
+
+export interface CreateComputedConfigInterface extends StateConfigInterface {
+ /**
+ * Hard-coded dependencies the Computed Class should depend on.
+ * @default []
+ */
+ computedDeps?: Array;
+ /**
+ * Whether the Computed should automatically detect
+ * used dependencies in the specified compute method.
+ *
+ * Note that the automatic dependency detection does not work
+ * in an asynchronous compute method!
+ *
+ * @default true if the compute method isn't asynchronous, otherwise false
+ */
+ autodetect?: boolean;
+}
+
+export interface ComputedConfigInterface {
+ /**
+ * Whether the Computed can automatically detect
+ * used dependencies in the compute method.
+ *
+ * Note that the automatic dependency detection does not work
+ * in an asynchronous compute method!
+ *
+ * @default true if the compute method isn't asynchronous, otherwise false
+ */
+ autodetect: boolean;
+}
+
+export interface ComputeConfigInterface {
+ /**
+ * Whether the Computed can automatically detect
+ * used dependencies in the compute method.
+ *
+ * Note that the automatic dependency detection does not work
+ * in an asynchronous compute method!
+ *
+ * @default true
+ */
+ autodetect?: boolean;
+}
+
+export interface RecomputeConfigInterface
+ extends StateIngestConfigInterface,
+ ComputeConfigInterface {}
+
+export type DependableAgileInstancesType =
+ | State
+ | Collection //https://stackoverflow.com/questions/66987727/type-classa-id-number-name-string-is-not-assignable-to-type-classar
+ | Observer;
diff --git a/packages/core/src/computed/index.ts b/packages/core/src/computed/index.ts
index 4499c73d..22217093 100644
--- a/packages/core/src/computed/index.ts
+++ b/packages/core/src/computed/index.ts
@@ -1,262 +1,86 @@
import {
- State,
- Agile,
- Observer,
- StateConfigInterface,
- ComputedTracker,
- Collection,
- StateIngestConfigInterface,
- removeProperties,
- LogCodeManager,
- isAsyncFunction,
- extractRelevantObservers,
+ Computed,
+ ComputeFunctionType,
+ CreateComputedConfigInterface,
+ DependableAgileInstancesType,
defineConfig,
+ removeProperties,
+ CreateAgileSubInstanceInterface,
+ shared,
} from '../internal';
-export class Computed extends State<
- ComputedValueType
-> {
- public config: ComputedConfigInterface;
-
- // Function to compute the Computed Class value
- public computeFunction: ComputeFunctionType;
- // All dependencies the Computed Class depends on (including hardCoded and automatically detected dependencies)
- public deps: Set = new Set();
- // Only hardCoded dependencies the Computed Class depends on
- public hardCodedDeps: Array = [];
-
- /**
- * A Computed is an extension of the State Class
- * that computes its value based on a specified compute function.
- *
- * The computed value will be cached to avoid unnecessary recomputes
- * and is only recomputed when one of its direct dependencies changes.
- *
- * Direct dependencies can be States and Collections.
- * So when, for example, a dependent State value changes, the computed value is recomputed.
- *
- * [Learn more..](https://agile-ts.org/docs/core/computed/)
- *
- * @public
- * @param agileInstance - Instance of Agile the Computed belongs to.
- * @param computeFunction - Function to compute the computed value.
- * @param config - Configuration object
- */
- constructor(
- agileInstance: Agile,
- computeFunction: ComputeFunctionType,
- config: CreateComputedConfigInterface = {}
- ) {
- super(agileInstance, null as any, {
- key: config.key,
- dependents: config.dependents,
- });
- config = defineConfig(config, {
- computedDeps: [],
- autodetect: !isAsyncFunction(computeFunction),
- });
- this.agileInstance = () => agileInstance;
- this.computeFunction = computeFunction;
- this.config = {
- autodetect: config.autodetect as any,
- };
-
- // Extract Observer of passed hardcoded dependency instances
- this.hardCodedDeps = extractRelevantObservers(
- config.computedDeps as DependableAgileInstancesType[]
- ).filter((dep): dep is Observer => dep !== undefined);
- this.deps = new Set(this.hardCodedDeps);
-
- // Make this Observer depend on the specified hard coded dep Observers
- this.deps.forEach((observer) => {
- observer.addDependent(this.observers['value']);
- });
-
- // Initial recompute to assign the computed initial value to the Computed
- // and autodetect missing dependencies
- this.recompute({ autodetect: config.autodetect, overwrite: true });
- }
-
- /**
- * Forces a recomputation of the cached value with the compute function.
- *
- * [Learn more..](https://agile-ts.org/docs/core/computed/methods/#recompute)
- *
- * @public
- * @param config - Configuration object
- */
- public recompute(config: RecomputeConfigInterface = {}): this {
- config = defineConfig(config, {
- autodetect: false,
- });
- this.compute({ autodetect: config.autodetect }).then((result) => {
- this.observers['value'].ingestValue(
- result,
- removeProperties(config, ['autodetect'])
- );
- });
- return this;
- }
-
- /**
- * Assigns a new function to the Computed Class for computing its value.
- *
- * The dependencies of the new compute function are automatically detected
- * and accordingly updated.
- *
- * An initial computation is performed with the new function
- * to change the obsolete cached value.
- *
- * [Learn more..](https://agile-ts.org/docs/core/computed/methods/#updatecomputefunction)
- *
- * @public
- * @param computeFunction - New function to compute the value of the Computed Class.
- * @param deps - Hard coded dependencies on which the Computed Class depends.
- * @param config - Configuration object
- */
- public updateComputeFunction(
- computeFunction: () => ComputedValueType,
- deps: Array = [],
- config: RecomputeConfigInterface = {}
- ): this {
- config = defineConfig(config, {
- autodetect: this.config.autodetect,
- });
-
- // Make this Observer no longer depend on the old dep Observers
- this.deps.forEach((observer) => {
- observer.removeDependent(this.observers['value']);
- });
-
- // Update dependencies of Computed
- this.hardCodedDeps = extractRelevantObservers(deps).filter(
- (dep): dep is Observer => dep !== undefined
- );
- this.deps = new Set(this.hardCodedDeps);
-
- // Make this Observer depend on the new hard coded dep Observers
- this.deps.forEach((observer) => {
- observer.addDependent(this.observers['value']);
+export * from './computed';
+// export * from './computed.tracker';
+
+export interface CreateComputedConfigInterfaceWithAgile
+ extends CreateAgileSubInstanceInterface,
+ CreateComputedConfigInterface {}
+
+/**
+ * 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?:
+ | CreateComputedConfigInterfaceWithAgile
+ | Array
+): Computed {
+ let _config: CreateComputedConfigInterfaceWithAgile = {};
+
+ if (Array.isArray(configOrDeps)) {
+ _config = defineConfig(_config, {
+ computedDeps: configOrDeps,
});
-
- // Update computeFunction
- this.computeFunction = computeFunction;
-
- // Recompute to assign the new computed value to the Computed
- // and autodetect missing dependencies
- this.recompute(removeProperties(config, ['overwriteDeps']));
-
- return this;
+ } else {
+ if (configOrDeps) _config = configOrDeps;
}
- /**
- * Computes and returns the new value of the Computed Class
- * and autodetects used dependencies in the compute function.
- *
- * @internal
- * @param config - Configuration object
- */
- public async compute(
- config: ComputeConfigInterface = {}
- ): Promise {
- config = defineConfig(config, {
- autodetect: this.config.autodetect,
- });
-
- // Start auto tracking of Observers on which the computeFunction might depend
- if (config.autodetect) ComputedTracker.track();
-
- const computedValue = this.computeFunction();
-
- // Handle auto tracked Observers
- if (config.autodetect) {
- const foundDeps = ComputedTracker.getTrackedObservers();
-
- // Clean up old dependencies
- this.deps.forEach((observer) => {
- if (
- !foundDeps.includes(observer) &&
- !this.hardCodedDeps.includes(observer)
- ) {
- this.deps.delete(observer);
- observer.removeDependent(this.observers['value']);
- }
- });
-
- // Make this Observer depend on the newly found dep Observers
- foundDeps.forEach((observer) => {
- if (!this.deps.has(observer)) {
- this.deps.add(observer);
- observer.addDependent(this.observers['value']);
- }
- });
- }
-
- return computedValue;
- }
+ _config = defineConfig(_config, { agileInstance: shared });
- /**
- * Not usable in Computed Class.
- */
- public persist(): this {
- LogCodeManager.log('19:03:00');
- return this;
- }
-}
-
-export type ComputeFunctionType = () =>
- | ComputedValueType
- | Promise;
-
-export interface CreateComputedConfigInterface extends StateConfigInterface {
- /**
- * Hard-coded dependencies the Computed Class should depend on.
- * @default []
- */
- computedDeps?: Array;
- /**
- * Whether the Computed should automatically detect
- * used dependencies in the specified compute method.
- *
- * Note that the automatic dependency detection does not work
- * in an asynchronous compute method!
- *
- * @default true if the compute method isn't asynchronous, otherwise false
- */
- autodetect?: boolean;
-}
-
-export interface ComputedConfigInterface {
- /**
- * Whether the Computed can automatically detect
- * used dependencies in the compute method.
- *
- * Note that the automatic dependency detection does not work
- * in an asynchronous compute method!
- *
- * @default true if the compute method isn't asynchronous, otherwise false
- */
- autodetect: boolean;
-}
-
-export interface ComputeConfigInterface {
- /**
- * Whether the Computed can automatically detect
- * used dependencies in the compute method.
- *
- * Note that the automatic dependency detection does not work
- * in an asynchronous compute method!
- *
- * @default true
- */
- autodetect?: boolean;
+ return new Computed(
+ _config.agileInstance as any,
+ computeFunction,
+ removeProperties(_config, ['agileInstance'])
+ );
}
-
-export interface RecomputeConfigInterface
- extends StateIngestConfigInterface,
- ComputeConfigInterface {}
-
-export type DependableAgileInstancesType =
- | State
- | Collection //https://stackoverflow.com/questions/66987727/type-classa-id-number-name-string-is-not-assignable-to-type-classar
- | Observer;
diff --git a/packages/core/src/integrations/index.ts b/packages/core/src/integrations/index.ts
index c13e081b..196a738b 100644
--- a/packages/core/src/integrations/index.ts
+++ b/packages/core/src/integrations/index.ts
@@ -1,151 +1,2 @@
-import { Agile, Integration, LogCodeManager, defineConfig } from '../internal';
-
-const onRegisterInitialIntegrationCallbacks: ((
- integration: Integration
-) => void)[] = [];
-
-export class Integrations {
- // Agile Instance the Integrations belongs to
- public agileInstance: () => Agile;
-
- // 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
- * and invoke functions in all registered Integrations.
- *
- * @internal
- * @param agileInstance - Instance of Agile the Integrations belongs to.
- * @param config - Configuration object
- */
- constructor(agileInstance: Agile, config: IntegrationsConfigInterface = {}) {
- config = defineConfig(config, {
- autoIntegrate: true,
- });
- this.agileInstance = () => agileInstance;
-
- if (config.autoIntegrate) {
- // Setup listener to be notified when an external registered Integration was added
- Integrations.onRegisterInitialIntegration((integration) => {
- this.integrate(integration);
- });
- }
- }
-
- /**
- * Integrates the specified Integration into AgileTs
- * and sets it to ready when the binding was successful.
- *
- * @public
- * @param integration - Integration to be integrated into AgileTs.
- */
- public async integrate(integration: Integration): Promise {
- // Check if Integration is valid
- if (integration._key == null) {
- LogCodeManager.log(
- '18:03:00',
- [integration._key, this.agileInstance().key],
- integration
- );
- return false;
- }
-
- // Bind to integrate Integration to AgileTs
- if (integration.methods.bind)
- integration.ready = await integration.methods.bind(this.agileInstance());
- else integration.ready = true;
-
- // Integrate Integration
- this.integrations.add(integration);
- integration.integrated = true;
-
- LogCodeManager.log(
- '18:00:00',
- [integration._key, this.agileInstance().key],
- integration
- );
-
- return true;
- }
-
- /**
- * Updates the specified UI-Component Instance
- * with the updated data object in all registered Integrations that are ready.
- *
- * In doing so, it calls the `updateMethod()` method
- * in all registered Integrations with the specified parameters.
- *
- * @public
- * @param componentInstance - Component Instance to be updated.
- * @param updatedData - Data object with updated data.
- */
- public update(componentInstance: any, updatedData: Object): void {
- this.integrations.forEach((integration) => {
- if (!integration.ready) {
- LogCodeManager.log('18:02:00', [integration._key]);
- return;
- }
- if (integration.methods.updateMethod)
- integration.methods.updateMethod(componentInstance, updatedData);
- });
- }
-
- /**
- * Returns a boolean indicating whether any Integration
- * has been registered with the Agile Instance or not.
- *
- * @public
- */
- public hasIntegration(): boolean {
- 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;
-}
+export * from './integrations';
+// export * from './integration';
diff --git a/packages/core/src/integrations/integrations.ts b/packages/core/src/integrations/integrations.ts
new file mode 100644
index 00000000..c13e081b
--- /dev/null
+++ b/packages/core/src/integrations/integrations.ts
@@ -0,0 +1,151 @@
+import { Agile, Integration, LogCodeManager, defineConfig } from '../internal';
+
+const onRegisterInitialIntegrationCallbacks: ((
+ integration: Integration
+) => void)[] = [];
+
+export class Integrations {
+ // Agile Instance the Integrations belongs to
+ public agileInstance: () => Agile;
+
+ // 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
+ * and invoke functions in all registered Integrations.
+ *
+ * @internal
+ * @param agileInstance - Instance of Agile the Integrations belongs to.
+ * @param config - Configuration object
+ */
+ constructor(agileInstance: Agile, config: IntegrationsConfigInterface = {}) {
+ config = defineConfig(config, {
+ autoIntegrate: true,
+ });
+ this.agileInstance = () => agileInstance;
+
+ if (config.autoIntegrate) {
+ // Setup listener to be notified when an external registered Integration was added
+ Integrations.onRegisterInitialIntegration((integration) => {
+ this.integrate(integration);
+ });
+ }
+ }
+
+ /**
+ * Integrates the specified Integration into AgileTs
+ * and sets it to ready when the binding was successful.
+ *
+ * @public
+ * @param integration - Integration to be integrated into AgileTs.
+ */
+ public async integrate(integration: Integration): Promise {
+ // Check if Integration is valid
+ if (integration._key == null) {
+ LogCodeManager.log(
+ '18:03:00',
+ [integration._key, this.agileInstance().key],
+ integration
+ );
+ return false;
+ }
+
+ // Bind to integrate Integration to AgileTs
+ if (integration.methods.bind)
+ integration.ready = await integration.methods.bind(this.agileInstance());
+ else integration.ready = true;
+
+ // Integrate Integration
+ this.integrations.add(integration);
+ integration.integrated = true;
+
+ LogCodeManager.log(
+ '18:00:00',
+ [integration._key, this.agileInstance().key],
+ integration
+ );
+
+ return true;
+ }
+
+ /**
+ * Updates the specified UI-Component Instance
+ * with the updated data object in all registered Integrations that are ready.
+ *
+ * In doing so, it calls the `updateMethod()` method
+ * in all registered Integrations with the specified parameters.
+ *
+ * @public
+ * @param componentInstance - Component Instance to be updated.
+ * @param updatedData - Data object with updated data.
+ */
+ public update(componentInstance: any, updatedData: Object): void {
+ this.integrations.forEach((integration) => {
+ if (!integration.ready) {
+ LogCodeManager.log('18:02:00', [integration._key]);
+ return;
+ }
+ if (integration.methods.updateMethod)
+ integration.methods.updateMethod(componentInstance, updatedData);
+ });
+ }
+
+ /**
+ * Returns a boolean indicating whether any Integration
+ * has been registered with the Agile Instance or not.
+ *
+ * @public
+ */
+ public hasIntegration(): boolean {
+ 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 b9356568..a1c8852c 100644
--- a/packages/core/src/internal.ts
+++ b/packages/core/src/internal.ts
@@ -1,6 +1,7 @@
-// This file exposes Agile functions and types to the outside world
-// It also serves as a cyclic dependency workaround
-// https://medium.com/visual-development/how-to-fix-nasty-circular-dependency-issues-once-and-for-all-in-javascript-typescript-a04c987cf0de.
+// This file exposes Agile functions and types to the outside world.
+// It also serves as a cyclic dependency workaround,
+// and allows us to structure the loading order as needed (for example, './agile' need to be loaded before './state')
+// https://medium.com/visual-development/how-to-fix-nasty-circular-dependency-issues-once-and-for-all-in-javascript-typescript-a04c987cf0de
// !! All internal Agile modules must be imported from here!!
diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts
index 413b46b6..819563fa 100644
--- a/packages/core/src/logCodeManager.ts
+++ b/packages/core/src/logCodeManager.ts
@@ -1,11 +1,3 @@
-// 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.
//
@@ -31,7 +23,7 @@ const logCodeTypes = {
// ---
// 00:00:|00| third digits are based on the Log Message (ascending counted)
-const logCodeMessages = {
+const niceLogCodeMessages = {
// Agile
'10:00:00': 'Created new AgileInstance.',
'10:02:00':
@@ -75,8 +67,6 @@ const logCodeMessages = {
'13:03:00': "Invalid Storage '${0}()' method provided!",
// State
- '14:02:00': "Incorrect type '${0}' was provided! Requires type of ${1}.",
- '14:03:00': "Incorrect type '${0}' was provided! Requires type of ${1}.",
'14:03:01':
"'${1}' is a not supported type! Supported types are: String, Boolean, Array, Object, Number.",
'14:03:02': "The 'patch()' method works only in object based States!",
@@ -173,6 +163,13 @@ const logCodeMessages = {
'00:03:01': "'${0}' has to be of the type ${1}!",
};
+// Note: Not outsource the 'production' env check,
+// because then webpack can't treeshake based on the current env
+const logCodeMessages: typeof niceLogCodeMessages =
+ typeof process === 'object' && process.env.NODE_ENV !== 'production'
+ ? niceLogCodeMessages
+ : ({} as any);
+
/**
* Returns the log message according to the specified log code.
*
@@ -185,7 +182,8 @@ function getLog>(
logCode: T,
replacers: any[] = []
): string {
- let result = logCodeMessages[logCode] ?? `'${logCode}' is a unknown logCode!`;
+ let result = logCodeMessages[logCode];
+ if (result == null) return logCode;
// Replace '${x}' with the specified replacer instances
for (let i = 0; i < replacers.length; i++) {
@@ -257,25 +255,57 @@ function logIfTags>(
// Handle logging with Logger
logger.if.tag(tags)[logType](getLog(logCode, replacers), ...data);
}
-
/**
* The Log Code Manager keeps track
* and manages all important Logs of AgileTs.
*
* @internal
*/
-export const LogCodeManager = {
- getLog,
- 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,
+let tempLogCodeManager: {
+ getLog: typeof getLog;
+ log: typeof log;
+ logCodeLogTypes: typeof logCodeTypes;
+ logCodeMessages: typeof logCodeMessages;
+ getLogger: () => any;
+ logIfTags: typeof logIfTags;
};
+if (typeof process === 'object' && process.env.NODE_ENV !== 'production') {
+ tempLogCodeManager = {
+ getLog,
+ log,
+ logCodeLogTypes: logCodeTypes,
+ logCodeMessages: logCodeMessages,
+ // Not doing 'logger: loggerPackage?.sharedAgileLogger'
+ // because only by calling a function (now 'getLogger()') the 'sharedLogger' is refetched
+ getLogger: () => {
+ let loggerPackage: any = null;
+ try {
+ loggerPackage = require('@agile-ts/logger');
+ } catch (e) {
+ // empty catch block
+ }
+ return loggerPackage?.sharedAgileLogger ?? null;
+ },
+ logIfTags,
+ };
+} else {
+ tempLogCodeManager = {
+ // Log only logCode
+ getLog: (logCode, replacers) => logCode,
+ log,
+ logCodeLogTypes: logCodeTypes,
+ logCodeMessages: logCodeMessages,
+ // Not doing 'logger: loggerPackage?.sharedAgileLogger'
+ // because only by calling a function (now 'getLogger()') the 'sharedLogger' is refetched
+ getLogger: () => {
+ return null;
+ },
+ logIfTags: (tags, logCode, replacers) => {
+ /* empty */
+ },
+ };
+}
+export const LogCodeManager = tempLogCodeManager;
export type LogCodesArrayType = {
[K in keyof T]: T[K] extends string ? K : never;
diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts
index 0dd3419f..de2b0edb 100644
--- a/packages/core/src/runtime/index.ts
+++ b/packages/core/src/runtime/index.ts
@@ -1,358 +1,7 @@
-import {
- Agile,
- SubscriptionContainer,
- RuntimeJob,
- CallbackSubscriptionContainer,
- ComponentSubscriptionContainer,
- notEqual,
- LogCodeManager,
- defineConfig,
-} from '../internal';
-
-export class Runtime {
- // Agile Instance the Runtime belongs to
- public agileInstance: () => Agile;
-
- // Job that is currently being performed
- public currentJob: RuntimeJob | null = null;
- // Jobs to be performed
- public jobQueue: Array = [];
-
- // Jobs that were performed and are ready to re-render
- public jobsToRerender: Array = [];
- // Jobs that were performed and couldn't be re-rendered yet.
- // That is the case when at least one Subscription Container (UI-Component) in the Job
- // wasn't ready to update (re-render).
- public notReadyJobsToRerender: Set = new Set();
-
- // 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.)
- * and optimized the re-rendering of the Observer's subscribed UI-Components.
- *
- * Each queued Job is executed when it is its turn
- * by calling the Job Observer's `perform()` method.
- *
- * After successful execution, the Job is added to a re-render queue,
- * which is first put into the browser's 'Bucket' and started to work off
- * when resources are left.
- *
- * The re-render queue is designed for optimizing the render count
- * by batching multiple re-render Jobs of the same UI-Component
- * and ignoring re-render requests for unmounted UI-Components.
- *
- * @internal
- * @param agileInstance - Instance of Agile the Runtime belongs to.
- */
- constructor(agileInstance: Agile) {
- this.agileInstance = () => agileInstance;
- }
-
- /**
- * Adds the specified Observer-based Job to the internal Job queue,
- * where it is executed when it is its turn.
- *
- * After successful execution, the Job is assigned to the re-render queue,
- * where all the Observer's subscribed Subscription Containers (UI-Components)
- * are updated (re-rendered).
- *
- * @public
- * @param job - Job to be added to the Job queue.
- * @param config - Configuration object
- */
- public ingest(job: RuntimeJob, config: IngestConfigInterface = {}): void {
- config = defineConfig(config, {
- perform: !this.isPerformingJobs,
- });
-
- // Add specified Job to the queue
- this.jobQueue.push(job);
-
- LogCodeManager.logIfTags(['runtime'], '16:01:00', [job._key], job);
-
- // Run first Job from the queue
- if (config.perform) {
- const performJob = this.jobQueue.shift();
- if (performJob) this.perform(performJob);
- }
- }
-
- /**
- * Performs the specified Job
- * and assigns it to the re-render queue if necessary.
- *
- * After the execution of the provided Job, it is checked whether
- * there are still Jobs left in the Job queue.
- * - If so, the next Job in the `jobQueue` is performed.
- * - If not, the `jobsToRerender` queue is started to work off.
- *
- * @internal
- * @param job - Job to be performed.
- */
- public perform(job: RuntimeJob): void {
- this.isPerformingJobs = true;
- this.currentJob = job;
-
- // Perform Job
- job.observer.perform(job);
- job.performed = true;
-
- // Ingest dependents of the Observer into runtime,
- // since they depend on the Observer and therefore have properly changed too
- job.observer.dependents.forEach((observer) => observer.ingest());
-
- // Add Job to rerender queue and reset current Job property
- if (job.rerender) this.jobsToRerender.push(job);
- this.currentJob = null;
-
- 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)
- // of the Job based on the 'jobsToRerender' queue.
- if (this.jobQueue.length > 0) {
- const performJob = this.jobQueue.shift();
- if (performJob) this.perform(performJob);
- } else {
- this.isPerformingJobs = false;
- if (this.jobsToRerender.length > 0) {
- 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();
- }
- }
- }
-
- /**
- * Processes the `jobsToRerender` queue by updating (causing a re-render on)
- * the subscribed Subscription Containers (UI-Components) of each Job Observer.
- *
- * It returns a boolean indicating whether
- * any Subscription Container (UI-Component) was updated (re-rendered) or not.
- *
- * @internal
- */
- public updateSubscribers(): boolean {
- // Build final 'jobsToRerender' array
- // based on the new 'jobsToRerender' array and the 'notReadyJobsToRerender' array
- const jobsToRerender = this.jobsToRerender.concat(
- Array.from(this.notReadyJobsToRerender)
- );
- this.notReadyJobsToRerender = new Set();
- this.jobsToRerender = [];
-
- if (!this.agileInstance().hasIntegration() || jobsToRerender.length <= 0)
- return false;
-
- // Extract the Subscription Container to be re-rendered from the Jobs
- const subscriptionContainerToUpdate = this.extractToUpdateSubscriptionContainer(
- jobsToRerender
- );
- if (subscriptionContainerToUpdate.length <= 0) return false;
-
- // Update Subscription Container (trigger re-render on the UI-Component they represent)
- this.updateSubscriptionContainer(subscriptionContainerToUpdate);
-
- return true;
- }
-
- /**
- * Extracts the Subscription Containers (UI-Components)
- * to be updated (re-rendered) from the specified Runtime Jobs.
- *
- * @internal
- * @param jobs - Jobs from which to extract the Subscription Containers to be updated.
- */
- public extractToUpdateSubscriptionContainer(
- jobs: Array
- ): Array {
- // https://medium.com/@bretcameron/how-to-make-your-code-faster-using-javascript-sets-b432457a4a77
- const subscriptionsToUpdate = new Set();
-
- // 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;
-
- // Handle not ready Subscription Container
- if (!subscriptionContainer.ready) {
- if (
- !job.config.maxTriesToUpdate ||
- job.timesTriedToUpdateCount < job.config.maxTriesToUpdate
- ) {
- job.timesTriedToUpdateCount++;
- this.notReadyJobsToRerender.add(job);
- LogCodeManager.log(
- '16:02:00',
- [subscriptionContainer.key],
- subscriptionContainer
- );
- } else {
- LogCodeManager.log(
- '16:02:01',
- [job.config.maxTriesToUpdate],
- subscriptionContainer
- );
- }
- return;
- }
-
- // TODO has to be overthought because when it is a Component based Subscription
- // the rerender is triggered via merging the changed properties into the Component.
- // Although the 'componentId' might be equal, it doesn't mean
- // that the changed properties are equal! (-> changed properties might get missing)
- // Check if Subscription Container with same 'componentId'
- // is already in the 'subscriptionToUpdate' queue (rerender optimisation)
- // updateSubscriptionContainer =
- // updateSubscriptionContainer &&
- // Array.from(subscriptionsToUpdate).findIndex(
- // (sc) => sc.componentId === subscriptionContainer.componentId
- // ) === -1;
-
- // Check whether a selected part of the Observer value has changed
- updateSubscriptionContainer =
- updateSubscriptionContainer &&
- this.handleSelectors(subscriptionContainer, job);
-
- // Add Subscription Container to the 'subscriptionsToUpdate' queue
- if (updateSubscriptionContainer) {
- subscriptionContainer.updatedSubscribers.add(job.observer);
- subscriptionsToUpdate.add(subscriptionContainer);
- }
-
- job.subscriptionContainersToUpdate.delete(subscriptionContainer);
- });
- }
-
- return Array.from(subscriptionsToUpdate);
- }
-
- /**
- * Updates the specified Subscription Containers.
- *
- * Updating a Subscription Container triggers a re-render
- * on the Component it represents, based on the type of the Subscription Containers.
- *
- * @internal
- * @param subscriptionsToUpdate - Subscription Containers to be updated.
- */
- public updateSubscriptionContainer(
- subscriptionsToUpdate: Array
- ): void {
- // 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();
-
- // Call 'update method' in Integrations if Component based Subscription
- if (subscriptionContainer instanceof ComponentSubscriptionContainer)
- this.agileInstance().integrations.update(
- subscriptionContainer.component,
- this.getUpdatedObserverValues(subscriptionContainer)
- );
-
- subscriptionContainer.updatedSubscribers.clear();
- }
-
- LogCodeManager.logIfTags(
- ['runtime'],
- '16:01:02',
- [],
- subscriptionsToUpdate
- );
- }
-
- /**
- * Maps the values of the updated Observers (`updatedSubscribers`)
- * of the specified Subscription Container into a key map object.
- *
- * The key containing the Observer value is extracted from the Observer itself
- * or from the Subscription Container's `subscriberKeysWeakMap`.
- *
- * @internal
- * @param subscriptionContainer - Subscription Container from which the `updatedSubscribers` are to be mapped into a key map.
- */
- public getUpdatedObserverValues(
- subscriptionContainer: SubscriptionContainer
- ): { [key: string]: any } {
- const props: { [key: string]: any } = {};
- for (const observer of subscriptionContainer.updatedSubscribers) {
- const key =
- subscriptionContainer.subscriberKeysWeakMap.get(observer) ??
- observer.key;
- if (key != null) props[key] = observer.value;
- }
- return props;
- }
-
- /**
- * Returns a boolean indicating whether the specified Subscription Container can be updated or not,
- * based on its selector functions (`selectorsWeakMap`).
- *
- * This is done by checking the '.value' and the '.previousValue' property of the Observer represented by the Job.
- * If a selected property differs, the Subscription Container (UI-Component) is allowed to update (re-render)
- * and `true` is returned.
- *
- * If the Subscription Container has no selector function at all, `true` is returned.
- *
- * @internal
- * @param subscriptionContainer - Subscription Container to be checked if it can be updated.
- * @param job - Job containing the Observer that is subscribed to the Subscription Container.
- */
- public handleSelectors(
- subscriptionContainer: SubscriptionContainer,
- job: RuntimeJob
- ): boolean {
- const selectorMethods = subscriptionContainer.selectorsWeakMap.get(
- job.observer
- )?.methods;
-
- // If no selector functions found, return true.
- // Because no specific part of the Observer was selected.
- // -> The Subscription Container should be updated
- // no matter what has updated in the Observer.
- if (selectorMethods == null) return true;
-
- // Check if a selected part of the Observer value has changed
- const previousValue = job.observer.previousValue;
- const newValue = job.observer.value;
- for (const selectorMethod of selectorMethods) {
- if (
- notEqual(selectorMethod(newValue), selectorMethod(previousValue))
- // || newValueDeepness !== previousValueDeepness // Not possible to check the object deepness
- )
- return true;
- }
-
- return false;
- }
-}
-
-export interface IngestConfigInterface {
- /**
- * Whether the ingested Job should be performed immediately
- * or added to the queue first and then executed when it is his turn.
- */
- perform?: boolean;
-}
+export * from './runtime';
+// export * from './observer';
+// export * from './runtime.job';
+// export * from './subscription/container/SubscriptionContainer';
+// export * from './subscription/container/CallbackSubscriptionContainer';
+// export * from './subscription/container/ComponentSubscriptionContainer';
+// export * from './subscription/sub.controller';
diff --git a/packages/core/src/runtime/runtime.ts b/packages/core/src/runtime/runtime.ts
new file mode 100644
index 00000000..0dd3419f
--- /dev/null
+++ b/packages/core/src/runtime/runtime.ts
@@ -0,0 +1,358 @@
+import {
+ Agile,
+ SubscriptionContainer,
+ RuntimeJob,
+ CallbackSubscriptionContainer,
+ ComponentSubscriptionContainer,
+ notEqual,
+ LogCodeManager,
+ defineConfig,
+} from '../internal';
+
+export class Runtime {
+ // Agile Instance the Runtime belongs to
+ public agileInstance: () => Agile;
+
+ // Job that is currently being performed
+ public currentJob: RuntimeJob | null = null;
+ // Jobs to be performed
+ public jobQueue: Array = [];
+
+ // Jobs that were performed and are ready to re-render
+ public jobsToRerender: Array = [];
+ // Jobs that were performed and couldn't be re-rendered yet.
+ // That is the case when at least one Subscription Container (UI-Component) in the Job
+ // wasn't ready to update (re-render).
+ public notReadyJobsToRerender: Set = new Set();
+
+ // 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.)
+ * and optimized the re-rendering of the Observer's subscribed UI-Components.
+ *
+ * Each queued Job is executed when it is its turn
+ * by calling the Job Observer's `perform()` method.
+ *
+ * After successful execution, the Job is added to a re-render queue,
+ * which is first put into the browser's 'Bucket' and started to work off
+ * when resources are left.
+ *
+ * The re-render queue is designed for optimizing the render count
+ * by batching multiple re-render Jobs of the same UI-Component
+ * and ignoring re-render requests for unmounted UI-Components.
+ *
+ * @internal
+ * @param agileInstance - Instance of Agile the Runtime belongs to.
+ */
+ constructor(agileInstance: Agile) {
+ this.agileInstance = () => agileInstance;
+ }
+
+ /**
+ * Adds the specified Observer-based Job to the internal Job queue,
+ * where it is executed when it is its turn.
+ *
+ * After successful execution, the Job is assigned to the re-render queue,
+ * where all the Observer's subscribed Subscription Containers (UI-Components)
+ * are updated (re-rendered).
+ *
+ * @public
+ * @param job - Job to be added to the Job queue.
+ * @param config - Configuration object
+ */
+ public ingest(job: RuntimeJob, config: IngestConfigInterface = {}): void {
+ config = defineConfig(config, {
+ perform: !this.isPerformingJobs,
+ });
+
+ // Add specified Job to the queue
+ this.jobQueue.push(job);
+
+ LogCodeManager.logIfTags(['runtime'], '16:01:00', [job._key], job);
+
+ // Run first Job from the queue
+ if (config.perform) {
+ const performJob = this.jobQueue.shift();
+ if (performJob) this.perform(performJob);
+ }
+ }
+
+ /**
+ * Performs the specified Job
+ * and assigns it to the re-render queue if necessary.
+ *
+ * After the execution of the provided Job, it is checked whether
+ * there are still Jobs left in the Job queue.
+ * - If so, the next Job in the `jobQueue` is performed.
+ * - If not, the `jobsToRerender` queue is started to work off.
+ *
+ * @internal
+ * @param job - Job to be performed.
+ */
+ public perform(job: RuntimeJob): void {
+ this.isPerformingJobs = true;
+ this.currentJob = job;
+
+ // Perform Job
+ job.observer.perform(job);
+ job.performed = true;
+
+ // Ingest dependents of the Observer into runtime,
+ // since they depend on the Observer and therefore have properly changed too
+ job.observer.dependents.forEach((observer) => observer.ingest());
+
+ // Add Job to rerender queue and reset current Job property
+ if (job.rerender) this.jobsToRerender.push(job);
+ this.currentJob = null;
+
+ 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)
+ // of the Job based on the 'jobsToRerender' queue.
+ if (this.jobQueue.length > 0) {
+ const performJob = this.jobQueue.shift();
+ if (performJob) this.perform(performJob);
+ } else {
+ this.isPerformingJobs = false;
+ if (this.jobsToRerender.length > 0) {
+ 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();
+ }
+ }
+ }
+
+ /**
+ * Processes the `jobsToRerender` queue by updating (causing a re-render on)
+ * the subscribed Subscription Containers (UI-Components) of each Job Observer.
+ *
+ * It returns a boolean indicating whether
+ * any Subscription Container (UI-Component) was updated (re-rendered) or not.
+ *
+ * @internal
+ */
+ public updateSubscribers(): boolean {
+ // Build final 'jobsToRerender' array
+ // based on the new 'jobsToRerender' array and the 'notReadyJobsToRerender' array
+ const jobsToRerender = this.jobsToRerender.concat(
+ Array.from(this.notReadyJobsToRerender)
+ );
+ this.notReadyJobsToRerender = new Set();
+ this.jobsToRerender = [];
+
+ if (!this.agileInstance().hasIntegration() || jobsToRerender.length <= 0)
+ return false;
+
+ // Extract the Subscription Container to be re-rendered from the Jobs
+ const subscriptionContainerToUpdate = this.extractToUpdateSubscriptionContainer(
+ jobsToRerender
+ );
+ if (subscriptionContainerToUpdate.length <= 0) return false;
+
+ // Update Subscription Container (trigger re-render on the UI-Component they represent)
+ this.updateSubscriptionContainer(subscriptionContainerToUpdate);
+
+ return true;
+ }
+
+ /**
+ * Extracts the Subscription Containers (UI-Components)
+ * to be updated (re-rendered) from the specified Runtime Jobs.
+ *
+ * @internal
+ * @param jobs - Jobs from which to extract the Subscription Containers to be updated.
+ */
+ public extractToUpdateSubscriptionContainer(
+ jobs: Array
+ ): Array {
+ // https://medium.com/@bretcameron/how-to-make-your-code-faster-using-javascript-sets-b432457a4a77
+ const subscriptionsToUpdate = new Set();
+
+ // 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;
+
+ // Handle not ready Subscription Container
+ if (!subscriptionContainer.ready) {
+ if (
+ !job.config.maxTriesToUpdate ||
+ job.timesTriedToUpdateCount < job.config.maxTriesToUpdate
+ ) {
+ job.timesTriedToUpdateCount++;
+ this.notReadyJobsToRerender.add(job);
+ LogCodeManager.log(
+ '16:02:00',
+ [subscriptionContainer.key],
+ subscriptionContainer
+ );
+ } else {
+ LogCodeManager.log(
+ '16:02:01',
+ [job.config.maxTriesToUpdate],
+ subscriptionContainer
+ );
+ }
+ return;
+ }
+
+ // TODO has to be overthought because when it is a Component based Subscription
+ // the rerender is triggered via merging the changed properties into the Component.
+ // Although the 'componentId' might be equal, it doesn't mean
+ // that the changed properties are equal! (-> changed properties might get missing)
+ // Check if Subscription Container with same 'componentId'
+ // is already in the 'subscriptionToUpdate' queue (rerender optimisation)
+ // updateSubscriptionContainer =
+ // updateSubscriptionContainer &&
+ // Array.from(subscriptionsToUpdate).findIndex(
+ // (sc) => sc.componentId === subscriptionContainer.componentId
+ // ) === -1;
+
+ // Check whether a selected part of the Observer value has changed
+ updateSubscriptionContainer =
+ updateSubscriptionContainer &&
+ this.handleSelectors(subscriptionContainer, job);
+
+ // Add Subscription Container to the 'subscriptionsToUpdate' queue
+ if (updateSubscriptionContainer) {
+ subscriptionContainer.updatedSubscribers.add(job.observer);
+ subscriptionsToUpdate.add(subscriptionContainer);
+ }
+
+ job.subscriptionContainersToUpdate.delete(subscriptionContainer);
+ });
+ }
+
+ return Array.from(subscriptionsToUpdate);
+ }
+
+ /**
+ * Updates the specified Subscription Containers.
+ *
+ * Updating a Subscription Container triggers a re-render
+ * on the Component it represents, based on the type of the Subscription Containers.
+ *
+ * @internal
+ * @param subscriptionsToUpdate - Subscription Containers to be updated.
+ */
+ public updateSubscriptionContainer(
+ subscriptionsToUpdate: Array
+ ): void {
+ // 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();
+
+ // Call 'update method' in Integrations if Component based Subscription
+ if (subscriptionContainer instanceof ComponentSubscriptionContainer)
+ this.agileInstance().integrations.update(
+ subscriptionContainer.component,
+ this.getUpdatedObserverValues(subscriptionContainer)
+ );
+
+ subscriptionContainer.updatedSubscribers.clear();
+ }
+
+ LogCodeManager.logIfTags(
+ ['runtime'],
+ '16:01:02',
+ [],
+ subscriptionsToUpdate
+ );
+ }
+
+ /**
+ * Maps the values of the updated Observers (`updatedSubscribers`)
+ * of the specified Subscription Container into a key map object.
+ *
+ * The key containing the Observer value is extracted from the Observer itself
+ * or from the Subscription Container's `subscriberKeysWeakMap`.
+ *
+ * @internal
+ * @param subscriptionContainer - Subscription Container from which the `updatedSubscribers` are to be mapped into a key map.
+ */
+ public getUpdatedObserverValues(
+ subscriptionContainer: SubscriptionContainer
+ ): { [key: string]: any } {
+ const props: { [key: string]: any } = {};
+ for (const observer of subscriptionContainer.updatedSubscribers) {
+ const key =
+ subscriptionContainer.subscriberKeysWeakMap.get(observer) ??
+ observer.key;
+ if (key != null) props[key] = observer.value;
+ }
+ return props;
+ }
+
+ /**
+ * Returns a boolean indicating whether the specified Subscription Container can be updated or not,
+ * based on its selector functions (`selectorsWeakMap`).
+ *
+ * This is done by checking the '.value' and the '.previousValue' property of the Observer represented by the Job.
+ * If a selected property differs, the Subscription Container (UI-Component) is allowed to update (re-render)
+ * and `true` is returned.
+ *
+ * If the Subscription Container has no selector function at all, `true` is returned.
+ *
+ * @internal
+ * @param subscriptionContainer - Subscription Container to be checked if it can be updated.
+ * @param job - Job containing the Observer that is subscribed to the Subscription Container.
+ */
+ public handleSelectors(
+ subscriptionContainer: SubscriptionContainer,
+ job: RuntimeJob
+ ): boolean {
+ const selectorMethods = subscriptionContainer.selectorsWeakMap.get(
+ job.observer
+ )?.methods;
+
+ // If no selector functions found, return true.
+ // Because no specific part of the Observer was selected.
+ // -> The Subscription Container should be updated
+ // no matter what has updated in the Observer.
+ if (selectorMethods == null) return true;
+
+ // Check if a selected part of the Observer value has changed
+ const previousValue = job.observer.previousValue;
+ const newValue = job.observer.value;
+ for (const selectorMethod of selectorMethods) {
+ if (
+ notEqual(selectorMethod(newValue), selectorMethod(previousValue))
+ // || newValueDeepness !== previousValueDeepness // Not possible to check the object deepness
+ )
+ return true;
+ }
+
+ return false;
+ }
+}
+
+export interface IngestConfigInterface {
+ /**
+ * Whether the ingested Job should be performed immediately
+ * or added to the queue first and then executed when it is his turn.
+ */
+ perform?: boolean;
+}
diff --git a/packages/core/src/shared.ts b/packages/core/src/shared.ts
index 5f000c31..604d1c89 100644
--- a/packages/core/src/shared.ts
+++ b/packages/core/src/shared.ts
@@ -1,21 +1,4 @@
-import {
- Agile,
- Collection,
- CollectionConfig,
- Computed,
- ComputeFunctionType,
- CreateComputedConfigInterface,
- CreateStorageConfigInterface,
- DefaultItem,
- defineConfig,
- DependableAgileInstancesType,
- flatMerge,
- removeProperties,
- runsOnServer,
- State,
- StateConfigInterface,
- Storage,
-} from './internal';
+import { Agile, runsOnServer } from './internal';
/**
* Shared Agile Instance that is used when no Agile Instance was specified.
@@ -36,149 +19,6 @@ 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 = defineConfig(config, {
- agileInstance: sharedAgileInstance,
- });
- 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 = defineConfig(_config, {
- computedDeps: configOrDeps,
- });
- } else {
- if (configOrDeps) _config = configOrDeps;
- }
-
- _config = defineConfig(_config, { agileInstance: sharedAgileInstance });
-
- return new Computed