@yaasl/core 0.7.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/@types/base/Stateful.d.ts +19 -0
  2. package/dist/@types/base/atom.d.ts +38 -0
  3. package/dist/@types/base/config.d.ts +16 -0
  4. package/dist/@types/base/derive.d.ts +19 -0
  5. package/dist/@types/base/index.d.ts +4 -0
  6. package/dist/@types/index.d.ts +2 -0
  7. package/dist/@types/middleware/MiddlewareDispatcher.d.ts +14 -0
  8. package/dist/@types/middleware/expiration.d.ts +17 -0
  9. package/dist/@types/middleware/index.d.ts +10 -0
  10. package/dist/@types/middleware/indexedDb.d.ts +15 -0
  11. package/dist/@types/middleware/localStorage.d.ts +28 -0
  12. package/dist/@types/middleware/middleware.d.ts +31 -0
  13. package/dist/@types/middleware/migration.d.ts +19 -0
  14. package/dist/@types/utils/Expiration.d.ts +18 -0
  15. package/dist/@types/utils/LocalStorage.d.ts +17 -0
  16. package/dist/@types/utils/Store.d.ts +9 -0
  17. package/dist/@types/utils/Thenable.d.ts +7 -0
  18. package/dist/cjs/base/Stateful.js +36 -0
  19. package/dist/cjs/base/atom.js +67 -0
  20. package/dist/cjs/base/config.js +11 -0
  21. package/dist/cjs/base/derive.js +32 -0
  22. package/dist/cjs/base/index.js +20 -0
  23. package/dist/cjs/index.js +18 -0
  24. package/dist/cjs/middleware/MiddlewareDispatcher.js +34 -0
  25. package/dist/cjs/middleware/expiration.js +48 -0
  26. package/dist/cjs/middleware/index.js +14 -0
  27. package/dist/cjs/middleware/indexedDb.js +49 -0
  28. package/dist/cjs/middleware/localStorage.js +38 -0
  29. package/dist/cjs/middleware/middleware.js +19 -0
  30. package/dist/cjs/middleware/migration.js +91 -0
  31. package/dist/cjs/utils/Expiration.js +64 -0
  32. package/dist/cjs/utils/LocalStorage.js +60 -0
  33. package/dist/cjs/utils/Store.js +50 -0
  34. package/dist/cjs/utils/Thenable.js +25 -0
  35. package/dist/mjs/base/Stateful.js +32 -0
  36. package/dist/mjs/base/atom.js +51 -0
  37. package/dist/mjs/base/config.js +8 -0
  38. package/dist/mjs/base/derive.js +27 -0
  39. package/dist/mjs/base/index.js +4 -0
  40. package/dist/mjs/index.js +2 -0
  41. package/dist/mjs/middleware/MiddlewareDispatcher.js +30 -0
  42. package/dist/mjs/middleware/expiration.js +47 -0
  43. package/dist/mjs/middleware/index.js +5 -0
  44. package/dist/mjs/middleware/indexedDb.js +35 -0
  45. package/dist/mjs/middleware/localStorage.js +35 -0
  46. package/dist/mjs/middleware/middleware.js +15 -0
  47. package/dist/mjs/middleware/migration.js +86 -0
  48. package/dist/mjs/utils/Expiration.js +60 -0
  49. package/dist/mjs/utils/LocalStorage.js +54 -0
  50. package/dist/mjs/utils/Store.js +33 -0
  51. package/dist/mjs/utils/Thenable.js +21 -0
  52. package/package.json +57 -0
  53. package/tsconfig.json +3 -0
  54. package/tsconfig.node.json +9 -0
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.localStorage = void 0;
4
+ const middleware_1 = require("./middleware");
5
+ const base_1 = require("../base");
6
+ const LocalStorage_1 = require("../utils/LocalStorage");
7
+ /** Middleware to save and load atom values to the local storage.
8
+ *
9
+ * @param {LocalStorageOptions | undefined} options
10
+ * @param options.key Use your own key for the local storage.
11
+ * Will be "{config-name}/{atom-name}" by default.
12
+ * @param options.noTabSync Disable the synchronization of values over browser tabs.
13
+ * @param options.parser Custom functions to stringify and parse values. Defaults to JSON.stringify and JSON.parse. Use this when handling complex datatypes like Maps or Sets.
14
+ *
15
+ * @returns The middleware to be used on atoms.
16
+ **/
17
+ exports.localStorage = (0, middleware_1.middleware)(({ atom, options = {} }) => {
18
+ const internalKey = base_1.CONFIG.name ? `${base_1.CONFIG.name}/${atom.name}` : atom.name;
19
+ const { key = internalKey, parser, noTabSync } = options;
20
+ const storage = new LocalStorage_1.LocalStorage(key, {
21
+ parser,
22
+ onTabSync: noTabSync ? undefined : value => atom.set(value),
23
+ });
24
+ return {
25
+ init: ({ atom }) => {
26
+ const existing = storage.get();
27
+ if (existing === null) {
28
+ storage.set(atom.defaultValue);
29
+ }
30
+ else {
31
+ atom.set(existing);
32
+ }
33
+ },
34
+ set: ({ value }) => {
35
+ storage.set(value);
36
+ },
37
+ };
38
+ });
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.middleware = void 0;
4
+ /** Create middlewares to be used in combination with atoms.
5
+ *
6
+ * @param setup Middleware actions or function to create middleware actions.
7
+ * Middleware actions are fired in the atom lifecycle, alongside to the subscriptions.
8
+ *
9
+ * @returns A middleware function to be used in atoms.
10
+ **/
11
+ const middleware = (setup) => (...[optionsArg]) => (atom) => {
12
+ const options = optionsArg;
13
+ const actions = setup instanceof Function ? setup({ options, atom }) : setup;
14
+ return {
15
+ options,
16
+ actions,
17
+ };
18
+ };
19
+ exports.middleware = middleware;
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createMigrationStep = exports.migration = void 0;
4
+ const utils_1 = require("@yaasl/utils");
5
+ const middleware_1 = require("./middleware");
6
+ const base_1 = require("../base");
7
+ const sortMigrations = (migrations) => {
8
+ const first = migrations.find(migration => migration.previous === null);
9
+ if (!first) {
10
+ utils_1.log.error("No migration with previous version null found");
11
+ return [];
12
+ }
13
+ return migrations.reduce(sorted => {
14
+ const last = sorted.at(-1);
15
+ const next = migrations.find(migration => migration.previous === (last === null || last === void 0 ? void 0 : last.version));
16
+ if (next) {
17
+ sorted.push(next);
18
+ }
19
+ return sorted;
20
+ }, [first]);
21
+ };
22
+ const getVersion = (atom) => {
23
+ const key = base_1.CONFIG.name ? `${base_1.CONFIG.name}/${atom.name}` : atom.name;
24
+ return localStorage.getItem(`${key}-version`);
25
+ };
26
+ const setVersion = (atom, version) => {
27
+ const key = base_1.CONFIG.name ? `${base_1.CONFIG.name}/${atom.name}` : atom.name;
28
+ localStorage.setItem(`${key}-version`, version);
29
+ };
30
+ const migrateVersion = (atom, data, migration) => {
31
+ if (migration.validate && !migration.validate(data)) {
32
+ return {
33
+ error: `The data of the "${atom.name}" atom does not match its version.`,
34
+ };
35
+ }
36
+ try {
37
+ return {
38
+ data: migration.migrate(data),
39
+ version: migration.version,
40
+ };
41
+ }
42
+ catch (_a) {
43
+ return {
44
+ error: `Something went wrong while migrating the data of the "${atom.name}" atom.`,
45
+ };
46
+ }
47
+ };
48
+ const performMigration = (atom, version, migrations) => {
49
+ const currentState = {
50
+ version,
51
+ data: atom.get(),
52
+ };
53
+ return migrations.reduce((result, migration) => {
54
+ const { data, version, error } = result;
55
+ if (error || version !== migration.previous) {
56
+ return result;
57
+ }
58
+ return migrateVersion(atom, data, migration);
59
+ }, currentState);
60
+ };
61
+ exports.migration = (0, middleware_1.middleware)({
62
+ didInit: ({ atom, options }) => {
63
+ var _a, _b;
64
+ const steps = sortMigrations(options.steps);
65
+ const currentVersion = getVersion(atom);
66
+ const isLatestVersion = currentVersion === ((_a = steps.at(-1)) === null || _a === void 0 ? void 0 : _a.version);
67
+ if (steps.length < 1 || isLatestVersion) {
68
+ return;
69
+ }
70
+ if (atom.get() === atom.defaultValue) {
71
+ const version = (_b = steps.at(-1)) === null || _b === void 0 ? void 0 : _b.version;
72
+ if (version)
73
+ setVersion(atom, version);
74
+ return;
75
+ }
76
+ const { data, version, error } = performMigration(atom, currentVersion, steps);
77
+ if (error) {
78
+ utils_1.log.error(error, {
79
+ version: currentVersion,
80
+ data: atom.get(),
81
+ });
82
+ return;
83
+ }
84
+ if (version == null)
85
+ return;
86
+ setVersion(atom, version);
87
+ atom.set(data);
88
+ },
89
+ });
90
+ const createMigrationStep = (migration) => migration;
91
+ exports.createMigrationStep = createMigrationStep;
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Expiration = void 0;
4
+ const STORAGE = window.localStorage;
5
+ class Expiration {
6
+ constructor({ key, expiresAt, expiresIn }) {
7
+ this.timeout = null;
8
+ this.dispatchExpiration = (expiresIn, onExpire) => {
9
+ this.timeout = setTimeout(() => {
10
+ onExpire();
11
+ this.remove();
12
+ }, expiresIn);
13
+ };
14
+ this.timeout = null;
15
+ this.key = key;
16
+ if (expiresAt) {
17
+ this.getExpiration = () => expiresAt instanceof Function ? expiresAt() : expiresAt;
18
+ }
19
+ else if (expiresIn) {
20
+ this.getExpiration = () => {
21
+ const afterMs = expiresIn instanceof Function ? expiresIn() : expiresIn;
22
+ return new Date(Date.now() + afterMs);
23
+ };
24
+ }
25
+ else {
26
+ this.getExpiration = null;
27
+ }
28
+ }
29
+ remove() {
30
+ STORAGE.removeItem(this.key);
31
+ if (this.timeout != null) {
32
+ clearTimeout(this.timeout);
33
+ this.timeout = null;
34
+ }
35
+ }
36
+ init(onExpire) {
37
+ if (!this.getExpiration)
38
+ return;
39
+ const existing = STORAGE.getItem(this.key);
40
+ if (!existing) {
41
+ this.remove();
42
+ return;
43
+ }
44
+ const expiresIn = Number(existing) - Date.now();
45
+ const isExpired = Number.isNaN(expiresIn) || expiresIn <= 0;
46
+ if (isExpired) {
47
+ onExpire();
48
+ this.remove();
49
+ }
50
+ else {
51
+ this.dispatchExpiration(expiresIn, onExpire);
52
+ }
53
+ }
54
+ set(onExpire) {
55
+ this.remove();
56
+ if (!this.getExpiration)
57
+ return;
58
+ const expiration = this.getExpiration();
59
+ const expiresIn = Number(expiration) - Date.now();
60
+ this.dispatchExpiration(expiresIn, onExpire);
61
+ STORAGE.setItem(this.key, String(expiration.valueOf()));
62
+ }
63
+ }
64
+ exports.Expiration = Expiration;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LocalStorage = void 0;
4
+ const utils_1 = require("@yaasl/utils");
5
+ const STORAGE = window.localStorage;
6
+ const defaultParser = {
7
+ parse: JSON.parse,
8
+ stringify: JSON.stringify,
9
+ };
10
+ const syncOverBrowserTabs = (observingKey, onTabSync) => window.addEventListener("storage", ({ key, newValue }) => {
11
+ if (observingKey !== key)
12
+ return;
13
+ onTabSync(newValue);
14
+ });
15
+ class LocalStorage {
16
+ constructor(key, options = {}) {
17
+ var _a;
18
+ this.key = key;
19
+ this.parser = (_a = options.parser) !== null && _a !== void 0 ? _a : defaultParser;
20
+ if (!options.onTabSync)
21
+ return;
22
+ syncOverBrowserTabs(key, value => {
23
+ var _a;
24
+ const newValue = value === null ? null : this.parser.parse(value);
25
+ if (newValue === null) {
26
+ this.remove();
27
+ }
28
+ else {
29
+ this.set(newValue);
30
+ }
31
+ (_a = options.onTabSync) === null || _a === void 0 ? void 0 : _a.call(options, newValue);
32
+ });
33
+ }
34
+ get() {
35
+ const value = STORAGE.getItem(this.key);
36
+ try {
37
+ return typeof value !== "string" ? null : this.parser.parse(value);
38
+ }
39
+ catch (_a) {
40
+ throw new Error((0, utils_1.consoleMessage)(`Value of local storage key "${this.key}" could not be parsed.`));
41
+ }
42
+ }
43
+ set(value) {
44
+ try {
45
+ if (value === null) {
46
+ STORAGE.removeItem(this.key);
47
+ }
48
+ else {
49
+ STORAGE.setItem(this.key, this.parser.stringify(value));
50
+ }
51
+ }
52
+ catch (_a) {
53
+ utils_1.log.error(`Value of atom with local storage key "${this.key}" could not be set.`, { value });
54
+ }
55
+ }
56
+ remove() {
57
+ STORAGE.removeItem(this.key);
58
+ }
59
+ }
60
+ exports.LocalStorage = LocalStorage;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.Store = void 0;
13
+ const promisifyRequest = (request) => new Promise((resolve, reject) => {
14
+ request.onsuccess = () => resolve(request.result);
15
+ request.onerror = () => reject(request.error);
16
+ });
17
+ class Store {
18
+ constructor(name) {
19
+ this.name = name;
20
+ const openRequest = indexedDB.open(`${name}-database`);
21
+ this.database = promisifyRequest(openRequest);
22
+ openRequest.onupgradeneeded = () => {
23
+ const database = openRequest.result;
24
+ const exists = database.objectStoreNames.contains(name);
25
+ if (!exists) {
26
+ database.createObjectStore(name);
27
+ }
28
+ };
29
+ }
30
+ getStore(mode) {
31
+ return __awaiter(this, void 0, void 0, function* () {
32
+ const database = yield this.database;
33
+ return database.transaction(this.name, mode).objectStore(this.name);
34
+ });
35
+ }
36
+ get(key) {
37
+ return __awaiter(this, void 0, void 0, function* () {
38
+ return this.getStore("readonly").then(store => promisifyRequest(store.get(key)));
39
+ });
40
+ }
41
+ set(key, value) {
42
+ return this.getStore("readwrite").then((store) => __awaiter(this, void 0, void 0, function* () {
43
+ yield promisifyRequest(store.put(value, key));
44
+ }));
45
+ }
46
+ delete(key) {
47
+ return this.getStore("readwrite").then(store => promisifyRequest(store.delete(key)));
48
+ }
49
+ }
50
+ exports.Store = Store;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Thenable = void 0;
4
+ const isPromiselike = (value) => typeof value === "object" &&
5
+ value !== null &&
6
+ "then" in value &&
7
+ typeof value.then === "function";
8
+ class Thenable {
9
+ constructor(value) {
10
+ this.value = value;
11
+ }
12
+ then(onfulfilled, onrejected) {
13
+ try {
14
+ const result = !onfulfilled ? this.value : onfulfilled(this.value);
15
+ return isPromiselike(result) ? result : new Thenable(result);
16
+ }
17
+ catch (error) {
18
+ if (!onrejected)
19
+ throw error;
20
+ const result = onrejected(error);
21
+ return isPromiselike(result) ? result : new Thenable(result);
22
+ }
23
+ }
24
+ }
25
+ exports.Thenable = Thenable;
@@ -0,0 +1,32 @@
1
+ export class Stateful {
2
+ constructor(value) {
3
+ this.value = value;
4
+ this.listeners = new Set();
5
+ }
6
+ /** Read the value of state.
7
+ *
8
+ * @returns The current value.
9
+ **/
10
+ get() {
11
+ return this.value;
12
+ }
13
+ /** Subscribe to value changes.
14
+ *
15
+ * @param callback Function to use the new value.
16
+ *
17
+ * @returns A callback to unsubscribe the passed callback.
18
+ */
19
+ subscribe(callback) {
20
+ this.listeners.add(callback);
21
+ return () => this.listeners.delete(callback);
22
+ }
23
+ emit() {
24
+ this.listeners.forEach(listener => listener(this.get()));
25
+ }
26
+ update(value) {
27
+ if (this.value !== value) {
28
+ this.value = value;
29
+ this.emit();
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,51 @@
1
+ import { Stateful } from "./Stateful";
2
+ import { MiddlewareDispatcher } from "../middleware/MiddlewareDispatcher";
3
+ let key = 0;
4
+ export class Atom extends Stateful {
5
+ constructor({ defaultValue, name = `atom-${++key}`, middleware, }) {
6
+ super(defaultValue);
7
+ this.didInit = false;
8
+ this.name = name;
9
+ this.defaultValue = defaultValue;
10
+ if (!middleware || middleware.length === 0) {
11
+ this.didInit = true;
12
+ return;
13
+ }
14
+ const { didInit } = new MiddlewareDispatcher({ atom: this, middleware });
15
+ if (typeof didInit === "boolean") {
16
+ this.didInit = didInit;
17
+ }
18
+ else {
19
+ this.didInit = didInit.then(() => {
20
+ this.didInit = true;
21
+ });
22
+ }
23
+ }
24
+ /** Set the value of the atom.
25
+ *
26
+ * @param next New value or function to create the
27
+ * new value based off the previous value.
28
+ */
29
+ set(next) {
30
+ const value = next instanceof Function ? next(this.get()) : next;
31
+ super.update(value);
32
+ }
33
+ /** Resolve the value of a promise and set as atom value.
34
+ *
35
+ * @param promise Promise to unwrap
36
+ */
37
+ async unwrap(promise) {
38
+ const value = await promise;
39
+ this.set(value);
40
+ return value;
41
+ }
42
+ }
43
+ /** Creates an atom store.
44
+ *
45
+ * @param config.defaultValue Value that will be used initially.
46
+ * @param config.name Name of the atom. Must be unique among all atoms. Defaults to "atom-{number}".
47
+ * @param config.middleware Middleware that will be applied on the atom.
48
+ *
49
+ * @returns An atom instance.
50
+ **/
51
+ export const atom = (config) => new Atom(config);
@@ -0,0 +1,8 @@
1
+ /** Global configuration object to change internal behavior of yaasl.
2
+ *
3
+ * Values should be set once in your application entrypoint,
4
+ * before yaasl is being used.
5
+ **/
6
+ export const CONFIG = {
7
+ name: undefined,
8
+ };
@@ -0,0 +1,27 @@
1
+ import { Stateful } from "./Stateful";
2
+ export class Derive extends Stateful {
3
+ constructor(derivation) {
4
+ super(undefined);
5
+ this.derivation = derivation;
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ this.dependencies = new Set();
8
+ this.value = derivation({ get: dep => this.addDependency(dep) });
9
+ }
10
+ addDependency(dependency) {
11
+ if (!this.dependencies.has(dependency)) {
12
+ dependency.subscribe(() => this.deriveUpdate());
13
+ this.dependencies.add(dependency);
14
+ }
15
+ return dependency.get();
16
+ }
17
+ deriveUpdate() {
18
+ this.update(this.derivation({ get: dep => this.addDependency(dep) }));
19
+ }
20
+ }
21
+ /** Creates a value, derived from one or more atoms or other derived values.
22
+ *
23
+ * @param get Function to derive the new value.
24
+ *
25
+ * @returns A derived instance.
26
+ **/
27
+ export const derive = (get) => new Derive(get);
@@ -0,0 +1,4 @@
1
+ export * from "./atom";
2
+ export * from "./derive";
3
+ export * from "./config";
4
+ export * from "./Stateful";
@@ -0,0 +1,2 @@
1
+ export * from "./base";
2
+ export * from "./middleware";
@@ -0,0 +1,30 @@
1
+ import { Thenable } from "../utils/Thenable";
2
+ const isPromise = (value) => value instanceof Promise;
3
+ export class MiddlewareDispatcher {
4
+ constructor({ atom, middleware }) {
5
+ this.didInit = false;
6
+ this.middleware = [];
7
+ this.middleware = middleware.map(create => create(atom));
8
+ const result = this.callMiddlewareAction("init", atom)
9
+ .then(() => this.subscribeSetters(atom))
10
+ .then(() => this.callMiddlewareAction("didInit", atom))
11
+ .then(() => {
12
+ this.didInit = true;
13
+ });
14
+ if (result instanceof Promise) {
15
+ this.didInit = result;
16
+ }
17
+ }
18
+ subscribeSetters(atom) {
19
+ atom.subscribe(value => void this.callMiddlewareAction("set", atom, value));
20
+ }
21
+ callMiddlewareAction(action, atom, value = atom.get()) {
22
+ const result = this.middleware.map(middleware => {
23
+ const { actions, options } = middleware;
24
+ const actionFn = actions[action];
25
+ return actionFn?.({ value, atom, options });
26
+ });
27
+ const promises = result.filter(isPromise);
28
+ return promises.length ? Promise.all(promises) : new Thenable();
29
+ }
30
+ }
@@ -0,0 +1,47 @@
1
+ import { middleware } from "./middleware";
2
+ import { CONFIG } from "../base";
3
+ import { Expiration } from "../utils/Expiration";
4
+ const STORAGE = window.localStorage;
5
+ const syncOverBrowserTabs = (observingKey, onChange) => window.addEventListener("storage", ({ key, newValue }) => {
6
+ if (observingKey !== key)
7
+ return;
8
+ const currentValue = STORAGE.getItem(observingKey);
9
+ if (currentValue === newValue)
10
+ return;
11
+ onChange(newValue);
12
+ });
13
+ /** Middleware to make an atom value expirable and reset to its defaulValue.
14
+ *
15
+ * __Note:__ When using `expiresAt`, a function returning the date should be prefered since using a static date might end in an infinite loop.
16
+ *
17
+ * @param {ExpirationOptions | undefined} options
18
+ * @param options.expiresAt Date at which the value expires
19
+ * @param options.expiresIn Milliseconds in which the value expires. Will be ignored if expiresAt is set.
20
+ *
21
+ * @returns The middleware to be used on atoms.
22
+ **/
23
+ export const expiration = middleware(({ atom, options = {} }) => {
24
+ const hasExpiration = Boolean(options.expiresAt ?? options.expiresIn);
25
+ if (!hasExpiration)
26
+ return {};
27
+ const key = CONFIG.name ? `${CONFIG.name}/${atom.name}` : atom.name;
28
+ const expiration = new Expiration({
29
+ ...options,
30
+ key: `${key}-expires-at`,
31
+ });
32
+ const reset = () => {
33
+ expiration.remove();
34
+ atom.set(atom.defaultValue);
35
+ };
36
+ return {
37
+ init: () => {
38
+ expiration.init(reset);
39
+ syncOverBrowserTabs(key, () => expiration.init(reset));
40
+ },
41
+ set: ({ value }) => {
42
+ if (value === atom.defaultValue)
43
+ return;
44
+ expiration.set(reset);
45
+ },
46
+ };
47
+ });
@@ -0,0 +1,5 @@
1
+ export { middleware } from "./middleware";
2
+ export { localStorage } from "./localStorage";
3
+ export { indexedDb } from "./indexedDb";
4
+ export { expiration } from "./expiration";
5
+ export { migration, createMigrationStep } from "./migration";
@@ -0,0 +1,35 @@
1
+ import { middleware } from "./middleware";
2
+ import { CONFIG } from "../base";
3
+ import { Store } from "../utils/Store";
4
+ let atomDb = null;
5
+ /** Middleware to save and load atom values to an indexedDb.
6
+ *
7
+ * Will use one database and store for all atoms with your `CONFIG.name`
8
+ * as name or `yaasl` if not set.
9
+ *
10
+ * @param {IndexedDbOptions | undefined} options
11
+ * @param options.key Use your own store key. Will be `atom.name` by default.
12
+ *
13
+ * @returns The middleware to be used on atoms.
14
+ **/
15
+ export const indexedDb = middleware(({ atom, options }) => {
16
+ const key = options?.key ?? atom.name;
17
+ return {
18
+ init: ({ atom }) => {
19
+ if (!atomDb) {
20
+ atomDb = new Store(CONFIG.name ?? "yaasl");
21
+ }
22
+ return atomDb.get(key).then(async (value) => {
23
+ if (value !== undefined) {
24
+ atom.set(value);
25
+ }
26
+ else {
27
+ await atomDb?.set(key, atom.defaultValue);
28
+ }
29
+ });
30
+ },
31
+ set: ({ value }) => {
32
+ void atomDb?.set(key, value);
33
+ },
34
+ };
35
+ });
@@ -0,0 +1,35 @@
1
+ import { middleware } from "./middleware";
2
+ import { CONFIG } from "../base";
3
+ import { LocalStorage } from "../utils/LocalStorage";
4
+ /** Middleware to save and load atom values to the local storage.
5
+ *
6
+ * @param {LocalStorageOptions | undefined} options
7
+ * @param options.key Use your own key for the local storage.
8
+ * Will be "{config-name}/{atom-name}" by default.
9
+ * @param options.noTabSync Disable the synchronization of values over browser tabs.
10
+ * @param options.parser Custom functions to stringify and parse values. Defaults to JSON.stringify and JSON.parse. Use this when handling complex datatypes like Maps or Sets.
11
+ *
12
+ * @returns The middleware to be used on atoms.
13
+ **/
14
+ export const localStorage = middleware(({ atom, options = {} }) => {
15
+ const internalKey = CONFIG.name ? `${CONFIG.name}/${atom.name}` : atom.name;
16
+ const { key = internalKey, parser, noTabSync } = options;
17
+ const storage = new LocalStorage(key, {
18
+ parser,
19
+ onTabSync: noTabSync ? undefined : value => atom.set(value),
20
+ });
21
+ return {
22
+ init: ({ atom }) => {
23
+ const existing = storage.get();
24
+ if (existing === null) {
25
+ storage.set(atom.defaultValue);
26
+ }
27
+ else {
28
+ atom.set(existing);
29
+ }
30
+ },
31
+ set: ({ value }) => {
32
+ storage.set(value);
33
+ },
34
+ };
35
+ });
@@ -0,0 +1,15 @@
1
+ /** Create middlewares to be used in combination with atoms.
2
+ *
3
+ * @param setup Middleware actions or function to create middleware actions.
4
+ * Middleware actions are fired in the atom lifecycle, alongside to the subscriptions.
5
+ *
6
+ * @returns A middleware function to be used in atoms.
7
+ **/
8
+ export const middleware = (setup) => (...[optionsArg]) => (atom) => {
9
+ const options = optionsArg;
10
+ const actions = setup instanceof Function ? setup({ options, atom }) : setup;
11
+ return {
12
+ options,
13
+ actions,
14
+ };
15
+ };