@nateabele/use-app-state 0.1.0 → 0.2.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.
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # @nateabele/use-app-state
2
+
3
+ A React state management library using immutable event-based updates powered by Ramda.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @nateabele/use-app-state
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Basic Hook Usage
14
+
15
+ ```tsx
16
+ import { useAppState, Events } from '@nateabele/use-app-state';
17
+
18
+ function App() {
19
+ const [state, emit] = useAppState({
20
+ user: { name: '', email: '' },
21
+ items: []
22
+ });
23
+
24
+ return (
25
+ <div>
26
+ <input
27
+ value={state.user.name}
28
+ onChange={e => emit(Events.set(['user', 'name'], e.target.value))}
29
+ />
30
+ <button onClick={() => emit(Events.append(['items'], { id: Date.now() }))}>
31
+ Add Item
32
+ </button>
33
+ </div>
34
+ );
35
+ }
36
+ ```
37
+
38
+ ### Scoped Emitters
39
+
40
+ Emitters can be scoped to a path, making nested components cleaner:
41
+
42
+ ```tsx
43
+ function UserForm({ emit }) {
44
+ const scopedEmit = emit.scope(['user']);
45
+
46
+ return (
47
+ <input onChange={e => scopedEmit(Events.set(['name'], e.target.value))} />
48
+ );
49
+ }
50
+ ```
51
+
52
+ ### Configuration Options
53
+
54
+ ```tsx
55
+ const [state, emit, controls] = useAppState(initialState, {
56
+ log: true, // Log all events to console
57
+ expose: 'appState', // Expose state to window.appState for debugging
58
+ onEvent: (event, before, after) => {
59
+ // Subscribe to all state changes
60
+ }
61
+ });
62
+ ```
63
+
64
+ ## Events
65
+
66
+ | Event | Description | Example |
67
+ |-------|-------------|---------|
68
+ | `Events.set(path, value)` | Set a value at path | `Events.set(['user', 'name'], 'John')` |
69
+ | `Events.merge(path, obj)` | Merge object at path | `Events.merge(['user'], { name: 'John' })` |
70
+ | `Events.append(path, value)` | Append to array | `Events.append(['items'], newItem)` |
71
+ | `Events.push(path, index, value)` | Insert at index | `Events.push(['items'], 0, newItem)` |
72
+ | `Events.pull(path, index, count?)` | Remove from array/object | `Events.pull(['items'], 2, 1)` |
73
+ | `Events.drop(path, key)` | Remove key from object | `Events.drop(['user'], 'tempField')` |
74
+ | `Events.batch(events[])` | Batch multiple events | `Events.batch([...])` |
75
+ | `Events.noOp` | No operation | `Events.noOp` |
76
+
77
+ ## Alternative: Non-Hook Initialization
78
+
79
+ For apps that need direct control over rendering:
80
+
81
+ ```tsx
82
+ import { init } from '@nateabele/use-app-state';
83
+
84
+ const app = init(App, document.getElementById('root'), initialData);
85
+
86
+ app.onEvent((event, before, after) => {
87
+ console.log('State changed:', event.name);
88
+ });
89
+
90
+ app.render();
91
+ ```
92
+
93
+ ## License
94
+
95
+ MIT
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Result of a validation operation
3
+ */
4
+ type ValidationResult<T> = {
5
+ success: true;
6
+ data: T;
7
+ } | {
8
+ success: false;
9
+ errors: ValidationError[];
10
+ };
11
+ /**
12
+ * Describes a single validation error
13
+ */
14
+ interface ValidationError {
15
+ path: (string | number)[];
16
+ message: string;
17
+ code?: string;
18
+ }
19
+ /**
20
+ * A schema adapter with the schema bound at creation time.
21
+ * Created via zodSchema() or typeboxSchema().
22
+ */
23
+ interface SchemaAdapter<T> {
24
+ validate(data: unknown): ValidationResult<T>;
25
+ parse(data: unknown): T;
26
+ isValid(data: unknown): boolean;
27
+ }
28
+
29
+ /**
30
+ * Validation mode determines when and how validation errors are handled
31
+ */
32
+ type ValidationMode = 'strict' | 'warn' | 'development' | 'off';
33
+
34
+ export type { SchemaAdapter as S, ValidationMode as V, ValidationError as a, ValidationResult as b };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Result of a validation operation
3
+ */
4
+ type ValidationResult<T> = {
5
+ success: true;
6
+ data: T;
7
+ } | {
8
+ success: false;
9
+ errors: ValidationError[];
10
+ };
11
+ /**
12
+ * Describes a single validation error
13
+ */
14
+ interface ValidationError {
15
+ path: (string | number)[];
16
+ message: string;
17
+ code?: string;
18
+ }
19
+ /**
20
+ * A schema adapter with the schema bound at creation time.
21
+ * Created via zodSchema() or typeboxSchema().
22
+ */
23
+ interface SchemaAdapter<T> {
24
+ validate(data: unknown): ValidationResult<T>;
25
+ parse(data: unknown): T;
26
+ isValid(data: unknown): boolean;
27
+ }
28
+
29
+ /**
30
+ * Validation mode determines when and how validation errors are handled
31
+ */
32
+ type ValidationMode = 'strict' | 'warn' | 'development' | 'off';
33
+
34
+ export type { SchemaAdapter as S, ValidationMode as V, ValidationError as a, ValidationResult as b };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as ts_toolbelt_out_Function_Curry from 'ts-toolbelt/out/Function/Curry';
2
+ import { S as SchemaAdapter, V as ValidationMode, a as ValidationError } from './config-B90La2nK.mjs';
2
3
  import * as React from 'react';
3
4
 
4
5
  type PathKey = string;
@@ -129,10 +130,55 @@ declare const preventDefault: (fn: Function) => (e: {
129
130
  preventDefault: (...args: any[]) => any;
130
131
  }) => any;
131
132
 
133
+ type Events_AllEvents = AllEvents;
134
+ type Events_Append = Append;
135
+ declare const Events_Append: typeof Append;
136
+ type Events_Batch = Batch;
137
+ declare const Events_Batch: typeof Batch;
138
+ type Events_Ctor<T> = Ctor<T>;
139
+ type Events_Emitter = Emitter;
140
+ type Events_Merge = Merge;
141
+ declare const Events_Merge: typeof Merge;
142
+ type Events_NoOp = NoOp;
143
+ declare const Events_NoOp: typeof NoOp;
144
+ type Events_Pull = Pull;
145
+ declare const Events_Pull: typeof Pull;
146
+ type Events_Push = Push;
147
+ declare const Events_Push: typeof Push;
148
+ type Events_Set = Set;
149
+ declare const Events_Set: typeof Set;
150
+ type Events_Submit = Submit;
151
+ declare const Events_Submit: typeof Submit;
152
+ type Events_TwoArity<One, Two, Return> = TwoArity<One, Two, Return>;
153
+ type Events_UIEvent = UIEvent;
154
+ declare const Events_UIEvent: typeof UIEvent;
155
+ type Events_Update = Update;
156
+ declare const Events_Update: typeof Update;
157
+ declare const Events_append: typeof append;
158
+ declare const Events_batch: typeof batch;
159
+ declare const Events_drop: typeof drop;
160
+ declare const Events_emitter: typeof emitter;
161
+ declare const Events_merge: typeof merge;
162
+ declare const Events_noOp: typeof noOp;
163
+ declare const Events_preventDefault: typeof preventDefault;
164
+ declare const Events_pull: typeof pull;
165
+ declare const Events_push: typeof push;
166
+ declare const Events_set: typeof set;
167
+ declare const Events_update: typeof update;
168
+ declare const Events_updateOne: typeof updateOne;
169
+ declare namespace Events {
170
+ export { type Events_AllEvents as AllEvents, Events_Append as Append, Events_Batch as Batch, type Events_Ctor as Ctor, type Events_Emitter as Emitter, Events_Merge as Merge, Events_NoOp as NoOp, Events_Pull as Pull, Events_Push as Push, Events_Set as Set, Events_Submit as Submit, type Events_TwoArity as TwoArity, Events_UIEvent as UIEvent, Events_Update as Update, Events_append as append, Events_batch as batch, Events_drop as drop, Events_emitter as emitter, Events_merge as merge, Events_noOp as noOp, Events_preventDefault as preventDefault, Events_pull as pull, Events_push as push, Events_set as set, Events_update as update, Events_updateOne as updateOne };
171
+ }
172
+
132
173
  type WithEmitter = {
133
174
  emit: Emitter;
134
175
  };
135
- declare const init: <AppData extends object>(App: React.FC<AppData & WithEmitter>, node: HTMLElement, data: AppData) => (({
176
+ type InitConfig<AppData> = {
177
+ schema?: SchemaAdapter<AppData>;
178
+ mode?: ValidationMode;
179
+ onValidationError?: (errors: ValidationError[], event: unknown, state: AppData) => void;
180
+ };
181
+ declare const init: <AppData extends object>(App: React.FC<AppData & WithEmitter>, node: HTMLElement, data: AppData, config?: InitConfig<AppData>) => (({
136
182
  data: AppData;
137
183
  }) & {
138
184
  render: () => ReturnType<(children: React.ReactNode) => void>;
@@ -151,6 +197,9 @@ declare const init: <AppData extends object>(App: React.FC<AppData & WithEmitter
151
197
  type EventCallback<AppState> = (event: AllEvents, before: AppState, after: AppState) => void;
152
198
  type ReducerConfig<AppState> = {
153
199
  onEvent?: EventCallback<AppState>;
200
+ schema?: SchemaAdapter<AppState>;
201
+ mode?: ValidationMode;
202
+ onValidationError?: (errors: ValidationError[], event: unknown, state: AppState) => void;
154
203
  };
155
204
  /**
156
205
  * This hook manages the state of the app. It's the only way to change the state, and
@@ -168,8 +217,11 @@ declare function useAppState<AppState extends object>(initialState: AppState, co
168
217
  log?: boolean;
169
218
  expose?: string | boolean;
170
219
  onEvent?: EventCallback<AppState>;
220
+ schema?: SchemaAdapter<AppState>;
221
+ mode?: ValidationMode;
222
+ onValidationError?: (errors: ValidationError[], event: unknown, state: AppState) => void;
171
223
  }): readonly [AppState, Emitter, {
172
224
  onEvent: (fn: EventCallback<AppState>) => number;
173
225
  }];
174
226
 
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 };
227
+ export { type Atom, type ChangeSet, type ChangeSpec, type EventCallback, Events, type NestedObject, type Path, type PathKey, type PathSegment, type RecursivePartial, type ReducerConfig, type Scalar, type URL, init, useAppState };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as ts_toolbelt_out_Function_Curry from 'ts-toolbelt/out/Function/Curry';
2
+ import { S as SchemaAdapter, V as ValidationMode, a as ValidationError } from './config-B90La2nK.js';
2
3
  import * as React from 'react';
3
4
 
4
5
  type PathKey = string;
@@ -129,10 +130,55 @@ declare const preventDefault: (fn: Function) => (e: {
129
130
  preventDefault: (...args: any[]) => any;
130
131
  }) => any;
131
132
 
133
+ type Events_AllEvents = AllEvents;
134
+ type Events_Append = Append;
135
+ declare const Events_Append: typeof Append;
136
+ type Events_Batch = Batch;
137
+ declare const Events_Batch: typeof Batch;
138
+ type Events_Ctor<T> = Ctor<T>;
139
+ type Events_Emitter = Emitter;
140
+ type Events_Merge = Merge;
141
+ declare const Events_Merge: typeof Merge;
142
+ type Events_NoOp = NoOp;
143
+ declare const Events_NoOp: typeof NoOp;
144
+ type Events_Pull = Pull;
145
+ declare const Events_Pull: typeof Pull;
146
+ type Events_Push = Push;
147
+ declare const Events_Push: typeof Push;
148
+ type Events_Set = Set;
149
+ declare const Events_Set: typeof Set;
150
+ type Events_Submit = Submit;
151
+ declare const Events_Submit: typeof Submit;
152
+ type Events_TwoArity<One, Two, Return> = TwoArity<One, Two, Return>;
153
+ type Events_UIEvent = UIEvent;
154
+ declare const Events_UIEvent: typeof UIEvent;
155
+ type Events_Update = Update;
156
+ declare const Events_Update: typeof Update;
157
+ declare const Events_append: typeof append;
158
+ declare const Events_batch: typeof batch;
159
+ declare const Events_drop: typeof drop;
160
+ declare const Events_emitter: typeof emitter;
161
+ declare const Events_merge: typeof merge;
162
+ declare const Events_noOp: typeof noOp;
163
+ declare const Events_preventDefault: typeof preventDefault;
164
+ declare const Events_pull: typeof pull;
165
+ declare const Events_push: typeof push;
166
+ declare const Events_set: typeof set;
167
+ declare const Events_update: typeof update;
168
+ declare const Events_updateOne: typeof updateOne;
169
+ declare namespace Events {
170
+ export { type Events_AllEvents as AllEvents, Events_Append as Append, Events_Batch as Batch, type Events_Ctor as Ctor, type Events_Emitter as Emitter, Events_Merge as Merge, Events_NoOp as NoOp, Events_Pull as Pull, Events_Push as Push, Events_Set as Set, Events_Submit as Submit, type Events_TwoArity as TwoArity, Events_UIEvent as UIEvent, Events_Update as Update, Events_append as append, Events_batch as batch, Events_drop as drop, Events_emitter as emitter, Events_merge as merge, Events_noOp as noOp, Events_preventDefault as preventDefault, Events_pull as pull, Events_push as push, Events_set as set, Events_update as update, Events_updateOne as updateOne };
171
+ }
172
+
132
173
  type WithEmitter = {
133
174
  emit: Emitter;
134
175
  };
135
- declare const init: <AppData extends object>(App: React.FC<AppData & WithEmitter>, node: HTMLElement, data: AppData) => (({
176
+ type InitConfig<AppData> = {
177
+ schema?: SchemaAdapter<AppData>;
178
+ mode?: ValidationMode;
179
+ onValidationError?: (errors: ValidationError[], event: unknown, state: AppData) => void;
180
+ };
181
+ declare const init: <AppData extends object>(App: React.FC<AppData & WithEmitter>, node: HTMLElement, data: AppData, config?: InitConfig<AppData>) => (({
136
182
  data: AppData;
137
183
  }) & {
138
184
  render: () => ReturnType<(children: React.ReactNode) => void>;
@@ -151,6 +197,9 @@ declare const init: <AppData extends object>(App: React.FC<AppData & WithEmitter
151
197
  type EventCallback<AppState> = (event: AllEvents, before: AppState, after: AppState) => void;
152
198
  type ReducerConfig<AppState> = {
153
199
  onEvent?: EventCallback<AppState>;
200
+ schema?: SchemaAdapter<AppState>;
201
+ mode?: ValidationMode;
202
+ onValidationError?: (errors: ValidationError[], event: unknown, state: AppState) => void;
154
203
  };
155
204
  /**
156
205
  * This hook manages the state of the app. It's the only way to change the state, and
@@ -168,8 +217,11 @@ declare function useAppState<AppState extends object>(initialState: AppState, co
168
217
  log?: boolean;
169
218
  expose?: string | boolean;
170
219
  onEvent?: EventCallback<AppState>;
220
+ schema?: SchemaAdapter<AppState>;
221
+ mode?: ValidationMode;
222
+ onValidationError?: (errors: ValidationError[], event: unknown, state: AppState) => void;
171
223
  }): readonly [AppState, Emitter, {
172
224
  onEvent: (fn: EventCallback<AppState>) => number;
173
225
  }];
174
226
 
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 };
227
+ export { type Atom, type ChangeSet, type ChangeSpec, type EventCallback, Events, type NestedObject, type Path, type PathKey, type PathSegment, type RecursivePartial, type ReducerConfig, type Scalar, type URL, init, useAppState };
package/dist/index.js CHANGED
@@ -30,6 +30,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ Events: () => events_exports,
34
+ init: () => init,
35
+ useAppState: () => useAppState
36
+ });
37
+ module.exports = __toCommonJS(index_exports);
38
+ var import_react = require("react");
39
+ var import_ramda3 = require("ramda");
40
+
41
+ // src/events.ts
42
+ var events_exports = {};
43
+ __export(events_exports, {
33
44
  Append: () => Append,
34
45
  Batch: () => Batch,
35
46
  Merge: () => Merge,
@@ -44,7 +55,6 @@ __export(index_exports, {
44
55
  batch: () => batch,
45
56
  drop: () => drop,
46
57
  emitter: () => emitter,
47
- init: () => init,
48
58
  merge: () => merge,
49
59
  noOp: () => noOp,
50
60
  preventDefault: () => preventDefault,
@@ -52,14 +62,8 @@ __export(index_exports, {
52
62
  push: () => push,
53
63
  set: () => set,
54
64
  update: () => update,
55
- updateOne: () => updateOne,
56
- useAppState: () => useAppState
65
+ updateOne: () => updateOne
57
66
  });
58
- module.exports = __toCommonJS(index_exports);
59
- var import_react = require("react");
60
- var import_ramda3 = require("ramda");
61
-
62
- // src/events.ts
63
67
  var import_ramda = require("ramda");
64
68
  var UIEvent = class {
65
69
  map(fn) {
@@ -194,12 +198,43 @@ var preventDefault = (fn) => (e) => {
194
198
  return fn(e);
195
199
  };
196
200
 
201
+ // src/validation/errors.ts
202
+ var StateValidationError = class extends Error {
203
+ constructor(errors, event) {
204
+ const message = errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ");
205
+ super(`State validation failed: ${message}`);
206
+ this.name = "StateValidationError";
207
+ this.errors = errors;
208
+ this.event = event;
209
+ }
210
+ };
211
+
212
+ // src/validation/validate.ts
213
+ var shouldValidate = (mode) => {
214
+ if (mode === "off") return false;
215
+ if (mode === "development") {
216
+ return typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
217
+ }
218
+ return true;
219
+ };
220
+ function runValidation(state, schema, mode = "strict", onValidationError, event = null) {
221
+ if (!schema || !shouldValidate(mode)) return;
222
+ const result = schema.validate(state);
223
+ if (result.success) return;
224
+ onValidationError?.(result.errors, event, state);
225
+ if (mode === "strict") {
226
+ throw new StateValidationError(result.errors, event);
227
+ }
228
+ console.warn("[use-app-state] Validation failed:", result.errors);
229
+ }
230
+
197
231
  // src/init.ts
198
232
  var import_ramda2 = require("ramda");
199
233
  var React = __toESM(require("react"));
200
234
  var import_client = __toESM(require("react-dom/client"));
201
235
  var append2 = (0, import_ramda2.flip)(import_ramda2.concat);
202
- var init = (App, node, data) => {
236
+ var init = (App, node, data, config = {}) => {
237
+ runValidation(data, config.schema, config.mode, config.onValidationError);
203
238
  const root = import_client.default.createRoot(node);
204
239
  let render;
205
240
  let emit;
@@ -250,6 +285,7 @@ var init = (App, node, data) => {
250
285
  }
251
286
  if (evt instanceof Submit) {
252
287
  }
288
+ runValidation(app.data, config.schema, config.mode, config.onValidationError, evt);
253
289
  subscribers.forEach((s) => s(evt, before, app.data));
254
290
  if (!inUpdateLoop) {
255
291
  render();
@@ -299,6 +335,7 @@ var createReducer = (config) => {
299
335
  };
300
336
  reducer2 = (state, e) => {
301
337
  const after = transformState(e)(state);
338
+ runValidation(after, config.schema, config.mode, config.onValidationError, e);
302
339
  (config.onEvent || import_ramda3.identity)(e, state, after);
303
340
  return after;
304
341
  };
@@ -309,6 +346,7 @@ var log = (evt, indent = 0) => {
309
346
  console.log(" ".repeat(indent), evt.name, (evt.path || []).join("."), evt.value);
310
347
  };
311
348
  function useAppState(initialState, config = {}) {
349
+ runValidation(initialState, config.schema, config.mode, config.onValidationError);
312
350
  let subscribers = [];
313
351
  const [state, dispatch] = (0, import_react.useReducer)(reducer(config), initialState);
314
352
  if (config.expose) {
@@ -346,29 +384,8 @@ function useAppState(initialState, config = {}) {
346
384
  }
347
385
  // Annotate the CommonJS export names for ESM import in node:
348
386
  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,
387
+ Events,
363
388
  init,
364
- merge,
365
- noOp,
366
- preventDefault,
367
- pull,
368
- push,
369
- set,
370
- update,
371
- updateOne,
372
389
  useAppState
373
390
  });
374
391
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +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"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/events.ts","../src/validation/errors.ts","../src/validation/validate.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';\nimport { runValidation } from './validation/validate';\nimport type { SchemaAdapter, ValidationError } from './validation/adapter';\nimport type { ValidationMode } from './validation/config';\n\nexport * as Events 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 schema?: SchemaAdapter<AppState>;\n mode?: ValidationMode;\n onValidationError?: (errors: ValidationError[], event: unknown, state: AppState) => void;\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 runValidation(after, config.schema, config.mode, config.onValidationError, e);\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 schema?: SchemaAdapter<AppState>;\n mode?: ValidationMode;\n onValidationError?: (errors: ValidationError[], event: unknown, state: AppState) => void;\n} = {}) {\n // Validate initial state\n runValidation(initialState, config.schema, config.mode, config.onValidationError);\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 type { ValidationError } from './adapter';\n\n/**\n * Error thrown when state validation fails in strict mode\n */\nexport class StateValidationError extends Error {\n public readonly errors: ValidationError[];\n public readonly event: unknown;\n\n constructor(errors: ValidationError[], event: unknown) {\n const message = errors\n .map((e) => `${e.path.join('.')}: ${e.message}`)\n .join('; ');\n super(`State validation failed: ${message}`);\n this.name = 'StateValidationError';\n this.errors = errors;\n this.event = event;\n }\n}\n","import type { SchemaAdapter, ValidationError } from './adapter';\nimport { StateValidationError } from './errors';\nimport type { ValidationMode } from './config';\n\ndeclare const process: { env: { NODE_ENV?: string } } | undefined;\n\n/**\n * Determines if validation should run based on mode\n */\nconst shouldValidate = (mode: ValidationMode): boolean => {\n if (mode === 'off') return false;\n if (mode === 'development') {\n return typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production';\n }\n return true; // 'strict' or 'warn'\n};\n\n/**\n * Shared validation runner used by both useAppState and init()\n */\nexport function runValidation<T>(\n state: T,\n schema: SchemaAdapter<T> | undefined,\n mode: ValidationMode = 'strict',\n onValidationError?: (errors: ValidationError[], event: unknown, state: T) => void,\n event: unknown = null,\n): void {\n if (!schema || !shouldValidate(mode)) return;\n\n const result = schema.validate(state);\n if (result.success) return;\n\n onValidationError?.(result.errors, event, state);\n\n if (mode === 'strict') {\n throw new StateValidationError(result.errors, event);\n }\n console.warn('[use-app-state] Validation failed:', result.errors);\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';\nimport { runValidation } from './validation/validate';\nimport type { SchemaAdapter, ValidationError } from './validation/adapter';\nimport type { ValidationMode } from './validation/config';\n\ntype WithEmitter = { emit: Events.Emitter };\n\ntype InitConfig<AppData> = {\n schema?: SchemaAdapter<AppData>;\n mode?: ValidationMode;\n onValidationError?: (errors: ValidationError[], event: unknown, state: AppData) => void;\n};\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 config: InitConfig<AppData> = {},\n) => {\n // Validate initial state\n runValidation(data, config.schema, config.mode, config.onValidationError);\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\n // Validate state after update\n runValidation(app.data, config.schema, config.mode, config.onValidationError, evt);\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAwC;AACxC,IAAAA,gBAAqG;;;ACDrG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA+E;AAOxE,IAAe,UAAf,MAAuB;AAAA,EAErB,IAAI,IAAsC;AAC/C,WAAO,OAAO,OAAO,IAAK,KAAK,YAAoB,GAAG,GAAG,IAAI,CAAC;AAAA,EAChE;AACF;AAEO,IAAM,OAAN,cAAmB,QAAQ;AAAA,EAA3B;AAAA;AACL,SAAO,OAAO;AACd,SAAO,OAAa;AAAA;AACtB;AAEO,IAAM,SAAN,MAAM,gBAAe,QAAQ;AAAA,EAIlC,YAAmB,KAAoB,QAA6B;AAAE,UAAM;AAAzD;AAAoB;AAHvC,SAAO,OAAO;AACd,SAAO,OAAa;AAAA,EAE2D;AAAA,EAExE,IAAI,IAAmC;AAC5C,UAAM,EAAE,KAAK,OAAO,IAAI,GAAG,IAAI;AAC/B,WAAO,IAAI,QAAO,KAAK,MAAM;AAAA,EAC/B;AACF;AAEO,IAAM,MAAN,MAAM,aAAY,QAAQ;AAAA,EAE/B,YAAmB,MAAmB,OAAY;AAAE,UAAM;AAAvC;AAAmB;AADtC,SAAO,OAAO;AAAA,EAC+C;AAAA,EAEtD,IAAI,IAA0B;AACnC,UAAM,EAAE,MAAM,MAAM,IAAI,GAAG,IAAI;AAC/B,WAAO,IAAI,KAAI,MAAM,KAAK;AAAA,EAC5B;AACF;AAKO,IAAM,SAAN,MAAM,gBAAe,QAAQ;AAAA,EAElC,YAAmB,MAAmB,OAAY;AAAE,UAAM;AAAvC;AAAmB;AADtC,SAAO,OAAO;AAAA,EAC+C;AAAA,EAEtD,IAAI,IAAmC;AAC5C,UAAM,EAAE,MAAM,MAAM,IAAI,GAAG,IAAI;AAC/B,WAAO,IAAI,QAAO,MAAM,KAAK;AAAA,EAC/B;AACF;AAKO,IAAM,OAAN,MAAM,cAAa,QAAQ;AAAA,EAEhC,YAAmB,MAAmB,OAAsB,OAAY;AAAE,UAAM;AAA7D;AAAmB;AAAsB;AAD5D,SAAO,OAAO;AAAA,EACqE;AAAA,EAE5E,IAAI,IAA6B;AACtC,UAAM,EAAE,MAAM,OAAO,MAAM,IAAI,GAAG,IAAI;AACtC,WAAO,IAAI,MAAK,MAAM,OAAO,KAAK;AAAA,EACpC;AACF;AAKO,IAAM,OAAN,MAAM,cAAa,QAAQ;AAAA,EAEhC,YAAmB,MAAmB,OAA+B,OAAgB;AAAE,UAAM;AAA1E;AAAmB;AAA+B;AADrE,SAAO,OAAO;AAAA,EACkF;AAAA,EAEzF,IAAI,IAA6B;AACtC,UAAM,EAAE,MAAM,OAAO,MAAM,IAAI,GAAG,IAAI;AACtC,WAAO,IAAI,MAAK,MAAM,OAAO,KAAK;AAAA,EACpC;AACF;AAEO,IAAM,QAAN,MAAM,eAAc,QAAQ;AAAA,EAEjC,YAAmB,MAAmB,OAA+B;AAAE,UAAM;AAA1D;AAAmB;AADtC,SAAO,OAAO;AAAA,EACkE;AAAA,EAEzE,IAAI,IAAgC;AACzC,UAAM,EAAE,MAAM,MAAM,IAAI,GAAG,IAAI;AAC/B,WAAO,IAAI,OAAM,MAAM,KAAK;AAAA,EAC9B;AACF;AAEO,IAAM,QAAN,MAAM,eAAc,QAAQ;AAAA,EAIjC,YAAmB,OAAoB;AAAE,UAAM;AAA5B;AAHnB,SAAO,OAAO;AACd,SAAO,OAAa;AAAA,EAE8B;AAAA,EAE3C,IAAI,IAA4C;AACrD,WAAO,IAAI,OAAM,KAAK,MAAM,QAAI,kBAAI,EAAE,CAAQ,CAAC;AAAA,EACjD;AACF;AAEO,IAAM,SAAN,MAAM,gBAAe,QAAQ;AAAA,EAIlC,YAAmB,KAAoB,MAAW;AAAE,UAAM;AAAvC;AAAoB;AAHvC,SAAO,OAAO;AACd,SAAO,OAAa;AAAA,EAEyC;AAAA,EAEtD,IAAI,IAAmC;AAC5C,UAAM,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI;AAC7B,WAAO,IAAI,QAAO,KAAK,IAAI;AAAA,EAC7B;AACF;AAMO,IAAM,UAAU,CAAC,YAAiC;AACvD,MAAI;AAEJ,eAAa,CAAC,OAAO,MACnB,OAAO,WAAO,mBAAK,CAAC,MAAe,EAAE,QAAI,qBAAO,EAAE,UAAM,uBAAK,wBAAU,CAAC,CAAC,OAAG,qBAAO,KAAK,CAAC,EAAE,CAAC,CAAQ,GAAG,CAAC,GAAG;AAAA,IACzG,OAAO,CAAC,eAAqB,WAAW,MAAM,OAAO,UAAU,GAAG,CAAC;AAAA,EACrE,CAAC;AAGH,SAAO,WAAW,CAAC,GAAG,OAAO;AAC/B;AAIO,IAAM,OAAO,IAAI,KAAK;AAKtB,IAAM,UAAM,qBAAO,OAAG,wBAAU,GAAG,CAAC;AACpC,IAAM,YAAQ,qBAAO,OAAG,wBAAU,KAAK,CAAC;AACxC,IAAM,WAAO,oBAAM,CAAC,GAAS,MAAc,IAAI,KAAK,GAAG,CAAC,CAAC;AAKzD,IAAM,aAAS,qBAAO,OAAG,wBAAU,MAAM,CAAC;AAC1C,IAAM,WAAO,qBAAO,OAAG,wBAAU,IAAI,CAAC;AACtC,IAAM,WAAO,qBAAO,OAAG,wBAAU,IAAI,CAAC;AAEtC,IAAM,YAAQ,qBAAO,OAAG,wBAAU,KAAK,CAAC;AAKxC,IAAM,aAAS,qBAAO,OAAG,wBAAU,MAAM,CAAC;AAC1C,IAAM,gBAAY,qBAAO,GAAG,CAAC,KAAa,KAAa,UAAe,IAAI,OAAO,KAAK,EAAE,CAAC,GAAG,GAAG,MAAM,CAAC,CAAC;AAEvG,IAAM,iBAAiB,CAAC,OAAiB,CAAC,MAAmD;AAClG,IAAE,eAAe;AACjB,SAAO,GAAG,CAAC;AACb;;;ACzJO,IAAM,uBAAN,cAAmC,MAAM;AAAA,EAI9C,YAAY,QAA2B,OAAgB;AACrD,UAAM,UAAU,OACb,IAAI,CAAC,MAAM,GAAG,EAAE,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAC9C,KAAK,IAAI;AACZ,UAAM,4BAA4B,OAAO,EAAE;AAC3C,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,QAAQ;AAAA,EACf;AACF;;;ACTA,IAAM,iBAAiB,CAAC,SAAkC;AACxD,MAAI,SAAS,MAAO,QAAO;AAC3B,MAAI,SAAS,eAAe;AAC1B,WAAO,OAAO,YAAY,eAAe,QAAQ,KAAK,aAAa;AAAA,EACrE;AACA,SAAO;AACT;AAKO,SAAS,cACd,OACA,QACA,OAAuB,UACvB,mBACA,QAAiB,MACX;AACN,MAAI,CAAC,UAAU,CAAC,eAAe,IAAI,EAAG;AAEtC,QAAM,SAAS,OAAO,SAAS,KAAK;AACpC,MAAI,OAAO,QAAS;AAEpB,sBAAoB,OAAO,QAAQ,OAAO,KAAK;AAE/C,MAAI,SAAS,UAAU;AACrB,UAAM,IAAI,qBAAqB,OAAO,QAAQ,KAAK;AAAA,EACrD;AACA,UAAQ,KAAK,sCAAsC,OAAO,MAAM;AAClE;;;ACtCA,IAAAC,gBAA6G;AAC7G,YAAuB;AACvB,oBAAqB;AAcrB,IAAMC,cAAS,oBAAK,oBAAM;AAEnB,IAAM,OAAO,CAClB,KACA,MACA,MACA,SAA8B,CAAC,MAC5B;AAEH,gBAAc,MAAM,OAAO,QAAQ,OAAO,MAAM,OAAO,iBAAiB;AAExE,QAAM,OAAO,cAAAC,QAAS,WAAW,IAAI;AACrC,MAAI;AACJ,MAAI;AACJ,MAAI,MAAM,EAAE,KAAK;AACjB,MAAI,cAAqB,CAAC;AAE1B,QAAMC,UAAS,CAAC,OAAkC;AAChD,QAAI;AACF,UAAI,OAAO,GAAG,IAAI,IAAI;AAAA,IACxB,SAAS,GAAG;AACV,cAAQ,MAAM,4BAA4B,CAAC;AAAA,IAC7C;AAAA,EACF;AACA,MAAI,eAAe;AAEnB,MAAI;AACJ,YAAU,CAAC,QAAwB;AACjC,UAAM,SAAS,IAAI;AAEnB,QAAI,eAAsB,KAAK;AAC7B,MAAAA,YAAO,uBAAI,wBAAS,IAAI,IAAI,GAAG,IAAI,KAAK,CAAC;AAAA,IAC3C;AACA,QAAI,eAAsB,QAAQ;AAChC,MAAAA,YAAO,wBAAK,wBAAS,IAAI,IAAI,GAAGF,QAAO,IAAI,KAAK,CAAC,CAAC;AAAA,IACpD;AACA,QAAI,eAAsB,MAAM;AAC9B,MAAAE,YAAO,wBAAK,wBAAS,IAAI,IAAI,OAAG,sBAAO,IAAI,OAAO,IAAI,KAAK,CAAC,CAAC;AAAA,IAC/D;AACA,QAAI,eAAsB,MAAM;AAC9B,MAAAA,YAAO;AAAA,YACL,wBAAS,IAAI,IAAI;AAAA,YACjB,0BAAO,kBAAG,KAAK,OAAG,sBAAO,IAAI,OAAiB,IAAI,SAAS,CAAC,OAAG,sBAAO,IAAI,KAAK,CAAC;AAAA,MAClF,CAAC;AAAA,IACH;AACA,QAAI,eAAsB,OAAO;AAC/B,MAAAA,YAAO,wBAAK,wBAAS,IAAI,IAAI,OAAG,yBAAU,IAAI,KAAK,CAAQ,CAAC;AAAA,IAC9D;AACA,QAAI,eAAsB,QAAQ;AAChC,YAAM,IAAI,KAAK;AAAA,QACb,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,KAAK,UAAU,IAAI,MAAM;AAAA,MACjC,CAAC;AAAA,IACH;AACA,QAAI,eAAsB,OAAO;AAC/B,qBAAe;AACf,UAAI,MAAM,QAAQ,OAAO;AACzB,qBAAe;AAAA,IACjB;AACA,QAAI,eAAsB,QAAQ;AAAA,IAElC;AAGA,kBAAc,IAAI,MAAM,OAAO,QAAQ,OAAO,MAAM,OAAO,mBAAmB,GAAG;AAEjF,gBAAY,QAAQ,OAAK,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC;AAEjD,QAAI,CAAC,cAAc;AACjB,aAAO;AAAA,IACT;AAAA,EACF;AACA,QAAM,SAAS;AACf,SAAc,QAAQ,OAAO;AAE7B,WAAS,MAAM;AACb,UAAM,YAAQ,0BAAW,IAAI,MAAM,EAAE,KAAK,CAAC;AAC3C,UAAM,KAAW,oBAAc,KAAK,KAAK;AACzC,SAAK,OAAO,SAAe,oBAAoB,kBAAY,CAAC,GAAG,EAAE,IAAI,EAAE;AAAA,EACzE;AAEA,QAAM,UAAU,CAAC,OAA2E;AAC1F,gBAAY,KAAK,EAAE;AACnB,WAAO;AAAA,EACT;AAEA,SAAO,OAAO,KAAK,EAAE,QAAQ,MAAM,QAAAA,SAAQ,QAAQ,CAAC;AACpD,SAAO;AACT;;;AJvFA,IAAM,gBAAgB,CAAW,WAAoC;AAEnE,MAAIC;AAEJ,QAAM,iBAAiB,CAAC,MAAqD;AAC3E,YAAQ,MAAM;AAAA,MACZ,KAAK,aAAoB;AACvB,eAAO;AAAA,MACT,KAAK,aAAoB;AACvB,mBAAO,uBAAI,wBAAS,EAAE,IAAI,GAAG,EAAE,KAAK;AAAA,MACtC,KAAK,aAAoB;AACvB,mBAAO,wBAAK,wBAAS,EAAE,IAAI,OAAG,sBAAO,EAAE,KAAK,CAAC;AAAA,MAC/C,KAAK,aAAoB;AACvB,mBAAO,wBAAK,wBAAS,EAAE,IAAI,OAAG,sBAAO,EAAE,OAAO,EAAE,KAAK,CAAC;AAAA,MACxD,KAAK,aAAoB;AACvB,mBAAO,wBAAK,wBAAS,EAAE,IAAI,OAAG,0BAAO,kBAAG,KAAK,OAAG,sBAAO,EAAE,OAAiB,EAAE,SAAS,CAAC,OAAG,sBAAO,EAAE,KAAK,CAAC,CAAQ;AAAA,MAClH,KAAK,aAAoB;AACvB,mBAAO,wBAAK,wBAAS,EAAE,IAAI,OAAG,yBAAU,EAAE,KAAK,CAAQ;AAAA,MACzD,KAAK,aAAoB;AACvB,eAAO,CAAC,UAAoB,EAAE,MAAM,OAAOA,UAAS,KAAK;AAAA,MAC3D,KAAK,aAAoB;AAAA,MACzB,KAAK,aAAoB;AACvB,eAAO;AAAA,MACT;AACE,gBAAQ,KAAK,wBAAwB,CAAC;AACtC,eAAO;AAAA,IACX;AAAA,EACF;AAEA,EAAAA,WAAU,CAAC,OAAO,MAAM;AACtB,UAAM,QAAQ,eAAe,CAAC,EAAE,KAAK;AACrC,kBAAc,OAAO,OAAO,QAAQ,OAAO,MAAM,OAAO,mBAAmB,CAAC;AAC5E,KAAC,OAAO,WAAW,wBAAU,GAAuB,OAAO,KAAK;AAChE,WAAO;AAAA,EACT;AAEA,SAAOA;AACT;AAGA,IAAM,UAAU,CAAW,WAAoC,cAAc,MAAM;AAEnF,IAAM,MAAM,CAAC,KAAqB,SAAS,MAAM;AAC/C,UAAQ,IAAI,IAAI,OAAO,MAAM,GAAG,IAAI,OAAQ,IAAY,QAAQ,CAAC,GAAG,KAAK,GAAG,GAAI,IAAY,KAAK;AACnG;AAcO,SAAS,YAAqC,cAAwB,SAOzE,CAAC,GAAG;AAEN,gBAAc,cAAc,OAAO,QAAQ,OAAO,MAAM,OAAO,iBAAiB;AAEhF,MAAI,cAA2F,CAAC;AAChG,QAAM,CAAC,OAAO,QAAQ,QAAI,yBAAW,QAAQ,MAAM,GAAG,YAAY;AAElE,MAAI,OAAO,QAAQ;AACjB,WAAO,OAAO,QAAQ,EAAE,KAAC,kBAAG,QAAQ,OAAO,MAAM,IAAI,OAAO,SAAS,UAAU,GAAG,MAAM,CAAC;AAAA,EAC3F;AAEA,QAAM,kBAAc,0BAAY,CAAC,UAA0B;AACzD,QAAI,OAAO,KAAK;AACd,UAAI,MAAM,SAAS,SAAS;AAC1B,gBAAQ,IAAI,OAAO;AACnB,QAAC,MAAuB,MAAM,QAAQ,OAAK,IAAI,GAAG,CAAC,CAAC;AAAA,MACtD,OAAO;AACL,YAAI,KAAK;AAAA,MACX;AAAA,IACF;AAEA,QAAI,iBAAwB,QAAQ;AAClC,YAAM,MAAM,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,MAAM,MAAM;AAAA,MACnC,CAAC;AAAA,IACH,WAAW,iBAAwB,QAAQ;AACzC,YAAM,MAAM,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,MAAM,IAAI;AAAA,MACjC,CAAC;AAAA,IACH,OAAO;AACL,eAAS,KAAK;AAAA,IAChB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,WAAW;AAAA,IACf,SAAS,CAAC,OAAgC,YAAY,KAAK,EAAE;AAAA,EAC/D;AAEA,SAAO,CAAC,OAA0B,QAAQ,WAAW,GAAqB,QAAQ;AACpF;","names":["import_ramda","import_ramda","append","ReactDOM","update","reducer"]}
package/dist/index.mjs CHANGED
@@ -1,8 +1,39 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
1
7
  // src/index.ts
2
8
  import { useReducer, useCallback } from "react";
3
9
  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
10
 
5
11
  // src/events.ts
12
+ var events_exports = {};
13
+ __export(events_exports, {
14
+ Append: () => Append,
15
+ Batch: () => Batch,
16
+ Merge: () => Merge,
17
+ NoOp: () => NoOp,
18
+ Pull: () => Pull,
19
+ Push: () => Push,
20
+ Set: () => Set,
21
+ Submit: () => Submit,
22
+ UIEvent: () => UIEvent,
23
+ Update: () => Update,
24
+ append: () => append,
25
+ batch: () => batch,
26
+ drop: () => drop,
27
+ emitter: () => emitter,
28
+ merge: () => merge,
29
+ noOp: () => noOp,
30
+ preventDefault: () => preventDefault,
31
+ pull: () => pull,
32
+ push: () => push,
33
+ set: () => set,
34
+ update: () => update,
35
+ updateOne: () => updateOne
36
+ });
6
37
  import { concat, construct, curry, curryN, defaultTo, evolve, map, pipe } from "ramda";
7
38
  var UIEvent = class {
8
39
  map(fn) {
@@ -137,12 +168,43 @@ var preventDefault = (fn) => (e) => {
137
168
  return fn(e);
138
169
  };
139
170
 
171
+ // src/validation/errors.ts
172
+ var StateValidationError = class extends Error {
173
+ constructor(errors, event) {
174
+ const message = errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ");
175
+ super(`State validation failed: ${message}`);
176
+ this.name = "StateValidationError";
177
+ this.errors = errors;
178
+ this.event = event;
179
+ }
180
+ };
181
+
182
+ // src/validation/validate.ts
183
+ var shouldValidate = (mode) => {
184
+ if (mode === "off") return false;
185
+ if (mode === "development") {
186
+ return typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
187
+ }
188
+ return true;
189
+ };
190
+ function runValidation(state, schema, mode = "strict", onValidationError, event = null) {
191
+ if (!schema || !shouldValidate(mode)) return;
192
+ const result = schema.validate(state);
193
+ if (result.success) return;
194
+ onValidationError?.(result.errors, event, state);
195
+ if (mode === "strict") {
196
+ throw new StateValidationError(result.errors, event);
197
+ }
198
+ console.warn("[use-app-state] Validation failed:", result.errors);
199
+ }
200
+
140
201
  // src/init.ts
141
202
  import { concat as concat2, dissoc, flip, ifElse, insert, is, lensPath, mergeLeft, mergeRight, over, remove, set as set2 } from "ramda";
142
203
  import * as React from "react";
143
204
  import ReactDOM from "react-dom/client";
144
205
  var append2 = flip(concat2);
145
- var init = (App, node, data) => {
206
+ var init = (App, node, data, config = {}) => {
207
+ runValidation(data, config.schema, config.mode, config.onValidationError);
146
208
  const root = ReactDOM.createRoot(node);
147
209
  let render;
148
210
  let emit;
@@ -193,6 +255,7 @@ var init = (App, node, data) => {
193
255
  }
194
256
  if (evt instanceof Submit) {
195
257
  }
258
+ runValidation(app.data, config.schema, config.mode, config.onValidationError, evt);
196
259
  subscribers.forEach((s) => s(evt, before, app.data));
197
260
  if (!inUpdateLoop) {
198
261
  render();
@@ -242,6 +305,7 @@ var createReducer = (config) => {
242
305
  };
243
306
  reducer2 = (state, e) => {
244
307
  const after = transformState(e)(state);
308
+ runValidation(after, config.schema, config.mode, config.onValidationError, e);
245
309
  (config.onEvent || identity)(e, state, after);
246
310
  return after;
247
311
  };
@@ -252,6 +316,7 @@ var log = (evt, indent = 0) => {
252
316
  console.log(" ".repeat(indent), evt.name, (evt.path || []).join("."), evt.value);
253
317
  };
254
318
  function useAppState(initialState, config = {}) {
319
+ runValidation(initialState, config.schema, config.mode, config.onValidationError);
255
320
  let subscribers = [];
256
321
  const [state, dispatch] = useReducer(reducer(config), initialState);
257
322
  if (config.expose) {
@@ -288,29 +353,8 @@ function useAppState(initialState, config = {}) {
288
353
  return [state, emitter(handleEvent), controls];
289
354
  }
290
355
  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,
356
+ events_exports as Events,
305
357
  init,
306
- merge,
307
- noOp,
308
- preventDefault,
309
- pull,
310
- push,
311
- set,
312
- update,
313
- updateOne,
314
358
  useAppState
315
359
  };
316
360
  //# sourceMappingURL=index.mjs.map
@@ -1 +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"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/events.ts","../src/validation/errors.ts","../src/validation/validate.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';\nimport { runValidation } from './validation/validate';\nimport type { SchemaAdapter, ValidationError } from './validation/adapter';\nimport type { ValidationMode } from './validation/config';\n\nexport * as Events 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 schema?: SchemaAdapter<AppState>;\n mode?: ValidationMode;\n onValidationError?: (errors: ValidationError[], event: unknown, state: AppState) => void;\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 runValidation(after, config.schema, config.mode, config.onValidationError, e);\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 schema?: SchemaAdapter<AppState>;\n mode?: ValidationMode;\n onValidationError?: (errors: ValidationError[], event: unknown, state: AppState) => void;\n} = {}) {\n // Validate initial state\n runValidation(initialState, config.schema, config.mode, config.onValidationError);\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 type { ValidationError } from './adapter';\n\n/**\n * Error thrown when state validation fails in strict mode\n */\nexport class StateValidationError extends Error {\n public readonly errors: ValidationError[];\n public readonly event: unknown;\n\n constructor(errors: ValidationError[], event: unknown) {\n const message = errors\n .map((e) => `${e.path.join('.')}: ${e.message}`)\n .join('; ');\n super(`State validation failed: ${message}`);\n this.name = 'StateValidationError';\n this.errors = errors;\n this.event = event;\n }\n}\n","import type { SchemaAdapter, ValidationError } from './adapter';\nimport { StateValidationError } from './errors';\nimport type { ValidationMode } from './config';\n\ndeclare const process: { env: { NODE_ENV?: string } } | undefined;\n\n/**\n * Determines if validation should run based on mode\n */\nconst shouldValidate = (mode: ValidationMode): boolean => {\n if (mode === 'off') return false;\n if (mode === 'development') {\n return typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production';\n }\n return true; // 'strict' or 'warn'\n};\n\n/**\n * Shared validation runner used by both useAppState and init()\n */\nexport function runValidation<T>(\n state: T,\n schema: SchemaAdapter<T> | undefined,\n mode: ValidationMode = 'strict',\n onValidationError?: (errors: ValidationError[], event: unknown, state: T) => void,\n event: unknown = null,\n): void {\n if (!schema || !shouldValidate(mode)) return;\n\n const result = schema.validate(state);\n if (result.success) return;\n\n onValidationError?.(result.errors, event, state);\n\n if (mode === 'strict') {\n throw new StateValidationError(result.errors, event);\n }\n console.warn('[use-app-state] Validation failed:', result.errors);\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';\nimport { runValidation } from './validation/validate';\nimport type { SchemaAdapter, ValidationError } from './validation/adapter';\nimport type { ValidationMode } from './validation/config';\n\ntype WithEmitter = { emit: Events.Emitter };\n\ntype InitConfig<AppData> = {\n schema?: SchemaAdapter<AppData>;\n mode?: ValidationMode;\n onValidationError?: (errors: ValidationError[], event: unknown, state: AppData) => void;\n};\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 config: InitConfig<AppData> = {},\n) => {\n // Validate initial state\n runValidation(data, config.schema, config.mode, config.onValidationError);\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\n // Validate state after update\n runValidation(app.data, config.schema, config.mode, config.onValidationError, evt);\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"]}
@@ -0,0 +1,25 @@
1
+ import { a as ValidationError, S as SchemaAdapter } from '../config-B90La2nK.mjs';
2
+ export { V as ValidationMode, b as ValidationResult } from '../config-B90La2nK.mjs';
3
+ import { z } from 'zod';
4
+ import { TSchema, Static } from '@sinclair/typebox';
5
+
6
+ /**
7
+ * Error thrown when state validation fails in strict mode
8
+ */
9
+ declare class StateValidationError extends Error {
10
+ readonly errors: ValidationError[];
11
+ readonly event: unknown;
12
+ constructor(errors: ValidationError[], event: unknown);
13
+ }
14
+
15
+ /**
16
+ * Creates a schema adapter for Zod. Schema is bound in the closure.
17
+ */
18
+ declare function zodSchema<T extends z.ZodTypeAny>(schema: T): SchemaAdapter<z.infer<T>>;
19
+
20
+ /**
21
+ * Creates a schema adapter for TypeBox. Schema is bound in the closure.
22
+ */
23
+ declare function typeboxSchema<T extends TSchema>(schema: T): SchemaAdapter<Static<T>>;
24
+
25
+ export { SchemaAdapter, StateValidationError, ValidationError, typeboxSchema, zodSchema };
@@ -0,0 +1,25 @@
1
+ import { a as ValidationError, S as SchemaAdapter } from '../config-B90La2nK.js';
2
+ export { V as ValidationMode, b as ValidationResult } from '../config-B90La2nK.js';
3
+ import { z } from 'zod';
4
+ import { TSchema, Static } from '@sinclair/typebox';
5
+
6
+ /**
7
+ * Error thrown when state validation fails in strict mode
8
+ */
9
+ declare class StateValidationError extends Error {
10
+ readonly errors: ValidationError[];
11
+ readonly event: unknown;
12
+ constructor(errors: ValidationError[], event: unknown);
13
+ }
14
+
15
+ /**
16
+ * Creates a schema adapter for Zod. Schema is bound in the closure.
17
+ */
18
+ declare function zodSchema<T extends z.ZodTypeAny>(schema: T): SchemaAdapter<z.infer<T>>;
19
+
20
+ /**
21
+ * Creates a schema adapter for TypeBox. Schema is bound in the closure.
22
+ */
23
+ declare function typeboxSchema<T extends TSchema>(schema: T): SchemaAdapter<Static<T>>;
24
+
25
+ export { SchemaAdapter, StateValidationError, ValidationError, typeboxSchema, zodSchema };
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/validation/index.ts
21
+ var validation_exports = {};
22
+ __export(validation_exports, {
23
+ StateValidationError: () => StateValidationError,
24
+ typeboxSchema: () => typeboxSchema,
25
+ zodSchema: () => zodSchema
26
+ });
27
+ module.exports = __toCommonJS(validation_exports);
28
+
29
+ // src/validation/errors.ts
30
+ var StateValidationError = class extends Error {
31
+ constructor(errors, event) {
32
+ const message = errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ");
33
+ super(`State validation failed: ${message}`);
34
+ this.name = "StateValidationError";
35
+ this.errors = errors;
36
+ this.event = event;
37
+ }
38
+ };
39
+
40
+ // src/validation/adapters/zod.ts
41
+ function zodSchema(schema) {
42
+ return {
43
+ validate(data) {
44
+ const result = schema.safeParse(data);
45
+ if (result.success) {
46
+ return { success: true, data: result.data };
47
+ }
48
+ return {
49
+ success: false,
50
+ errors: result.error.issues.map((issue) => ({
51
+ path: issue.path,
52
+ message: issue.message,
53
+ code: issue.code
54
+ }))
55
+ };
56
+ },
57
+ parse(data) {
58
+ return schema.parse(data);
59
+ },
60
+ isValid(data) {
61
+ return schema.safeParse(data).success;
62
+ }
63
+ };
64
+ }
65
+
66
+ // src/validation/adapters/typebox.ts
67
+ var import_value = require("@sinclair/typebox/value");
68
+ function typeboxSchema(schema) {
69
+ return {
70
+ validate(data) {
71
+ const errors = [...import_value.Value.Errors(schema, data)];
72
+ if (errors.length === 0) {
73
+ return { success: true, data };
74
+ }
75
+ return {
76
+ success: false,
77
+ errors: errors.map((err) => ({
78
+ path: err.path.split("/").filter(Boolean).map((s) => /^\d+$/.test(s) ? parseInt(s, 10) : s),
79
+ message: err.message,
80
+ code: err.type?.toString()
81
+ }))
82
+ };
83
+ },
84
+ parse(data) {
85
+ const errors = [...import_value.Value.Errors(schema, data)];
86
+ if (errors.length > 0) {
87
+ const message = errors.map((e) => `${e.path}: ${e.message}`).join("; ");
88
+ throw new Error(`Validation failed: ${message}`);
89
+ }
90
+ return data;
91
+ },
92
+ isValid(data) {
93
+ return import_value.Value.Check(schema, data);
94
+ }
95
+ };
96
+ }
97
+ // Annotate the CommonJS export names for ESM import in node:
98
+ 0 && (module.exports = {
99
+ StateValidationError,
100
+ typeboxSchema,
101
+ zodSchema
102
+ });
103
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/validation/index.ts","../../src/validation/errors.ts","../../src/validation/adapters/zod.ts","../../src/validation/adapters/typebox.ts"],"sourcesContent":["// Core types\nexport type { ValidationResult, ValidationError, SchemaAdapter } from './adapter';\nexport type { ValidationMode } from './config';\nexport { StateValidationError } from './errors';\n\n// Adapters\nexport { zodSchema } from './adapters/zod';\nexport { typeboxSchema } from './adapters/typebox';\n","import type { ValidationError } from './adapter';\n\n/**\n * Error thrown when state validation fails in strict mode\n */\nexport class StateValidationError extends Error {\n public readonly errors: ValidationError[];\n public readonly event: unknown;\n\n constructor(errors: ValidationError[], event: unknown) {\n const message = errors\n .map((e) => `${e.path.join('.')}: ${e.message}`)\n .join('; ');\n super(`State validation failed: ${message}`);\n this.name = 'StateValidationError';\n this.errors = errors;\n this.event = event;\n }\n}\n","import type { z } from 'zod';\nimport type { SchemaAdapter, ValidationResult } from '../adapter';\n\n/**\n * Creates a schema adapter for Zod. Schema is bound in the closure.\n */\nexport function zodSchema<T extends z.ZodTypeAny>(\n schema: T\n): SchemaAdapter<z.infer<T>> {\n return {\n validate(data: unknown): ValidationResult<z.infer<T>> {\n const result = schema.safeParse(data);\n if (result.success) {\n return { success: true, data: result.data };\n }\n return {\n success: false,\n errors: result.error.issues.map((issue) => ({\n path: issue.path as (string | number)[],\n message: issue.message,\n code: issue.code,\n })),\n };\n },\n\n parse(data: unknown): z.infer<T> {\n return schema.parse(data);\n },\n\n isValid(data: unknown): boolean {\n return schema.safeParse(data).success;\n },\n };\n}\n","import type { TSchema, Static } from '@sinclair/typebox';\nimport { Value } from '@sinclair/typebox/value';\nimport type { SchemaAdapter, ValidationResult } from '../adapter';\n\n/**\n * Creates a schema adapter for TypeBox. Schema is bound in the closure.\n */\nexport function typeboxSchema<T extends TSchema>(\n schema: T\n): SchemaAdapter<Static<T>> {\n return {\n validate(data: unknown): ValidationResult<Static<T>> {\n const errors = [...Value.Errors(schema, data)];\n if (errors.length === 0) {\n return { success: true, data: data as Static<T> };\n }\n return {\n success: false,\n errors: errors.map((err) => ({\n path: err.path\n .split('/')\n .filter(Boolean)\n .map((s) => (/^\\d+$/.test(s) ? parseInt(s, 10) : s)),\n message: err.message,\n code: err.type?.toString(),\n })),\n };\n },\n\n parse(data: unknown): Static<T> {\n const errors = [...Value.Errors(schema, data)];\n if (errors.length > 0) {\n const message = errors\n .map((e) => `${e.path}: ${e.message}`)\n .join('; ');\n throw new Error(`Validation failed: ${message}`);\n }\n return data as Static<T>;\n },\n\n isValid(data: unknown): boolean {\n return Value.Check(schema, data);\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACKO,IAAM,uBAAN,cAAmC,MAAM;AAAA,EAI9C,YAAY,QAA2B,OAAgB;AACrD,UAAM,UAAU,OACb,IAAI,CAAC,MAAM,GAAG,EAAE,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAC9C,KAAK,IAAI;AACZ,UAAM,4BAA4B,OAAO,EAAE;AAC3C,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,QAAQ;AAAA,EACf;AACF;;;ACZO,SAAS,UACd,QAC2B;AAC3B,SAAO;AAAA,IACL,SAAS,MAA6C;AACpD,YAAM,SAAS,OAAO,UAAU,IAAI;AACpC,UAAI,OAAO,SAAS;AAClB,eAAO,EAAE,SAAS,MAAM,MAAM,OAAO,KAAK;AAAA,MAC5C;AACA,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ,OAAO,MAAM,OAAO,IAAI,CAAC,WAAW;AAAA,UAC1C,MAAM,MAAM;AAAA,UACZ,SAAS,MAAM;AAAA,UACf,MAAM,MAAM;AAAA,QACd,EAAE;AAAA,MACJ;AAAA,IACF;AAAA,IAEA,MAAM,MAA2B;AAC/B,aAAO,OAAO,MAAM,IAAI;AAAA,IAC1B;AAAA,IAEA,QAAQ,MAAwB;AAC9B,aAAO,OAAO,UAAU,IAAI,EAAE;AAAA,IAChC;AAAA,EACF;AACF;;;AChCA,mBAAsB;AAMf,SAAS,cACd,QAC0B;AAC1B,SAAO;AAAA,IACL,SAAS,MAA4C;AACnD,YAAM,SAAS,CAAC,GAAG,mBAAM,OAAO,QAAQ,IAAI,CAAC;AAC7C,UAAI,OAAO,WAAW,GAAG;AACvB,eAAO,EAAE,SAAS,MAAM,KAAwB;AAAA,MAClD;AACA,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ,OAAO,IAAI,CAAC,SAAS;AAAA,UAC3B,MAAM,IAAI,KACP,MAAM,GAAG,EACT,OAAO,OAAO,EACd,IAAI,CAAC,MAAO,QAAQ,KAAK,CAAC,IAAI,SAAS,GAAG,EAAE,IAAI,CAAE;AAAA,UACrD,SAAS,IAAI;AAAA,UACb,MAAM,IAAI,MAAM,SAAS;AAAA,QAC3B,EAAE;AAAA,MACJ;AAAA,IACF;AAAA,IAEA,MAAM,MAA0B;AAC9B,YAAM,SAAS,CAAC,GAAG,mBAAM,OAAO,QAAQ,IAAI,CAAC;AAC7C,UAAI,OAAO,SAAS,GAAG;AACrB,cAAM,UAAU,OACb,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,KAAK,EAAE,OAAO,EAAE,EACpC,KAAK,IAAI;AACZ,cAAM,IAAI,MAAM,sBAAsB,OAAO,EAAE;AAAA,MACjD;AACA,aAAO;AAAA,IACT;AAAA,IAEA,QAAQ,MAAwB;AAC9B,aAAO,mBAAM,MAAM,QAAQ,IAAI;AAAA,IACjC;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,74 @@
1
+ // src/validation/errors.ts
2
+ var StateValidationError = class extends Error {
3
+ constructor(errors, event) {
4
+ const message = errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ");
5
+ super(`State validation failed: ${message}`);
6
+ this.name = "StateValidationError";
7
+ this.errors = errors;
8
+ this.event = event;
9
+ }
10
+ };
11
+
12
+ // src/validation/adapters/zod.ts
13
+ function zodSchema(schema) {
14
+ return {
15
+ validate(data) {
16
+ const result = schema.safeParse(data);
17
+ if (result.success) {
18
+ return { success: true, data: result.data };
19
+ }
20
+ return {
21
+ success: false,
22
+ errors: result.error.issues.map((issue) => ({
23
+ path: issue.path,
24
+ message: issue.message,
25
+ code: issue.code
26
+ }))
27
+ };
28
+ },
29
+ parse(data) {
30
+ return schema.parse(data);
31
+ },
32
+ isValid(data) {
33
+ return schema.safeParse(data).success;
34
+ }
35
+ };
36
+ }
37
+
38
+ // src/validation/adapters/typebox.ts
39
+ import { Value } from "@sinclair/typebox/value";
40
+ function typeboxSchema(schema) {
41
+ return {
42
+ validate(data) {
43
+ const errors = [...Value.Errors(schema, data)];
44
+ if (errors.length === 0) {
45
+ return { success: true, data };
46
+ }
47
+ return {
48
+ success: false,
49
+ errors: errors.map((err) => ({
50
+ path: err.path.split("/").filter(Boolean).map((s) => /^\d+$/.test(s) ? parseInt(s, 10) : s),
51
+ message: err.message,
52
+ code: err.type?.toString()
53
+ }))
54
+ };
55
+ },
56
+ parse(data) {
57
+ const errors = [...Value.Errors(schema, data)];
58
+ if (errors.length > 0) {
59
+ const message = errors.map((e) => `${e.path}: ${e.message}`).join("; ");
60
+ throw new Error(`Validation failed: ${message}`);
61
+ }
62
+ return data;
63
+ },
64
+ isValid(data) {
65
+ return Value.Check(schema, data);
66
+ }
67
+ };
68
+ }
69
+ export {
70
+ StateValidationError,
71
+ typeboxSchema,
72
+ zodSchema
73
+ };
74
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/validation/errors.ts","../../src/validation/adapters/zod.ts","../../src/validation/adapters/typebox.ts"],"sourcesContent":["import type { ValidationError } from './adapter';\n\n/**\n * Error thrown when state validation fails in strict mode\n */\nexport class StateValidationError extends Error {\n public readonly errors: ValidationError[];\n public readonly event: unknown;\n\n constructor(errors: ValidationError[], event: unknown) {\n const message = errors\n .map((e) => `${e.path.join('.')}: ${e.message}`)\n .join('; ');\n super(`State validation failed: ${message}`);\n this.name = 'StateValidationError';\n this.errors = errors;\n this.event = event;\n }\n}\n","import type { z } from 'zod';\nimport type { SchemaAdapter, ValidationResult } from '../adapter';\n\n/**\n * Creates a schema adapter for Zod. Schema is bound in the closure.\n */\nexport function zodSchema<T extends z.ZodTypeAny>(\n schema: T\n): SchemaAdapter<z.infer<T>> {\n return {\n validate(data: unknown): ValidationResult<z.infer<T>> {\n const result = schema.safeParse(data);\n if (result.success) {\n return { success: true, data: result.data };\n }\n return {\n success: false,\n errors: result.error.issues.map((issue) => ({\n path: issue.path as (string | number)[],\n message: issue.message,\n code: issue.code,\n })),\n };\n },\n\n parse(data: unknown): z.infer<T> {\n return schema.parse(data);\n },\n\n isValid(data: unknown): boolean {\n return schema.safeParse(data).success;\n },\n };\n}\n","import type { TSchema, Static } from '@sinclair/typebox';\nimport { Value } from '@sinclair/typebox/value';\nimport type { SchemaAdapter, ValidationResult } from '../adapter';\n\n/**\n * Creates a schema adapter for TypeBox. Schema is bound in the closure.\n */\nexport function typeboxSchema<T extends TSchema>(\n schema: T\n): SchemaAdapter<Static<T>> {\n return {\n validate(data: unknown): ValidationResult<Static<T>> {\n const errors = [...Value.Errors(schema, data)];\n if (errors.length === 0) {\n return { success: true, data: data as Static<T> };\n }\n return {\n success: false,\n errors: errors.map((err) => ({\n path: err.path\n .split('/')\n .filter(Boolean)\n .map((s) => (/^\\d+$/.test(s) ? parseInt(s, 10) : s)),\n message: err.message,\n code: err.type?.toString(),\n })),\n };\n },\n\n parse(data: unknown): Static<T> {\n const errors = [...Value.Errors(schema, data)];\n if (errors.length > 0) {\n const message = errors\n .map((e) => `${e.path}: ${e.message}`)\n .join('; ');\n throw new Error(`Validation failed: ${message}`);\n }\n return data as Static<T>;\n },\n\n isValid(data: unknown): boolean {\n return Value.Check(schema, data);\n },\n };\n}\n"],"mappings":";AAKO,IAAM,uBAAN,cAAmC,MAAM;AAAA,EAI9C,YAAY,QAA2B,OAAgB;AACrD,UAAM,UAAU,OACb,IAAI,CAAC,MAAM,GAAG,EAAE,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAC9C,KAAK,IAAI;AACZ,UAAM,4BAA4B,OAAO,EAAE;AAC3C,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,QAAQ;AAAA,EACf;AACF;;;ACZO,SAAS,UACd,QAC2B;AAC3B,SAAO;AAAA,IACL,SAAS,MAA6C;AACpD,YAAM,SAAS,OAAO,UAAU,IAAI;AACpC,UAAI,OAAO,SAAS;AAClB,eAAO,EAAE,SAAS,MAAM,MAAM,OAAO,KAAK;AAAA,MAC5C;AACA,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ,OAAO,MAAM,OAAO,IAAI,CAAC,WAAW;AAAA,UAC1C,MAAM,MAAM;AAAA,UACZ,SAAS,MAAM;AAAA,UACf,MAAM,MAAM;AAAA,QACd,EAAE;AAAA,MACJ;AAAA,IACF;AAAA,IAEA,MAAM,MAA2B;AAC/B,aAAO,OAAO,MAAM,IAAI;AAAA,IAC1B;AAAA,IAEA,QAAQ,MAAwB;AAC9B,aAAO,OAAO,UAAU,IAAI,EAAE;AAAA,IAChC;AAAA,EACF;AACF;;;AChCA,SAAS,aAAa;AAMf,SAAS,cACd,QAC0B;AAC1B,SAAO;AAAA,IACL,SAAS,MAA4C;AACnD,YAAM,SAAS,CAAC,GAAG,MAAM,OAAO,QAAQ,IAAI,CAAC;AAC7C,UAAI,OAAO,WAAW,GAAG;AACvB,eAAO,EAAE,SAAS,MAAM,KAAwB;AAAA,MAClD;AACA,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ,OAAO,IAAI,CAAC,SAAS;AAAA,UAC3B,MAAM,IAAI,KACP,MAAM,GAAG,EACT,OAAO,OAAO,EACd,IAAI,CAAC,MAAO,QAAQ,KAAK,CAAC,IAAI,SAAS,GAAG,EAAE,IAAI,CAAE;AAAA,UACrD,SAAS,IAAI;AAAA,UACb,MAAM,IAAI,MAAM,SAAS;AAAA,QAC3B,EAAE;AAAA,MACJ;AAAA,IACF;AAAA,IAEA,MAAM,MAA0B;AAC9B,YAAM,SAAS,CAAC,GAAG,MAAM,OAAO,QAAQ,IAAI,CAAC;AAC7C,UAAI,OAAO,SAAS,GAAG;AACrB,cAAM,UAAU,OACb,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,KAAK,EAAE,OAAO,EAAE,EACpC,KAAK,IAAI;AACZ,cAAM,IAAI,MAAM,sBAAsB,OAAO,EAAE;AAAA,MACjD;AACA,aAAO;AAAA,IACT;AAAA,IAEA,QAAQ,MAAwB;AAC9B,aAAO,MAAM,MAAM,QAAQ,IAAI;AAAA,IACjC;AAAA,EACF;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nateabele/use-app-state",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "A React state management library using events and immutable data operations",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -10,6 +10,11 @@
10
10
  "types": "./dist/index.d.ts",
11
11
  "import": "./dist/index.mjs",
12
12
  "require": "./dist/index.js"
13
+ },
14
+ "./validation": {
15
+ "types": "./dist/validation/index.d.ts",
16
+ "import": "./dist/validation/index.mjs",
17
+ "require": "./dist/validation/index.js"
13
18
  }
14
19
  },
15
20
  "files": [
@@ -18,6 +23,8 @@
18
23
  "scripts": {
19
24
  "build": "tsup",
20
25
  "dev": "tsup --watch",
26
+ "test": "vitest run",
27
+ "test:watch": "vitest",
21
28
  "prepublishOnly": "npm run build"
22
29
  },
23
30
  "keywords": [
@@ -30,19 +37,32 @@
30
37
  "author": "Nate Abele <nate.abele@gmail.com>",
31
38
  "license": "MIT",
32
39
  "peerDependencies": {
40
+ "@sinclair/typebox": ">=0.32.0",
41
+ "ramda": ">=0.30.0",
33
42
  "react": ">=17.0.0",
34
- "react-dom": ">=17.0.0"
43
+ "react-dom": ">=17.0.0",
44
+ "zod": ">=3.0.0"
35
45
  },
36
- "dependencies": {
37
- "ramda": "^0.30.1"
46
+ "peerDependenciesMeta": {
47
+ "zod": {
48
+ "optional": true
49
+ },
50
+ "@sinclair/typebox": {
51
+ "optional": true
52
+ }
38
53
  },
39
54
  "devDependencies": {
55
+ "@sinclair/typebox": "^0.34.48",
56
+ "@testing-library/react": "^16.3.2",
40
57
  "@types/ramda": "^0.30.2",
41
58
  "@types/react": "^18.3.18",
42
59
  "@types/react-dom": "^18.3.5",
60
+ "jsdom": "^28.1.0",
43
61
  "react": "^18.3.1",
44
62
  "react-dom": "^18.3.1",
45
63
  "tsup": "^8.3.6",
46
- "typescript": "^5.7.3"
64
+ "typescript": "^5.7.3",
65
+ "vitest": "^4.0.18",
66
+ "zod": "^4.3.6"
47
67
  }
48
68
  }