├── logo ├── logo.png ├── logo-title-dark.png ├── logo-title-light.png ├── README.md ├── logo.svg ├── logo-title-dark.svg ├── logo-title-light.svg └── LICENSE-logo ├── playground ├── www │ ├── index.css │ └── index.html ├── dogStore.ts ├── index.tsx └── store.ts ├── types └── types.d.ts ├── src ├── create-id.ts ├── state.ts ├── constants.ts ├── devtools.ts ├── exome.ts ├── ghost.ts ├── utils │ ├── id.ts │ ├── save-state.ts │ ├── id.test.ts │ ├── wrapper.ts │ ├── load-state.ts │ ├── wrapper.test.ts │ ├── save-state.test.ts │ └── load-state.test.ts ├── exome.test.ts ├── constructor.ts ├── rxjs.ts ├── svelte.ts ├── solid.ts ├── react.ts ├── preact.ts ├── subscribe.ts ├── on-action.ts ├── lit.ts ├── jest │ ├── serializer.ts │ └── serializer.test.ts ├── vue.ts ├── middleware.ts ├── angular.ts ├── utils.ts ├── constructor.test.ts ├── devtools-redux.ts ├── utils.test.ts ├── middleware.test.ts ├── devtools-exome.ts └── on-action.test.ts ├── e2e ├── setup │ ├── www │ │ └── index.html │ └── playwright.ts ├── stores │ ├── counter.ts │ ├── recursive.ts │ └── async-store.ts ├── tsconfig.json ├── preact │ ├── counter.tsx │ ├── tsconfig.json │ └── counter.test.ts ├── react │ ├── tsconfig.json │ ├── counter.tsx │ ├── async-action.tsx │ ├── recursive.tsx │ ├── counter.test.ts │ ├── async-action.test.ts │ └── recursive.test.ts └── lit │ ├── tsconfig.json │ ├── counter.ts │ ├── async-action.ts │ ├── counter.test.ts │ ├── recursive.ts │ ├── async-action.test.ts │ └── recursive.test.ts ├── .editorconfig ├── .github ├── SECURITY.md ├── workflows │ ├── main.yml │ └── publish.yml ├── CONTRIBUTING.md └── CODE_OF_CONDUCT.md ├── scripts ├── dev.mjs ├── common.mjs └── build.mjs ├── tsconfig.json ├── biome.json ├── LICENSE ├── deno.json ├── .gitignore ├── package.json ├── CHANGELOG.md └── README.md /logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marcisbee/exome/HEAD/logo/logo.png -------------------------------------------------------------------------------- /playground/www/index.css: -------------------------------------------------------------------------------- 1 | /* src/style.css */ 2 | h1, 3 | p { 4 | font-family: Lato; 5 | } 6 | -------------------------------------------------------------------------------- /logo/logo-title-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marcisbee/exome/HEAD/logo/logo-title-dark.png -------------------------------------------------------------------------------- /logo/logo-title-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marcisbee/exome/HEAD/logo/logo-title-light.png -------------------------------------------------------------------------------- /types/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'package.json' { 2 | const content: { version: string }; 3 | export default content; 4 | } 5 | -------------------------------------------------------------------------------- /src/create-id.ts: -------------------------------------------------------------------------------- 1 | export const createID = (): string => 2 | ( 3 | Date.now().toString(36) + ((Math.random() * 1e5) ^ 1).toString(36) 4 | ).toUpperCase(); 5 | -------------------------------------------------------------------------------- /src/state.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module exome/state 3 | */ 4 | export { saveState } from "./utils/save-state.ts"; 5 | export { loadState, registerLoadable } from "./utils/load-state.ts"; 6 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const exomeId: unique symbol = Symbol(); 2 | export const exomeName: unique symbol = Symbol(); 3 | export const FUNCTION = "function"; 4 | export const CONSTRUCTOR = "constructor"; 5 | -------------------------------------------------------------------------------- /src/devtools.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module exome/devtools 3 | */ 4 | export { exomeReduxDevtools } from "./devtools-redux.ts"; 5 | export { exomeDevtools as unstableExomeDevtools } from "./devtools-exome.ts"; 6 | -------------------------------------------------------------------------------- /e2e/setup/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /e2e/stores/counter.ts: -------------------------------------------------------------------------------- 1 | import { Exome } from 'exome' 2 | 3 | class CounterStore extends Exome { 4 | public count = 0 5 | 6 | public increment() { 7 | this.count += 1 8 | } 9 | } 10 | 11 | export const counter = new CounterStore() 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = tab 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [{package.json,package-lock.json}] 14 | indent_style = space 15 | 16 | [*.{yml,yaml}] 17 | indent_style = space 18 | -------------------------------------------------------------------------------- /src/exome.ts: -------------------------------------------------------------------------------- 1 | export { Exome } from "./constructor.ts"; 2 | export { subscribe, update, updateAll } from "./subscribe.ts"; 3 | export { exomeId, exomeName } from "./constants.ts"; 4 | export { onAction } from "./on-action.ts"; 5 | export { getExomeId, setExomeId } from "./utils/id.ts"; 6 | export { addMiddleware, runMiddleware, type Middleware } from "./middleware.ts"; 7 | -------------------------------------------------------------------------------- /src/ghost.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module exome/ghost 3 | */ 4 | import { exomeId } from "exome"; 5 | 6 | import { createID } from "./create-id.ts"; 7 | 8 | /** 9 | * This is a class that pretends to be Exome store, but doesn't apply same change detection logic. 10 | * This is useful for testing and mocking data. 11 | */ 12 | export class GhostExome { 13 | private [exomeId] = this.constructor.name + "-" + createID(); 14 | } 15 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Versions currently being supported with security updates. 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 2.0.x | :white_check_mark: | 10 | | 1.5.x | :white_check_mark: | 11 | | < 1.5 | :x: | 12 | 13 | ## Reporting a Vulnerability 14 | 15 | Please create a new issue if you find any security vulnerabilities. 16 | -------------------------------------------------------------------------------- /src/utils/id.ts: -------------------------------------------------------------------------------- 1 | import { exomeId } from "../constants.ts"; 2 | import type { Exome } from "../constructor.ts"; 3 | 4 | /** 5 | * Gets unique id of specific store instance. 6 | */ 7 | export const getExomeId = (store: Exome): string => { 8 | return store[exomeId]; 9 | }; 10 | 11 | /** 12 | * Sets custom id to specific store instance. 13 | */ 14 | export const setExomeId = (store: Exome, id: string): void => { 15 | const [name] = getExomeId(store).split("-"); 16 | store[exomeId] = `${name}-${id}`; 17 | }; 18 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "lib": [ 6 | "DOM", 7 | "es2015" 8 | ], 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "jsx": "react", 14 | "paths": { 15 | "exome": [ 16 | "../src/exome.ts" 17 | ], 18 | "exome/react": [ 19 | "../src/react.ts" 20 | ] 21 | }, 22 | "allowJs": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /e2e/preact/counter.tsx: -------------------------------------------------------------------------------- 1 | import { h, Fragment, render } from 'preact' 2 | import { useRef } from 'preact/hooks' 3 | import { useStore } from 'exome/preact' 4 | 5 | import { counter } from '../stores/counter' 6 | 7 | function App() { 8 | const { count, increment } = useStore(counter) 9 | const renders = useRef(0) 10 | 11 | renders.current += 1 12 | 13 | return ( 14 | <> 15 |
9 |
10 | Download as [PNG](https://raw.githubusercontent.com/Marcisbee/exome/main/logo/logo.png) or [SVG](https://raw.githubusercontent.com/Marcisbee/exome/main/logo/logo.svg).
11 |
12 | ## Logo with a Dark Title
13 |
14 |
15 |
16 | Download as [PNG](https://raw.githubusercontent.com/Marcisbee/exome/main/logo/logo-title-dark.png) or [SVG](https://raw.githubusercontent.com/Marcisbee/exome/main/logo/logo-title-dark.svg).
17 |
18 | ## Logo with a Light Title
19 |
20 |
21 |
22 | _(You can't see the text but it's there, in white.)_
23 |
24 | Download as [PNG](https://raw.githubusercontent.com/Marcisbee/exome/main/logo/logo-title-light.png) or [SVG](https://raw.githubusercontent.com/Marcisbee/exome/main/logo/logo-title-light.svg).
25 |
26 | ## Modifications
27 |
28 | Whenever possible, we ask you to use the originals provided on this page.
29 |
30 | ## Credits
31 |
32 | The Exome logo was designed by [Marcis Bergmanis](https://twitter.com/marcisbee/).
33 |
34 | ## License
35 |
36 | The Exome logo is licensed under CC0, waiving all copyright.
37 | [Read the license](../LICENSE-logo).
38 |
--------------------------------------------------------------------------------
/playground/dogStore.ts:
--------------------------------------------------------------------------------
1 | import { Exome } from "../src/exome";
2 | import { loadState, registerLoadable, saveState } from "../src/state";
3 |
4 | export class Dog extends Exome {
5 | constructor(public name: string, public breed: string) {
6 | super();
7 | }
8 |
9 | public rename(name: string) {
10 | this.name = name;
11 | }
12 |
13 | public changeBreed(breed: string) {
14 | this.breed = breed;
15 | }
16 | }
17 |
18 | export class Person extends Exome {
19 | constructor(public name: string, public dogs: Dog[] = []) {
20 | super();
21 | }
22 |
23 | public rename(name: string) {
24 | this.name = name;
25 | }
26 |
27 | public addDog(dog: Dog) {
28 | this.dogs.push(dog);
29 | }
30 | }
31 |
32 | export class Store extends Exome {
33 | public persons: Person[] = [];
34 |
35 | public addPerson(person: Person) {
36 | this.persons.push(person);
37 | }
38 | }
39 |
40 | export const dogStorePre = new Store();
41 | export const dogStore = new Store();
42 |
43 | const dogAndyPre = new Dog("Andy", "beagle pup");
44 |
45 | dogStorePre.addPerson(new Person("John Wick", [dogAndyPre]));
46 |
47 | dogStorePre.addPerson(new Person("Jane Doe", [dogAndyPre]));
48 |
49 | dogStorePre.addPerson(new Person("Daniel Craig"));
50 |
51 | const savedStore = saveState(dogStorePre);
52 |
53 | registerLoadable({
54 | Person,
55 | Dog,
56 | });
57 |
58 | loadState(dogStore, savedStore);
59 |
60 | export const dogAndy = dogStore.persons[0].dogs[0];
61 |
--------------------------------------------------------------------------------
/src/middleware.ts:
--------------------------------------------------------------------------------
1 | import { FUNCTION } from "./constants.ts";
2 | import type { Exome } from "./constructor.ts";
3 | import { update } from "./subscribe.ts";
4 |
5 | export type Middleware = (
6 | instance: Exome,
7 | action: string,
8 | payload: any[],
9 | ) => void | ((error?: Error, response?: any) => void);
10 |
11 | export const middleware: Middleware[] = [];
12 |
13 | /**
14 | * Listens to middleware calls for any store instance.
15 | */
16 | export const addMiddleware = (fn: Middleware): (() => void) => {
17 | middleware.push(fn);
18 |
19 | return () => {
20 | middleware.splice(middleware.indexOf(fn), 1);
21 | };
22 | };
23 |
24 | /**
25 | * Triggers middleware for particular store instance to be called.
26 | * When return function gets called, it maks that the middleware action
27 | * was completed with or without errors.
28 | */
29 | export const runMiddleware = (
30 | parent: Parameters
2 |
3 |
4 |