@uistate/examples 1.0.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 +40 -0
- package/cssState/.gitkeep +0 -0
- package/eventState/001-counter/README.md +44 -0
- package/eventState/001-counter/index.html +33 -0
- package/eventState/002-counter-improved/README.md +44 -0
- package/eventState/002-counter-improved/index.html +47 -0
- package/eventState/003-input-reactive/README.md +44 -0
- package/eventState/003-input-reactive/index.html +33 -0
- package/eventState/004-computed-state/README.md +45 -0
- package/eventState/004-computed-state/index.html +65 -0
- package/eventState/005-conditional-rendering/README.md +42 -0
- package/eventState/005-conditional-rendering/index.html +39 -0
- package/eventState/006-list-rendering/README.md +49 -0
- package/eventState/006-list-rendering/index.html +63 -0
- package/eventState/007-form-validation/README.md +52 -0
- package/eventState/007-form-validation/index.html +102 -0
- package/eventState/008-undo-redo/README.md +70 -0
- package/eventState/008-undo-redo/index.html +108 -0
- package/eventState/009-localStorage-side-effects/README.md +72 -0
- package/eventState/009-localStorage-side-effects/index.html +57 -0
- package/eventState/010-decoupled-components/README.md +74 -0
- package/eventState/010-decoupled-components/index.html +93 -0
- package/eventState/011-async-patterns/README.md +98 -0
- package/eventState/011-async-patterns/index.html +132 -0
- package/eventState/028-counter-improved-eventTest/LICENSE +55 -0
- package/eventState/028-counter-improved-eventTest/README.md +131 -0
- package/eventState/028-counter-improved-eventTest/app/store.js +9 -0
- package/eventState/028-counter-improved-eventTest/index.html +49 -0
- package/eventState/028-counter-improved-eventTest/runtime/core/behaviors.runtime.js +282 -0
- package/eventState/028-counter-improved-eventTest/runtime/core/eventState.js +100 -0
- package/eventState/028-counter-improved-eventTest/runtime/core/eventStateNew.js +149 -0
- package/eventState/028-counter-improved-eventTest/runtime/core/helpers.js +212 -0
- package/eventState/028-counter-improved-eventTest/runtime/core/router.js +271 -0
- package/eventState/028-counter-improved-eventTest/store.d.ts +8 -0
- package/eventState/028-counter-improved-eventTest/style.css +170 -0
- package/eventState/028-counter-improved-eventTest/tests/README.md +208 -0
- package/eventState/028-counter-improved-eventTest/tests/counter.test.js +116 -0
- package/eventState/028-counter-improved-eventTest/tests/eventTest.js +176 -0
- package/eventState/028-counter-improved-eventTest/tests/generateTypes.js +168 -0
- package/eventState/028-counter-improved-eventTest/tests/run.js +20 -0
- package/eventState/030-todo-app-with-eventTest/LICENSE +55 -0
- package/eventState/030-todo-app-with-eventTest/README.md +121 -0
- package/eventState/030-todo-app-with-eventTest/app/router.js +25 -0
- package/eventState/030-todo-app-with-eventTest/app/store.js +16 -0
- package/eventState/030-todo-app-with-eventTest/app/views/home.js +11 -0
- package/eventState/030-todo-app-with-eventTest/app/views/todoDemo.js +88 -0
- package/eventState/030-todo-app-with-eventTest/index.html +65 -0
- package/eventState/030-todo-app-with-eventTest/runtime/core/behaviors.runtime.js +282 -0
- package/eventState/030-todo-app-with-eventTest/runtime/core/eventState.js +100 -0
- package/eventState/030-todo-app-with-eventTest/runtime/core/eventStateNew.js +149 -0
- package/eventState/030-todo-app-with-eventTest/runtime/core/helpers.js +212 -0
- package/eventState/030-todo-app-with-eventTest/runtime/core/router.js +271 -0
- package/eventState/030-todo-app-with-eventTest/store.d.ts +18 -0
- package/eventState/030-todo-app-with-eventTest/style.css +170 -0
- package/eventState/030-todo-app-with-eventTest/tests/README.md +208 -0
- package/eventState/030-todo-app-with-eventTest/tests/eventTest.js +176 -0
- package/eventState/030-todo-app-with-eventTest/tests/generateTypes.js +189 -0
- package/eventState/030-todo-app-with-eventTest/tests/run.js +20 -0
- package/eventState/030-todo-app-with-eventTest/tests/todos.test.js +167 -0
- package/eventState/031-todo-app-with-eventTest/LICENSE +55 -0
- package/eventState/031-todo-app-with-eventTest/README.md +54 -0
- package/eventState/031-todo-app-with-eventTest/TUTORIAL.md +390 -0
- package/eventState/031-todo-app-with-eventTest/WHY_EVENTSTATE.md +777 -0
- package/eventState/031-todo-app-with-eventTest/app/bridges.js +113 -0
- package/eventState/031-todo-app-with-eventTest/app/router.js +26 -0
- package/eventState/031-todo-app-with-eventTest/app/store.js +15 -0
- package/eventState/031-todo-app-with-eventTest/app/views/home.js +46 -0
- package/eventState/031-todo-app-with-eventTest/app/views/todoDemo.js +69 -0
- package/eventState/031-todo-app-with-eventTest/devtools/dock.js +41 -0
- package/eventState/031-todo-app-with-eventTest/devtools/stateTracker.dock.js +10 -0
- package/eventState/031-todo-app-with-eventTest/devtools/stateTracker.js +246 -0
- package/eventState/031-todo-app-with-eventTest/devtools/telemetry.js +104 -0
- package/eventState/031-todo-app-with-eventTest/devtools/typeGenerator.js +339 -0
- package/eventState/031-todo-app-with-eventTest/index.html +103 -0
- package/eventState/031-todo-app-with-eventTest/package-lock.json +2184 -0
- package/eventState/031-todo-app-with-eventTest/package.json +24 -0
- package/eventState/031-todo-app-with-eventTest/runtime/core/behaviors.runtime.js +282 -0
- package/eventState/031-todo-app-with-eventTest/runtime/core/eventState.js +100 -0
- package/eventState/031-todo-app-with-eventTest/runtime/core/eventStateNew.js +149 -0
- package/eventState/031-todo-app-with-eventTest/runtime/core/helpers.js +212 -0
- package/eventState/031-todo-app-with-eventTest/runtime/core/router.js +271 -0
- package/eventState/031-todo-app-with-eventTest/runtime/extensions/boundary.js +36 -0
- package/eventState/031-todo-app-with-eventTest/runtime/extensions/converge.js +63 -0
- package/eventState/031-todo-app-with-eventTest/runtime/extensions/eventState.plus.js +210 -0
- package/eventState/031-todo-app-with-eventTest/runtime/extensions/hydrate.js +157 -0
- package/eventState/031-todo-app-with-eventTest/runtime/extensions/queryBinding.js +69 -0
- package/eventState/031-todo-app-with-eventTest/runtime/forms/computed.js +78 -0
- package/eventState/031-todo-app-with-eventTest/runtime/forms/meta.js +51 -0
- package/eventState/031-todo-app-with-eventTest/runtime/forms/submitWithBoundary.js +28 -0
- package/eventState/031-todo-app-with-eventTest/runtime/forms/validators.js +55 -0
- package/eventState/031-todo-app-with-eventTest/store.d.ts +23 -0
- package/eventState/031-todo-app-with-eventTest/style.css +170 -0
- package/eventState/031-todo-app-with-eventTest/tests/README.md +208 -0
- package/eventState/031-todo-app-with-eventTest/tests/eventTest.js +176 -0
- package/eventState/031-todo-app-with-eventTest/tests/generateTypes.js +191 -0
- package/eventState/031-todo-app-with-eventTest/tests/run.js +20 -0
- package/eventState/031-todo-app-with-eventTest/tests/todos.test.js +192 -0
- package/eventState/032-todo-app-with-eventTest/LICENSE +55 -0
- package/eventState/032-todo-app-with-eventTest/README.md +54 -0
- package/eventState/032-todo-app-with-eventTest/TUTORIAL.md +390 -0
- package/eventState/032-todo-app-with-eventTest/WHY_EVENTSTATE.md +777 -0
- package/eventState/032-todo-app-with-eventTest/app/actions/index.js +153 -0
- package/eventState/032-todo-app-with-eventTest/app/bridges.js +113 -0
- package/eventState/032-todo-app-with-eventTest/app/router.js +26 -0
- package/eventState/032-todo-app-with-eventTest/app/store.js +15 -0
- package/eventState/032-todo-app-with-eventTest/app/views/home.js +46 -0
- package/eventState/032-todo-app-with-eventTest/app/views/todoDemo.js +69 -0
- package/eventState/032-todo-app-with-eventTest/devtools/dock.js +41 -0
- package/eventState/032-todo-app-with-eventTest/devtools/stateTracker.dock.js +10 -0
- package/eventState/032-todo-app-with-eventTest/devtools/stateTracker.js +246 -0
- package/eventState/032-todo-app-with-eventTest/devtools/telemetry.js +104 -0
- package/eventState/032-todo-app-with-eventTest/devtools/typeGenerator.js +339 -0
- package/eventState/032-todo-app-with-eventTest/index.html +87 -0
- package/eventState/032-todo-app-with-eventTest/package-lock.json +2184 -0
- package/eventState/032-todo-app-with-eventTest/package.json +24 -0
- package/eventState/032-todo-app-with-eventTest/runtime/core/behaviors.runtime.js +282 -0
- package/eventState/032-todo-app-with-eventTest/runtime/core/eventState.js +100 -0
- package/eventState/032-todo-app-with-eventTest/runtime/core/eventStateNew.js +149 -0
- package/eventState/032-todo-app-with-eventTest/runtime/core/helpers.js +212 -0
- package/eventState/032-todo-app-with-eventTest/runtime/core/router.js +271 -0
- package/eventState/032-todo-app-with-eventTest/runtime/extensions/boundary.js +36 -0
- package/eventState/032-todo-app-with-eventTest/runtime/extensions/converge.js +63 -0
- package/eventState/032-todo-app-with-eventTest/runtime/extensions/eventState.plus.js +210 -0
- package/eventState/032-todo-app-with-eventTest/runtime/extensions/hydrate.js +157 -0
- package/eventState/032-todo-app-with-eventTest/runtime/extensions/queryBinding.js +69 -0
- package/eventState/032-todo-app-with-eventTest/runtime/forms/computed.js +78 -0
- package/eventState/032-todo-app-with-eventTest/runtime/forms/meta.js +51 -0
- package/eventState/032-todo-app-with-eventTest/runtime/forms/submitWithBoundary.js +28 -0
- package/eventState/032-todo-app-with-eventTest/runtime/forms/validators.js +55 -0
- package/eventState/032-todo-app-with-eventTest/store.d.ts +23 -0
- package/eventState/032-todo-app-with-eventTest/style.css +170 -0
- package/eventState/032-todo-app-with-eventTest/tests/README.md +208 -0
- package/eventState/032-todo-app-with-eventTest/tests/eventTest.js +176 -0
- package/eventState/032-todo-app-with-eventTest/tests/generateTypes.js +191 -0
- package/eventState/032-todo-app-with-eventTest/tests/run.js +20 -0
- package/eventState/032-todo-app-with-eventTest/tests/todos.test.js +192 -0
- package/package.json +27 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
// typeGenerator.js — Generate TypeScript definitions from telemetry
|
|
2
|
+
// Analyzes runtime telemetry to extract state shape and generate .d.ts files
|
|
3
|
+
|
|
4
|
+
import store from '../app/store.js';
|
|
5
|
+
|
|
6
|
+
class TypeGenerator {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.pathTypes = new Map(); // path → type info
|
|
9
|
+
this.pathSamples = new Map(); // path → sample values
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Observe a state change and infer its type
|
|
14
|
+
*/
|
|
15
|
+
observe(path, value) {
|
|
16
|
+
const type = this.inferType(value);
|
|
17
|
+
|
|
18
|
+
// Store sample for complex types
|
|
19
|
+
if (typeof value === 'object' && value !== null) {
|
|
20
|
+
if (!this.pathSamples.has(path)) {
|
|
21
|
+
this.pathSamples.set(path, []);
|
|
22
|
+
}
|
|
23
|
+
const samples = this.pathSamples.get(path);
|
|
24
|
+
if (samples.length < 10) {
|
|
25
|
+
samples.push(value);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Merge with existing type info
|
|
30
|
+
if (this.pathTypes.has(path)) {
|
|
31
|
+
const existing = this.pathTypes.get(path);
|
|
32
|
+
this.pathTypes.set(path, this.mergeTypes(existing, type));
|
|
33
|
+
} else {
|
|
34
|
+
this.pathTypes.set(path, type);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Infer TypeScript type from a value
|
|
40
|
+
*/
|
|
41
|
+
inferType(value) {
|
|
42
|
+
if (value === null) return 'null';
|
|
43
|
+
if (value === undefined) return 'undefined';
|
|
44
|
+
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
if (value.length === 0) return 'Array<unknown>';
|
|
47
|
+
|
|
48
|
+
// Sample first few elements and merge their types
|
|
49
|
+
const samples = value.slice(0, 10);
|
|
50
|
+
let mergedElementType = null;
|
|
51
|
+
|
|
52
|
+
for (const sample of samples) {
|
|
53
|
+
const elementType = this.inferType(sample);
|
|
54
|
+
if (mergedElementType === null) {
|
|
55
|
+
mergedElementType = elementType;
|
|
56
|
+
} else {
|
|
57
|
+
mergedElementType = this.mergeTypes(mergedElementType, elementType);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return `Array<${this.typeToString(mergedElementType)}>`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (typeof value === 'object') {
|
|
65
|
+
// Infer object shape
|
|
66
|
+
const shape = {};
|
|
67
|
+
for (const [key, val] of Object.entries(value)) {
|
|
68
|
+
shape[key] = this.inferType(val);
|
|
69
|
+
}
|
|
70
|
+
return shape;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Primitives
|
|
74
|
+
if (typeof value === 'string') return 'string';
|
|
75
|
+
if (typeof value === 'number') return 'number';
|
|
76
|
+
if (typeof value === 'boolean') return 'boolean';
|
|
77
|
+
|
|
78
|
+
return 'unknown';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Convert type representation to string
|
|
83
|
+
*/
|
|
84
|
+
typeToString(type) {
|
|
85
|
+
if (typeof type === 'string') return type;
|
|
86
|
+
|
|
87
|
+
if (typeof type === 'object' && !Array.isArray(type)) {
|
|
88
|
+
const props = Object.entries(type)
|
|
89
|
+
.map(([k, v]) => `${k}: ${this.typeToString(v)}`)
|
|
90
|
+
.join('; ');
|
|
91
|
+
return `{ ${props} }`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return 'unknown';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Merge two type definitions (for union types)
|
|
99
|
+
*/
|
|
100
|
+
mergeTypes(type1, type2) {
|
|
101
|
+
// Normalize for comparison
|
|
102
|
+
const str1 = this.normalizeType(type1);
|
|
103
|
+
const str2 = this.normalizeType(type2);
|
|
104
|
+
|
|
105
|
+
if (str1 === str2) return type1;
|
|
106
|
+
|
|
107
|
+
// Handle string types
|
|
108
|
+
if (typeof type1 === 'string' && typeof type2 === 'string') {
|
|
109
|
+
if (type1 === type2) return type1;
|
|
110
|
+
// Avoid duplicate unions
|
|
111
|
+
if (type1.includes(type2)) return type1;
|
|
112
|
+
if (type2.includes(type1)) return type2;
|
|
113
|
+
return `${type1} | ${type2}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Handle object merging (merge properties, not create union)
|
|
117
|
+
if (typeof type1 === 'object' && typeof type2 === 'object' &&
|
|
118
|
+
!Array.isArray(type1) && !Array.isArray(type2)) {
|
|
119
|
+
const merged = { ...type1 };
|
|
120
|
+
for (const [key, val] of Object.entries(type2)) {
|
|
121
|
+
if (key in merged) {
|
|
122
|
+
merged[key] = this.mergeTypes(merged[key], val);
|
|
123
|
+
} else {
|
|
124
|
+
merged[key] = val;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return merged;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Mixed types: create union
|
|
131
|
+
if (typeof type1 === 'string' && typeof type2 === 'object') {
|
|
132
|
+
return `${type1} | ${this.typeToString(type2)}`;
|
|
133
|
+
}
|
|
134
|
+
if (typeof type1 === 'object' && typeof type2 === 'string') {
|
|
135
|
+
return `${this.typeToString(type1)} | ${type2}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return type1; // Default: keep first type
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Normalize type for comparison
|
|
143
|
+
*/
|
|
144
|
+
normalizeType(type) {
|
|
145
|
+
if (typeof type === 'string') return type;
|
|
146
|
+
return JSON.stringify(type, Object.keys(type).sort());
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Build hierarchical tree from flat paths
|
|
151
|
+
*/
|
|
152
|
+
buildTree() {
|
|
153
|
+
const tree = {};
|
|
154
|
+
|
|
155
|
+
for (const [path, type] of this.pathTypes) {
|
|
156
|
+
const parts = path.split('.');
|
|
157
|
+
let current = tree;
|
|
158
|
+
|
|
159
|
+
for (let i = 0; i < parts.length; i++) {
|
|
160
|
+
const part = parts[i];
|
|
161
|
+
|
|
162
|
+
if (i === parts.length - 1) {
|
|
163
|
+
// Leaf node
|
|
164
|
+
current[part] = { __type: type };
|
|
165
|
+
} else {
|
|
166
|
+
// Branch node
|
|
167
|
+
if (!current[part]) {
|
|
168
|
+
current[part] = {};
|
|
169
|
+
}
|
|
170
|
+
current = current[part];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return tree;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Generate TypeScript interface from type info
|
|
180
|
+
*/
|
|
181
|
+
generateInterface(obj, indent = 0) {
|
|
182
|
+
const lines = [];
|
|
183
|
+
const spaces = ' '.repeat(indent);
|
|
184
|
+
|
|
185
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
186
|
+
if (value.__type !== undefined) {
|
|
187
|
+
// Leaf node with type
|
|
188
|
+
const type = this.formatType(value.__type);
|
|
189
|
+
lines.push(`${spaces}${key}: ${type};`);
|
|
190
|
+
} else {
|
|
191
|
+
// Nested object
|
|
192
|
+
lines.push(`${spaces}${key}: {`);
|
|
193
|
+
lines.push(this.generateInterface(value, indent + 1));
|
|
194
|
+
lines.push(`${spaces}};`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return lines.join('\n');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Format type for TypeScript output
|
|
203
|
+
*/
|
|
204
|
+
formatType(type) {
|
|
205
|
+
if (typeof type === 'string') {
|
|
206
|
+
return type;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (typeof type === 'object' && !Array.isArray(type)) {
|
|
210
|
+
// Inline object type
|
|
211
|
+
const props = Object.entries(type)
|
|
212
|
+
.map(([k, v]) => `${k}: ${this.formatType(v)}`)
|
|
213
|
+
.join('; ');
|
|
214
|
+
return `{ ${props} }`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return 'unknown';
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Generate complete .d.ts file
|
|
222
|
+
*/
|
|
223
|
+
generateDTS() {
|
|
224
|
+
const lines = [
|
|
225
|
+
'// Auto-generated from runtime telemetry',
|
|
226
|
+
'// DO NOT EDIT - regenerate by using the app and clicking "Generate Types"',
|
|
227
|
+
'',
|
|
228
|
+
'export interface StoreState {',
|
|
229
|
+
];
|
|
230
|
+
|
|
231
|
+
const tree = this.buildTree();
|
|
232
|
+
lines.push(this.generateInterface(tree, 1));
|
|
233
|
+
lines.push('}');
|
|
234
|
+
lines.push('');
|
|
235
|
+
lines.push('export default StoreState;');
|
|
236
|
+
lines.push('');
|
|
237
|
+
|
|
238
|
+
return lines.join('\n');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Parse telemetry buffer and extract types
|
|
243
|
+
*/
|
|
244
|
+
parseBuffer(buffer) {
|
|
245
|
+
for (const entry of buffer) {
|
|
246
|
+
if (entry.level !== 'log') continue;
|
|
247
|
+
|
|
248
|
+
const args = entry.args;
|
|
249
|
+
if (!args || args.length < 2) continue;
|
|
250
|
+
|
|
251
|
+
const tag = args[0];
|
|
252
|
+
|
|
253
|
+
// Parse [state] entries
|
|
254
|
+
if (typeof tag === 'string' && tag.startsWith('[state]')) {
|
|
255
|
+
const path = tag.replace('[state] ', '');
|
|
256
|
+
const value = args[1];
|
|
257
|
+
this.observe(path, value);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Parse [intent] entries
|
|
261
|
+
if (typeof tag === 'string' && tag.startsWith('[intent]')) {
|
|
262
|
+
const intentName = tag.replace('[intent] ', '');
|
|
263
|
+
const path = `intent.${intentName}`;
|
|
264
|
+
const value = args[1];
|
|
265
|
+
this.observe(path, value);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Parse test assertions and extract types
|
|
272
|
+
*/
|
|
273
|
+
parseTestAssertions(assertions) {
|
|
274
|
+
for (const assertion of assertions) {
|
|
275
|
+
const { path, type, elementShape, shape } = assertion;
|
|
276
|
+
|
|
277
|
+
if (type === 'array' && elementShape) {
|
|
278
|
+
// Array type with element shape
|
|
279
|
+
this.pathTypes.set(path, `Array<${this.typeToString(elementShape)}>`);
|
|
280
|
+
} else if (type === 'object' && shape) {
|
|
281
|
+
// Object type with shape
|
|
282
|
+
this.pathTypes.set(path, shape);
|
|
283
|
+
} else if (type) {
|
|
284
|
+
// Primitive type
|
|
285
|
+
this.pathTypes.set(path, type);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Save .d.ts file (browser download)
|
|
292
|
+
*/
|
|
293
|
+
save(filename = 'store.d.ts') {
|
|
294
|
+
const dts = this.generateDTS();
|
|
295
|
+
const blob = new Blob([dts], { type: 'text/plain' });
|
|
296
|
+
const url = URL.createObjectURL(blob);
|
|
297
|
+
const a = document.createElement('a');
|
|
298
|
+
a.href = url;
|
|
299
|
+
a.download = filename;
|
|
300
|
+
a.click();
|
|
301
|
+
URL.revokeObjectURL(url);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Create and expose type generator
|
|
306
|
+
const typeGenerator = new TypeGenerator();
|
|
307
|
+
window.__typeGenerator = typeGenerator;
|
|
308
|
+
|
|
309
|
+
// Subscribe to all state changes for live type inference
|
|
310
|
+
if (store && store.subscribe) {
|
|
311
|
+
store.subscribe('*', (detail) => {
|
|
312
|
+
const { path, value } = detail;
|
|
313
|
+
typeGenerator.observe(path, value);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
console.info('[typeGenerator] Live type inference enabled');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Register with dev dock
|
|
320
|
+
if (window.__devdock && typeof window.__devdock.register === 'function') {
|
|
321
|
+
window.__devdock.register({
|
|
322
|
+
id: 'generate-types',
|
|
323
|
+
label: 'Types',
|
|
324
|
+
title: 'Generate TypeScript definitions',
|
|
325
|
+
onClick: () => {
|
|
326
|
+
// Also parse telemetry buffer for historical data
|
|
327
|
+
if (window.__telemetry) {
|
|
328
|
+
const buffer = window.__telemetry.get();
|
|
329
|
+
typeGenerator.parseBuffer(buffer);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
typeGenerator.save('store.d.ts');
|
|
333
|
+
console.info('[typeGenerator] Generated store.d.ts');
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export default typeGenerator;
|
|
339
|
+
export { TypeGenerator };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>009 Todo App with eventTest</title>
|
|
7
|
+
<link rel="stylesheet" href="style.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body data-theme="dark" data-bind="data-theme: ui.theme">
|
|
10
|
+
<nav style="display:flex; gap:10px; align-items:center; padding:10px 12px;">
|
|
11
|
+
<a href="/" data-link>Home</a>
|
|
12
|
+
<a href="/todo-demo" data-link>Todo app demo</a>
|
|
13
|
+
<span class="loading-badge" aria-live="polite">Loading…</span>
|
|
14
|
+
<span style="flex:1"></span>
|
|
15
|
+
<button class="btn" title="Toggle theme" data-on="click: toggleTheme() | log('toggle theme') | logPath('ui.theme')">Theme</button>
|
|
16
|
+
</nav>
|
|
17
|
+
<div data-route-root></div>
|
|
18
|
+
|
|
19
|
+
<script type="module" src="./app/router.js"></script>
|
|
20
|
+
<!-- Expose the store globally so stateTracker (optional) can bind without tight coupling -->
|
|
21
|
+
<script type="module">
|
|
22
|
+
import store from './app/store.js';
|
|
23
|
+
window.stateTrackerStore = store;
|
|
24
|
+
|
|
25
|
+
// Bind loading badge to route transitioning state (after DOM ready)
|
|
26
|
+
requestAnimationFrame(() => {
|
|
27
|
+
const badge = document.querySelector('.loading-badge');
|
|
28
|
+
if (badge) {
|
|
29
|
+
store.subscribe('ui.route.transitioning', (isTransitioning) => {
|
|
30
|
+
badge.style.display = isTransitioning ? 'inline' : 'none';
|
|
31
|
+
});
|
|
32
|
+
// Set initial state (default hidden in CSS)
|
|
33
|
+
badge.style.display = store.get('ui.route.transitioning') ? 'inline' : 'none';
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
</script>
|
|
37
|
+
<!-- Install behaviors runtime with inline actions (no separate registry file) -->
|
|
38
|
+
<script type="module">
|
|
39
|
+
import store from './app/store.js';
|
|
40
|
+
import { installBehaviors } from './runtime/core/behaviors.runtime.js';
|
|
41
|
+
|
|
42
|
+
// Inline action registry - just the actions used in this example
|
|
43
|
+
const registry = {
|
|
44
|
+
toggleTheme(ctx) {
|
|
45
|
+
const cur = ctx.get('ui.theme');
|
|
46
|
+
const next = (cur === 'dark') ? 'light' : 'dark';
|
|
47
|
+
ctx.set('ui.theme', next);
|
|
48
|
+
console.log('[action] theme ->', next);
|
|
49
|
+
},
|
|
50
|
+
log(ctx, ...args) {
|
|
51
|
+
console.log('[action]', ...args);
|
|
52
|
+
},
|
|
53
|
+
logPath(ctx, path) {
|
|
54
|
+
console.log('[action]', String(path), ctx.get(String(path)));
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
window.behaviorsWhitelistDefault = ['ui.counter', 'ui.name', 'ui.items', 'ui.newItem', 'ui.itemsCount', 'ui.guard.whitelist', 'ui.theme', 'intent.**'];
|
|
59
|
+
window.behaviorsWhitelist = [...window.behaviorsWhitelistDefault];
|
|
60
|
+
const recUn = window.__recorder?.install?.(store);
|
|
61
|
+
window.behaviorsUninstall = installBehaviors(store, {
|
|
62
|
+
registry,
|
|
63
|
+
root: document,
|
|
64
|
+
writablePrefixes: ['ui.','intent.'],
|
|
65
|
+
writableWhitelist: window.behaviorsWhitelist,
|
|
66
|
+
debug: true,
|
|
67
|
+
onStep: (e) => {
|
|
68
|
+
try { window.__recorder?.onStep?.(e); } catch{}
|
|
69
|
+
// Log all behavior steps to telemetry
|
|
70
|
+
try {
|
|
71
|
+
if (e.phase === 'started') {
|
|
72
|
+
console.log(`[action] ${e.name}(${e.args?.join(', ') || ''})`, { event: e.event?.type, element: e.el?.tagName });
|
|
73
|
+
}
|
|
74
|
+
if (e.phase === 'applied' && e.write) {
|
|
75
|
+
console.log(`[write] ${e.write}`, store.get(e.write));
|
|
76
|
+
}
|
|
77
|
+
if (e.phase === 'blocked') {
|
|
78
|
+
console.warn(`[blocked] ${e.name} tried to write ${e.write}`, e.reason);
|
|
79
|
+
}
|
|
80
|
+
} catch{}
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
try { store.set('ui.guard.whitelist', [...window.behaviorsWhitelist]); } catch {}
|
|
84
|
+
try {
|
|
85
|
+
const cur = store.get('ui.theme');
|
|
86
|
+
if (!cur) {
|
|
87
|
+
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
88
|
+
store.set('ui.theme', prefersDark ? 'dark' : 'light');
|
|
89
|
+
}
|
|
90
|
+
try { store.subscribe && store.subscribe('ui.theme', () => { try { localStorage.setItem('ui.theme', store.get('ui.theme')); } catch {} }); } catch {}
|
|
91
|
+
} catch {}
|
|
92
|
+
</script>
|
|
93
|
+
<script type="module" src="./devtools/stateTracker.js"></script>
|
|
94
|
+
<!-- Dev dock MUST load first so __devdock is available -->
|
|
95
|
+
<script type="module" src="./devtools/dock.js"></script>
|
|
96
|
+
<!-- Now load tools that register with the dock -->
|
|
97
|
+
<script type="module" src="./devtools/telemetry.js"></script>
|
|
98
|
+
<script type="module" src="./devtools/typeGenerator.js"></script>
|
|
99
|
+
<script type="module" src="./devtools/stateTracker.dock.js"></script>
|
|
100
|
+
<!-- App bridges: intent→domain and derived wildcards -->
|
|
101
|
+
<script type="module" src="./app/bridges.js"></script>
|
|
102
|
+
</body>
|
|
103
|
+
</html>
|