qnce-engine 0.1.0 → 1.2.1
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 +959 -5
- package/dist/cli/audit.js +6 -4
- package/dist/cli/audit.js.map +1 -1
- package/dist/cli/init.js +11 -9
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/perf.d.ts +30 -0
- package/dist/cli/perf.d.ts.map +1 -0
- package/dist/cli/perf.js +220 -0
- package/dist/cli/perf.js.map +1 -0
- package/dist/cli/play.d.ts +4 -0
- package/dist/cli/play.d.ts.map +1 -0
- package/dist/cli/play.js +259 -0
- package/dist/cli/play.js.map +1 -0
- package/dist/engine/condition.d.ts +69 -0
- package/dist/engine/condition.d.ts.map +1 -0
- package/dist/engine/condition.js +195 -0
- package/dist/engine/condition.js.map +1 -0
- package/dist/engine/core.d.ts +378 -11
- package/dist/engine/core.d.ts.map +1 -1
- package/dist/engine/core.js +1433 -13
- package/dist/engine/core.js.map +1 -1
- package/dist/engine/demo-story.d.ts.map +1 -1
- package/dist/engine/demo-story.js +103 -14
- package/dist/engine/demo-story.js.map +1 -1
- package/dist/engine/errors.d.ts +76 -0
- package/dist/engine/errors.d.ts.map +1 -0
- package/dist/engine/errors.js +178 -0
- package/dist/engine/errors.js.map +1 -0
- package/dist/engine/types.d.ts +445 -0
- package/dist/engine/types.d.ts.map +1 -0
- package/dist/engine/types.js +9 -0
- package/dist/engine/types.js.map +1 -0
- package/dist/engine/validation.d.ts +110 -0
- package/dist/engine/validation.d.ts.map +1 -0
- package/dist/engine/validation.js +261 -0
- package/dist/engine/validation.js.map +1 -0
- package/dist/examples/examples/autosave-undo-demo.js +248 -0
- package/dist/examples/examples/persistence-demo.js +63 -0
- package/dist/examples/src/engine/condition.js +194 -0
- package/dist/examples/src/engine/core.js +1382 -0
- package/dist/examples/src/engine/demo-story.js +200 -0
- package/dist/examples/src/engine/types.js +8 -0
- package/dist/examples/src/index.js +35 -0
- package/dist/examples/src/integrations/react.js +322 -0
- package/dist/examples/src/narrative/branching/engine-simple.js +348 -0
- package/dist/examples/src/narrative/branching/index.js +55 -0
- package/dist/examples/src/narrative/branching/models.js +5 -0
- package/dist/examples/src/performance/ObjectPool.js +296 -0
- package/dist/examples/src/performance/PerfReporter.js +280 -0
- package/dist/examples/src/performance/ThreadPool.js +347 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -3
- package/dist/index.js.map +1 -1
- package/dist/integrations/react.d.ts +200 -0
- package/dist/integrations/react.d.ts.map +1 -0
- package/dist/integrations/react.js +365 -0
- package/dist/integrations/react.js.map +1 -0
- package/dist/narrative/branching/engine-simple.d.ts +84 -0
- package/dist/narrative/branching/engine-simple.d.ts.map +1 -0
- package/dist/narrative/branching/engine-simple.js +349 -0
- package/dist/narrative/branching/engine-simple.js.map +1 -0
- package/dist/narrative/branching/engine.d.ts +1 -0
- package/dist/narrative/branching/engine.d.ts.map +1 -0
- package/dist/narrative/branching/engine.js +2 -0
- package/dist/narrative/branching/engine.js.map +1 -0
- package/dist/narrative/branching/index.d.ts +12 -0
- package/dist/narrative/branching/index.d.ts.map +1 -0
- package/dist/narrative/branching/index.js +56 -0
- package/dist/narrative/branching/index.js.map +1 -0
- package/dist/narrative/branching/models.d.ts +223 -0
- package/dist/narrative/branching/models.d.ts.map +1 -0
- package/dist/narrative/branching/models.js +6 -0
- package/dist/narrative/branching/models.js.map +1 -0
- package/dist/performance/HotReloadDelta.d.ts +124 -0
- package/dist/performance/HotReloadDelta.d.ts.map +1 -0
- package/dist/performance/HotReloadDelta.js +328 -0
- package/dist/performance/HotReloadDelta.js.map +1 -0
- package/dist/performance/ObjectPool.d.ts +150 -0
- package/dist/performance/ObjectPool.d.ts.map +1 -0
- package/dist/performance/ObjectPool.js +297 -0
- package/dist/performance/ObjectPool.js.map +1 -0
- package/dist/performance/PerfReporter.d.ts +123 -0
- package/dist/performance/PerfReporter.d.ts.map +1 -0
- package/dist/performance/PerfReporter.js +281 -0
- package/dist/performance/PerfReporter.js.map +1 -0
- package/dist/performance/ThreadPool.d.ts +107 -0
- package/dist/performance/ThreadPool.d.ts.map +1 -0
- package/dist/performance/ThreadPool.js +348 -0
- package/dist/performance/ThreadPool.js.map +1 -0
- package/dist/ui/__tests__/AutosaveIndicator.test.d.ts +2 -0
- package/dist/ui/__tests__/AutosaveIndicator.test.d.ts.map +1 -0
- package/dist/ui/__tests__/AutosaveIndicator.test.js +329 -0
- package/dist/ui/__tests__/AutosaveIndicator.test.js.map +1 -0
- package/dist/ui/__tests__/UndoRedoControls.test.d.ts +2 -0
- package/dist/ui/__tests__/UndoRedoControls.test.d.ts.map +1 -0
- package/dist/ui/__tests__/UndoRedoControls.test.js +245 -0
- package/dist/ui/__tests__/UndoRedoControls.test.js.map +1 -0
- package/dist/ui/__tests__/autosave-simple.test.d.ts +2 -0
- package/dist/ui/__tests__/autosave-simple.test.d.ts.map +1 -0
- package/dist/ui/__tests__/autosave-simple.test.js +29 -0
- package/dist/ui/__tests__/autosave-simple.test.js.map +1 -0
- package/dist/ui/__tests__/setup.d.ts +2 -0
- package/dist/ui/__tests__/setup.d.ts.map +1 -0
- package/dist/ui/__tests__/setup.js +40 -0
- package/dist/ui/__tests__/setup.js.map +1 -0
- package/dist/ui/__tests__/smoke-test.d.ts +2 -0
- package/dist/ui/__tests__/smoke-test.d.ts.map +1 -0
- package/dist/ui/__tests__/smoke-test.js +18 -0
- package/dist/ui/__tests__/smoke-test.js.map +1 -0
- package/dist/ui/__tests__/smoke-test.test.d.ts +2 -0
- package/dist/ui/__tests__/smoke-test.test.d.ts.map +1 -0
- package/dist/ui/__tests__/smoke-test.test.js +18 -0
- package/dist/ui/__tests__/smoke-test.test.js.map +1 -0
- package/dist/ui/__tests__/useKeyboardShortcuts.test.d.ts +2 -0
- package/dist/ui/__tests__/useKeyboardShortcuts.test.d.ts.map +1 -0
- package/dist/ui/__tests__/useKeyboardShortcuts.test.js +374 -0
- package/dist/ui/__tests__/useKeyboardShortcuts.test.js.map +1 -0
- package/dist/ui/components/AutosaveIndicator.d.ts +18 -0
- package/dist/ui/components/AutosaveIndicator.d.ts.map +1 -0
- package/dist/ui/components/AutosaveIndicator.js +175 -0
- package/dist/ui/components/AutosaveIndicator.js.map +1 -0
- package/dist/ui/components/UndoRedoControls.d.ts +16 -0
- package/dist/ui/components/UndoRedoControls.d.ts.map +1 -0
- package/dist/ui/components/UndoRedoControls.js +144 -0
- package/dist/ui/components/UndoRedoControls.js.map +1 -0
- package/dist/ui/hooks/useKeyboardShortcuts.d.ts +22 -0
- package/dist/ui/hooks/useKeyboardShortcuts.d.ts.map +1 -0
- package/dist/ui/hooks/useKeyboardShortcuts.js +162 -0
- package/dist/ui/hooks/useKeyboardShortcuts.js.map +1 -0
- package/dist/ui/index.d.ts +9 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +14 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/types.d.ts +141 -0
- package/dist/ui/types.d.ts.map +1 -0
- package/dist/ui/types.js +51 -0
- package/dist/ui/types.js.map +1 -0
- package/docs/PERFORMANCE.md +355 -0
- package/docs/branching/ERD.md +214 -0
- package/docs/branching/PDM.md +443 -0
- package/docs/branching/RELEASE-v1.2.0.md +169 -0
- package/examples/autosave-undo-demo.ts +306 -0
- package/examples/branching-advanced-demo.ts +339 -0
- package/examples/branching-demo-simple.ts +0 -0
- package/examples/branching-demo.ts +0 -0
- package/examples/branching-quickstart.ts +314 -0
- package/examples/persistence-demo.ts +84 -0
- package/examples/quickstart-demo.js +82 -0
- package/examples/tsconfig.json +13 -0
- package/examples/ui-components-demo.tsx +320 -0
- package/examples/validation-demo-story.json +177 -0
- package/examples/validation-demo.js +163 -0
- package/package.json +41 -8
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// S2-T4: Profiler Event Instrumentation
|
|
3
|
+
// PerfReporter for batched performance event collection and reporting
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.perf = exports.PerfReporter = void 0;
|
|
6
|
+
exports.getPerfReporter = getPerfReporter;
|
|
7
|
+
exports.shutdownPerfReporter = shutdownPerfReporter;
|
|
8
|
+
/**
|
|
9
|
+
* PerfReporter - Batched performance event collection and analysis
|
|
10
|
+
* Designed to work off main thread for minimal performance impact
|
|
11
|
+
*/
|
|
12
|
+
class PerfReporter {
|
|
13
|
+
events = [];
|
|
14
|
+
config;
|
|
15
|
+
flushTimer = null;
|
|
16
|
+
startTime;
|
|
17
|
+
activeSpans = new Map();
|
|
18
|
+
constructor(config = {}) {
|
|
19
|
+
this.config = {
|
|
20
|
+
batchSize: config.batchSize || 100,
|
|
21
|
+
flushInterval: config.flushInterval || 5000, // 5 seconds
|
|
22
|
+
enableBackgroundFlush: config.enableBackgroundFlush !== false,
|
|
23
|
+
maxEventHistory: config.maxEventHistory || 1000,
|
|
24
|
+
enableConsoleOutput: config.enableConsoleOutput || false
|
|
25
|
+
};
|
|
26
|
+
this.startTime = performance.now();
|
|
27
|
+
if (this.config.enableBackgroundFlush) {
|
|
28
|
+
this.startFlushTimer();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Record a performance event
|
|
33
|
+
*/
|
|
34
|
+
record(type, metadata = {}, category = 'engine') {
|
|
35
|
+
const event = {
|
|
36
|
+
id: this.generateEventId(),
|
|
37
|
+
type,
|
|
38
|
+
timestamp: performance.now(),
|
|
39
|
+
metadata,
|
|
40
|
+
category
|
|
41
|
+
};
|
|
42
|
+
this.events.push(event);
|
|
43
|
+
if (this.config.enableConsoleOutput) {
|
|
44
|
+
console.log(`[PERF] ${type}:`, metadata);
|
|
45
|
+
}
|
|
46
|
+
// Auto-flush if batch size reached
|
|
47
|
+
if (this.events.length >= this.config.batchSize) {
|
|
48
|
+
this.flush();
|
|
49
|
+
}
|
|
50
|
+
return event.id;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Start a performance span (for measuring duration)
|
|
54
|
+
*/
|
|
55
|
+
startSpan(type, metadata = {}, category = 'engine') {
|
|
56
|
+
const spanId = this.generateEventId();
|
|
57
|
+
const event = {
|
|
58
|
+
id: spanId,
|
|
59
|
+
type,
|
|
60
|
+
timestamp: performance.now(),
|
|
61
|
+
metadata: { ...metadata, spanStart: true },
|
|
62
|
+
category
|
|
63
|
+
};
|
|
64
|
+
this.activeSpans.set(spanId, event);
|
|
65
|
+
return spanId;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* End a performance span and record the complete event
|
|
69
|
+
*/
|
|
70
|
+
endSpan(spanId, additionalMetadata = {}) {
|
|
71
|
+
const startEvent = this.activeSpans.get(spanId);
|
|
72
|
+
if (!startEvent) {
|
|
73
|
+
console.warn(`[PERF] Span ${spanId} not found`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const endTime = performance.now();
|
|
77
|
+
const duration = endTime - startEvent.timestamp;
|
|
78
|
+
const completeEvent = {
|
|
79
|
+
...startEvent,
|
|
80
|
+
duration,
|
|
81
|
+
metadata: {
|
|
82
|
+
...startEvent.metadata,
|
|
83
|
+
...additionalMetadata,
|
|
84
|
+
spanEnd: true
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
this.events.push(completeEvent);
|
|
88
|
+
this.activeSpans.delete(spanId);
|
|
89
|
+
if (this.config.enableConsoleOutput) {
|
|
90
|
+
console.log(`[PERF] ${startEvent.type} completed in ${duration.toFixed(2)}ms`);
|
|
91
|
+
}
|
|
92
|
+
// Auto-flush if batch size reached
|
|
93
|
+
if (this.events.length >= this.config.batchSize) {
|
|
94
|
+
this.flush();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Record flow start event (S2-T4 requirement)
|
|
99
|
+
*/
|
|
100
|
+
recordFlowStart(nodeId, metadata = {}) {
|
|
101
|
+
return this.startSpan('flow-start', { nodeId, ...metadata }, 'engine');
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Record flow completion event (S2-T4 requirement)
|
|
105
|
+
*/
|
|
106
|
+
recordFlowComplete(spanId, nextNodeId, metadata = {}) {
|
|
107
|
+
this.endSpan(spanId, { nextNodeId, ...metadata });
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Record cache hit event (S2-T4 requirement)
|
|
111
|
+
*/
|
|
112
|
+
recordCacheHit(cacheKey, metadata = {}) {
|
|
113
|
+
return this.record('cache-hit', { cacheKey, ...metadata }, 'cache');
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Record cache miss event (S2-T4 requirement)
|
|
117
|
+
*/
|
|
118
|
+
recordCacheMiss(cacheKey, metadata = {}) {
|
|
119
|
+
return this.record('cache-miss', { cacheKey, ...metadata }, 'cache');
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Record hot-reload start event (S2-T4 requirement)
|
|
123
|
+
*/
|
|
124
|
+
recordHotReloadStart(deltaSize, metadata = {}) {
|
|
125
|
+
return this.startSpan('hot-reload-start', { deltaSize, ...metadata }, 'hot-reload');
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Record hot-reload completion event (S2-T4 requirement)
|
|
129
|
+
*/
|
|
130
|
+
recordHotReloadEnd(spanId, success, metadata = {}) {
|
|
131
|
+
this.endSpan(spanId, { success, ...metadata });
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Generate performance summary for CLI dashboard
|
|
135
|
+
*/
|
|
136
|
+
summary() {
|
|
137
|
+
const now = performance.now();
|
|
138
|
+
const eventsByType = {};
|
|
139
|
+
const durationsByType = {};
|
|
140
|
+
let cacheHits = 0;
|
|
141
|
+
let cacheMisses = 0;
|
|
142
|
+
let hotReloads = [];
|
|
143
|
+
// Analyze events
|
|
144
|
+
for (const event of this.events) {
|
|
145
|
+
// Count events by type
|
|
146
|
+
eventsByType[event.type] = (eventsByType[event.type] || 0) + 1;
|
|
147
|
+
// Collect durations
|
|
148
|
+
if (event.duration !== undefined) {
|
|
149
|
+
if (!durationsByType[event.type]) {
|
|
150
|
+
durationsByType[event.type] = [];
|
|
151
|
+
}
|
|
152
|
+
durationsByType[event.type].push(event.duration);
|
|
153
|
+
}
|
|
154
|
+
// Cache metrics
|
|
155
|
+
if (event.type === 'cache-hit')
|
|
156
|
+
cacheHits++;
|
|
157
|
+
if (event.type === 'cache-miss')
|
|
158
|
+
cacheMisses++;
|
|
159
|
+
// Hot-reload metrics
|
|
160
|
+
if (event.type === 'hot-reload-start' && event.duration !== undefined) {
|
|
161
|
+
hotReloads.push(event.duration);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Calculate averages, mins, maxs
|
|
165
|
+
const avgDurations = {};
|
|
166
|
+
const maxDurations = {};
|
|
167
|
+
const minDurations = {};
|
|
168
|
+
for (const [type, durations] of Object.entries(durationsByType)) {
|
|
169
|
+
if (durations.length > 0) {
|
|
170
|
+
avgDurations[type] = durations.reduce((sum, d) => sum + d, 0) / durations.length;
|
|
171
|
+
maxDurations[type] = Math.max(...durations);
|
|
172
|
+
minDurations[type] = Math.min(...durations);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Cache hit rate
|
|
176
|
+
const totalCacheEvents = cacheHits + cacheMisses;
|
|
177
|
+
const cacheHitRate = totalCacheEvents > 0 ? (cacheHits / totalCacheEvents) * 100 : 0;
|
|
178
|
+
// Hot-reload performance
|
|
179
|
+
const hotReloadPerformance = {
|
|
180
|
+
avgTime: hotReloads.length > 0 ? hotReloads.reduce((sum, d) => sum + d, 0) / hotReloads.length : 0,
|
|
181
|
+
maxTime: hotReloads.length > 0 ? Math.max(...hotReloads) : 0,
|
|
182
|
+
totalReloads: hotReloads.length
|
|
183
|
+
};
|
|
184
|
+
return {
|
|
185
|
+
totalEvents: this.events.length,
|
|
186
|
+
eventsByType,
|
|
187
|
+
avgDurations,
|
|
188
|
+
maxDurations,
|
|
189
|
+
minDurations,
|
|
190
|
+
cacheHitRate,
|
|
191
|
+
hotReloadPerformance,
|
|
192
|
+
timeRange: {
|
|
193
|
+
start: this.startTime,
|
|
194
|
+
end: now,
|
|
195
|
+
duration: now - this.startTime
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Get raw events for detailed analysis
|
|
201
|
+
*/
|
|
202
|
+
getEvents() {
|
|
203
|
+
return [...this.events];
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Clear event history
|
|
207
|
+
*/
|
|
208
|
+
clear() {
|
|
209
|
+
this.events.length = 0;
|
|
210
|
+
this.activeSpans.clear();
|
|
211
|
+
this.startTime = performance.now();
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Flush events to background processing
|
|
215
|
+
*/
|
|
216
|
+
flush() {
|
|
217
|
+
if (this.events.length === 0)
|
|
218
|
+
return;
|
|
219
|
+
// TODO: Body - integrate with ThreadPool for background processing
|
|
220
|
+
// For now, just maintain event history with size limit
|
|
221
|
+
if (this.events.length > this.config.maxEventHistory) {
|
|
222
|
+
const excess = this.events.length - this.config.maxEventHistory;
|
|
223
|
+
this.events.splice(0, excess);
|
|
224
|
+
}
|
|
225
|
+
if (this.config.enableConsoleOutput) {
|
|
226
|
+
console.log(`[PERF] Flushed ${this.events.length} events`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Start automatic flush timer
|
|
231
|
+
*/
|
|
232
|
+
startFlushTimer() {
|
|
233
|
+
this.flushTimer = setInterval(() => {
|
|
234
|
+
this.flush();
|
|
235
|
+
}, this.config.flushInterval);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Stop automatic flush timer
|
|
239
|
+
*/
|
|
240
|
+
stopFlushTimer() {
|
|
241
|
+
if (this.flushTimer) {
|
|
242
|
+
clearInterval(this.flushTimer);
|
|
243
|
+
this.flushTimer = null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Generate unique event ID
|
|
248
|
+
*/
|
|
249
|
+
generateEventId() {
|
|
250
|
+
return `perf-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
exports.PerfReporter = PerfReporter;
|
|
254
|
+
// Singleton instance for global access
|
|
255
|
+
let globalPerfReporter = null;
|
|
256
|
+
function getPerfReporter(config) {
|
|
257
|
+
if (!globalPerfReporter) {
|
|
258
|
+
globalPerfReporter = new PerfReporter(config);
|
|
259
|
+
}
|
|
260
|
+
return globalPerfReporter;
|
|
261
|
+
}
|
|
262
|
+
function shutdownPerfReporter() {
|
|
263
|
+
if (globalPerfReporter) {
|
|
264
|
+
globalPerfReporter.stopFlushTimer();
|
|
265
|
+
globalPerfReporter.flush();
|
|
266
|
+
globalPerfReporter = null;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// Convenience functions for common operations
|
|
270
|
+
exports.perf = {
|
|
271
|
+
flowStart: (nodeId, metadata) => getPerfReporter().recordFlowStart(nodeId, metadata),
|
|
272
|
+
flowComplete: (spanId, nextNodeId, metadata) => getPerfReporter().recordFlowComplete(spanId, nextNodeId, metadata),
|
|
273
|
+
cacheHit: (cacheKey, metadata) => getPerfReporter().recordCacheHit(cacheKey, metadata),
|
|
274
|
+
cacheMiss: (cacheKey, metadata) => getPerfReporter().recordCacheMiss(cacheKey, metadata),
|
|
275
|
+
hotReloadStart: (deltaSize, metadata) => getPerfReporter().recordHotReloadStart(deltaSize, metadata),
|
|
276
|
+
hotReloadEnd: (spanId, success, metadata) => getPerfReporter().recordHotReloadEnd(spanId, success, metadata),
|
|
277
|
+
record: (type, metadata, category) => getPerfReporter().record(type, metadata, category),
|
|
278
|
+
summary: () => getPerfReporter().summary(),
|
|
279
|
+
clear: () => getPerfReporter().clear()
|
|
280
|
+
};
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// S2-T2: Multithreaded Job Scheduler Integration
|
|
3
|
+
// QnceThreadPool for cache loads and telemetry writes off main thread
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.QnceThreadPool = void 0;
|
|
6
|
+
exports.getThreadPool = getThreadPool;
|
|
7
|
+
exports.shutdownThreadPool = shutdownThreadPool;
|
|
8
|
+
/**
|
|
9
|
+
* QnceThreadPool - Background job processing for QNCE engine
|
|
10
|
+
* Handles cache operations, telemetry, and other non-blocking tasks
|
|
11
|
+
*/
|
|
12
|
+
class QnceThreadPool {
|
|
13
|
+
workers = [];
|
|
14
|
+
jobQueue = [];
|
|
15
|
+
activeJobs = new Map();
|
|
16
|
+
config;
|
|
17
|
+
stats;
|
|
18
|
+
isShuttingDown = false;
|
|
19
|
+
constructor(config = {}) {
|
|
20
|
+
this.config = {
|
|
21
|
+
maxWorkers: config.maxWorkers || Math.max(1, Math.floor(navigator?.hardwareConcurrency || 4) / 2),
|
|
22
|
+
queueLimit: config.queueLimit || 100,
|
|
23
|
+
idleTimeout: config.idleTimeout || 30000,
|
|
24
|
+
enableProfiling: config.enableProfiling || false
|
|
25
|
+
};
|
|
26
|
+
this.stats = {
|
|
27
|
+
activeWorkers: 0,
|
|
28
|
+
queuedJobs: 0,
|
|
29
|
+
completedJobs: 0,
|
|
30
|
+
failedJobs: 0,
|
|
31
|
+
avgExecutionTime: 0,
|
|
32
|
+
workerUtilization: 0
|
|
33
|
+
};
|
|
34
|
+
this.initializeWorkers();
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Submit job for background processing
|
|
38
|
+
*/
|
|
39
|
+
async submitJob(type, payload, priority = 'normal') {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
if (this.isShuttingDown) {
|
|
42
|
+
reject(new Error('ThreadPool is shutting down'));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (this.jobQueue.length >= this.config.queueLimit) {
|
|
46
|
+
reject(new Error('Job queue limit exceeded'));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const job = {
|
|
50
|
+
id: `job-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
51
|
+
type,
|
|
52
|
+
priority,
|
|
53
|
+
payload,
|
|
54
|
+
timestamp: performance.now(),
|
|
55
|
+
resolve: resolve,
|
|
56
|
+
reject
|
|
57
|
+
};
|
|
58
|
+
// Insert based on priority (high -> normal -> low)
|
|
59
|
+
const insertIndex = this.findInsertionIndex(priority);
|
|
60
|
+
this.jobQueue.splice(insertIndex, 0, job);
|
|
61
|
+
this.stats.queuedJobs = this.jobQueue.length;
|
|
62
|
+
this.processQueue();
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Cache load operation (S2-T2 primary use case)
|
|
67
|
+
*/
|
|
68
|
+
async loadFromCache(cacheKey, loader) {
|
|
69
|
+
return this.submitJob('cache-load', { cacheKey, loader: loader.toString() }, 'normal');
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Telemetry write operation (S2-T2 primary use case)
|
|
73
|
+
*/
|
|
74
|
+
async writeTelemetry(eventData) {
|
|
75
|
+
return this.submitJob('telemetry-write', eventData, 'low');
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Hot-reload preparation (integration with S2-T3)
|
|
79
|
+
*/
|
|
80
|
+
async prepareHotReload(deltaData) {
|
|
81
|
+
return this.submitJob('hot-reload-prep', deltaData, 'high');
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get current thread pool statistics
|
|
85
|
+
*/
|
|
86
|
+
getStats() {
|
|
87
|
+
return { ...this.stats };
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Graceful shutdown of thread pool
|
|
91
|
+
*/
|
|
92
|
+
async shutdown(timeoutMs = 5000) {
|
|
93
|
+
this.isShuttingDown = true;
|
|
94
|
+
const startTime = Date.now();
|
|
95
|
+
// Wait for active jobs to complete or timeout
|
|
96
|
+
while (this.activeJobs.size > 0 && (Date.now() - startTime) < timeoutMs) {
|
|
97
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
98
|
+
}
|
|
99
|
+
// Terminate all workers
|
|
100
|
+
for (const worker of this.workers) {
|
|
101
|
+
worker.terminate();
|
|
102
|
+
}
|
|
103
|
+
this.workers.length = 0;
|
|
104
|
+
this.stats.activeWorkers = 0;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Initialize worker threads based on environment
|
|
108
|
+
*/
|
|
109
|
+
initializeWorkers() {
|
|
110
|
+
// Browser environment: Use Web Workers
|
|
111
|
+
if (typeof Worker !== 'undefined' && typeof window !== 'undefined') {
|
|
112
|
+
this.initializeWebWorkers();
|
|
113
|
+
}
|
|
114
|
+
// Node.js environment: Use worker_threads
|
|
115
|
+
else if (typeof require !== 'undefined') {
|
|
116
|
+
this.initializeNodeWorkers();
|
|
117
|
+
}
|
|
118
|
+
// Fallback: Simulate workers with setTimeout (for testing)
|
|
119
|
+
else {
|
|
120
|
+
this.initializeFallbackWorkers();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Web Workers for browser environment
|
|
125
|
+
*/
|
|
126
|
+
initializeWebWorkers() {
|
|
127
|
+
const workerCode = this.generateWebWorkerCode();
|
|
128
|
+
const workerBlob = new Blob([workerCode], { type: 'application/javascript' });
|
|
129
|
+
const workerUrl = URL.createObjectURL(workerBlob);
|
|
130
|
+
for (let i = 0; i < this.config.maxWorkers; i++) {
|
|
131
|
+
try {
|
|
132
|
+
const worker = new Worker(workerUrl);
|
|
133
|
+
this.setupWorkerHandlers(worker, i);
|
|
134
|
+
this.workers.push(worker);
|
|
135
|
+
this.stats.activeWorkers++;
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
console.warn(`Failed to create worker ${i}:`, error);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Node.js worker_threads (placeholder - would need actual implementation)
|
|
144
|
+
*/
|
|
145
|
+
initializeNodeWorkers() {
|
|
146
|
+
// TODO: Implement worker_threads for Node.js environment
|
|
147
|
+
// For now, fall back to simulation
|
|
148
|
+
this.initializeFallbackWorkers();
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Fallback simulation for testing/development
|
|
152
|
+
*/
|
|
153
|
+
initializeFallbackWorkers() {
|
|
154
|
+
// Simulate workers with async processing
|
|
155
|
+
for (let i = 0; i < this.config.maxWorkers; i++) {
|
|
156
|
+
const mockWorker = {
|
|
157
|
+
postMessage: (data) => {
|
|
158
|
+
// Simulate async processing
|
|
159
|
+
setTimeout(() => {
|
|
160
|
+
this.handleWorkerMessage({
|
|
161
|
+
data: {
|
|
162
|
+
jobId: data.jobId,
|
|
163
|
+
result: `Processed: ${JSON.stringify(data.payload)}`,
|
|
164
|
+
success: true
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}, Math.random() * 100 + 50); // 50-150ms simulation
|
|
168
|
+
},
|
|
169
|
+
terminate: () => { },
|
|
170
|
+
addEventListener: () => { },
|
|
171
|
+
removeEventListener: () => { }
|
|
172
|
+
};
|
|
173
|
+
this.workers.push(mockWorker);
|
|
174
|
+
this.stats.activeWorkers++;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Generate Web Worker code for browser execution
|
|
179
|
+
*/
|
|
180
|
+
generateWebWorkerCode() {
|
|
181
|
+
return `
|
|
182
|
+
// QNCE Thread Pool Worker
|
|
183
|
+
self.addEventListener('message', function(e) {
|
|
184
|
+
const { jobId, type, payload } = e.data;
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
let result;
|
|
188
|
+
|
|
189
|
+
switch (type) {
|
|
190
|
+
case 'cache-load':
|
|
191
|
+
// Simulate cache loading
|
|
192
|
+
result = processCache(payload);
|
|
193
|
+
break;
|
|
194
|
+
|
|
195
|
+
case 'telemetry-write':
|
|
196
|
+
// Simulate telemetry writing
|
|
197
|
+
result = writeTelemetryData(payload);
|
|
198
|
+
break;
|
|
199
|
+
|
|
200
|
+
case 'hot-reload-prep':
|
|
201
|
+
// Simulate hot-reload preparation
|
|
202
|
+
result = prepareReload(payload);
|
|
203
|
+
break;
|
|
204
|
+
|
|
205
|
+
case 'asset-process':
|
|
206
|
+
// Simulate asset processing
|
|
207
|
+
result = processAsset(payload);
|
|
208
|
+
break;
|
|
209
|
+
|
|
210
|
+
default:
|
|
211
|
+
throw new Error('Unknown job type: ' + type);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
self.postMessage({
|
|
215
|
+
jobId,
|
|
216
|
+
result,
|
|
217
|
+
success: true
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
} catch (error) {
|
|
221
|
+
self.postMessage({
|
|
222
|
+
jobId,
|
|
223
|
+
error: error.message,
|
|
224
|
+
success: false
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
function processCache(payload) {
|
|
230
|
+
// Simulate cache processing work
|
|
231
|
+
const data = JSON.parse(JSON.stringify(payload));
|
|
232
|
+
return { cached: true, data, timestamp: Date.now() };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function writeTelemetryData(payload) {
|
|
236
|
+
// Simulate telemetry write
|
|
237
|
+
return { written: true, bytes: JSON.stringify(payload).length };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function prepareReload(payload) {
|
|
241
|
+
// Simulate hot-reload preparation
|
|
242
|
+
return { prepared: true, deltaSize: JSON.stringify(payload).length };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function processAsset(payload) {
|
|
246
|
+
// Simulate asset processing
|
|
247
|
+
return { processed: true, asset: payload };
|
|
248
|
+
}
|
|
249
|
+
`;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Setup worker message handlers
|
|
253
|
+
*/
|
|
254
|
+
setupWorkerHandlers(worker, workerId) {
|
|
255
|
+
worker.addEventListener('message', (e) => this.handleWorkerMessage(e));
|
|
256
|
+
worker.addEventListener('error', (e) => this.handleWorkerError(e, workerId));
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Handle worker completion messages
|
|
260
|
+
*/
|
|
261
|
+
handleWorkerMessage(event) {
|
|
262
|
+
const { jobId, result, error, success } = event.data;
|
|
263
|
+
const job = this.activeJobs.get(jobId);
|
|
264
|
+
if (!job)
|
|
265
|
+
return;
|
|
266
|
+
this.activeJobs.delete(jobId);
|
|
267
|
+
if (success) {
|
|
268
|
+
job.resolve(result);
|
|
269
|
+
this.stats.completedJobs++;
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
job.reject(new Error(error));
|
|
273
|
+
this.stats.failedJobs++;
|
|
274
|
+
}
|
|
275
|
+
// Update execution time stats
|
|
276
|
+
const executionTime = performance.now() - job.timestamp;
|
|
277
|
+
this.updateExecutionTimeStats(executionTime);
|
|
278
|
+
// Process next job in queue
|
|
279
|
+
this.processQueue();
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Handle worker errors
|
|
283
|
+
*/
|
|
284
|
+
handleWorkerError(error, workerId) {
|
|
285
|
+
console.error(`Worker ${workerId} error:`, error);
|
|
286
|
+
// TODO: Implement worker recovery/restart logic
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Process job queue by assigning jobs to available workers
|
|
290
|
+
*/
|
|
291
|
+
processQueue() {
|
|
292
|
+
if (this.jobQueue.length === 0)
|
|
293
|
+
return;
|
|
294
|
+
const availableWorkers = this.config.maxWorkers - this.activeJobs.size;
|
|
295
|
+
if (availableWorkers <= 0)
|
|
296
|
+
return;
|
|
297
|
+
const job = this.jobQueue.shift();
|
|
298
|
+
this.stats.queuedJobs = this.jobQueue.length;
|
|
299
|
+
this.activeJobs.set(job.id, job);
|
|
300
|
+
// Find least busy worker (for now, just use round-robin)
|
|
301
|
+
const workerIndex = this.stats.completedJobs % this.workers.length;
|
|
302
|
+
const worker = this.workers[workerIndex];
|
|
303
|
+
worker.postMessage({
|
|
304
|
+
jobId: job.id,
|
|
305
|
+
type: job.type,
|
|
306
|
+
payload: job.payload
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Find insertion index for job based on priority
|
|
311
|
+
*/
|
|
312
|
+
findInsertionIndex(priority) {
|
|
313
|
+
const priorityValues = { high: 3, normal: 2, low: 1 };
|
|
314
|
+
const jobPriority = priorityValues[priority];
|
|
315
|
+
for (let i = 0; i < this.jobQueue.length; i++) {
|
|
316
|
+
if (priorityValues[this.jobQueue[i].priority] < jobPriority) {
|
|
317
|
+
return i;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return this.jobQueue.length;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Update execution time statistics
|
|
324
|
+
*/
|
|
325
|
+
updateExecutionTimeStats(executionTime) {
|
|
326
|
+
const totalJobs = this.stats.completedJobs + this.stats.failedJobs;
|
|
327
|
+
this.stats.avgExecutionTime = ((this.stats.avgExecutionTime * (totalJobs - 1)) + executionTime) / totalJobs;
|
|
328
|
+
this.stats.workerUtilization = (this.activeJobs.size / this.config.maxWorkers) * 100;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
exports.QnceThreadPool = QnceThreadPool;
|
|
332
|
+
// Singleton instance for global access
|
|
333
|
+
let globalThreadPool = null;
|
|
334
|
+
function getThreadPool(config) {
|
|
335
|
+
if (!globalThreadPool) {
|
|
336
|
+
globalThreadPool = new QnceThreadPool(config);
|
|
337
|
+
}
|
|
338
|
+
return globalThreadPool;
|
|
339
|
+
}
|
|
340
|
+
function shutdownThreadPool() {
|
|
341
|
+
if (globalThreadPool) {
|
|
342
|
+
const shutdown = globalThreadPool.shutdown();
|
|
343
|
+
globalThreadPool = null;
|
|
344
|
+
return shutdown;
|
|
345
|
+
}
|
|
346
|
+
return Promise.resolve();
|
|
347
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export * from './engine/core.js';
|
|
2
2
|
export { DEMO_STORY } from './engine/demo-story.js';
|
|
3
|
+
export * from './engine/condition.js';
|
|
4
|
+
export * from './integrations/react.js';
|
|
5
|
+
export * from './ui/index.js';
|
|
3
6
|
export { QNCEEngine, createQNCEEngine, loadStoryData, type Choice, type NarrativeNode, type QNCEState, type StoryData } from './engine/core.js';
|
|
7
|
+
export { ConditionEvaluator, ConditionEvaluationError, conditionEvaluator, type ConditionContext, type CustomEvaluatorFunction } from './engine/condition.js';
|
|
4
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAGpD,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,aAAa,EACb,KAAK,MAAM,EACX,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAGpD,cAAc,uBAAuB,CAAC;AAGtC,cAAc,yBAAyB,CAAC;AAGxC,cAAc,eAAe,CAAC;AAG9B,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,aAAa,EACb,KAAK,MAAM,EACX,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,kBAAkB,EAClB,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAC7B,MAAM,uBAAuB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.conditionEvaluator = exports.ConditionEvaluationError = exports.ConditionEvaluator = exports.loadStoryData = exports.createQNCEEngine = exports.QNCEEngine = exports.DEMO_STORY = void 0;
|
|
1
18
|
// QNCE Engine - Main Export
|
|
2
|
-
|
|
3
|
-
|
|
19
|
+
__exportStar(require("./engine/core.js"), exports);
|
|
20
|
+
var demo_story_js_1 = require("./engine/demo-story.js");
|
|
21
|
+
Object.defineProperty(exports, "DEMO_STORY", { enumerable: true, get: function () { return demo_story_js_1.DEMO_STORY; } });
|
|
22
|
+
// Sprint 3.4: Conditional choice display exports
|
|
23
|
+
__exportStar(require("./engine/condition.js"), exports);
|
|
24
|
+
// Sprint 3.5: React integration for autosave/undo functionality
|
|
25
|
+
__exportStar(require("./integrations/react.js"), exports);
|
|
26
|
+
// Sprint 3.6: UI Components
|
|
27
|
+
__exportStar(require("./ui/index.js"), exports);
|
|
4
28
|
// Re-export for convenience
|
|
5
|
-
|
|
29
|
+
var core_js_1 = require("./engine/core.js");
|
|
30
|
+
Object.defineProperty(exports, "QNCEEngine", { enumerable: true, get: function () { return core_js_1.QNCEEngine; } });
|
|
31
|
+
Object.defineProperty(exports, "createQNCEEngine", { enumerable: true, get: function () { return core_js_1.createQNCEEngine; } });
|
|
32
|
+
Object.defineProperty(exports, "loadStoryData", { enumerable: true, get: function () { return core_js_1.loadStoryData; } });
|
|
33
|
+
// Sprint 3.4: Conditional choice types
|
|
34
|
+
var condition_js_1 = require("./engine/condition.js");
|
|
35
|
+
Object.defineProperty(exports, "ConditionEvaluator", { enumerable: true, get: function () { return condition_js_1.ConditionEvaluator; } });
|
|
36
|
+
Object.defineProperty(exports, "ConditionEvaluationError", { enumerable: true, get: function () { return condition_js_1.ConditionEvaluationError; } });
|
|
37
|
+
Object.defineProperty(exports, "conditionEvaluator", { enumerable: true, get: function () { return condition_js_1.conditionEvaluator; } });
|
|
6
38
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAC5B,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,4BAA4B;AAC5B,mDAAiC;AACjC,wDAAoD;AAA3C,2GAAA,UAAU,OAAA;AAEnB,iDAAiD;AACjD,wDAAsC;AAEtC,gEAAgE;AAChE,0DAAwC;AAExC,4BAA4B;AAC5B,gDAA8B;AAE9B,4BAA4B;AAC5B,4CAQ0B;AAPxB,qGAAA,UAAU,OAAA;AACV,2GAAA,gBAAgB,OAAA;AAChB,wGAAA,aAAa,OAAA;AAOf,uCAAuC;AACvC,sDAM+B;AAL7B,kHAAA,kBAAkB,OAAA;AAClB,wHAAA,wBAAwB,OAAA;AACxB,kHAAA,kBAAkB,OAAA"}
|