plug-code 1.2.0 → 2.0.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 +135 -94
- package/dist/core/helpers/core.d.ts +2 -0
- package/dist/core/helpers/core.js +32 -0
- package/dist/core/hooks/plcHooks.d.ts +15 -0
- package/dist/core/hooks/plcHooks.js +47 -0
- package/dist/core/plcAPI.d.ts +83 -0
- package/dist/core/plcAPI.js +472 -0
- package/dist/core/plcPipeline.d.ts +8 -0
- package/dist/core/plcPipeline.js +35 -0
- package/dist/core/plcScheduler.d.ts +7 -0
- package/dist/core/plcScheduler.js +22 -0
- package/dist/core/plcStore.d.ts +33 -0
- package/dist/core/plcStore.js +159 -0
- package/dist/core/ui/plcCore.d.ts +8 -0
- package/dist/core/ui/plcCore.js +40 -0
- package/dist/core/ui/plcErrorBoundary.d.ts +17 -0
- package/dist/core/ui/plcErrorBoundary.js +17 -0
- package/dist/core/ui/plcInspector.d.ts +5 -0
- package/dist/core/ui/plcInspector.js +27 -0
- package/dist/core/ui/plcLayout.d.ts +28 -0
- package/dist/core/ui/plcLayout.js +125 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/types/core/api.d.ts +7 -0
- package/dist/types/core/api.js +1 -0
- package/dist/types/core/general.d.ts +15 -0
- package/dist/types/core/general.js +1 -0
- package/dist/types/core/registry.d.ts +7 -0
- package/dist/types/core/registry.js +1 -0
- package/dist/types/core/ui.d.ts +7 -0
- package/dist/types/core/ui.js +1 -0
- package/dist/types/registry.d.ts +20 -0
- package/dist/types/registry.js +1 -0
- package/package.json +16 -22
- package/index.d.ts +0 -1
- package/src/contexts/pipeline.tsx +0 -4
- package/src/core/plcAPI.tsx +0 -393
- package/src/core/plcPipeline.tsx +0 -87
- package/src/core/plcStore.tsx +0 -94
- package/src/helpers/core.ts +0 -10
- package/src/plug-code.tsx +0 -64
- package/src/types/api.ts +0 -8
- package/src/types/features.ts +0 -7
- package/src/types/general.ts +0 -2
- package/src/types/pipeline.ts +0 -3
- package/src/types/registry.ts +0 -4
- package/src/types/store.ts +0 -2
- package/tsconfig.json +0 -15
- package/types/plug-code.d.ts +0 -164
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { isEqual, shallowCompare } from "./helpers/core";
|
|
3
|
+
import { PlcPipeline } from "./plcPipeline";
|
|
4
|
+
import { LRUCache } from "./plcStore";
|
|
5
|
+
import { Scheduler } from "./plcScheduler";
|
|
6
|
+
import { PlcLayout } from "./ui/plcLayout";
|
|
7
|
+
export class PlcAPI {
|
|
8
|
+
store;
|
|
9
|
+
pipeline;
|
|
10
|
+
scheduler;
|
|
11
|
+
layout;
|
|
12
|
+
compiledPipelines = new Map();
|
|
13
|
+
receiveCache = new LRUCache(300);
|
|
14
|
+
// Comandos registry
|
|
15
|
+
commands = new Map();
|
|
16
|
+
activeDependencyTracker = null;
|
|
17
|
+
// Loaded features manager
|
|
18
|
+
loadedFeatures = new Set();
|
|
19
|
+
constructor(store) {
|
|
20
|
+
this.store = store;
|
|
21
|
+
this.pipeline = new PlcPipeline();
|
|
22
|
+
this.scheduler = new Scheduler();
|
|
23
|
+
this.layout = new PlcLayout();
|
|
24
|
+
}
|
|
25
|
+
// ----------------------
|
|
26
|
+
// Root store
|
|
27
|
+
// ----------------------
|
|
28
|
+
createStore(key, initial) {
|
|
29
|
+
this.store.createStore(key, initial);
|
|
30
|
+
}
|
|
31
|
+
getStore(key) {
|
|
32
|
+
const value = this.store.getStore(key);
|
|
33
|
+
this.recordDependency(key, value);
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
setStore(key, updater, priority = "MED", useTransition = false) {
|
|
37
|
+
this.scheduler.schedule(() => {
|
|
38
|
+
if (useTransition) {
|
|
39
|
+
React.startTransition(() => {
|
|
40
|
+
this.store.setStore(key, updater);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
this.store.setStore(key, updater);
|
|
45
|
+
}
|
|
46
|
+
}, priority);
|
|
47
|
+
}
|
|
48
|
+
// ----------------------
|
|
49
|
+
// Substores
|
|
50
|
+
// ----------------------
|
|
51
|
+
createSubstore(substore, key, initial) {
|
|
52
|
+
this.store.createSubstore(substore, key, initial);
|
|
53
|
+
}
|
|
54
|
+
getSubstore(substore, key) {
|
|
55
|
+
const value = this.store.getSubstore(substore, key);
|
|
56
|
+
this.recordDependency(`${substore}:${key}`, value);
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
setSubstore(substore, key, updater, priority = "MED", useTransition = false) {
|
|
60
|
+
this.scheduler.schedule(() => {
|
|
61
|
+
if (useTransition) {
|
|
62
|
+
React.startTransition(() => {
|
|
63
|
+
this.store.setSubstore(substore, key, updater);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
this.store.setSubstore(substore, key, updater);
|
|
68
|
+
}
|
|
69
|
+
}, priority);
|
|
70
|
+
}
|
|
71
|
+
// ----------------------
|
|
72
|
+
// Dependency tracking
|
|
73
|
+
// ----------------------
|
|
74
|
+
recordDependency(key, value) {
|
|
75
|
+
if (this.activeDependencyTracker)
|
|
76
|
+
this.activeDependencyTracker.set(key, value);
|
|
77
|
+
}
|
|
78
|
+
// ----------------------
|
|
79
|
+
// Transforms
|
|
80
|
+
// ----------------------
|
|
81
|
+
makeTransform(channel, id, fn, priority = 0) {
|
|
82
|
+
const t = { id, priority, fn: (d, c) => fn(d, c) };
|
|
83
|
+
this.pipeline.registerTransform(channel, t);
|
|
84
|
+
this.compiledPipelines.delete(channel);
|
|
85
|
+
this.receiveCache.delete(channel);
|
|
86
|
+
}
|
|
87
|
+
async getTransform(channel, initialData, context = {}, opts) {
|
|
88
|
+
const channelStr = channel;
|
|
89
|
+
const cached = this.receiveCache.get(channelStr);
|
|
90
|
+
const equality = opts?.equality ?? 'identity';
|
|
91
|
+
if (cached) {
|
|
92
|
+
const sameInput = equality === 'identity' ? cached.input === initialData : isEqual(cached.input, initialData);
|
|
93
|
+
const sameContext = shallowCompare(cached.contextArgs, context);
|
|
94
|
+
if (sameInput && sameContext) {
|
|
95
|
+
let depsChanged = false;
|
|
96
|
+
for (const [key, lastValue] of cached.dependencies) {
|
|
97
|
+
let current;
|
|
98
|
+
if (key.includes(":")) {
|
|
99
|
+
const [substore, subKey] = key.split(":");
|
|
100
|
+
current = this.store.getSubstore(substore, subKey);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
current = this.store.getStore(key);
|
|
104
|
+
}
|
|
105
|
+
if (current !== lastValue) {
|
|
106
|
+
depsChanged = true;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!depsChanged)
|
|
111
|
+
return cached.result;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
let runner = this.compiledPipelines.get(channelStr);
|
|
115
|
+
if (!runner) {
|
|
116
|
+
const compiled = this.pipeline.compilePipeline(channelStr);
|
|
117
|
+
this.compiledPipelines.set(channelStr, compiled);
|
|
118
|
+
runner = compiled;
|
|
119
|
+
}
|
|
120
|
+
const prevTracker = this.activeDependencyTracker;
|
|
121
|
+
const currentTracker = new Map();
|
|
122
|
+
this.activeDependencyTracker = currentTracker;
|
|
123
|
+
let result;
|
|
124
|
+
try {
|
|
125
|
+
result = await this.store.runWithDependencyTracker(currentTracker, async () => {
|
|
126
|
+
return await runner(initialData, context);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
this.activeDependencyTracker = prevTracker;
|
|
131
|
+
}
|
|
132
|
+
this.receiveCache.set(channelStr, {
|
|
133
|
+
input: initialData,
|
|
134
|
+
result,
|
|
135
|
+
dependencies: currentTracker,
|
|
136
|
+
contextArgs: context
|
|
137
|
+
});
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
receive(channel, initialData, context = {}) {
|
|
141
|
+
const channelStr = channel;
|
|
142
|
+
const runner = this.compiledPipelines.get(channelStr);
|
|
143
|
+
if (!runner)
|
|
144
|
+
throw new Error("[PlcAPI] Pipeline not compiled yet. Use getTransform (async).");
|
|
145
|
+
const res = runner(initialData, context);
|
|
146
|
+
if (res && typeof res.then === 'function')
|
|
147
|
+
throw new Error("[PlcAPI] Pipeline contains async transforms; use getTransform (async).");
|
|
148
|
+
return res;
|
|
149
|
+
}
|
|
150
|
+
// ----------------------
|
|
151
|
+
// Command System (SOLUCIÓN AQUI)
|
|
152
|
+
// ----------------------
|
|
153
|
+
registerCommand(id, fn) {
|
|
154
|
+
if (this.commands.has(id)) {
|
|
155
|
+
console.warn(`[PlcAPI] Overwriting command '${id}'`);
|
|
156
|
+
}
|
|
157
|
+
this.commands.set(id, fn);
|
|
158
|
+
}
|
|
159
|
+
wrapCommand(id, wrapper) {
|
|
160
|
+
const currentFn = this.commands.get(id);
|
|
161
|
+
if (!currentFn) {
|
|
162
|
+
console.error(`[PlcAPI] Cannot wrap '${id}', command does not exist.`);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
this.commands.set(id, wrapper(currentFn));
|
|
166
|
+
}
|
|
167
|
+
async execute(id, payload) {
|
|
168
|
+
const fn = this.commands.get(id);
|
|
169
|
+
if (!fn) {
|
|
170
|
+
throw new Error(`[PlcAPI] Command '${id}' not found.`);
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
return await fn(payload);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
console.error(`[PlcAPI] Error executing '${id}':`, error);
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// ----------------------
|
|
181
|
+
// Derives
|
|
182
|
+
// ----------------------
|
|
183
|
+
deriveStore(outputKey, outputSlot, dependencies, calculator) {
|
|
184
|
+
let lastInputs = null;
|
|
185
|
+
let lastOutput;
|
|
186
|
+
let isComputing = false;
|
|
187
|
+
let scheduled = false;
|
|
188
|
+
const scheduleUpdate = () => {
|
|
189
|
+
if (scheduled)
|
|
190
|
+
return;
|
|
191
|
+
scheduled = true;
|
|
192
|
+
Promise.resolve().then(() => {
|
|
193
|
+
scheduled = false;
|
|
194
|
+
this.store.batch(runCompute);
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
const runCompute = () => {
|
|
198
|
+
if (isComputing)
|
|
199
|
+
return;
|
|
200
|
+
isComputing = true;
|
|
201
|
+
try {
|
|
202
|
+
const inputs = dependencies.map(dep => this.getStore(dep));
|
|
203
|
+
if (lastInputs && inputs.every((v, i) => isEqual(v, lastInputs[i])))
|
|
204
|
+
return;
|
|
205
|
+
const result = calculator(...inputs);
|
|
206
|
+
if (isEqual(result, lastOutput)) {
|
|
207
|
+
lastInputs = inputs;
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
lastInputs = inputs;
|
|
211
|
+
lastOutput = result;
|
|
212
|
+
this.setStore(outputSlot, (draft) => {
|
|
213
|
+
if (!draft)
|
|
214
|
+
return { [String(outputKey)]: result };
|
|
215
|
+
draft[String(outputKey)] = result;
|
|
216
|
+
return;
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
finally {
|
|
220
|
+
isComputing = false;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
runCompute();
|
|
224
|
+
const unsubscribers = dependencies.map(dep => this.store.subscribe(dep, scheduleUpdate));
|
|
225
|
+
return {
|
|
226
|
+
getValue: () => lastOutput,
|
|
227
|
+
trigger: scheduleUpdate,
|
|
228
|
+
dispose: () => unsubscribers.forEach(u => u())
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
deriveSubstore(substore, outputKey, outputSlot, dependencies, calculator) {
|
|
232
|
+
let lastInputs = null;
|
|
233
|
+
let lastOutput;
|
|
234
|
+
let isComputing = false;
|
|
235
|
+
let scheduled = false;
|
|
236
|
+
const scheduleUpdate = () => {
|
|
237
|
+
if (scheduled)
|
|
238
|
+
return;
|
|
239
|
+
scheduled = true;
|
|
240
|
+
Promise.resolve().then(() => {
|
|
241
|
+
scheduled = false;
|
|
242
|
+
this.store.batch(runCompute);
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
const runCompute = () => {
|
|
246
|
+
if (isComputing)
|
|
247
|
+
return;
|
|
248
|
+
isComputing = true;
|
|
249
|
+
try {
|
|
250
|
+
const inputs = dependencies.map(dep => this.getSubstore(substore, dep));
|
|
251
|
+
if (lastInputs && inputs.every((v, i) => isEqual(v, lastInputs[i])))
|
|
252
|
+
return;
|
|
253
|
+
const result = calculator(...inputs);
|
|
254
|
+
if (isEqual(result, lastOutput)) {
|
|
255
|
+
lastInputs = inputs;
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
lastInputs = inputs;
|
|
259
|
+
lastOutput = result;
|
|
260
|
+
this.setSubstore(substore, outputSlot, (draft) => {
|
|
261
|
+
if (!draft)
|
|
262
|
+
return { [String(outputKey)]: result };
|
|
263
|
+
draft[String(outputKey)] = result;
|
|
264
|
+
return;
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
finally {
|
|
268
|
+
isComputing = false;
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
runCompute();
|
|
272
|
+
const unsubscribers = dependencies.map(dep => this.store.subscribe(`${substore}:${dep}`, scheduleUpdate));
|
|
273
|
+
return {
|
|
274
|
+
getValue: () => lastOutput,
|
|
275
|
+
trigger: scheduleUpdate,
|
|
276
|
+
dispose: () => unsubscribers.forEach(u => u())
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
// ----------------------
|
|
280
|
+
// Watchers
|
|
281
|
+
// ----------------------
|
|
282
|
+
_triggerVersion = new Map();
|
|
283
|
+
bumpVersion(key) {
|
|
284
|
+
this._triggerVersion.set(key, (this._triggerVersion.get(key) || 0) + 1);
|
|
285
|
+
}
|
|
286
|
+
watch(key, selector, callback) {
|
|
287
|
+
if (key.includes(":")) {
|
|
288
|
+
const [feature, subKey] = key.split(":");
|
|
289
|
+
return this.watchSubstore(feature, subKey, selector, callback);
|
|
290
|
+
}
|
|
291
|
+
return this.watchStore(key, selector, callback);
|
|
292
|
+
}
|
|
293
|
+
watchStore(storeKey, selector, callback) {
|
|
294
|
+
let prevVersion = this._triggerVersion.get(storeKey) || 0;
|
|
295
|
+
let prevValue = selector(this.getStore(storeKey));
|
|
296
|
+
const handleChange = () => {
|
|
297
|
+
const version = this._triggerVersion.get(storeKey) || 0;
|
|
298
|
+
const newValue = selector(this.getStore(storeKey));
|
|
299
|
+
if (version !== prevVersion || !isEqual(newValue, prevValue)) {
|
|
300
|
+
const old = prevValue;
|
|
301
|
+
prevValue = newValue;
|
|
302
|
+
prevVersion = version;
|
|
303
|
+
callback(newValue, old);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
return this.store.subscribe(storeKey, handleChange);
|
|
307
|
+
}
|
|
308
|
+
watchSubstore(substore, key, selector, callback) {
|
|
309
|
+
const storeKey = `${substore}:${key}`;
|
|
310
|
+
let prevVersion = this._triggerVersion.get(storeKey) || 0;
|
|
311
|
+
let prevValue = selector(this.getSubstore(substore, key));
|
|
312
|
+
const handleChange = () => {
|
|
313
|
+
const version = this._triggerVersion.get(storeKey) || 0;
|
|
314
|
+
const newValue = selector(this.getSubstore(substore, key));
|
|
315
|
+
if (version !== prevVersion || !isEqual(newValue, prevValue)) {
|
|
316
|
+
const old = prevValue;
|
|
317
|
+
prevValue = newValue;
|
|
318
|
+
prevVersion = version;
|
|
319
|
+
callback(newValue, old);
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
return this.store.subscribe(storeKey, handleChange);
|
|
323
|
+
}
|
|
324
|
+
watchAllStores(definitions, callback) {
|
|
325
|
+
const getCombinedState = () => {
|
|
326
|
+
let result = {};
|
|
327
|
+
for (const def of definitions) {
|
|
328
|
+
const slice = def.substore
|
|
329
|
+
? def.selector(this.getSubstore(def.substore, def.store))
|
|
330
|
+
: def.selector(this.getStore(def.store));
|
|
331
|
+
result = { ...result, ...slice };
|
|
332
|
+
}
|
|
333
|
+
return result;
|
|
334
|
+
};
|
|
335
|
+
let prevCombined = getCombinedState();
|
|
336
|
+
const handleChange = () => {
|
|
337
|
+
const currentCombined = getCombinedState();
|
|
338
|
+
if (!isEqual(prevCombined, currentCombined)) {
|
|
339
|
+
const old = prevCombined;
|
|
340
|
+
prevCombined = currentCombined;
|
|
341
|
+
callback(currentCombined, old);
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
const unsubscribers = definitions.map(def => {
|
|
345
|
+
const key = def.substore ? `${def.substore}:${def.store}` : def.store;
|
|
346
|
+
return this.store.subscribe(key, handleChange);
|
|
347
|
+
});
|
|
348
|
+
return () => unsubscribers.forEach(u => u());
|
|
349
|
+
}
|
|
350
|
+
// ----------------------
|
|
351
|
+
// Redraw
|
|
352
|
+
// ----------------------
|
|
353
|
+
redraw(keyOrSlot) {
|
|
354
|
+
this.bumpVersion(keyOrSlot);
|
|
355
|
+
this.layout.invalidate(keyOrSlot);
|
|
356
|
+
const currentRoot = this.store.getStore(keyOrSlot);
|
|
357
|
+
if (currentRoot !== undefined) {
|
|
358
|
+
this.store.setStore(keyOrSlot, (d) => d);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
// ----------------------
|
|
362
|
+
// UI
|
|
363
|
+
// ----------------------
|
|
364
|
+
register(slot, id, componentFn, priority = 0, keepAlive = false) {
|
|
365
|
+
this.layout.register(slot, id, componentFn, priority, keepAlive);
|
|
366
|
+
}
|
|
367
|
+
wrap(slot, wrapper) {
|
|
368
|
+
this.layout.wrap(slot, wrapper);
|
|
369
|
+
}
|
|
370
|
+
after(slot, targetId, newId, componentFn) {
|
|
371
|
+
const targetPriority = this.layout.getPriority(slot, targetId);
|
|
372
|
+
let newPriority;
|
|
373
|
+
if (targetPriority !== undefined) {
|
|
374
|
+
newPriority = targetPriority - 1;
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
console.warn(`[PlcAPI] 'after': Target '${targetId}' not found in slot '${slot}'. Registering '${newId}' at the end.`);
|
|
378
|
+
newPriority = -999;
|
|
379
|
+
}
|
|
380
|
+
this.layout.register(slot, newId, componentFn, newPriority);
|
|
381
|
+
}
|
|
382
|
+
render(slot, props) {
|
|
383
|
+
return this.layout.render(slot, props);
|
|
384
|
+
}
|
|
385
|
+
markVirtual(slot, config) {
|
|
386
|
+
this.layout.markVirtual(slot, config);
|
|
387
|
+
}
|
|
388
|
+
connect(renderFn, dependencies) {
|
|
389
|
+
return (props) => {
|
|
390
|
+
const [, forceUpdate] = React.useReducer(x => x + 1, 0);
|
|
391
|
+
const prevValuesRef = React.useRef(new Map());
|
|
392
|
+
React.useEffect(() => {
|
|
393
|
+
const unsubscribers = [];
|
|
394
|
+
dependencies.forEach(dep => {
|
|
395
|
+
const keyStr = typeof dep === 'string' ? dep : dep.store;
|
|
396
|
+
const getValue = () => typeof dep === 'string'
|
|
397
|
+
? this.getStore(dep)
|
|
398
|
+
: dep.store.includes(":")
|
|
399
|
+
? this.store.getSubstore(...dep.store.split(":"))
|
|
400
|
+
: this.getStore(dep.store);
|
|
401
|
+
prevValuesRef.current.set(keyStr, getValue());
|
|
402
|
+
unsubscribers.push(this.store.subscribe(keyStr, () => {
|
|
403
|
+
const newVal = getValue();
|
|
404
|
+
if (!isEqual(prevValuesRef.current.get(keyStr), newVal)) {
|
|
405
|
+
prevValuesRef.current.set(keyStr, newVal);
|
|
406
|
+
forceUpdate();
|
|
407
|
+
}
|
|
408
|
+
}));
|
|
409
|
+
});
|
|
410
|
+
return () => unsubscribers.forEach(u => u());
|
|
411
|
+
}, []);
|
|
412
|
+
return renderFn(props);
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
// ----------------------
|
|
416
|
+
// Modules
|
|
417
|
+
// ----------------------
|
|
418
|
+
registerModule(manifest) {
|
|
419
|
+
if (this.loadedFeatures.has(manifest.name))
|
|
420
|
+
return;
|
|
421
|
+
if (manifest.state) {
|
|
422
|
+
Object.entries(manifest.state).forEach(([key, val]) => {
|
|
423
|
+
this.createSubstore(manifest.name, key, val);
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
if (manifest.commands) {
|
|
427
|
+
Object.entries(manifest.commands).forEach(([cmdId, fn]) => {
|
|
428
|
+
const fullId = cmdId.includes(":") ? cmdId : `${manifest.name}:${cmdId}`;
|
|
429
|
+
this.commands.set(fullId, fn);
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
if (manifest.slots) {
|
|
433
|
+
Object.entries(manifest.slots).forEach(([slotName, components]) => {
|
|
434
|
+
components.forEach(comp => {
|
|
435
|
+
this.register(slotName, comp.id, comp.component, comp.priority, comp.keepAlive);
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
if (manifest.onLoad) {
|
|
440
|
+
manifest.onLoad(this);
|
|
441
|
+
}
|
|
442
|
+
this.loadedFeatures.add(manifest.name);
|
|
443
|
+
console.debug(`[PlcAPI] Feature loaded: ${manifest.name}`);
|
|
444
|
+
}
|
|
445
|
+
async loadFeature(importer) {
|
|
446
|
+
try {
|
|
447
|
+
const module = await importer();
|
|
448
|
+
this.registerModule(module.default);
|
|
449
|
+
}
|
|
450
|
+
catch (err) {
|
|
451
|
+
console.error(`[PlcAPI] Failed to load feature`, err);
|
|
452
|
+
throw err;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
// ----------------------
|
|
456
|
+
// Selector
|
|
457
|
+
// ----------------------
|
|
458
|
+
createSelector(extractor, calculator) {
|
|
459
|
+
let lastArgs = null;
|
|
460
|
+
let lastResult = null;
|
|
461
|
+
return (state) => {
|
|
462
|
+
const args = extractor(state);
|
|
463
|
+
if (lastArgs && args.length === lastArgs.length && args.every((val, i) => val === lastArgs[i])) {
|
|
464
|
+
return lastResult;
|
|
465
|
+
}
|
|
466
|
+
const result = calculator(...args);
|
|
467
|
+
lastArgs = args;
|
|
468
|
+
lastResult = result;
|
|
469
|
+
return result;
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { transformerType } from "../types/core/api";
|
|
2
|
+
export declare class PlcPipeline {
|
|
3
|
+
private transforms;
|
|
4
|
+
registerTransform(channel: string, t: transformerType): void;
|
|
5
|
+
removeTransform(channel: string, id: string): void;
|
|
6
|
+
getTransforms(channel: string): transformerType[];
|
|
7
|
+
compilePipeline(channel: string): (input: any, ctx: any) => Promise<any>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export class PlcPipeline {
|
|
2
|
+
transforms = new Map();
|
|
3
|
+
registerTransform(channel, t) {
|
|
4
|
+
if (!this.transforms.has(channel))
|
|
5
|
+
this.transforms.set(channel, []);
|
|
6
|
+
const list = this.transforms.get(channel);
|
|
7
|
+
const idx = list.findIndex(x => x.id === t.id);
|
|
8
|
+
if (idx >= 0)
|
|
9
|
+
list[idx] = t;
|
|
10
|
+
else
|
|
11
|
+
list.push(t);
|
|
12
|
+
list.sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
|
|
13
|
+
}
|
|
14
|
+
removeTransform(channel, id) {
|
|
15
|
+
const list = this.transforms.get(channel);
|
|
16
|
+
if (!list)
|
|
17
|
+
return;
|
|
18
|
+
const idx = list.findIndex(x => x.id === id);
|
|
19
|
+
if (idx >= 0)
|
|
20
|
+
list.splice(idx, 1);
|
|
21
|
+
}
|
|
22
|
+
getTransforms(channel) {
|
|
23
|
+
return this.transforms.get(channel) || [];
|
|
24
|
+
}
|
|
25
|
+
compilePipeline(channel) {
|
|
26
|
+
const list = this.getTransforms(channel);
|
|
27
|
+
return async (input, ctx) => {
|
|
28
|
+
let data = input;
|
|
29
|
+
for (const t of list) {
|
|
30
|
+
data = await t.fn(data, ctx);
|
|
31
|
+
}
|
|
32
|
+
return data;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export class Scheduler {
|
|
2
|
+
isFlushing = false;
|
|
3
|
+
queue = new Map([
|
|
4
|
+
["HIGH", new Set()],
|
|
5
|
+
["MED", new Set()],
|
|
6
|
+
["LOW", new Set()],
|
|
7
|
+
]);
|
|
8
|
+
schedule(fn, priority = "MED") {
|
|
9
|
+
this.queue.get(priority).add(fn);
|
|
10
|
+
if (!this.isFlushing)
|
|
11
|
+
this.flush();
|
|
12
|
+
}
|
|
13
|
+
flush() {
|
|
14
|
+
this.isFlushing = true;
|
|
15
|
+
for (const level of ["HIGH", "MED", "LOW"]) {
|
|
16
|
+
const set = this.queue.get(level);
|
|
17
|
+
set.forEach(fn => fn());
|
|
18
|
+
set.clear();
|
|
19
|
+
}
|
|
20
|
+
this.isFlushing = false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Draft } from "immer";
|
|
2
|
+
export type Listener = () => void;
|
|
3
|
+
export type keyListenerMap = Map<string, Set<Listener>>;
|
|
4
|
+
export declare class PlcStore {
|
|
5
|
+
private storeData;
|
|
6
|
+
private substoresData;
|
|
7
|
+
private subscriptions;
|
|
8
|
+
private isBatching;
|
|
9
|
+
private dirtyKeys;
|
|
10
|
+
private activeDependencyTracker;
|
|
11
|
+
createStore<K extends string, T>(key: K, initial: T): void;
|
|
12
|
+
getStore(key: string): any;
|
|
13
|
+
setStore<T>(key: string, updater: T | ((draft: Draft<T>) => void | T)): void;
|
|
14
|
+
createSubstore<K extends string, T>(substore: string, key: K, initial: T): void;
|
|
15
|
+
getSubstore(substore: string, key: string): any;
|
|
16
|
+
setSubstore<T>(substore: string, key: string, updater: T | ((draft: Draft<T>) => void | T)): void;
|
|
17
|
+
protected recordDependency(key: string, value: any): void;
|
|
18
|
+
runWithDependencyTracker<T>(tracker: Map<string, any> | null, fn: () => T | Promise<T>): Promise<T>;
|
|
19
|
+
batch(fn: () => void): void;
|
|
20
|
+
subscribe(key: string, listener: Listener): () => void;
|
|
21
|
+
private markDirty;
|
|
22
|
+
private flush;
|
|
23
|
+
private notify;
|
|
24
|
+
}
|
|
25
|
+
export declare class LRUCache<K, V> {
|
|
26
|
+
private maxEntries;
|
|
27
|
+
private map;
|
|
28
|
+
constructor(maxEntries?: number);
|
|
29
|
+
get(key: K): V | undefined;
|
|
30
|
+
set(key: K, value: V): void;
|
|
31
|
+
delete(key: K): void;
|
|
32
|
+
clear(): void;
|
|
33
|
+
}
|