@nateabele/use-app-state 0.1.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.
@@ -0,0 +1,175 @@
1
+ import * as ts_toolbelt_out_Function_Curry from 'ts-toolbelt/out/Function/Curry';
2
+ import * as React from 'react';
3
+
4
+ type PathKey = string;
5
+ type PathSegment = string | (string | number)[];
6
+ type URL = string;
7
+ type Path = (string | number)[];
8
+ type ChangeSpec = [Path, any];
9
+ type ChangeSet = ChangeSpec[];
10
+ type Scalar = string | number | boolean;
11
+ type Atom = Scalar | symbol;
12
+ type NestedObject = {
13
+ [key: string]: NestedObject | Scalar | Array<NestedObject | Scalar>;
14
+ };
15
+ type RecursivePartial<T> = {
16
+ [P in keyof T]?: T[P] extends (infer U)[] ? RecursivePartial<U>[] : T[P] extends object | undefined ? RecursivePartial<T[P]> : T[P];
17
+ };
18
+
19
+ interface Ctor<T> {
20
+ new (...args: any[]): T;
21
+ }
22
+ declare abstract class UIEvent {
23
+ abstract name: string;
24
+ map(fn: (v: UIEvent) => UIEvent): UIEvent;
25
+ }
26
+ declare class NoOp extends UIEvent {
27
+ name: string;
28
+ path: null;
29
+ }
30
+ declare class Update extends UIEvent {
31
+ url: string;
32
+ values: Record<string, any>;
33
+ name: string;
34
+ path: null;
35
+ constructor(url: string, values: Record<string, any>);
36
+ map(fn: (v: Update) => Update): Update;
37
+ }
38
+ declare class Set extends UIEvent {
39
+ path: Path;
40
+ value: any;
41
+ name: string;
42
+ constructor(path: Path, value: any);
43
+ map(fn: (v: Set) => Set): Set;
44
+ }
45
+ /**
46
+ * Add a value to the end of an array
47
+ */
48
+ declare class Append extends UIEvent {
49
+ path: Path;
50
+ value: any;
51
+ name: string;
52
+ constructor(path: Path, value: any);
53
+ map(fn: (v: Append) => Append): Append;
54
+ }
55
+ /**
56
+ * Push a value to an array at a specific index
57
+ */
58
+ declare class Push extends UIEvent {
59
+ path: Path;
60
+ index: number;
61
+ value: any;
62
+ name: string;
63
+ constructor(path: Path, index: number, value: any);
64
+ map(fn: (v: Push) => Push): Push;
65
+ }
66
+ /**
67
+ * Pull values out of an array, or fields out of an object
68
+ */
69
+ declare class Pull extends UIEvent {
70
+ path: Path;
71
+ index: number | string;
72
+ count?: number | undefined;
73
+ name: string;
74
+ constructor(path: Path, index: number | string, count?: number | undefined);
75
+ map(fn: (v: Pull) => Pull): Pull;
76
+ }
77
+ declare class Merge extends UIEvent {
78
+ path: Path;
79
+ value: {
80
+ [key: string]: any;
81
+ };
82
+ name: string;
83
+ constructor(path: Path, value: {
84
+ [key: string]: any;
85
+ });
86
+ map(fn: (v: Merge) => Merge): Merge;
87
+ }
88
+ declare class Batch extends UIEvent {
89
+ value: AllEvents[];
90
+ name: string;
91
+ path: null;
92
+ constructor(value: AllEvents[]);
93
+ map(fn: (v: AllEvents) => AllEvents): AllEvents;
94
+ }
95
+ declare class Submit extends UIEvent {
96
+ url: string;
97
+ data: any;
98
+ name: string;
99
+ path: null;
100
+ constructor(url: string, data: any);
101
+ map(fn: (v: Submit) => Submit): Submit;
102
+ }
103
+ type AllEvents = NoOp | Set | Append | Merge | Batch | Submit | Update | Push | Pull;
104
+ type Emitter = ReturnType<typeof emitter> & {
105
+ scope: (childScope: Path) => Emitter;
106
+ };
107
+ declare const emitter: (handler: (e: UIEvent) => any) => (e: UIEvent) => void;
108
+ type TwoArity<One, Two, Return> = ((one: One, two: Two) => Return) & ((one: One) => (two: Two) => Return);
109
+ declare const noOp: NoOp;
110
+ /**
111
+ * Object operations
112
+ */
113
+ declare const set: TwoArity<Path, any, Set>;
114
+ declare const merge: TwoArity<Path, any, Merge>;
115
+ declare const drop: TwoArity<Path, string, Pull>;
116
+ /**
117
+ * Array operations
118
+ */
119
+ declare const append: TwoArity<Path, any, Append>;
120
+ declare const push: (path: Path, index: number, value: any) => Push;
121
+ declare const pull: (path: Path, index: number, count: number) => Pull;
122
+ declare const batch: (events: AllEvents[]) => Batch;
123
+ /**
124
+ * API operations
125
+ */
126
+ declare const update: TwoArity<string, Record<string, any>, Update>;
127
+ declare const updateOne: ts_toolbelt_out_Function_Curry.Curry<(a_0: string, a_1: string, a_2: any) => Update>;
128
+ declare const preventDefault: (fn: Function) => (e: {
129
+ preventDefault: (...args: any[]) => any;
130
+ }) => any;
131
+
132
+ type WithEmitter = {
133
+ emit: Emitter;
134
+ };
135
+ declare const init: <AppData extends object>(App: React.FC<AppData & WithEmitter>, node: HTMLElement, data: AppData) => (({
136
+ data: AppData;
137
+ }) & {
138
+ render: () => ReturnType<(children: React.ReactNode) => void>;
139
+ emit: (e: UIEvent) => void;
140
+ update: (fn: (val: AppData) => AppData) => void;
141
+ onEvent: (fn: (event: AllEvents, before: AppData, after: AppData) => void) => (({
142
+ data: AppData;
143
+ }) & {
144
+ render: () => ReturnType<(children: React.ReactNode) => void>;
145
+ emit: (e: UIEvent) => void;
146
+ update: (fn: (val: AppData) => AppData) => void;
147
+ onEvent: /*elided*/ any;
148
+ });
149
+ });
150
+
151
+ type EventCallback<AppState> = (event: AllEvents, before: AppState, after: AppState) => void;
152
+ type ReducerConfig<AppState> = {
153
+ onEvent?: EventCallback<AppState>;
154
+ };
155
+ /**
156
+ * This hook manages the state of the app. It's the only way to change the state, and
157
+ * it's the only way to subscribe to changes. Initializes a read-only root state object, and
158
+ * an emit() function that takes immutable Event objects, which are used to change the state.
159
+ *
160
+ * The changes are then broadcast to any subscribers, and the app is re-rendered.
161
+ *
162
+ * It also takes some flags that are useful for debugging:
163
+ * - `log`: will log all changes to the console.
164
+ * - `expose`: will expose the state to the global window object as `window.appState`, or as
165
+ * the `window[expose]`, if `expose` is a string.
166
+ */
167
+ declare function useAppState<AppState extends object>(initialState: AppState, config?: {
168
+ log?: boolean;
169
+ expose?: string | boolean;
170
+ onEvent?: EventCallback<AppState>;
171
+ }): readonly [AppState, Emitter, {
172
+ onEvent: (fn: EventCallback<AppState>) => number;
173
+ }];
174
+
175
+ export { type AllEvents, Append, type Atom, Batch, type ChangeSet, type ChangeSpec, type Ctor, type Emitter, type EventCallback, Merge, type NestedObject, NoOp, type Path, type PathKey, type PathSegment, Pull, Push, type RecursivePartial, type ReducerConfig, type Scalar, Set, Submit, type TwoArity, UIEvent, type URL, Update, append, batch, drop, emitter, init, merge, noOp, preventDefault, pull, push, set, update, updateOne, useAppState };
@@ -0,0 +1,175 @@
1
+ import * as ts_toolbelt_out_Function_Curry from 'ts-toolbelt/out/Function/Curry';
2
+ import * as React from 'react';
3
+
4
+ type PathKey = string;
5
+ type PathSegment = string | (string | number)[];
6
+ type URL = string;
7
+ type Path = (string | number)[];
8
+ type ChangeSpec = [Path, any];
9
+ type ChangeSet = ChangeSpec[];
10
+ type Scalar = string | number | boolean;
11
+ type Atom = Scalar | symbol;
12
+ type NestedObject = {
13
+ [key: string]: NestedObject | Scalar | Array<NestedObject | Scalar>;
14
+ };
15
+ type RecursivePartial<T> = {
16
+ [P in keyof T]?: T[P] extends (infer U)[] ? RecursivePartial<U>[] : T[P] extends object | undefined ? RecursivePartial<T[P]> : T[P];
17
+ };
18
+
19
+ interface Ctor<T> {
20
+ new (...args: any[]): T;
21
+ }
22
+ declare abstract class UIEvent {
23
+ abstract name: string;
24
+ map(fn: (v: UIEvent) => UIEvent): UIEvent;
25
+ }
26
+ declare class NoOp extends UIEvent {
27
+ name: string;
28
+ path: null;
29
+ }
30
+ declare class Update extends UIEvent {
31
+ url: string;
32
+ values: Record<string, any>;
33
+ name: string;
34
+ path: null;
35
+ constructor(url: string, values: Record<string, any>);
36
+ map(fn: (v: Update) => Update): Update;
37
+ }
38
+ declare class Set extends UIEvent {
39
+ path: Path;
40
+ value: any;
41
+ name: string;
42
+ constructor(path: Path, value: any);
43
+ map(fn: (v: Set) => Set): Set;
44
+ }
45
+ /**
46
+ * Add a value to the end of an array
47
+ */
48
+ declare class Append extends UIEvent {
49
+ path: Path;
50
+ value: any;
51
+ name: string;
52
+ constructor(path: Path, value: any);
53
+ map(fn: (v: Append) => Append): Append;
54
+ }
55
+ /**
56
+ * Push a value to an array at a specific index
57
+ */
58
+ declare class Push extends UIEvent {
59
+ path: Path;
60
+ index: number;
61
+ value: any;
62
+ name: string;
63
+ constructor(path: Path, index: number, value: any);
64
+ map(fn: (v: Push) => Push): Push;
65
+ }
66
+ /**
67
+ * Pull values out of an array, or fields out of an object
68
+ */
69
+ declare class Pull extends UIEvent {
70
+ path: Path;
71
+ index: number | string;
72
+ count?: number | undefined;
73
+ name: string;
74
+ constructor(path: Path, index: number | string, count?: number | undefined);
75
+ map(fn: (v: Pull) => Pull): Pull;
76
+ }
77
+ declare class Merge extends UIEvent {
78
+ path: Path;
79
+ value: {
80
+ [key: string]: any;
81
+ };
82
+ name: string;
83
+ constructor(path: Path, value: {
84
+ [key: string]: any;
85
+ });
86
+ map(fn: (v: Merge) => Merge): Merge;
87
+ }
88
+ declare class Batch extends UIEvent {
89
+ value: AllEvents[];
90
+ name: string;
91
+ path: null;
92
+ constructor(value: AllEvents[]);
93
+ map(fn: (v: AllEvents) => AllEvents): AllEvents;
94
+ }
95
+ declare class Submit extends UIEvent {
96
+ url: string;
97
+ data: any;
98
+ name: string;
99
+ path: null;
100
+ constructor(url: string, data: any);
101
+ map(fn: (v: Submit) => Submit): Submit;
102
+ }
103
+ type AllEvents = NoOp | Set | Append | Merge | Batch | Submit | Update | Push | Pull;
104
+ type Emitter = ReturnType<typeof emitter> & {
105
+ scope: (childScope: Path) => Emitter;
106
+ };
107
+ declare const emitter: (handler: (e: UIEvent) => any) => (e: UIEvent) => void;
108
+ type TwoArity<One, Two, Return> = ((one: One, two: Two) => Return) & ((one: One) => (two: Two) => Return);
109
+ declare const noOp: NoOp;
110
+ /**
111
+ * Object operations
112
+ */
113
+ declare const set: TwoArity<Path, any, Set>;
114
+ declare const merge: TwoArity<Path, any, Merge>;
115
+ declare const drop: TwoArity<Path, string, Pull>;
116
+ /**
117
+ * Array operations
118
+ */
119
+ declare const append: TwoArity<Path, any, Append>;
120
+ declare const push: (path: Path, index: number, value: any) => Push;
121
+ declare const pull: (path: Path, index: number, count: number) => Pull;
122
+ declare const batch: (events: AllEvents[]) => Batch;
123
+ /**
124
+ * API operations
125
+ */
126
+ declare const update: TwoArity<string, Record<string, any>, Update>;
127
+ declare const updateOne: ts_toolbelt_out_Function_Curry.Curry<(a_0: string, a_1: string, a_2: any) => Update>;
128
+ declare const preventDefault: (fn: Function) => (e: {
129
+ preventDefault: (...args: any[]) => any;
130
+ }) => any;
131
+
132
+ type WithEmitter = {
133
+ emit: Emitter;
134
+ };
135
+ declare const init: <AppData extends object>(App: React.FC<AppData & WithEmitter>, node: HTMLElement, data: AppData) => (({
136
+ data: AppData;
137
+ }) & {
138
+ render: () => ReturnType<(children: React.ReactNode) => void>;
139
+ emit: (e: UIEvent) => void;
140
+ update: (fn: (val: AppData) => AppData) => void;
141
+ onEvent: (fn: (event: AllEvents, before: AppData, after: AppData) => void) => (({
142
+ data: AppData;
143
+ }) & {
144
+ render: () => ReturnType<(children: React.ReactNode) => void>;
145
+ emit: (e: UIEvent) => void;
146
+ update: (fn: (val: AppData) => AppData) => void;
147
+ onEvent: /*elided*/ any;
148
+ });
149
+ });
150
+
151
+ type EventCallback<AppState> = (event: AllEvents, before: AppState, after: AppState) => void;
152
+ type ReducerConfig<AppState> = {
153
+ onEvent?: EventCallback<AppState>;
154
+ };
155
+ /**
156
+ * This hook manages the state of the app. It's the only way to change the state, and
157
+ * it's the only way to subscribe to changes. Initializes a read-only root state object, and
158
+ * an emit() function that takes immutable Event objects, which are used to change the state.
159
+ *
160
+ * The changes are then broadcast to any subscribers, and the app is re-rendered.
161
+ *
162
+ * It also takes some flags that are useful for debugging:
163
+ * - `log`: will log all changes to the console.
164
+ * - `expose`: will expose the state to the global window object as `window.appState`, or as
165
+ * the `window[expose]`, if `expose` is a string.
166
+ */
167
+ declare function useAppState<AppState extends object>(initialState: AppState, config?: {
168
+ log?: boolean;
169
+ expose?: string | boolean;
170
+ onEvent?: EventCallback<AppState>;
171
+ }): readonly [AppState, Emitter, {
172
+ onEvent: (fn: EventCallback<AppState>) => number;
173
+ }];
174
+
175
+ export { type AllEvents, Append, type Atom, Batch, type ChangeSet, type ChangeSpec, type Ctor, type Emitter, type EventCallback, Merge, type NestedObject, NoOp, type Path, type PathKey, type PathSegment, Pull, Push, type RecursivePartial, type ReducerConfig, type Scalar, Set, Submit, type TwoArity, UIEvent, type URL, Update, append, batch, drop, emitter, init, merge, noOp, preventDefault, pull, push, set, update, updateOne, useAppState };
package/dist/index.js ADDED
@@ -0,0 +1,374 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ Append: () => Append,
34
+ Batch: () => Batch,
35
+ Merge: () => Merge,
36
+ NoOp: () => NoOp,
37
+ Pull: () => Pull,
38
+ Push: () => Push,
39
+ Set: () => Set,
40
+ Submit: () => Submit,
41
+ UIEvent: () => UIEvent,
42
+ Update: () => Update,
43
+ append: () => append,
44
+ batch: () => batch,
45
+ drop: () => drop,
46
+ emitter: () => emitter,
47
+ init: () => init,
48
+ merge: () => merge,
49
+ noOp: () => noOp,
50
+ preventDefault: () => preventDefault,
51
+ pull: () => pull,
52
+ push: () => push,
53
+ set: () => set,
54
+ update: () => update,
55
+ updateOne: () => updateOne,
56
+ useAppState: () => useAppState
57
+ });
58
+ module.exports = __toCommonJS(index_exports);
59
+ var import_react = require("react");
60
+ var import_ramda3 = require("ramda");
61
+
62
+ // src/events.ts
63
+ var import_ramda = require("ramda");
64
+ var UIEvent = class {
65
+ map(fn) {
66
+ return Object.assign(new this.constructor(), fn(this));
67
+ }
68
+ };
69
+ var NoOp = class extends UIEvent {
70
+ constructor() {
71
+ super(...arguments);
72
+ this.name = "NoOp";
73
+ this.path = null;
74
+ }
75
+ };
76
+ var Update = class _Update extends UIEvent {
77
+ constructor(url, values) {
78
+ super();
79
+ this.url = url;
80
+ this.values = values;
81
+ this.name = "Update";
82
+ this.path = null;
83
+ }
84
+ map(fn) {
85
+ const { url, values } = fn(this);
86
+ return new _Update(url, values);
87
+ }
88
+ };
89
+ var Set = class _Set extends UIEvent {
90
+ constructor(path, value) {
91
+ super();
92
+ this.path = path;
93
+ this.value = value;
94
+ this.name = "Set";
95
+ }
96
+ map(fn) {
97
+ const { path, value } = fn(this);
98
+ return new _Set(path, value);
99
+ }
100
+ };
101
+ var Append = class _Append extends UIEvent {
102
+ constructor(path, value) {
103
+ super();
104
+ this.path = path;
105
+ this.value = value;
106
+ this.name = "Append";
107
+ }
108
+ map(fn) {
109
+ const { path, value } = fn(this);
110
+ return new _Append(path, value);
111
+ }
112
+ };
113
+ var Push = class _Push extends UIEvent {
114
+ constructor(path, index, value) {
115
+ super();
116
+ this.path = path;
117
+ this.index = index;
118
+ this.value = value;
119
+ this.name = "Push";
120
+ }
121
+ map(fn) {
122
+ const { path, index, value } = fn(this);
123
+ return new _Push(path, index, value);
124
+ }
125
+ };
126
+ var Pull = class _Pull extends UIEvent {
127
+ constructor(path, index, count) {
128
+ super();
129
+ this.path = path;
130
+ this.index = index;
131
+ this.count = count;
132
+ this.name = "Pull";
133
+ }
134
+ map(fn) {
135
+ const { path, index, count } = fn(this);
136
+ return new _Pull(path, index, count);
137
+ }
138
+ };
139
+ var Merge = class _Merge extends UIEvent {
140
+ constructor(path, value) {
141
+ super();
142
+ this.path = path;
143
+ this.value = value;
144
+ this.name = "Merge";
145
+ }
146
+ map(fn) {
147
+ const { path, value } = fn(this);
148
+ return new _Merge(path, value);
149
+ }
150
+ };
151
+ var Batch = class _Batch extends UIEvent {
152
+ constructor(value) {
153
+ super();
154
+ this.value = value;
155
+ this.name = "Batch";
156
+ this.path = null;
157
+ }
158
+ map(fn) {
159
+ return new _Batch(this.value.map((0, import_ramda.map)(fn)));
160
+ }
161
+ };
162
+ var Submit = class _Submit extends UIEvent {
163
+ constructor(url, data) {
164
+ super();
165
+ this.url = url;
166
+ this.data = data;
167
+ this.name = "Submit";
168
+ this.path = null;
169
+ }
170
+ map(fn) {
171
+ const { url, data } = fn(this);
172
+ return new _Submit(url, data);
173
+ }
174
+ };
175
+ var emitter = (handler) => {
176
+ let buildScope;
177
+ buildScope = (scope, h) => Object.assign((0, import_ramda.pipe)((e) => e.map((0, import_ramda.evolve)({ path: (0, import_ramda.pipe)((0, import_ramda.defaultTo)([]), (0, import_ramda.concat)(scope)) })), h), {
178
+ scope: (childScope) => buildScope(scope.concat(childScope), h)
179
+ });
180
+ return buildScope([], handler);
181
+ };
182
+ var noOp = new NoOp();
183
+ var set = (0, import_ramda.curryN)(2, (0, import_ramda.construct)(Set));
184
+ var merge = (0, import_ramda.curryN)(2, (0, import_ramda.construct)(Merge));
185
+ var drop = (0, import_ramda.curry)((p, k) => new Pull(p, k));
186
+ var append = (0, import_ramda.curryN)(2, (0, import_ramda.construct)(Append));
187
+ var push = (0, import_ramda.curryN)(3, (0, import_ramda.construct)(Push));
188
+ var pull = (0, import_ramda.curryN)(3, (0, import_ramda.construct)(Pull));
189
+ var batch = (0, import_ramda.curryN)(1, (0, import_ramda.construct)(Batch));
190
+ var update = (0, import_ramda.curryN)(2, (0, import_ramda.construct)(Update));
191
+ var updateOne = (0, import_ramda.curryN)(3, (url, key, value) => new Update(url, { [key]: value }));
192
+ var preventDefault = (fn) => (e) => {
193
+ e.preventDefault();
194
+ return fn(e);
195
+ };
196
+
197
+ // src/init.ts
198
+ var import_ramda2 = require("ramda");
199
+ var React = __toESM(require("react"));
200
+ var import_client = __toESM(require("react-dom/client"));
201
+ var append2 = (0, import_ramda2.flip)(import_ramda2.concat);
202
+ var init = (App, node, data) => {
203
+ const root = import_client.default.createRoot(node);
204
+ let render;
205
+ let emit;
206
+ let app = { data };
207
+ let subscribers = [];
208
+ const update2 = (fn) => {
209
+ try {
210
+ app.data = fn(app.data);
211
+ } catch (e) {
212
+ console.error("Update function exploded", e);
213
+ }
214
+ };
215
+ let inUpdateLoop = false;
216
+ let handler;
217
+ handler = (evt) => {
218
+ const before = app.data;
219
+ if (evt instanceof Set) {
220
+ update2((0, import_ramda2.set)((0, import_ramda2.lensPath)(evt.path), evt.value));
221
+ }
222
+ if (evt instanceof Append) {
223
+ update2((0, import_ramda2.over)((0, import_ramda2.lensPath)(evt.path), append2(evt.value)));
224
+ }
225
+ if (evt instanceof Push) {
226
+ update2((0, import_ramda2.over)((0, import_ramda2.lensPath)(evt.path), (0, import_ramda2.insert)(evt.index, evt.value)));
227
+ }
228
+ if (evt instanceof Pull) {
229
+ update2((0, import_ramda2.over)(
230
+ (0, import_ramda2.lensPath)(evt.path),
231
+ (0, import_ramda2.ifElse)((0, import_ramda2.is)(Array), (0, import_ramda2.remove)(evt.index, evt.count || 1), (0, import_ramda2.dissoc)(evt.index))
232
+ ));
233
+ }
234
+ if (evt instanceof Merge) {
235
+ update2((0, import_ramda2.over)((0, import_ramda2.lensPath)(evt.path), (0, import_ramda2.mergeLeft)(evt.value)));
236
+ }
237
+ if (evt instanceof Update) {
238
+ fetch(evt.url, {
239
+ method: "POST",
240
+ headers: {
241
+ "Content-Type": "application/json"
242
+ },
243
+ body: JSON.stringify(evt.values)
244
+ });
245
+ }
246
+ if (evt instanceof Batch) {
247
+ inUpdateLoop = true;
248
+ evt.value.forEach(handler);
249
+ inUpdateLoop = false;
250
+ }
251
+ if (evt instanceof Submit) {
252
+ }
253
+ subscribers.forEach((s) => s(evt, before, app.data));
254
+ if (!inUpdateLoop) {
255
+ render();
256
+ }
257
+ };
258
+ const strict = false;
259
+ emit = emitter(handler);
260
+ render = () => {
261
+ const props = (0, import_ramda2.mergeRight)(app.data, { emit });
262
+ const el = React.createElement(App, props);
263
+ root.render(strict ? React.createElement(React.StrictMode, {}, el) : el);
264
+ };
265
+ const onEvent = (fn) => {
266
+ subscribers.push(fn);
267
+ return app;
268
+ };
269
+ Object.assign(app, { render, emit, update: update2, onEvent });
270
+ return app;
271
+ };
272
+
273
+ // src/index.ts
274
+ var createReducer = (config) => {
275
+ let reducer2;
276
+ const transformState = (e) => {
277
+ switch (true) {
278
+ case e instanceof NoOp:
279
+ return import_ramda3.identity;
280
+ case e instanceof Set:
281
+ return (0, import_ramda3.set)((0, import_ramda3.lensPath)(e.path), e.value);
282
+ case e instanceof Append:
283
+ return (0, import_ramda3.over)((0, import_ramda3.lensPath)(e.path), (0, import_ramda3.append)(e.value));
284
+ case e instanceof Push:
285
+ return (0, import_ramda3.over)((0, import_ramda3.lensPath)(e.path), (0, import_ramda3.insert)(e.index, e.value));
286
+ case e instanceof Pull:
287
+ return (0, import_ramda3.over)((0, import_ramda3.lensPath)(e.path), (0, import_ramda3.ifElse)((0, import_ramda3.is)(Array), (0, import_ramda3.remove)(e.index, e.count || 1), (0, import_ramda3.dissoc)(e.index)));
288
+ case e instanceof Merge:
289
+ return (0, import_ramda3.over)((0, import_ramda3.lensPath)(e.path), (0, import_ramda3.mergeLeft)(e.value));
290
+ case e instanceof Batch:
291
+ return (state) => e.value.reduce(reducer2, state);
292
+ case e instanceof Update:
293
+ case e instanceof Submit:
294
+ return import_ramda3.identity;
295
+ default:
296
+ console.warn("Unhandled event type", e);
297
+ return import_ramda3.identity;
298
+ }
299
+ };
300
+ reducer2 = (state, e) => {
301
+ const after = transformState(e)(state);
302
+ (config.onEvent || import_ramda3.identity)(e, state, after);
303
+ return after;
304
+ };
305
+ return reducer2;
306
+ };
307
+ var reducer = (config) => createReducer(config);
308
+ var log = (evt, indent = 0) => {
309
+ console.log(" ".repeat(indent), evt.name, (evt.path || []).join("."), evt.value);
310
+ };
311
+ function useAppState(initialState, config = {}) {
312
+ let subscribers = [];
313
+ const [state, dispatch] = (0, import_react.useReducer)(reducer(config), initialState);
314
+ if (config.expose) {
315
+ Object.assign(window, { [(0, import_ramda3.is)(String, config.expose) ? config.expose : "appState"]: state });
316
+ }
317
+ const handleEvent = (0, import_react.useCallback)((event) => {
318
+ if (config.log) {
319
+ if (event.name === "Batch") {
320
+ console.log("Batch");
321
+ event.value.forEach((e) => log(e, 2));
322
+ } else {
323
+ log(event);
324
+ }
325
+ }
326
+ if (event instanceof Update) {
327
+ fetch(event.url, {
328
+ method: "POST",
329
+ headers: { "Content-Type": "application/json" },
330
+ body: JSON.stringify(event.values)
331
+ });
332
+ } else if (event instanceof Submit) {
333
+ fetch(event.url, {
334
+ method: "POST",
335
+ headers: { "Content-Type": "application/json" },
336
+ body: JSON.stringify(event.data)
337
+ });
338
+ } else {
339
+ dispatch(event);
340
+ }
341
+ }, []);
342
+ const controls = {
343
+ onEvent: (fn) => subscribers.push(fn)
344
+ };
345
+ return [state, emitter(handleEvent), controls];
346
+ }
347
+ // Annotate the CommonJS export names for ESM import in node:
348
+ 0 && (module.exports = {
349
+ Append,
350
+ Batch,
351
+ Merge,
352
+ NoOp,
353
+ Pull,
354
+ Push,
355
+ Set,
356
+ Submit,
357
+ UIEvent,
358
+ Update,
359
+ append,
360
+ batch,
361
+ drop,
362
+ emitter,
363
+ init,
364
+ merge,
365
+ noOp,
366
+ preventDefault,
367
+ pull,
368
+ push,
369
+ set,
370
+ update,
371
+ updateOne,
372
+ useAppState
373
+ });
374
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/events.ts","../src/init.ts"],"sourcesContent":["import { useReducer, useCallback } from 'react';\nimport { lensPath, set, over, append, insert, remove, mergeLeft, identity, ifElse, is, dissoc } from 'ramda';\nimport * as Events from './events';\n\nexport * from './events';\nexport * from './types';\nexport { init } from './init';\n\nexport type EventCallback<AppState> = (event: Events.AllEvents, before: AppState, after: AppState) => void;\nexport type ReducerConfig<AppState> = {\n onEvent?: EventCallback<AppState>;\n}\n\n/** Creates a reducer that handles state transitions and event callbacks */\nconst createReducer = <AppState>(config: ReducerConfig<AppState>) => {\n\n let reducer: (state: AppState, e: Events.UIEvent) => AppState;\n\n const transformState = (e: Events.UIEvent): (state: AppState) => AppState => {\n switch (true) {\n case e instanceof Events.NoOp:\n return identity;\n case e instanceof Events.Set:\n return set(lensPath(e.path), e.value);\n case e instanceof Events.Append:\n return over(lensPath(e.path), append(e.value));\n case e instanceof Events.Push:\n return over(lensPath(e.path), insert(e.index, e.value));\n case e instanceof Events.Pull:\n return over(lensPath(e.path), ifElse(is(Array), remove(e.index as number, e.count || 1), dissoc(e.index)) as any);\n case e instanceof Events.Merge:\n return over(lensPath(e.path), mergeLeft(e.value) as any);\n case e instanceof Events.Batch:\n return (state: AppState) => e.value.reduce(reducer, state);\n case e instanceof Events.Update:\n case e instanceof Events.Submit:\n return identity;\n default:\n console.warn('Unhandled event type', e);\n return identity;\n }\n };\n\n reducer = (state, e) => {\n const after = transformState(e)(state);\n (config.onEvent || identity)(e as Events.AllEvents, state, after);\n return after;\n };\n\n return reducer;\n};\n\n// Export the reducer factory\nconst reducer = <AppState>(config: ReducerConfig<AppState>) => createReducer(config);\n\nconst log = (evt: Events.UIEvent, indent = 0) => {\n console.log(' '.repeat(indent), evt.name, ((evt as any).path || []).join('.'), (evt as any).value);\n};\n\n/**\n * This hook manages the state of the app. It's the only way to change the state, and\n * it's the only way to subscribe to changes. Initializes a read-only root state object, and\n * an emit() function that takes immutable Event objects, which are used to change the state.\n *\n * The changes are then broadcast to any subscribers, and the app is re-rendered.\n *\n * It also takes some flags that are useful for debugging:\n * - `log`: will log all changes to the console.\n * - `expose`: will expose the state to the global window object as `window.appState`, or as\n * the `window[expose]`, if `expose` is a string.\n */\nexport function useAppState<AppState extends object>(initialState: AppState, config: {\n log?: boolean;\n expose?: string | boolean;\n onEvent?: EventCallback<AppState>;\n} = {}) {\n let subscribers: Array<(event: Events.AllEvents, before: AppState, after: AppState) => void> = [];\n const [state, dispatch] = useReducer(reducer(config), initialState);\n\n if (config.expose) {\n Object.assign(window, { [is(String, config.expose) ? config.expose : 'appState']: state });\n }\n\n const handleEvent = useCallback((event: Events.UIEvent) => {\n if (config.log) {\n if (event.name === 'Batch') {\n console.log('Batch');\n (event as Events.Batch).value.forEach(e => log(e, 2));\n } else {\n log(event);\n }\n }\n\n if (event instanceof Events.Update) {\n fetch(event.url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(event.values)\n });\n } else if (event instanceof Events.Submit) {\n fetch(event.url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(event.data)\n });\n } else {\n dispatch(event);\n }\n }, []);\n\n const controls = {\n onEvent: (fn: EventCallback<AppState>) => subscribers.push(fn)\n }\n\n return [state as AppState, Events.emitter(handleEvent) as Events.Emitter, controls] as const;\n}\n","import { concat, construct, curry, curryN, defaultTo, evolve, map, pipe } from \"ramda\";\nimport { Path } from \"./types\";\n\nexport interface Ctor<T> {\n new(...args: any[]): T\n}\n\nexport abstract class UIEvent {\n public abstract name: string;\n public map(fn: (v: UIEvent) => UIEvent): UIEvent {\n return Object.assign(new (this.constructor as any)(), fn(this));\n }\n}\n\nexport class NoOp extends UIEvent {\n public name = 'NoOp';\n public path: null = null;\n}\n\nexport class Update extends UIEvent {\n public name = 'Update';\n public path: null = null;\n\n constructor(public url: string, public values: Record<string, any>) { super(); }\n\n public map(fn: (v: Update) => Update): Update {\n const { url, values } = fn(this);\n return new Update(url, values);\n }\n}\n\nexport class Set extends UIEvent {\n public name = 'Set';\n constructor(public path: Path, public value: any) { super(); }\n\n public map(fn: (v: Set) => Set): Set {\n const { path, value } = fn(this);\n return new Set(path, value);\n }\n}\n\n/**\n * Add a value to the end of an array\n */\nexport class Append extends UIEvent {\n public name = 'Append';\n constructor(public path: Path, public value: any) { super(); }\n\n public map(fn: (v: Append) => Append): Append {\n const { path, value } = fn(this);\n return new Append(path, value);\n }\n}\n\n/**\n * Push a value to an array at a specific index\n */\nexport class Push extends UIEvent {\n public name = 'Push';\n constructor(public path: Path, public index: number, public value: any) { super(); }\n\n public map(fn: (v: Push) => Push): Push {\n const { path, index, value } = fn(this);\n return new Push(path, index, value);\n }\n}\n\n/**\n * Pull values out of an array, or fields out of an object\n */\nexport class Pull extends UIEvent {\n public name = 'Pull';\n constructor(public path: Path, public index: number | string, public count?: number) { super(); }\n\n public map(fn: (v: Pull) => Pull): Pull {\n const { path, index, count } = fn(this);\n return new Pull(path, index, count);\n }\n}\n\nexport class Merge extends UIEvent {\n public name = 'Merge';\n constructor(public path: Path, public value: { [key: string]: any }) { super(); }\n\n public map(fn: (v: Merge) => Merge): Merge {\n const { path, value } = fn(this);\n return new Merge(path, value);\n }\n}\n\nexport class Batch extends UIEvent {\n public name = 'Batch';\n public path: null = null;\n\n constructor(public value: AllEvents[]) { super(); }\n\n public map(fn: (v: AllEvents) => AllEvents): AllEvents {\n return new Batch(this.value.map(map(fn) as any));\n }\n}\n\nexport class Submit extends UIEvent {\n public name = 'Submit';\n public path: null = null;\n\n constructor(public url: string, public data: any) { super(); }\n\n public map(fn: (v: Submit) => Submit): Submit {\n const { url, data } = fn(this);\n return new Submit(url, data);\n }\n}\n\nexport type AllEvents = NoOp | Set | Append | Merge | Batch | Submit | Update | Push | Pull;\n\nexport type Emitter = ReturnType<typeof emitter> & { scope: (childScope: Path) => Emitter };\n\nexport const emitter = (handler: (e: UIEvent) => any) => {\n let buildScope: (scope: Path, h: typeof handler) => (e: UIEvent) => void;\n\n buildScope = (scope, h) => (\n Object.assign(pipe((e: UIEvent) => e.map(evolve({ path: pipe(defaultTo([]), concat(scope)) }) as any), h), {\n scope: (childScope: Path) => buildScope(scope.concat(childScope), h)\n })\n );\n\n return buildScope([], handler);\n};\n\nexport type TwoArity<One, Two, Return> = ((one: One, two: Two) => Return) & ((one: One) => (two: Two) => Return);\n\nexport const noOp = new NoOp();\n\n/**\n * Object operations\n */\nexport const set = curryN(2, construct(Set)) as TwoArity<Path, any, Set>;\nexport const merge = curryN(2, construct(Merge)) as TwoArity<Path, any, Merge>;\nexport const drop = curry((p: Path, k: string) => new Pull(p, k)) as TwoArity<Path, string, Pull>;\n\n/**\n * Array operations\n */\nexport const append = curryN(2, construct(Append)) as TwoArity<Path, any, Append>;\nexport const push = curryN(3, construct(Push)) as (path: Path, index: number, value: any) => Push;\nexport const pull = curryN(3, construct(Pull)) as (path: Path, index: number, count: number) => Pull;\n\nexport const batch = curryN(1, construct(Batch)) as (events: AllEvents[]) => Batch;\n\n/**\n * API operations\n */\nexport const update = curryN(2, construct(Update)) as TwoArity<string, Record<string, any>, Update>;\nexport const updateOne = curryN(3, (url: string, key: string, value: any) => new Update(url, { [key]: value }));\n\nexport const preventDefault = (fn: Function) => (e: { preventDefault: (...args: any[]) => any }) => {\n e.preventDefault();\n return fn(e);\n};\n","import { concat, dissoc, flip, ifElse, insert, is, lensPath, mergeLeft, mergeRight, over, remove, set } from 'ramda';\nimport * as React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport * as Events from './events';\n\ntype WithEmitter = { emit: Events.Emitter };\n\nconst append = flip(concat);\n\nexport const init = <AppData extends object>(\n App: React.FC<AppData & WithEmitter>,\n node: HTMLElement,\n data: AppData,\n) => {\n const root = ReactDOM.createRoot(node);\n let render: () => ReturnType<typeof root.render>;\n let emit: ReturnType<typeof Events.emitter>;\n let app = { data };\n let subscribers: any[] = [];\n\n const update = (fn: (val: AppData) => AppData) => {\n try {\n app.data = fn(app.data)\n } catch (e) {\n console.error('Update function exploded', e);\n }\n };\n let inUpdateLoop = false;\n\n let handler: (evt: Events.UIEvent) => void;\n handler = (evt: Events.UIEvent) => {\n const before = app.data;\n\n if (evt instanceof Events.Set) {\n update(set(lensPath(evt.path), evt.value));\n }\n if (evt instanceof Events.Append) {\n update(over(lensPath(evt.path), append(evt.value)));\n }\n if (evt instanceof Events.Push) {\n update(over(lensPath(evt.path), insert(evt.index, evt.value)));\n }\n if (evt instanceof Events.Pull) {\n update(over(\n lensPath(evt.path),\n ifElse(is(Array), remove(evt.index as number, evt.count || 1), dissoc(evt.index)) as any\n ));\n }\n if (evt instanceof Events.Merge) {\n update(over(lensPath(evt.path), mergeLeft(evt.value) as any));\n }\n if (evt instanceof Events.Update) {\n fetch(evt.url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(evt.values)\n });\n }\n if (evt instanceof Events.Batch) {\n inUpdateLoop = true;\n evt.value.forEach(handler)\n inUpdateLoop = false;\n }\n if (evt instanceof Events.Submit) {\n // @TODO Redirect on location header\n }\n subscribers.forEach(s => s(evt, before, app.data));\n\n if (!inUpdateLoop) {\n render();\n }\n };\n const strict = false;\n emit = Events.emitter(handler);\n\n render = () => {\n const props = mergeRight(app.data, { emit }) as unknown as AppData & WithEmitter;\n const el = React.createElement(App, props);\n root.render(strict ? React.createElement(React.StrictMode, {}, el) : el)\n };\n\n const onEvent = (fn: (event: Events.AllEvents, before: AppData, after: AppData) => void) => {\n subscribers.push(fn);\n return app as ((typeof app) & { render: typeof render, emit: typeof emit, update: typeof update, onEvent: typeof onEvent });\n };\n\n Object.assign(app, { render, emit, update, onEvent });\n return app as ((typeof app) & { render: typeof render, emit: typeof emit, update: typeof update, onEvent: typeof onEvent });\n};\n"],"mappings":"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","names":["import_ramda","import_ramda","append","ReactDOM","update","reducer"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,316 @@
1
+ // src/index.ts
2
+ import { useReducer, useCallback } from "react";
3
+ import { lensPath as lensPath2, set as set3, over as over2, append as append3, insert as insert2, remove as remove2, mergeLeft as mergeLeft2, identity, ifElse as ifElse2, is as is2, dissoc as dissoc2 } from "ramda";
4
+
5
+ // src/events.ts
6
+ import { concat, construct, curry, curryN, defaultTo, evolve, map, pipe } from "ramda";
7
+ var UIEvent = class {
8
+ map(fn) {
9
+ return Object.assign(new this.constructor(), fn(this));
10
+ }
11
+ };
12
+ var NoOp = class extends UIEvent {
13
+ constructor() {
14
+ super(...arguments);
15
+ this.name = "NoOp";
16
+ this.path = null;
17
+ }
18
+ };
19
+ var Update = class _Update extends UIEvent {
20
+ constructor(url, values) {
21
+ super();
22
+ this.url = url;
23
+ this.values = values;
24
+ this.name = "Update";
25
+ this.path = null;
26
+ }
27
+ map(fn) {
28
+ const { url, values } = fn(this);
29
+ return new _Update(url, values);
30
+ }
31
+ };
32
+ var Set = class _Set extends UIEvent {
33
+ constructor(path, value) {
34
+ super();
35
+ this.path = path;
36
+ this.value = value;
37
+ this.name = "Set";
38
+ }
39
+ map(fn) {
40
+ const { path, value } = fn(this);
41
+ return new _Set(path, value);
42
+ }
43
+ };
44
+ var Append = class _Append extends UIEvent {
45
+ constructor(path, value) {
46
+ super();
47
+ this.path = path;
48
+ this.value = value;
49
+ this.name = "Append";
50
+ }
51
+ map(fn) {
52
+ const { path, value } = fn(this);
53
+ return new _Append(path, value);
54
+ }
55
+ };
56
+ var Push = class _Push extends UIEvent {
57
+ constructor(path, index, value) {
58
+ super();
59
+ this.path = path;
60
+ this.index = index;
61
+ this.value = value;
62
+ this.name = "Push";
63
+ }
64
+ map(fn) {
65
+ const { path, index, value } = fn(this);
66
+ return new _Push(path, index, value);
67
+ }
68
+ };
69
+ var Pull = class _Pull extends UIEvent {
70
+ constructor(path, index, count) {
71
+ super();
72
+ this.path = path;
73
+ this.index = index;
74
+ this.count = count;
75
+ this.name = "Pull";
76
+ }
77
+ map(fn) {
78
+ const { path, index, count } = fn(this);
79
+ return new _Pull(path, index, count);
80
+ }
81
+ };
82
+ var Merge = class _Merge extends UIEvent {
83
+ constructor(path, value) {
84
+ super();
85
+ this.path = path;
86
+ this.value = value;
87
+ this.name = "Merge";
88
+ }
89
+ map(fn) {
90
+ const { path, value } = fn(this);
91
+ return new _Merge(path, value);
92
+ }
93
+ };
94
+ var Batch = class _Batch extends UIEvent {
95
+ constructor(value) {
96
+ super();
97
+ this.value = value;
98
+ this.name = "Batch";
99
+ this.path = null;
100
+ }
101
+ map(fn) {
102
+ return new _Batch(this.value.map(map(fn)));
103
+ }
104
+ };
105
+ var Submit = class _Submit extends UIEvent {
106
+ constructor(url, data) {
107
+ super();
108
+ this.url = url;
109
+ this.data = data;
110
+ this.name = "Submit";
111
+ this.path = null;
112
+ }
113
+ map(fn) {
114
+ const { url, data } = fn(this);
115
+ return new _Submit(url, data);
116
+ }
117
+ };
118
+ var emitter = (handler) => {
119
+ let buildScope;
120
+ buildScope = (scope, h) => Object.assign(pipe((e) => e.map(evolve({ path: pipe(defaultTo([]), concat(scope)) })), h), {
121
+ scope: (childScope) => buildScope(scope.concat(childScope), h)
122
+ });
123
+ return buildScope([], handler);
124
+ };
125
+ var noOp = new NoOp();
126
+ var set = curryN(2, construct(Set));
127
+ var merge = curryN(2, construct(Merge));
128
+ var drop = curry((p, k) => new Pull(p, k));
129
+ var append = curryN(2, construct(Append));
130
+ var push = curryN(3, construct(Push));
131
+ var pull = curryN(3, construct(Pull));
132
+ var batch = curryN(1, construct(Batch));
133
+ var update = curryN(2, construct(Update));
134
+ var updateOne = curryN(3, (url, key, value) => new Update(url, { [key]: value }));
135
+ var preventDefault = (fn) => (e) => {
136
+ e.preventDefault();
137
+ return fn(e);
138
+ };
139
+
140
+ // src/init.ts
141
+ import { concat as concat2, dissoc, flip, ifElse, insert, is, lensPath, mergeLeft, mergeRight, over, remove, set as set2 } from "ramda";
142
+ import * as React from "react";
143
+ import ReactDOM from "react-dom/client";
144
+ var append2 = flip(concat2);
145
+ var init = (App, node, data) => {
146
+ const root = ReactDOM.createRoot(node);
147
+ let render;
148
+ let emit;
149
+ let app = { data };
150
+ let subscribers = [];
151
+ const update2 = (fn) => {
152
+ try {
153
+ app.data = fn(app.data);
154
+ } catch (e) {
155
+ console.error("Update function exploded", e);
156
+ }
157
+ };
158
+ let inUpdateLoop = false;
159
+ let handler;
160
+ handler = (evt) => {
161
+ const before = app.data;
162
+ if (evt instanceof Set) {
163
+ update2(set2(lensPath(evt.path), evt.value));
164
+ }
165
+ if (evt instanceof Append) {
166
+ update2(over(lensPath(evt.path), append2(evt.value)));
167
+ }
168
+ if (evt instanceof Push) {
169
+ update2(over(lensPath(evt.path), insert(evt.index, evt.value)));
170
+ }
171
+ if (evt instanceof Pull) {
172
+ update2(over(
173
+ lensPath(evt.path),
174
+ ifElse(is(Array), remove(evt.index, evt.count || 1), dissoc(evt.index))
175
+ ));
176
+ }
177
+ if (evt instanceof Merge) {
178
+ update2(over(lensPath(evt.path), mergeLeft(evt.value)));
179
+ }
180
+ if (evt instanceof Update) {
181
+ fetch(evt.url, {
182
+ method: "POST",
183
+ headers: {
184
+ "Content-Type": "application/json"
185
+ },
186
+ body: JSON.stringify(evt.values)
187
+ });
188
+ }
189
+ if (evt instanceof Batch) {
190
+ inUpdateLoop = true;
191
+ evt.value.forEach(handler);
192
+ inUpdateLoop = false;
193
+ }
194
+ if (evt instanceof Submit) {
195
+ }
196
+ subscribers.forEach((s) => s(evt, before, app.data));
197
+ if (!inUpdateLoop) {
198
+ render();
199
+ }
200
+ };
201
+ const strict = false;
202
+ emit = emitter(handler);
203
+ render = () => {
204
+ const props = mergeRight(app.data, { emit });
205
+ const el = React.createElement(App, props);
206
+ root.render(strict ? React.createElement(React.StrictMode, {}, el) : el);
207
+ };
208
+ const onEvent = (fn) => {
209
+ subscribers.push(fn);
210
+ return app;
211
+ };
212
+ Object.assign(app, { render, emit, update: update2, onEvent });
213
+ return app;
214
+ };
215
+
216
+ // src/index.ts
217
+ var createReducer = (config) => {
218
+ let reducer2;
219
+ const transformState = (e) => {
220
+ switch (true) {
221
+ case e instanceof NoOp:
222
+ return identity;
223
+ case e instanceof Set:
224
+ return set3(lensPath2(e.path), e.value);
225
+ case e instanceof Append:
226
+ return over2(lensPath2(e.path), append3(e.value));
227
+ case e instanceof Push:
228
+ return over2(lensPath2(e.path), insert2(e.index, e.value));
229
+ case e instanceof Pull:
230
+ return over2(lensPath2(e.path), ifElse2(is2(Array), remove2(e.index, e.count || 1), dissoc2(e.index)));
231
+ case e instanceof Merge:
232
+ return over2(lensPath2(e.path), mergeLeft2(e.value));
233
+ case e instanceof Batch:
234
+ return (state) => e.value.reduce(reducer2, state);
235
+ case e instanceof Update:
236
+ case e instanceof Submit:
237
+ return identity;
238
+ default:
239
+ console.warn("Unhandled event type", e);
240
+ return identity;
241
+ }
242
+ };
243
+ reducer2 = (state, e) => {
244
+ const after = transformState(e)(state);
245
+ (config.onEvent || identity)(e, state, after);
246
+ return after;
247
+ };
248
+ return reducer2;
249
+ };
250
+ var reducer = (config) => createReducer(config);
251
+ var log = (evt, indent = 0) => {
252
+ console.log(" ".repeat(indent), evt.name, (evt.path || []).join("."), evt.value);
253
+ };
254
+ function useAppState(initialState, config = {}) {
255
+ let subscribers = [];
256
+ const [state, dispatch] = useReducer(reducer(config), initialState);
257
+ if (config.expose) {
258
+ Object.assign(window, { [is2(String, config.expose) ? config.expose : "appState"]: state });
259
+ }
260
+ const handleEvent = useCallback((event) => {
261
+ if (config.log) {
262
+ if (event.name === "Batch") {
263
+ console.log("Batch");
264
+ event.value.forEach((e) => log(e, 2));
265
+ } else {
266
+ log(event);
267
+ }
268
+ }
269
+ if (event instanceof Update) {
270
+ fetch(event.url, {
271
+ method: "POST",
272
+ headers: { "Content-Type": "application/json" },
273
+ body: JSON.stringify(event.values)
274
+ });
275
+ } else if (event instanceof Submit) {
276
+ fetch(event.url, {
277
+ method: "POST",
278
+ headers: { "Content-Type": "application/json" },
279
+ body: JSON.stringify(event.data)
280
+ });
281
+ } else {
282
+ dispatch(event);
283
+ }
284
+ }, []);
285
+ const controls = {
286
+ onEvent: (fn) => subscribers.push(fn)
287
+ };
288
+ return [state, emitter(handleEvent), controls];
289
+ }
290
+ export {
291
+ Append,
292
+ Batch,
293
+ Merge,
294
+ NoOp,
295
+ Pull,
296
+ Push,
297
+ Set,
298
+ Submit,
299
+ UIEvent,
300
+ Update,
301
+ append,
302
+ batch,
303
+ drop,
304
+ emitter,
305
+ init,
306
+ merge,
307
+ noOp,
308
+ preventDefault,
309
+ pull,
310
+ push,
311
+ set,
312
+ update,
313
+ updateOne,
314
+ useAppState
315
+ };
316
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/events.ts","../src/init.ts"],"sourcesContent":["import { useReducer, useCallback } from 'react';\nimport { lensPath, set, over, append, insert, remove, mergeLeft, identity, ifElse, is, dissoc } from 'ramda';\nimport * as Events from './events';\n\nexport * from './events';\nexport * from './types';\nexport { init } from './init';\n\nexport type EventCallback<AppState> = (event: Events.AllEvents, before: AppState, after: AppState) => void;\nexport type ReducerConfig<AppState> = {\n onEvent?: EventCallback<AppState>;\n}\n\n/** Creates a reducer that handles state transitions and event callbacks */\nconst createReducer = <AppState>(config: ReducerConfig<AppState>) => {\n\n let reducer: (state: AppState, e: Events.UIEvent) => AppState;\n\n const transformState = (e: Events.UIEvent): (state: AppState) => AppState => {\n switch (true) {\n case e instanceof Events.NoOp:\n return identity;\n case e instanceof Events.Set:\n return set(lensPath(e.path), e.value);\n case e instanceof Events.Append:\n return over(lensPath(e.path), append(e.value));\n case e instanceof Events.Push:\n return over(lensPath(e.path), insert(e.index, e.value));\n case e instanceof Events.Pull:\n return over(lensPath(e.path), ifElse(is(Array), remove(e.index as number, e.count || 1), dissoc(e.index)) as any);\n case e instanceof Events.Merge:\n return over(lensPath(e.path), mergeLeft(e.value) as any);\n case e instanceof Events.Batch:\n return (state: AppState) => e.value.reduce(reducer, state);\n case e instanceof Events.Update:\n case e instanceof Events.Submit:\n return identity;\n default:\n console.warn('Unhandled event type', e);\n return identity;\n }\n };\n\n reducer = (state, e) => {\n const after = transformState(e)(state);\n (config.onEvent || identity)(e as Events.AllEvents, state, after);\n return after;\n };\n\n return reducer;\n};\n\n// Export the reducer factory\nconst reducer = <AppState>(config: ReducerConfig<AppState>) => createReducer(config);\n\nconst log = (evt: Events.UIEvent, indent = 0) => {\n console.log(' '.repeat(indent), evt.name, ((evt as any).path || []).join('.'), (evt as any).value);\n};\n\n/**\n * This hook manages the state of the app. It's the only way to change the state, and\n * it's the only way to subscribe to changes. Initializes a read-only root state object, and\n * an emit() function that takes immutable Event objects, which are used to change the state.\n *\n * The changes are then broadcast to any subscribers, and the app is re-rendered.\n *\n * It also takes some flags that are useful for debugging:\n * - `log`: will log all changes to the console.\n * - `expose`: will expose the state to the global window object as `window.appState`, or as\n * the `window[expose]`, if `expose` is a string.\n */\nexport function useAppState<AppState extends object>(initialState: AppState, config: {\n log?: boolean;\n expose?: string | boolean;\n onEvent?: EventCallback<AppState>;\n} = {}) {\n let subscribers: Array<(event: Events.AllEvents, before: AppState, after: AppState) => void> = [];\n const [state, dispatch] = useReducer(reducer(config), initialState);\n\n if (config.expose) {\n Object.assign(window, { [is(String, config.expose) ? config.expose : 'appState']: state });\n }\n\n const handleEvent = useCallback((event: Events.UIEvent) => {\n if (config.log) {\n if (event.name === 'Batch') {\n console.log('Batch');\n (event as Events.Batch).value.forEach(e => log(e, 2));\n } else {\n log(event);\n }\n }\n\n if (event instanceof Events.Update) {\n fetch(event.url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(event.values)\n });\n } else if (event instanceof Events.Submit) {\n fetch(event.url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(event.data)\n });\n } else {\n dispatch(event);\n }\n }, []);\n\n const controls = {\n onEvent: (fn: EventCallback<AppState>) => subscribers.push(fn)\n }\n\n return [state as AppState, Events.emitter(handleEvent) as Events.Emitter, controls] as const;\n}\n","import { concat, construct, curry, curryN, defaultTo, evolve, map, pipe } from \"ramda\";\nimport { Path } from \"./types\";\n\nexport interface Ctor<T> {\n new(...args: any[]): T\n}\n\nexport abstract class UIEvent {\n public abstract name: string;\n public map(fn: (v: UIEvent) => UIEvent): UIEvent {\n return Object.assign(new (this.constructor as any)(), fn(this));\n }\n}\n\nexport class NoOp extends UIEvent {\n public name = 'NoOp';\n public path: null = null;\n}\n\nexport class Update extends UIEvent {\n public name = 'Update';\n public path: null = null;\n\n constructor(public url: string, public values: Record<string, any>) { super(); }\n\n public map(fn: (v: Update) => Update): Update {\n const { url, values } = fn(this);\n return new Update(url, values);\n }\n}\n\nexport class Set extends UIEvent {\n public name = 'Set';\n constructor(public path: Path, public value: any) { super(); }\n\n public map(fn: (v: Set) => Set): Set {\n const { path, value } = fn(this);\n return new Set(path, value);\n }\n}\n\n/**\n * Add a value to the end of an array\n */\nexport class Append extends UIEvent {\n public name = 'Append';\n constructor(public path: Path, public value: any) { super(); }\n\n public map(fn: (v: Append) => Append): Append {\n const { path, value } = fn(this);\n return new Append(path, value);\n }\n}\n\n/**\n * Push a value to an array at a specific index\n */\nexport class Push extends UIEvent {\n public name = 'Push';\n constructor(public path: Path, public index: number, public value: any) { super(); }\n\n public map(fn: (v: Push) => Push): Push {\n const { path, index, value } = fn(this);\n return new Push(path, index, value);\n }\n}\n\n/**\n * Pull values out of an array, or fields out of an object\n */\nexport class Pull extends UIEvent {\n public name = 'Pull';\n constructor(public path: Path, public index: number | string, public count?: number) { super(); }\n\n public map(fn: (v: Pull) => Pull): Pull {\n const { path, index, count } = fn(this);\n return new Pull(path, index, count);\n }\n}\n\nexport class Merge extends UIEvent {\n public name = 'Merge';\n constructor(public path: Path, public value: { [key: string]: any }) { super(); }\n\n public map(fn: (v: Merge) => Merge): Merge {\n const { path, value } = fn(this);\n return new Merge(path, value);\n }\n}\n\nexport class Batch extends UIEvent {\n public name = 'Batch';\n public path: null = null;\n\n constructor(public value: AllEvents[]) { super(); }\n\n public map(fn: (v: AllEvents) => AllEvents): AllEvents {\n return new Batch(this.value.map(map(fn) as any));\n }\n}\n\nexport class Submit extends UIEvent {\n public name = 'Submit';\n public path: null = null;\n\n constructor(public url: string, public data: any) { super(); }\n\n public map(fn: (v: Submit) => Submit): Submit {\n const { url, data } = fn(this);\n return new Submit(url, data);\n }\n}\n\nexport type AllEvents = NoOp | Set | Append | Merge | Batch | Submit | Update | Push | Pull;\n\nexport type Emitter = ReturnType<typeof emitter> & { scope: (childScope: Path) => Emitter };\n\nexport const emitter = (handler: (e: UIEvent) => any) => {\n let buildScope: (scope: Path, h: typeof handler) => (e: UIEvent) => void;\n\n buildScope = (scope, h) => (\n Object.assign(pipe((e: UIEvent) => e.map(evolve({ path: pipe(defaultTo([]), concat(scope)) }) as any), h), {\n scope: (childScope: Path) => buildScope(scope.concat(childScope), h)\n })\n );\n\n return buildScope([], handler);\n};\n\nexport type TwoArity<One, Two, Return> = ((one: One, two: Two) => Return) & ((one: One) => (two: Two) => Return);\n\nexport const noOp = new NoOp();\n\n/**\n * Object operations\n */\nexport const set = curryN(2, construct(Set)) as TwoArity<Path, any, Set>;\nexport const merge = curryN(2, construct(Merge)) as TwoArity<Path, any, Merge>;\nexport const drop = curry((p: Path, k: string) => new Pull(p, k)) as TwoArity<Path, string, Pull>;\n\n/**\n * Array operations\n */\nexport const append = curryN(2, construct(Append)) as TwoArity<Path, any, Append>;\nexport const push = curryN(3, construct(Push)) as (path: Path, index: number, value: any) => Push;\nexport const pull = curryN(3, construct(Pull)) as (path: Path, index: number, count: number) => Pull;\n\nexport const batch = curryN(1, construct(Batch)) as (events: AllEvents[]) => Batch;\n\n/**\n * API operations\n */\nexport const update = curryN(2, construct(Update)) as TwoArity<string, Record<string, any>, Update>;\nexport const updateOne = curryN(3, (url: string, key: string, value: any) => new Update(url, { [key]: value }));\n\nexport const preventDefault = (fn: Function) => (e: { preventDefault: (...args: any[]) => any }) => {\n e.preventDefault();\n return fn(e);\n};\n","import { concat, dissoc, flip, ifElse, insert, is, lensPath, mergeLeft, mergeRight, over, remove, set } from 'ramda';\nimport * as React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport * as Events from './events';\n\ntype WithEmitter = { emit: Events.Emitter };\n\nconst append = flip(concat);\n\nexport const init = <AppData extends object>(\n App: React.FC<AppData & WithEmitter>,\n node: HTMLElement,\n data: AppData,\n) => {\n const root = ReactDOM.createRoot(node);\n let render: () => ReturnType<typeof root.render>;\n let emit: ReturnType<typeof Events.emitter>;\n let app = { data };\n let subscribers: any[] = [];\n\n const update = (fn: (val: AppData) => AppData) => {\n try {\n app.data = fn(app.data)\n } catch (e) {\n console.error('Update function exploded', e);\n }\n };\n let inUpdateLoop = false;\n\n let handler: (evt: Events.UIEvent) => void;\n handler = (evt: Events.UIEvent) => {\n const before = app.data;\n\n if (evt instanceof Events.Set) {\n update(set(lensPath(evt.path), evt.value));\n }\n if (evt instanceof Events.Append) {\n update(over(lensPath(evt.path), append(evt.value)));\n }\n if (evt instanceof Events.Push) {\n update(over(lensPath(evt.path), insert(evt.index, evt.value)));\n }\n if (evt instanceof Events.Pull) {\n update(over(\n lensPath(evt.path),\n ifElse(is(Array), remove(evt.index as number, evt.count || 1), dissoc(evt.index)) as any\n ));\n }\n if (evt instanceof Events.Merge) {\n update(over(lensPath(evt.path), mergeLeft(evt.value) as any));\n }\n if (evt instanceof Events.Update) {\n fetch(evt.url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(evt.values)\n });\n }\n if (evt instanceof Events.Batch) {\n inUpdateLoop = true;\n evt.value.forEach(handler)\n inUpdateLoop = false;\n }\n if (evt instanceof Events.Submit) {\n // @TODO Redirect on location header\n }\n subscribers.forEach(s => s(evt, before, app.data));\n\n if (!inUpdateLoop) {\n render();\n }\n };\n const strict = false;\n emit = Events.emitter(handler);\n\n render = () => {\n const props = mergeRight(app.data, { emit }) as unknown as AppData & WithEmitter;\n const el = React.createElement(App, props);\n root.render(strict ? React.createElement(React.StrictMode, {}, el) : el)\n };\n\n const onEvent = (fn: (event: Events.AllEvents, before: AppData, after: AppData) => void) => {\n subscribers.push(fn);\n return app as ((typeof app) & { render: typeof render, emit: typeof emit, update: typeof update, onEvent: typeof onEvent });\n };\n\n Object.assign(app, { render, emit, update, onEvent });\n return app as ((typeof app) & { render: typeof render, emit: typeof emit, update: typeof update, onEvent: typeof onEvent });\n};\n"],"mappings":"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","names":["lensPath","set","over","append","insert","remove","mergeLeft","ifElse","is","dissoc","concat","set","append","concat","update","set","reducer","set","lensPath","over","append","insert","ifElse","is","remove","dissoc","mergeLeft"]}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@nateabele/use-app-state",
3
+ "version": "0.1.0",
4
+ "description": "A React state management library using events and immutable data operations",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup",
20
+ "dev": "tsup --watch",
21
+ "prepublishOnly": "npm run build"
22
+ },
23
+ "keywords": [
24
+ "react",
25
+ "state",
26
+ "hooks",
27
+ "state-management",
28
+ "immutable"
29
+ ],
30
+ "author": "Nate Abele <nate.abele@gmail.com>",
31
+ "license": "MIT",
32
+ "peerDependencies": {
33
+ "react": ">=17.0.0",
34
+ "react-dom": ">=17.0.0"
35
+ },
36
+ "dependencies": {
37
+ "ramda": "^0.30.1"
38
+ },
39
+ "devDependencies": {
40
+ "@types/ramda": "^0.30.2",
41
+ "@types/react": "^18.3.18",
42
+ "@types/react-dom": "^18.3.5",
43
+ "react": "^18.3.1",
44
+ "react-dom": "^18.3.1",
45
+ "tsup": "^8.3.6",
46
+ "typescript": "^5.7.3"
47
+ }
48
+ }