@almadar/ui 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/LICENSE +72 -0
- package/README.md +335 -0
- package/dist/ThemeContext-lI5bo85E.d.ts +103 -0
- package/dist/components/index.d.ts +4789 -0
- package/dist/components/index.js +21566 -0
- package/dist/components/index.js.map +1 -0
- package/dist/context/index.d.ts +208 -0
- package/dist/context/index.js +443 -0
- package/dist/context/index.js.map +1 -0
- package/dist/event-bus-types-8-cjyMxw.d.ts +65 -0
- package/dist/hooks/index.d.ts +1006 -0
- package/dist/hooks/index.js +2262 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/lib/index.d.ts +291 -0
- package/dist/lib/index.js +431 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/offline-executor-CHr4uAhf.d.ts +401 -0
- package/dist/providers/index.d.ts +386 -0
- package/dist/providers/index.js +1111 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/renderer/index.d.ts +382 -0
- package/dist/renderer/index.js +808 -0
- package/dist/renderer/index.js.map +1 -0
- package/dist/stores/index.d.ts +151 -0
- package/dist/stores/index.js +196 -0
- package/dist/stores/index.js.map +1 -0
- package/dist/useUISlots-mnggE9X9.d.ts +105 -0
- package/package.json +121 -0
- package/themes/almadar.css +196 -0
- package/themes/index.css +11 -0
- package/themes/minimalist.css +193 -0
- package/themes/wireframe.css +188 -0
|
@@ -0,0 +1,808 @@
|
|
|
1
|
+
import { createContext, useRef, useCallback, useEffect, useContext, useState } from 'react';
|
|
2
|
+
import componentMappingJson from '@almadar/patterns/component-mapping.json';
|
|
3
|
+
import registryJson from '@almadar/patterns/registry.json';
|
|
4
|
+
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
7
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
8
|
+
|
|
9
|
+
// renderer/pattern-resolver.ts
|
|
10
|
+
var componentMapping = {};
|
|
11
|
+
var patternRegistry = {};
|
|
12
|
+
function initializePatternResolver(config) {
|
|
13
|
+
componentMapping = config.componentMapping;
|
|
14
|
+
patternRegistry = config.patternRegistry;
|
|
15
|
+
}
|
|
16
|
+
function setComponentMapping(mapping) {
|
|
17
|
+
componentMapping = mapping;
|
|
18
|
+
}
|
|
19
|
+
function setPatternRegistry(registry) {
|
|
20
|
+
patternRegistry = registry;
|
|
21
|
+
}
|
|
22
|
+
function resolvePattern(config) {
|
|
23
|
+
const { type, ...props } = config;
|
|
24
|
+
const mapping = componentMapping[type];
|
|
25
|
+
if (!mapping) {
|
|
26
|
+
if (Object.keys(componentMapping).length === 0) {
|
|
27
|
+
console.warn(
|
|
28
|
+
"[PatternResolver] Component mapping not initialized. Call initializePatternResolver() at app startup."
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
throw new Error(`Unknown pattern type: ${type}`);
|
|
32
|
+
}
|
|
33
|
+
if (mapping.deprecated) {
|
|
34
|
+
console.warn(
|
|
35
|
+
`[PatternResolver] Pattern "${type}" is deprecated.` + (mapping.replacedBy ? ` Use "${mapping.replacedBy}" instead.` : "")
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
const validatedProps = validatePatternProps(type, props);
|
|
39
|
+
return {
|
|
40
|
+
component: mapping.component,
|
|
41
|
+
importPath: mapping.importPath,
|
|
42
|
+
category: mapping.category,
|
|
43
|
+
validatedProps
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function validatePatternProps(patternType, props) {
|
|
47
|
+
const definition = patternRegistry[patternType];
|
|
48
|
+
if (!definition || !definition.propsSchema) {
|
|
49
|
+
return props;
|
|
50
|
+
}
|
|
51
|
+
const validated = { ...props };
|
|
52
|
+
const schema = definition.propsSchema;
|
|
53
|
+
for (const [propName, propDef] of Object.entries(schema)) {
|
|
54
|
+
if (propDef.required && !(propName in validated)) {
|
|
55
|
+
console.warn(
|
|
56
|
+
`[PatternResolver] Missing required prop "${propName}" for pattern "${patternType}"`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return validated;
|
|
61
|
+
}
|
|
62
|
+
function isKnownPattern(type) {
|
|
63
|
+
return type in componentMapping;
|
|
64
|
+
}
|
|
65
|
+
function getKnownPatterns() {
|
|
66
|
+
return Object.keys(componentMapping);
|
|
67
|
+
}
|
|
68
|
+
function getPatternsByCategory(category) {
|
|
69
|
+
return Object.entries(componentMapping).filter(([, mapping]) => mapping.category === category).map(([type]) => type);
|
|
70
|
+
}
|
|
71
|
+
function getPatternMapping(type) {
|
|
72
|
+
return componentMapping[type];
|
|
73
|
+
}
|
|
74
|
+
function getPatternDefinition(type) {
|
|
75
|
+
return patternRegistry[type];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// renderer/client-effect-executor.ts
|
|
79
|
+
function executeClientEffects(effects, config) {
|
|
80
|
+
if (!effects || effects.length === 0) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
for (const effect of effects) {
|
|
84
|
+
try {
|
|
85
|
+
executeEffect(effect, config);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error(
|
|
88
|
+
`[ClientEffectExecutor] Error executing effect:`,
|
|
89
|
+
effect,
|
|
90
|
+
error
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
config.onComplete?.();
|
|
95
|
+
}
|
|
96
|
+
function executeEffect(effect, config) {
|
|
97
|
+
const [effectType, ...args] = effect;
|
|
98
|
+
switch (effectType) {
|
|
99
|
+
case "render-ui": {
|
|
100
|
+
const [slot, patternConfig] = args;
|
|
101
|
+
executeRenderUI(slot, patternConfig, config);
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case "navigate": {
|
|
105
|
+
const [path, params] = args;
|
|
106
|
+
executeNavigate(path, params, config);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case "notify": {
|
|
110
|
+
const [message, options] = args;
|
|
111
|
+
executeNotify(message, options, config);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
case "emit": {
|
|
115
|
+
const [event, payload] = args;
|
|
116
|
+
executeEmit(event, payload, config);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
default:
|
|
120
|
+
console.warn(`[ClientEffectExecutor] Unknown effect type: ${effectType}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function executeRenderUI(slot, patternConfig, config) {
|
|
124
|
+
config.renderToSlot(slot, patternConfig);
|
|
125
|
+
}
|
|
126
|
+
function executeNavigate(path, params, config) {
|
|
127
|
+
config.navigate(path, params);
|
|
128
|
+
}
|
|
129
|
+
function executeNotify(message, options, config) {
|
|
130
|
+
config.notify(message, options);
|
|
131
|
+
}
|
|
132
|
+
function executeEmit(event, payload, config) {
|
|
133
|
+
config.eventBus.emit(event, payload);
|
|
134
|
+
}
|
|
135
|
+
function parseClientEffect(raw) {
|
|
136
|
+
if (!Array.isArray(raw) || raw.length < 1) {
|
|
137
|
+
console.warn("[ClientEffectExecutor] Invalid effect format:", raw);
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
const [type, ...args] = raw;
|
|
141
|
+
if (typeof type !== "string") {
|
|
142
|
+
console.warn("[ClientEffectExecutor] Effect type must be string:", raw);
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
switch (type) {
|
|
146
|
+
case "render-ui":
|
|
147
|
+
return ["render-ui", args[0], args[1]];
|
|
148
|
+
case "navigate":
|
|
149
|
+
return ["navigate", args[0], args[1]];
|
|
150
|
+
case "notify":
|
|
151
|
+
return ["notify", args[0], args[1]];
|
|
152
|
+
case "emit":
|
|
153
|
+
return ["emit", args[0], args[1]];
|
|
154
|
+
default:
|
|
155
|
+
console.warn(`[ClientEffectExecutor] Unknown effect type: ${type}`);
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function parseClientEffects(raw) {
|
|
160
|
+
if (!raw || !Array.isArray(raw)) {
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
return raw.map((effect) => parseClientEffect(effect)).filter((effect) => effect !== null);
|
|
164
|
+
}
|
|
165
|
+
function filterEffectsByType(effects, type) {
|
|
166
|
+
return effects.filter(
|
|
167
|
+
(effect) => effect[0] === type
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
function getRenderUIEffects(effects) {
|
|
171
|
+
return filterEffectsByType(effects, "render-ui");
|
|
172
|
+
}
|
|
173
|
+
function getNavigateEffects(effects) {
|
|
174
|
+
return filterEffectsByType(effects, "navigate");
|
|
175
|
+
}
|
|
176
|
+
function getNotifyEffects(effects) {
|
|
177
|
+
return filterEffectsByType(effects, "notify");
|
|
178
|
+
}
|
|
179
|
+
function getEmitEffects(effects) {
|
|
180
|
+
return filterEffectsByType(effects, "emit");
|
|
181
|
+
}
|
|
182
|
+
function useClientEffects(effects, options) {
|
|
183
|
+
const {
|
|
184
|
+
enabled = true,
|
|
185
|
+
debug = false,
|
|
186
|
+
onComplete,
|
|
187
|
+
...config
|
|
188
|
+
} = options;
|
|
189
|
+
const executedRef = useRef(/* @__PURE__ */ new Set());
|
|
190
|
+
const executingRef = useRef(false);
|
|
191
|
+
const executedCountRef = useRef(0);
|
|
192
|
+
const getEffectKey = useCallback((effect) => {
|
|
193
|
+
return JSON.stringify(effect);
|
|
194
|
+
}, []);
|
|
195
|
+
const execute = useCallback((effectsToExecute) => {
|
|
196
|
+
if (executingRef.current || effectsToExecute.length === 0) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
executingRef.current = true;
|
|
200
|
+
const newEffects = effectsToExecute.filter((effect) => {
|
|
201
|
+
const key = getEffectKey(effect);
|
|
202
|
+
if (executedRef.current.has(key)) {
|
|
203
|
+
if (debug) {
|
|
204
|
+
console.log("[useClientEffects] Skipping duplicate effect:", effect);
|
|
205
|
+
}
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
return true;
|
|
209
|
+
});
|
|
210
|
+
if (newEffects.length === 0) {
|
|
211
|
+
executingRef.current = false;
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (debug) {
|
|
215
|
+
console.log("[useClientEffects] Executing effects:", newEffects);
|
|
216
|
+
}
|
|
217
|
+
newEffects.forEach((effect) => {
|
|
218
|
+
executedRef.current.add(getEffectKey(effect));
|
|
219
|
+
});
|
|
220
|
+
executeClientEffects(newEffects, {
|
|
221
|
+
...config,
|
|
222
|
+
onComplete: () => {
|
|
223
|
+
executedCountRef.current = newEffects.length;
|
|
224
|
+
executingRef.current = false;
|
|
225
|
+
onComplete?.();
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}, [config, debug, getEffectKey, onComplete]);
|
|
229
|
+
useEffect(() => {
|
|
230
|
+
if (!enabled || !effects || effects.length === 0) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
execute(effects);
|
|
234
|
+
}, [effects, enabled, execute]);
|
|
235
|
+
const prevEffectsRef = useRef();
|
|
236
|
+
useEffect(() => {
|
|
237
|
+
if (effects !== prevEffectsRef.current) {
|
|
238
|
+
prevEffectsRef.current = effects;
|
|
239
|
+
}
|
|
240
|
+
}, [effects]);
|
|
241
|
+
return {
|
|
242
|
+
executedCount: executedCountRef.current,
|
|
243
|
+
executing: executingRef.current,
|
|
244
|
+
execute
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
var ClientEffectConfigContext = createContext(null);
|
|
248
|
+
var ClientEffectConfigProvider = ClientEffectConfigContext.Provider;
|
|
249
|
+
function useClientEffectConfig() {
|
|
250
|
+
const context = useContext(ClientEffectConfigContext);
|
|
251
|
+
if (!context) {
|
|
252
|
+
throw new Error(
|
|
253
|
+
"useClientEffectConfig must be used within a ClientEffectConfigProvider. Make sure your component tree is wrapped with OrbitalProvider."
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
return context;
|
|
257
|
+
}
|
|
258
|
+
function useClientEffectConfigOptional() {
|
|
259
|
+
return useContext(ClientEffectConfigContext);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// renderer/data-resolver.ts
|
|
263
|
+
function resolveEntityData(entityName, context) {
|
|
264
|
+
if (context.fetchedData && entityName in context.fetchedData) {
|
|
265
|
+
const data = context.fetchedData[entityName];
|
|
266
|
+
return {
|
|
267
|
+
data: Array.isArray(data) ? data : [],
|
|
268
|
+
loading: false
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
if (context.entityStore) {
|
|
272
|
+
try {
|
|
273
|
+
const data = context.entityStore.getRecords(entityName);
|
|
274
|
+
return {
|
|
275
|
+
data: Array.isArray(data) ? data : [],
|
|
276
|
+
loading: false
|
|
277
|
+
};
|
|
278
|
+
} catch (error) {
|
|
279
|
+
console.warn(
|
|
280
|
+
`[DataResolver] Error getting records from entity store for "${entityName}":`,
|
|
281
|
+
error
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const hasAnySources = context.fetchedData || context.entityStore;
|
|
286
|
+
return {
|
|
287
|
+
data: [],
|
|
288
|
+
loading: !hasAnySources
|
|
289
|
+
// Only loading if no sources configured
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
function resolveEntityDataWithQuery(entityName, queryRef, context) {
|
|
293
|
+
const resolution = resolveEntityData(entityName, context);
|
|
294
|
+
if (!queryRef || !context.querySingleton) {
|
|
295
|
+
return resolution;
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
const filters = context.querySingleton.getFilters(queryRef);
|
|
299
|
+
const filteredData = applyFilters(resolution.data, filters);
|
|
300
|
+
return {
|
|
301
|
+
...resolution,
|
|
302
|
+
data: filteredData
|
|
303
|
+
};
|
|
304
|
+
} catch (error) {
|
|
305
|
+
console.warn(
|
|
306
|
+
`[DataResolver] Error applying query filters for "${queryRef}":`,
|
|
307
|
+
error
|
|
308
|
+
);
|
|
309
|
+
return resolution;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function applyFilters(data, filters) {
|
|
313
|
+
if (!filters || Object.keys(filters).length === 0) {
|
|
314
|
+
return data;
|
|
315
|
+
}
|
|
316
|
+
return data.filter((item) => {
|
|
317
|
+
if (typeof item !== "object" || item === null) {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
const record = item;
|
|
321
|
+
return Object.entries(filters).every(([key, value]) => {
|
|
322
|
+
if (value === void 0 || value === null) {
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
const recordValue = record[key];
|
|
326
|
+
if (Array.isArray(value)) {
|
|
327
|
+
return value.includes(recordValue);
|
|
328
|
+
}
|
|
329
|
+
if (typeof value === "string" && typeof recordValue === "string") {
|
|
330
|
+
if (value.startsWith("*") && value.endsWith("*")) {
|
|
331
|
+
const pattern = value.slice(1, -1);
|
|
332
|
+
return recordValue.toLowerCase().includes(pattern.toLowerCase());
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return recordValue === value;
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
function resolveEntityById(entityName, id, context) {
|
|
340
|
+
const { data } = resolveEntityData(entityName, context);
|
|
341
|
+
return data.find((item) => {
|
|
342
|
+
if (typeof item !== "object" || item === null) {
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
const record = item;
|
|
346
|
+
return record.id === id || record._id === id;
|
|
347
|
+
}) ?? null;
|
|
348
|
+
}
|
|
349
|
+
function resolveEntityCount(entityName, context, filters) {
|
|
350
|
+
const { data } = resolveEntityData(entityName, context);
|
|
351
|
+
if (filters) {
|
|
352
|
+
return applyFilters(data, filters).length;
|
|
353
|
+
}
|
|
354
|
+
return data.length;
|
|
355
|
+
}
|
|
356
|
+
function hasEntities(entityName, context) {
|
|
357
|
+
const { data } = resolveEntityData(entityName, context);
|
|
358
|
+
return data.length > 0;
|
|
359
|
+
}
|
|
360
|
+
function createFetchedDataContext(data) {
|
|
361
|
+
return { fetchedData: data };
|
|
362
|
+
}
|
|
363
|
+
function mergeDataContexts(...contexts) {
|
|
364
|
+
const merged = {};
|
|
365
|
+
for (const context of contexts) {
|
|
366
|
+
if (context.fetchedData) {
|
|
367
|
+
merged.fetchedData = {
|
|
368
|
+
...merged.fetchedData,
|
|
369
|
+
...context.fetchedData
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
if (context.entityStore) {
|
|
373
|
+
merged.entityStore = context.entityStore;
|
|
374
|
+
}
|
|
375
|
+
if (context.querySingleton) {
|
|
376
|
+
merged.querySingleton = context.querySingleton;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return merged;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// renderer/slot-definitions.ts
|
|
383
|
+
var SLOT_DEFINITIONS = {
|
|
384
|
+
// -------------------------------------------------------------------------
|
|
385
|
+
// Inline Slots - Render in place within the component tree
|
|
386
|
+
// -------------------------------------------------------------------------
|
|
387
|
+
main: {
|
|
388
|
+
name: "main",
|
|
389
|
+
type: "inline"
|
|
390
|
+
},
|
|
391
|
+
sidebar: {
|
|
392
|
+
name: "sidebar",
|
|
393
|
+
type: "inline"
|
|
394
|
+
},
|
|
395
|
+
// -------------------------------------------------------------------------
|
|
396
|
+
// Portal Slots - Render to document.body via React Portal
|
|
397
|
+
// -------------------------------------------------------------------------
|
|
398
|
+
modal: {
|
|
399
|
+
name: "modal",
|
|
400
|
+
type: "portal",
|
|
401
|
+
portalTarget: "body",
|
|
402
|
+
zIndex: 1e3
|
|
403
|
+
},
|
|
404
|
+
drawer: {
|
|
405
|
+
name: "drawer",
|
|
406
|
+
type: "portal",
|
|
407
|
+
portalTarget: "body",
|
|
408
|
+
zIndex: 900
|
|
409
|
+
},
|
|
410
|
+
overlay: {
|
|
411
|
+
name: "overlay",
|
|
412
|
+
type: "portal",
|
|
413
|
+
portalTarget: "body",
|
|
414
|
+
zIndex: 1100
|
|
415
|
+
},
|
|
416
|
+
center: {
|
|
417
|
+
name: "center",
|
|
418
|
+
type: "portal",
|
|
419
|
+
portalTarget: "body",
|
|
420
|
+
zIndex: 1e3
|
|
421
|
+
},
|
|
422
|
+
toast: {
|
|
423
|
+
name: "toast",
|
|
424
|
+
type: "portal",
|
|
425
|
+
portalTarget: "body",
|
|
426
|
+
zIndex: 1200
|
|
427
|
+
},
|
|
428
|
+
// -------------------------------------------------------------------------
|
|
429
|
+
// Game HUD Slots - Portal for game overlay UI
|
|
430
|
+
// -------------------------------------------------------------------------
|
|
431
|
+
"hud-top": {
|
|
432
|
+
name: "hud-top",
|
|
433
|
+
type: "portal",
|
|
434
|
+
portalTarget: "body",
|
|
435
|
+
zIndex: 500
|
|
436
|
+
},
|
|
437
|
+
"hud-bottom": {
|
|
438
|
+
name: "hud-bottom",
|
|
439
|
+
type: "portal",
|
|
440
|
+
portalTarget: "body",
|
|
441
|
+
zIndex: 500
|
|
442
|
+
},
|
|
443
|
+
floating: {
|
|
444
|
+
name: "floating",
|
|
445
|
+
type: "portal",
|
|
446
|
+
portalTarget: "body",
|
|
447
|
+
zIndex: 800
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
function getSlotDefinition(slot) {
|
|
451
|
+
return SLOT_DEFINITIONS[slot];
|
|
452
|
+
}
|
|
453
|
+
function isPortalSlot(slot) {
|
|
454
|
+
return SLOT_DEFINITIONS[slot]?.type === "portal";
|
|
455
|
+
}
|
|
456
|
+
function isInlineSlot(slot) {
|
|
457
|
+
return SLOT_DEFINITIONS[slot]?.type === "inline";
|
|
458
|
+
}
|
|
459
|
+
function getSlotsByType(type) {
|
|
460
|
+
return Object.entries(SLOT_DEFINITIONS).filter(([, def]) => def.type === type).map(([name]) => name);
|
|
461
|
+
}
|
|
462
|
+
function getInlineSlots() {
|
|
463
|
+
return getSlotsByType("inline");
|
|
464
|
+
}
|
|
465
|
+
function getPortalSlots() {
|
|
466
|
+
return getSlotsByType("portal");
|
|
467
|
+
}
|
|
468
|
+
var ALL_SLOTS = Object.keys(SLOT_DEFINITIONS);
|
|
469
|
+
var effectIdCounter = 0;
|
|
470
|
+
function generateEffectId() {
|
|
471
|
+
return `offline-effect-${++effectIdCounter}-${Date.now()}`;
|
|
472
|
+
}
|
|
473
|
+
var OfflineExecutor = class {
|
|
474
|
+
constructor(config) {
|
|
475
|
+
__publicField(this, "config");
|
|
476
|
+
__publicField(this, "state");
|
|
477
|
+
__publicField(this, "storage");
|
|
478
|
+
/**
|
|
479
|
+
* Handle going online
|
|
480
|
+
*/
|
|
481
|
+
__publicField(this, "handleOnline", () => {
|
|
482
|
+
this.state.isOffline = false;
|
|
483
|
+
});
|
|
484
|
+
/**
|
|
485
|
+
* Handle going offline
|
|
486
|
+
*/
|
|
487
|
+
__publicField(this, "handleOffline", () => {
|
|
488
|
+
this.state.isOffline = true;
|
|
489
|
+
});
|
|
490
|
+
this.config = {
|
|
491
|
+
enableSyncQueue: true,
|
|
492
|
+
maxQueueSize: 100,
|
|
493
|
+
...config
|
|
494
|
+
};
|
|
495
|
+
this.state = {
|
|
496
|
+
isOffline: !this.checkOnline(),
|
|
497
|
+
syncQueue: [],
|
|
498
|
+
localEffectsProcessed: 0,
|
|
499
|
+
effectsSynced: 0
|
|
500
|
+
};
|
|
501
|
+
this.storage = typeof localStorage !== "undefined" ? localStorage : null;
|
|
502
|
+
this.loadSyncQueue();
|
|
503
|
+
if (typeof window !== "undefined") {
|
|
504
|
+
window.addEventListener("online", this.handleOnline);
|
|
505
|
+
window.addEventListener("offline", this.handleOffline);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Check if we're online (browser API)
|
|
510
|
+
*/
|
|
511
|
+
checkOnline() {
|
|
512
|
+
return typeof navigator !== "undefined" ? navigator.onLine : true;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Load sync queue from localStorage
|
|
516
|
+
*/
|
|
517
|
+
loadSyncQueue() {
|
|
518
|
+
if (!this.storage) return;
|
|
519
|
+
try {
|
|
520
|
+
const stored = this.storage.getItem("orbital-offline-queue");
|
|
521
|
+
if (stored) {
|
|
522
|
+
this.state.syncQueue = JSON.parse(stored);
|
|
523
|
+
}
|
|
524
|
+
} catch (error) {
|
|
525
|
+
console.warn("[OfflineExecutor] Failed to load sync queue:", error);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Save sync queue to localStorage
|
|
530
|
+
*/
|
|
531
|
+
saveSyncQueue() {
|
|
532
|
+
if (!this.storage) return;
|
|
533
|
+
try {
|
|
534
|
+
this.storage.setItem("orbital-offline-queue", JSON.stringify(this.state.syncQueue));
|
|
535
|
+
} catch (error) {
|
|
536
|
+
console.warn("[OfflineExecutor] Failed to save sync queue:", error);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Add an effect to the sync queue
|
|
541
|
+
*/
|
|
542
|
+
queueForSync(type, payload) {
|
|
543
|
+
if (!this.config.enableSyncQueue) return;
|
|
544
|
+
const effect = {
|
|
545
|
+
id: generateEffectId(),
|
|
546
|
+
timestamp: Date.now(),
|
|
547
|
+
type,
|
|
548
|
+
payload,
|
|
549
|
+
retries: 0,
|
|
550
|
+
maxRetries: 3
|
|
551
|
+
};
|
|
552
|
+
this.state.syncQueue.push(effect);
|
|
553
|
+
if (this.state.syncQueue.length > (this.config.maxQueueSize ?? 100)) {
|
|
554
|
+
this.state.syncQueue.shift();
|
|
555
|
+
}
|
|
556
|
+
this.saveSyncQueue();
|
|
557
|
+
this.config.onEffectQueued?.(effect);
|
|
558
|
+
this.config.onQueueChange?.(this.state.syncQueue);
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Execute client effects immediately.
|
|
562
|
+
*/
|
|
563
|
+
executeClientEffects(effects) {
|
|
564
|
+
if (effects.length === 0) return;
|
|
565
|
+
executeClientEffects(effects, this.config);
|
|
566
|
+
this.state.localEffectsProcessed += effects.length;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Process an event in offline mode.
|
|
570
|
+
*
|
|
571
|
+
* Returns a simulated EventResponse with mock data.
|
|
572
|
+
* Client effects are executed immediately.
|
|
573
|
+
* Server effects are queued for sync.
|
|
574
|
+
*/
|
|
575
|
+
processEventOffline(event, payload, effects) {
|
|
576
|
+
const clientEffects = [];
|
|
577
|
+
const fetchedData = {};
|
|
578
|
+
if (effects) {
|
|
579
|
+
for (const effect of effects) {
|
|
580
|
+
if (!Array.isArray(effect) || effect.length < 1) continue;
|
|
581
|
+
const [type, ...args] = effect;
|
|
582
|
+
switch (type) {
|
|
583
|
+
// Client effects - execute immediately
|
|
584
|
+
case "render-ui":
|
|
585
|
+
case "navigate":
|
|
586
|
+
case "notify":
|
|
587
|
+
case "emit":
|
|
588
|
+
clientEffects.push(effect);
|
|
589
|
+
break;
|
|
590
|
+
// Fetch effect - use mock data
|
|
591
|
+
case "fetch": {
|
|
592
|
+
const [entityName, _query] = args;
|
|
593
|
+
if (typeof entityName === "string" && this.config.mockDataProvider) {
|
|
594
|
+
fetchedData[entityName] = this.config.mockDataProvider(entityName);
|
|
595
|
+
}
|
|
596
|
+
break;
|
|
597
|
+
}
|
|
598
|
+
// Server effects - queue for sync
|
|
599
|
+
case "persist":
|
|
600
|
+
case "call-service":
|
|
601
|
+
case "spawn":
|
|
602
|
+
case "despawn":
|
|
603
|
+
this.queueForSync(type, { args, event, payload });
|
|
604
|
+
break;
|
|
605
|
+
default:
|
|
606
|
+
console.warn(`[OfflineExecutor] Unknown effect type: ${type}`);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
if (clientEffects.length > 0) {
|
|
611
|
+
this.executeClientEffects(clientEffects);
|
|
612
|
+
}
|
|
613
|
+
return {
|
|
614
|
+
success: true,
|
|
615
|
+
data: Object.keys(fetchedData).length > 0 ? fetchedData : void 0,
|
|
616
|
+
clientEffects: clientEffects.length > 0 ? clientEffects : void 0
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Sync pending effects to server.
|
|
621
|
+
*
|
|
622
|
+
* @param serverUrl - Base URL for the orbital server
|
|
623
|
+
* @param authToken - Optional auth token for requests
|
|
624
|
+
* @returns Number of successfully synced effects
|
|
625
|
+
*/
|
|
626
|
+
async syncPendingEffects(serverUrl, authToken) {
|
|
627
|
+
if (this.state.syncQueue.length === 0) {
|
|
628
|
+
return 0;
|
|
629
|
+
}
|
|
630
|
+
this.state.lastSyncAttempt = Date.now();
|
|
631
|
+
let syncedCount = 0;
|
|
632
|
+
const failedEffects = [];
|
|
633
|
+
const headers = {
|
|
634
|
+
"Content-Type": "application/json"
|
|
635
|
+
};
|
|
636
|
+
if (authToken) {
|
|
637
|
+
headers["Authorization"] = `Bearer ${authToken}`;
|
|
638
|
+
}
|
|
639
|
+
for (const effect of this.state.syncQueue) {
|
|
640
|
+
try {
|
|
641
|
+
const response = await fetch(`${serverUrl}/sync-effect`, {
|
|
642
|
+
method: "POST",
|
|
643
|
+
headers,
|
|
644
|
+
body: JSON.stringify({
|
|
645
|
+
type: effect.type,
|
|
646
|
+
payload: effect.payload,
|
|
647
|
+
offlineId: effect.id,
|
|
648
|
+
offlineTimestamp: effect.timestamp
|
|
649
|
+
})
|
|
650
|
+
});
|
|
651
|
+
if (response.ok) {
|
|
652
|
+
syncedCount++;
|
|
653
|
+
} else {
|
|
654
|
+
effect.retries++;
|
|
655
|
+
if (effect.retries < effect.maxRetries) {
|
|
656
|
+
failedEffects.push(effect);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
} catch (error) {
|
|
660
|
+
effect.retries++;
|
|
661
|
+
if (effect.retries < effect.maxRetries) {
|
|
662
|
+
failedEffects.push(effect);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
this.state.syncQueue = failedEffects;
|
|
667
|
+
this.state.effectsSynced += syncedCount;
|
|
668
|
+
if (syncedCount > 0) {
|
|
669
|
+
this.state.lastSuccessfulSync = Date.now();
|
|
670
|
+
}
|
|
671
|
+
this.saveSyncQueue();
|
|
672
|
+
this.config.onQueueChange?.(this.state.syncQueue);
|
|
673
|
+
return syncedCount;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Get current executor state
|
|
677
|
+
*/
|
|
678
|
+
getState() {
|
|
679
|
+
return { ...this.state };
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Get number of pending effects
|
|
683
|
+
*/
|
|
684
|
+
getPendingCount() {
|
|
685
|
+
return this.state.syncQueue.length;
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Clear the sync queue
|
|
689
|
+
*/
|
|
690
|
+
clearQueue() {
|
|
691
|
+
this.state.syncQueue = [];
|
|
692
|
+
this.saveSyncQueue();
|
|
693
|
+
this.config.onQueueChange?.(this.state.syncQueue);
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Dispose the executor and clean up listeners
|
|
697
|
+
*/
|
|
698
|
+
dispose() {
|
|
699
|
+
if (typeof window !== "undefined") {
|
|
700
|
+
window.removeEventListener("online", this.handleOnline);
|
|
701
|
+
window.removeEventListener("offline", this.handleOffline);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
function createOfflineExecutor(config) {
|
|
706
|
+
return new OfflineExecutor(config);
|
|
707
|
+
}
|
|
708
|
+
function useOfflineExecutor(options) {
|
|
709
|
+
const executorRef = useRef(null);
|
|
710
|
+
const [state, setState] = useState({
|
|
711
|
+
isOffline: false,
|
|
712
|
+
syncQueue: [],
|
|
713
|
+
localEffectsProcessed: 0,
|
|
714
|
+
effectsSynced: 0
|
|
715
|
+
});
|
|
716
|
+
useEffect(() => {
|
|
717
|
+
const executor = new OfflineExecutor({
|
|
718
|
+
...options,
|
|
719
|
+
onQueueChange: (queue) => {
|
|
720
|
+
setState(executor.getState());
|
|
721
|
+
options.onQueueChange?.(queue);
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
executorRef.current = executor;
|
|
725
|
+
setState(executor.getState());
|
|
726
|
+
return () => {
|
|
727
|
+
executor.dispose();
|
|
728
|
+
executorRef.current = null;
|
|
729
|
+
};
|
|
730
|
+
}, []);
|
|
731
|
+
useEffect(() => {
|
|
732
|
+
if (!options.autoSync || !options.serverUrl) return;
|
|
733
|
+
const handleOnline = async () => {
|
|
734
|
+
if (executorRef.current) {
|
|
735
|
+
await executorRef.current.syncPendingEffects(
|
|
736
|
+
options.serverUrl,
|
|
737
|
+
options.authToken
|
|
738
|
+
);
|
|
739
|
+
setState(executorRef.current.getState());
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
window.addEventListener("online", handleOnline);
|
|
743
|
+
return () => window.removeEventListener("online", handleOnline);
|
|
744
|
+
}, [options.autoSync, options.serverUrl, options.authToken]);
|
|
745
|
+
const executeEffects = useCallback((effects) => {
|
|
746
|
+
executorRef.current?.executeClientEffects(effects);
|
|
747
|
+
if (executorRef.current) {
|
|
748
|
+
setState(executorRef.current.getState());
|
|
749
|
+
}
|
|
750
|
+
}, []);
|
|
751
|
+
const processEventOffline = useCallback(
|
|
752
|
+
(event, payload, effects) => {
|
|
753
|
+
const result = executorRef.current?.processEventOffline(event, payload, effects);
|
|
754
|
+
if (executorRef.current) {
|
|
755
|
+
setState(executorRef.current.getState());
|
|
756
|
+
}
|
|
757
|
+
return result ?? { success: false, error: "Executor not initialized" };
|
|
758
|
+
},
|
|
759
|
+
[]
|
|
760
|
+
);
|
|
761
|
+
const sync = useCallback(async () => {
|
|
762
|
+
if (!executorRef.current || !options.serverUrl) return 0;
|
|
763
|
+
const count = await executorRef.current.syncPendingEffects(
|
|
764
|
+
options.serverUrl,
|
|
765
|
+
options.authToken
|
|
766
|
+
);
|
|
767
|
+
setState(executorRef.current.getState());
|
|
768
|
+
return count;
|
|
769
|
+
}, [options.serverUrl, options.authToken]);
|
|
770
|
+
const clearQueue = useCallback(() => {
|
|
771
|
+
executorRef.current?.clearQueue();
|
|
772
|
+
if (executorRef.current) {
|
|
773
|
+
setState(executorRef.current.getState());
|
|
774
|
+
}
|
|
775
|
+
}, []);
|
|
776
|
+
return {
|
|
777
|
+
state,
|
|
778
|
+
isOffline: state.isOffline,
|
|
779
|
+
pendingCount: state.syncQueue.length,
|
|
780
|
+
executeClientEffects: executeEffects,
|
|
781
|
+
processEventOffline,
|
|
782
|
+
sync,
|
|
783
|
+
clearQueue
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
function initializePatterns() {
|
|
787
|
+
console.log("[PatternResolver] initializePatterns called");
|
|
788
|
+
console.log("[PatternResolver] componentMappingJson:", componentMappingJson);
|
|
789
|
+
console.log("[PatternResolver] registryJson keys:", Object.keys(registryJson));
|
|
790
|
+
const componentMappingData = componentMappingJson;
|
|
791
|
+
const componentMapping2 = componentMappingData.mappings || {};
|
|
792
|
+
console.log("[PatternResolver] Extracted mappings count:", Object.keys(componentMapping2).length);
|
|
793
|
+
console.log("[PatternResolver] Sample mappings:", Object.keys(componentMapping2).slice(0, 5));
|
|
794
|
+
const registryData = registryJson;
|
|
795
|
+
const patternRegistry2 = registryData.patterns || {};
|
|
796
|
+
console.log("[PatternResolver] Extracted patterns count:", Object.keys(patternRegistry2).length);
|
|
797
|
+
initializePatternResolver({
|
|
798
|
+
componentMapping: componentMapping2,
|
|
799
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
800
|
+
patternRegistry: patternRegistry2
|
|
801
|
+
});
|
|
802
|
+
console.log(`[PatternResolver] Initialized with ${Object.keys(componentMapping2).length} component mappings and ${Object.keys(patternRegistry2).length} pattern definitions`);
|
|
803
|
+
return Object.keys(componentMapping2).length;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
export { ALL_SLOTS, ClientEffectConfigContext, ClientEffectConfigProvider, OfflineExecutor, SLOT_DEFINITIONS, createFetchedDataContext, createOfflineExecutor, executeClientEffects, filterEffectsByType, getEmitEffects, getInlineSlots, getKnownPatterns, getNavigateEffects, getNotifyEffects, getPatternDefinition, getPatternMapping, getPatternsByCategory, getPortalSlots, getRenderUIEffects, getSlotDefinition, getSlotsByType, hasEntities, initializePatternResolver, initializePatterns, isInlineSlot, isKnownPattern, isPortalSlot, mergeDataContexts, parseClientEffect, parseClientEffects, resolveEntityById, resolveEntityCount, resolveEntityData, resolveEntityDataWithQuery, resolvePattern, setComponentMapping, setPatternRegistry, useClientEffectConfig, useClientEffectConfigOptional, useClientEffects, useOfflineExecutor };
|
|
807
|
+
//# sourceMappingURL=index.js.map
|
|
808
|
+
//# sourceMappingURL=index.js.map
|