@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 +95 -0
- package/dist/config-B90La2nK.d.mts +34 -0
- package/dist/config-B90La2nK.d.ts +34 -0
- package/dist/index.d.mts +54 -2
- package/dist/index.d.ts +54 -2
- package/dist/index.js +48 -31
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +67 -23
- package/dist/index.mjs.map +1 -1
- package/dist/validation/index.d.mts +25 -0
- package/dist/validation/index.d.ts +25 -0
- package/dist/validation/index.js +103 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/index.mjs +74 -0
- package/dist/validation/index.mjs.map +1 -0
- package/package.json +25 -5
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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":"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","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
|
-
|
|
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
|
package/dist/index.mjs.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":["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.
|
|
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
|
-
"
|
|
37
|
-
"
|
|
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
|
}
|