@djvlc/runtime-core 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/dist/index.cjs ADDED
@@ -0,0 +1,3368 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ActionBridge: () => ActionBridge,
24
+ ActionError: () => ActionError,
25
+ AssetLoader: () => AssetLoader,
26
+ BaseRenderer: () => BaseRenderer,
27
+ ComponentBlockedError: () => ComponentBlockedError,
28
+ ComponentLoadError: () => ComponentLoadError,
29
+ ComponentLoader: () => ComponentLoader,
30
+ DjvlcRuntime: () => DjvlcRuntime,
31
+ DjvlcRuntimeError: () => DjvlcRuntimeError,
32
+ Evaluator: () => Evaluator,
33
+ EventBus: () => EventBus,
34
+ ExpressionEngine: () => ExpressionEngine,
35
+ ExpressionError: () => ExpressionError,
36
+ HostAPIImpl: () => HostAPIImpl,
37
+ IntegrityError: () => IntegrityError,
38
+ Lexer: () => Lexer,
39
+ PageLoadError: () => PageLoadError,
40
+ PageLoader: () => PageLoader,
41
+ Parser: () => Parser,
42
+ QueryError: () => QueryError,
43
+ RenderError: () => RenderError,
44
+ SecurityManager: () => SecurityManager,
45
+ StateManager: () => StateManager,
46
+ TelemetryManager: () => TelemetryManager,
47
+ builtinFunctions: () => builtinFunctions,
48
+ createFallbackElement: () => createFallbackElement,
49
+ createRuntime: () => createRuntime,
50
+ registerFallbackComponents: () => registerFallbackComponents
51
+ });
52
+ module.exports = __toCommonJS(index_exports);
53
+
54
+ // src/types/errors.ts
55
+ var import_contracts_types = require("@djvlc/contracts-types");
56
+ var DjvlcRuntimeError = class extends Error {
57
+ constructor(code, message, details, traceId) {
58
+ super(message || import_contracts_types.ErrorMessages[code] || "Unknown error");
59
+ this.name = "DjvlcRuntimeError";
60
+ this.code = code;
61
+ this.details = details;
62
+ this.traceId = traceId;
63
+ this.timestamp = Date.now();
64
+ }
65
+ toJSON() {
66
+ return {
67
+ name: this.name,
68
+ code: this.code,
69
+ message: this.message,
70
+ details: this.details,
71
+ traceId: this.traceId,
72
+ timestamp: this.timestamp
73
+ };
74
+ }
75
+ };
76
+ var PageLoadError = class extends DjvlcRuntimeError {
77
+ constructor(message, details, traceId) {
78
+ super(import_contracts_types.ErrorCode.PAGE_NOT_FOUND, message, details, traceId);
79
+ this.name = "PageLoadError";
80
+ }
81
+ };
82
+ var ComponentLoadError = class extends DjvlcRuntimeError {
83
+ constructor(componentName, componentVersion, message, code = import_contracts_types.ErrorCode.COMPONENT_NOT_FOUND, details) {
84
+ super(code, message, { ...details, componentName, componentVersion });
85
+ this.name = "ComponentLoadError";
86
+ this.componentName = componentName;
87
+ this.componentVersion = componentVersion;
88
+ }
89
+ };
90
+ var IntegrityError = class extends DjvlcRuntimeError {
91
+ constructor(componentName, componentVersion, expectedHash, actualHash) {
92
+ super(
93
+ import_contracts_types.ErrorCode.COMPONENT_INTEGRITY_MISMATCH,
94
+ `Integrity check failed for ${componentName}@${componentVersion}`,
95
+ { expectedHash, actualHash }
96
+ );
97
+ this.name = "IntegrityError";
98
+ this.componentName = componentName;
99
+ this.componentVersion = componentVersion;
100
+ this.expectedHash = expectedHash;
101
+ this.actualHash = actualHash;
102
+ }
103
+ };
104
+ var ComponentBlockedError = class extends DjvlcRuntimeError {
105
+ constructor(componentName, componentVersion, reason) {
106
+ super(import_contracts_types.ErrorCode.COMPONENT_BLOCKED, `Component ${componentName}@${componentVersion} is blocked`, {
107
+ componentName,
108
+ componentVersion,
109
+ reason
110
+ });
111
+ this.name = "ComponentBlockedError";
112
+ this.componentName = componentName;
113
+ this.componentVersion = componentVersion;
114
+ this.reason = reason;
115
+ }
116
+ };
117
+ var ExpressionError = class extends DjvlcRuntimeError {
118
+ constructor(expression, message, position, details) {
119
+ super(import_contracts_types.ErrorCode.UNKNOWN, message, { ...details, expression, position });
120
+ this.name = "ExpressionError";
121
+ this.expression = expression;
122
+ this.position = position;
123
+ }
124
+ };
125
+ var ActionError = class extends DjvlcRuntimeError {
126
+ constructor(actionType, message, code = import_contracts_types.ErrorCode.ACTION_EXECUTION_FAILED, actionId, details) {
127
+ super(code, message, { ...details, actionType, actionId });
128
+ this.name = "ActionError";
129
+ this.actionType = actionType;
130
+ this.actionId = actionId;
131
+ }
132
+ };
133
+ var QueryError = class extends DjvlcRuntimeError {
134
+ constructor(queryId, message, code = import_contracts_types.ErrorCode.QUERY_EXECUTION_FAILED, details) {
135
+ super(code, message, { ...details, queryId });
136
+ this.name = "QueryError";
137
+ this.queryId = queryId;
138
+ }
139
+ };
140
+ var RenderError = class extends DjvlcRuntimeError {
141
+ constructor(componentId, componentType, message, details) {
142
+ super(import_contracts_types.ErrorCode.UNKNOWN, message, { ...details, componentId, componentType });
143
+ this.name = "RenderError";
144
+ this.componentId = componentId;
145
+ this.componentType = componentType;
146
+ }
147
+ };
148
+
149
+ // src/loader/page-loader.ts
150
+ var PageLoader = class {
151
+ constructor(options) {
152
+ this.cache = /* @__PURE__ */ new Map();
153
+ this.options = {
154
+ channel: "prod",
155
+ cache: { enabled: true, maxAge: 300 },
156
+ ...options
157
+ };
158
+ }
159
+ /**
160
+ * 解析页面
161
+ * @param pageUid 页面 UID
162
+ * @param params 额外参数
163
+ */
164
+ async resolve(pageUid, params) {
165
+ const cacheKey = this.getCacheKey(pageUid, params);
166
+ if (this.options.cache?.enabled) {
167
+ const cached = this.cache.get(cacheKey);
168
+ if (cached && this.isCacheValid(cached.timestamp)) {
169
+ this.log("debug", `Page ${pageUid} loaded from cache`);
170
+ return cached.data;
171
+ }
172
+ }
173
+ const startTime = performance.now();
174
+ try {
175
+ const url = this.buildResolveUrl(pageUid, params);
176
+ const response = await fetch(url, {
177
+ method: "GET",
178
+ headers: this.buildHeaders()
179
+ });
180
+ if (!response.ok) {
181
+ throw new PageLoadError(
182
+ `Failed to resolve page: ${response.status} ${response.statusText}`,
183
+ { pageUid, status: response.status }
184
+ );
185
+ }
186
+ const result = await response.json();
187
+ if (!this.isValidPageResolveResult(result)) {
188
+ throw new PageLoadError("Invalid page resolve response", { pageUid });
189
+ }
190
+ const data = result.data;
191
+ if (this.options.cache?.enabled) {
192
+ this.cache.set(cacheKey, { data, timestamp: Date.now() });
193
+ }
194
+ const loadTime = performance.now() - startTime;
195
+ this.log("info", `Page ${pageUid} resolved in ${loadTime.toFixed(2)}ms`);
196
+ return data;
197
+ } catch (error) {
198
+ if (error instanceof PageLoadError) {
199
+ throw error;
200
+ }
201
+ throw new PageLoadError(
202
+ `Failed to resolve page: ${error instanceof Error ? error.message : "Unknown error"}`,
203
+ { pageUid }
204
+ );
205
+ }
206
+ }
207
+ /**
208
+ * 预连接 API 服务器
209
+ */
210
+ preconnect() {
211
+ const link = document.createElement("link");
212
+ link.rel = "preconnect";
213
+ link.href = this.options.apiBaseUrl;
214
+ document.head.appendChild(link);
215
+ }
216
+ /**
217
+ * 清除缓存
218
+ */
219
+ clearCache(pageUid) {
220
+ if (pageUid) {
221
+ for (const key of this.cache.keys()) {
222
+ if (key.startsWith(pageUid)) {
223
+ this.cache.delete(key);
224
+ }
225
+ }
226
+ } else {
227
+ this.cache.clear();
228
+ }
229
+ }
230
+ buildResolveUrl(pageUid, params) {
231
+ const url = new URL(`${this.options.apiBaseUrl}/page/resolve`);
232
+ url.searchParams.set("pageUid", pageUid);
233
+ if (this.options.channel) {
234
+ url.searchParams.set("channel", this.options.channel);
235
+ }
236
+ if (this.options.previewToken) {
237
+ url.searchParams.set("previewToken", this.options.previewToken);
238
+ }
239
+ if (params?.uid) {
240
+ url.searchParams.set("uid", params.uid);
241
+ }
242
+ if (params?.deviceId) {
243
+ url.searchParams.set("deviceId", params.deviceId);
244
+ }
245
+ return url.toString();
246
+ }
247
+ buildHeaders() {
248
+ const headers = {
249
+ "Content-Type": "application/json",
250
+ ...this.options.headers
251
+ };
252
+ if (this.options.authToken) {
253
+ headers["Authorization"] = `Bearer ${this.options.authToken}`;
254
+ }
255
+ return headers;
256
+ }
257
+ getCacheKey(pageUid, params) {
258
+ const parts = [pageUid, this.options.channel];
259
+ if (params?.uid) parts.push(params.uid);
260
+ if (params?.deviceId) parts.push(params.deviceId);
261
+ return parts.join(":");
262
+ }
263
+ isCacheValid(timestamp) {
264
+ const maxAge = (this.options.cache?.maxAge ?? 300) * 1e3;
265
+ return Date.now() - timestamp < maxAge;
266
+ }
267
+ isValidPageResolveResult(result) {
268
+ if (!result || typeof result !== "object") return false;
269
+ const r = result;
270
+ if (!r.data || typeof r.data !== "object") return false;
271
+ const data = r.data;
272
+ return typeof data.pageUid === "string" && typeof data.pageVersionId === "string" && data.pageJson !== void 0 && data.manifest !== void 0;
273
+ }
274
+ log(level, message) {
275
+ if (this.options.logger) {
276
+ this.options.logger[level](message);
277
+ }
278
+ }
279
+ };
280
+
281
+ // src/loader/component-loader.ts
282
+ var ComponentLoader = class {
283
+ constructor(options) {
284
+ this.loadedComponents = /* @__PURE__ */ new Map();
285
+ this.loadingPromises = /* @__PURE__ */ new Map();
286
+ this.options = {
287
+ enableSRI: true,
288
+ concurrency: 4,
289
+ timeout: 3e4,
290
+ blockedComponents: [],
291
+ ...options
292
+ };
293
+ this.blockedSet = new Set(this.options.blockedComponents);
294
+ }
295
+ /**
296
+ * 加载单个组件
297
+ */
298
+ async load(item) {
299
+ const key = this.getComponentKey(item.name, item.version);
300
+ if (this.isBlocked(item.name, item.version)) {
301
+ throw new ComponentBlockedError(item.name, item.version, "Component is blocked");
302
+ }
303
+ const loaded = this.loadedComponents.get(key);
304
+ if (loaded) {
305
+ return loaded;
306
+ }
307
+ const loading = this.loadingPromises.get(key);
308
+ if (loading) {
309
+ return loading;
310
+ }
311
+ const loadPromise = this.loadComponent(item);
312
+ this.loadingPromises.set(key, loadPromise);
313
+ try {
314
+ const component = await loadPromise;
315
+ this.loadedComponents.set(key, component);
316
+ return component;
317
+ } finally {
318
+ this.loadingPromises.delete(key);
319
+ }
320
+ }
321
+ /**
322
+ * 加载 Manifest 中的所有组件
323
+ */
324
+ async loadAll(manifest) {
325
+ const results = /* @__PURE__ */ new Map();
326
+ const { concurrency = 4 } = this.options;
327
+ const components = manifest.components;
328
+ for (let i = 0; i < components.length; i += concurrency) {
329
+ const batch = components.slice(i, i + concurrency);
330
+ const batchPromises = batch.map(async (item) => {
331
+ const key = this.getComponentKey(item.name, item.version);
332
+ const startTime = performance.now();
333
+ try {
334
+ const loaded = await this.load(item);
335
+ results.set(key, {
336
+ name: item.name,
337
+ version: item.version,
338
+ status: "loaded",
339
+ component: loaded.Component,
340
+ loadTime: performance.now() - startTime
341
+ });
342
+ } catch (error) {
343
+ const status = error instanceof ComponentBlockedError ? "blocked" : "failed";
344
+ results.set(key, {
345
+ name: item.name,
346
+ version: item.version,
347
+ status,
348
+ error: error instanceof Error ? error.message : "Unknown error",
349
+ loadTime: performance.now() - startTime
350
+ });
351
+ if (item.critical) {
352
+ throw error;
353
+ }
354
+ }
355
+ });
356
+ await Promise.all(batchPromises);
357
+ }
358
+ return results;
359
+ }
360
+ /**
361
+ * 预加载组件
362
+ */
363
+ preload(items) {
364
+ items.forEach((item) => {
365
+ const link = document.createElement("link");
366
+ link.rel = "preload";
367
+ link.as = "script";
368
+ link.href = this.resolveUrl(item.entryUrl);
369
+ if (this.options.enableSRI && item.integrity) {
370
+ link.integrity = item.integrity;
371
+ link.crossOrigin = "anonymous";
372
+ }
373
+ document.head.appendChild(link);
374
+ });
375
+ }
376
+ /**
377
+ * 检查组件是否已加载
378
+ */
379
+ isLoaded(name, version) {
380
+ return this.loadedComponents.has(this.getComponentKey(name, version));
381
+ }
382
+ /**
383
+ * 获取已加载的组件
384
+ */
385
+ get(name, version) {
386
+ return this.loadedComponents.get(this.getComponentKey(name, version));
387
+ }
388
+ /**
389
+ * 检查组件是否被阻断
390
+ */
391
+ isBlocked(name, version) {
392
+ return this.blockedSet.has(`${name}@${version}`) || this.blockedSet.has(name);
393
+ }
394
+ /**
395
+ * 更新阻断列表
396
+ */
397
+ updateBlockedList(blocked) {
398
+ this.blockedSet = new Set(blocked);
399
+ }
400
+ async loadComponent(item) {
401
+ const startTime = performance.now();
402
+ const url = this.resolveUrl(item.entryUrl);
403
+ this.log("debug", `Loading component ${item.name}@${item.version}`);
404
+ try {
405
+ const response = await this.fetchWithTimeout(url);
406
+ if (!response.ok) {
407
+ throw new ComponentLoadError(
408
+ item.name,
409
+ item.version,
410
+ `Failed to fetch component: ${response.status} ${response.statusText}`
411
+ );
412
+ }
413
+ const content = await response.text();
414
+ if (this.options.enableSRI && item.integrity) {
415
+ await this.validateIntegrity(item, content);
416
+ }
417
+ const Component = await this.executeScript(content, item);
418
+ const loadTime = performance.now() - startTime;
419
+ this.log("info", `Component ${item.name}@${item.version} loaded in ${loadTime.toFixed(2)}ms`);
420
+ return {
421
+ name: item.name,
422
+ version: item.version,
423
+ Component,
424
+ loadTime
425
+ };
426
+ } catch (error) {
427
+ if (error instanceof ComponentLoadError || error instanceof IntegrityError || error instanceof ComponentBlockedError) {
428
+ throw error;
429
+ }
430
+ throw new ComponentLoadError(
431
+ item.name,
432
+ item.version,
433
+ `Failed to load component: ${error instanceof Error ? error.message : "Unknown error"}`
434
+ );
435
+ }
436
+ }
437
+ async fetchWithTimeout(url) {
438
+ const controller = new AbortController();
439
+ const timeout = setTimeout(() => controller.abort(), this.options.timeout);
440
+ try {
441
+ return await fetch(url, {
442
+ signal: controller.signal,
443
+ credentials: "omit"
444
+ });
445
+ } finally {
446
+ clearTimeout(timeout);
447
+ }
448
+ }
449
+ async validateIntegrity(item, content) {
450
+ if (!item.integrity) return;
451
+ const [algorithm, expectedHash] = item.integrity.split("-");
452
+ if (!algorithm || !expectedHash) {
453
+ throw new IntegrityError(item.name, item.version, item.integrity, "Invalid format");
454
+ }
455
+ const hashBuffer = await crypto.subtle.digest(
456
+ algorithm.toUpperCase(),
457
+ new TextEncoder().encode(content)
458
+ );
459
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
460
+ const actualHash = btoa(String.fromCharCode(...hashArray));
461
+ if (actualHash !== expectedHash) {
462
+ throw new IntegrityError(item.name, item.version, expectedHash, actualHash);
463
+ }
464
+ }
465
+ async executeScript(content, item) {
466
+ const blob = new Blob([content], { type: "application/javascript" });
467
+ const blobUrl = URL.createObjectURL(blob);
468
+ try {
469
+ const module2 = await import(
470
+ /* @vite-ignore */
471
+ blobUrl
472
+ );
473
+ const Component = module2.default || module2[item.name] || module2.Component;
474
+ if (!Component) {
475
+ throw new ComponentLoadError(
476
+ item.name,
477
+ item.version,
478
+ "Component module does not export a valid component"
479
+ );
480
+ }
481
+ return Component;
482
+ } finally {
483
+ URL.revokeObjectURL(blobUrl);
484
+ }
485
+ }
486
+ resolveUrl(entryUrl) {
487
+ if (entryUrl.startsWith("http://") || entryUrl.startsWith("https://")) {
488
+ return entryUrl;
489
+ }
490
+ return `${this.options.cdnBaseUrl}/${entryUrl.replace(/^\//, "")}`;
491
+ }
492
+ getComponentKey(name, version) {
493
+ return `${name}@${version}`;
494
+ }
495
+ log(level, message) {
496
+ if (this.options.logger) {
497
+ this.options.logger[level](message);
498
+ }
499
+ }
500
+ };
501
+
502
+ // src/loader/asset-loader.ts
503
+ var AssetLoader = class {
504
+ constructor(options) {
505
+ this.preconnectedHosts = /* @__PURE__ */ new Set();
506
+ this.preloadedAssets = /* @__PURE__ */ new Set();
507
+ this.options = options;
508
+ }
509
+ /**
510
+ * 预连接所有域名
511
+ */
512
+ preconnectAll() {
513
+ const allHosts = [...this.options.cdnHosts, ...this.options.apiHosts];
514
+ allHosts.forEach((host) => this.preconnect(host));
515
+ }
516
+ /**
517
+ * 预连接单个域名
518
+ */
519
+ preconnect(host) {
520
+ if (this.preconnectedHosts.has(host)) return;
521
+ const link = document.createElement("link");
522
+ link.rel = "preconnect";
523
+ link.href = host.startsWith("http") ? host : `https://${host}`;
524
+ link.crossOrigin = "anonymous";
525
+ document.head.appendChild(link);
526
+ this.preconnectedHosts.add(host);
527
+ }
528
+ /**
529
+ * DNS 预解析
530
+ */
531
+ dnsPrefetch(host) {
532
+ const link = document.createElement("link");
533
+ link.rel = "dns-prefetch";
534
+ link.href = host.startsWith("http") ? host : `https://${host}`;
535
+ document.head.appendChild(link);
536
+ }
537
+ /**
538
+ * 预加载脚本
539
+ */
540
+ preloadScript(url, integrity) {
541
+ if (this.preloadedAssets.has(url)) return;
542
+ const link = document.createElement("link");
543
+ link.rel = "preload";
544
+ link.as = "script";
545
+ link.href = url;
546
+ if (integrity) {
547
+ link.integrity = integrity;
548
+ link.crossOrigin = "anonymous";
549
+ }
550
+ document.head.appendChild(link);
551
+ this.preloadedAssets.add(url);
552
+ }
553
+ /**
554
+ * 预加载样式
555
+ */
556
+ preloadStyle(url, integrity) {
557
+ if (this.preloadedAssets.has(url)) return;
558
+ const link = document.createElement("link");
559
+ link.rel = "preload";
560
+ link.as = "style";
561
+ link.href = url;
562
+ if (integrity) {
563
+ link.integrity = integrity;
564
+ link.crossOrigin = "anonymous";
565
+ }
566
+ document.head.appendChild(link);
567
+ this.preloadedAssets.add(url);
568
+ }
569
+ /**
570
+ * 预加载图片
571
+ */
572
+ preloadImage(url) {
573
+ if (this.preloadedAssets.has(url)) return;
574
+ const link = document.createElement("link");
575
+ link.rel = "preload";
576
+ link.as = "image";
577
+ link.href = url;
578
+ document.head.appendChild(link);
579
+ this.preloadedAssets.add(url);
580
+ }
581
+ /**
582
+ * 预获取资源(低优先级)
583
+ */
584
+ prefetch(url, as) {
585
+ const link = document.createElement("link");
586
+ link.rel = "prefetch";
587
+ link.href = url;
588
+ if (as) link.as = as;
589
+ document.head.appendChild(link);
590
+ }
591
+ /**
592
+ * 加载样式表
593
+ */
594
+ loadStylesheet(url, integrity) {
595
+ return new Promise((resolve, reject) => {
596
+ const link = document.createElement("link");
597
+ link.rel = "stylesheet";
598
+ link.href = url;
599
+ if (integrity) {
600
+ link.integrity = integrity;
601
+ link.crossOrigin = "anonymous";
602
+ }
603
+ link.onload = () => resolve();
604
+ link.onerror = () => reject(new Error(`Failed to load stylesheet: ${url}`));
605
+ document.head.appendChild(link);
606
+ });
607
+ }
608
+ /**
609
+ * 加载脚本
610
+ */
611
+ loadScript(url, integrity) {
612
+ return new Promise((resolve, reject) => {
613
+ const script = document.createElement("script");
614
+ script.src = url;
615
+ script.async = true;
616
+ if (integrity) {
617
+ script.integrity = integrity;
618
+ script.crossOrigin = "anonymous";
619
+ }
620
+ script.onload = () => resolve();
621
+ script.onerror = () => reject(new Error(`Failed to load script: ${url}`));
622
+ document.body.appendChild(script);
623
+ });
624
+ }
625
+ };
626
+
627
+ // src/state/state-manager.ts
628
+ var StateManager = class {
629
+ constructor() {
630
+ this.listeners = /* @__PURE__ */ new Set();
631
+ this.state = this.createInitialState();
632
+ }
633
+ /**
634
+ * 获取当前状态
635
+ */
636
+ getState() {
637
+ return this.state;
638
+ }
639
+ /**
640
+ * 获取当前阶段
641
+ */
642
+ getPhase() {
643
+ return this.state.phase;
644
+ }
645
+ /**
646
+ * 设置阶段
647
+ */
648
+ setPhase(phase) {
649
+ this.setState({ phase });
650
+ }
651
+ /**
652
+ * 设置页面数据
653
+ */
654
+ setPage(page) {
655
+ this.setState({
656
+ page,
657
+ variables: page.pageJson.page.variables || {}
658
+ });
659
+ }
660
+ /**
661
+ * 设置错误
662
+ */
663
+ setError(error) {
664
+ this.setState({
665
+ phase: "error",
666
+ error
667
+ });
668
+ }
669
+ /**
670
+ * 获取变量值
671
+ */
672
+ getVariable(key) {
673
+ return this.state.variables[key];
674
+ }
675
+ /**
676
+ * 设置变量值
677
+ */
678
+ setVariable(key, value) {
679
+ this.setState({
680
+ variables: {
681
+ ...this.state.variables,
682
+ [key]: value
683
+ }
684
+ });
685
+ }
686
+ /**
687
+ * 批量设置变量
688
+ */
689
+ setVariables(variables) {
690
+ this.setState({
691
+ variables: {
692
+ ...this.state.variables,
693
+ ...variables
694
+ }
695
+ });
696
+ }
697
+ /**
698
+ * 获取查询结果
699
+ */
700
+ getQuery(queryId) {
701
+ return this.state.queries[queryId];
702
+ }
703
+ /**
704
+ * 设置查询结果
705
+ */
706
+ setQuery(queryId, data) {
707
+ this.setState({
708
+ queries: {
709
+ ...this.state.queries,
710
+ [queryId]: data
711
+ }
712
+ });
713
+ }
714
+ /**
715
+ * 更新组件加载状态
716
+ */
717
+ setComponentStatus(key, result) {
718
+ const components = new Map(this.state.components);
719
+ components.set(key, result);
720
+ this.setState({ components });
721
+ }
722
+ /**
723
+ * 标记为已销毁
724
+ */
725
+ setDestroyed() {
726
+ this.setState({ destroyed: true });
727
+ }
728
+ /**
729
+ * 订阅状态变更
730
+ */
731
+ subscribe(listener) {
732
+ this.listeners.add(listener);
733
+ return () => {
734
+ this.listeners.delete(listener);
735
+ };
736
+ }
737
+ /**
738
+ * 重置状态
739
+ */
740
+ reset() {
741
+ this.state = this.createInitialState();
742
+ this.notifyListeners();
743
+ }
744
+ setState(partial) {
745
+ this.state = { ...this.state, ...partial };
746
+ this.notifyListeners();
747
+ }
748
+ notifyListeners() {
749
+ this.listeners.forEach((listener) => {
750
+ try {
751
+ listener(this.state);
752
+ } catch (error) {
753
+ console.error("State listener error:", error);
754
+ }
755
+ });
756
+ }
757
+ createInitialState() {
758
+ return {
759
+ phase: "idle",
760
+ page: null,
761
+ variables: {},
762
+ queries: {},
763
+ components: /* @__PURE__ */ new Map(),
764
+ error: null,
765
+ destroyed: false
766
+ };
767
+ }
768
+ };
769
+
770
+ // src/events/event-bus.ts
771
+ var EventBus = class {
772
+ constructor(options = {}) {
773
+ this.handlers = /* @__PURE__ */ new Map();
774
+ this.options = {
775
+ debug: false,
776
+ maxListeners: 100,
777
+ ...options
778
+ };
779
+ }
780
+ /**
781
+ * 发送事件
782
+ */
783
+ emit(event) {
784
+ if (this.options.debug) {
785
+ this.log("debug", `Event emitted: ${event.type}`, event);
786
+ }
787
+ const handlers = this.handlers.get(event.type);
788
+ if (!handlers) return;
789
+ handlers.forEach((handler) => {
790
+ try {
791
+ handler(event);
792
+ } catch (error) {
793
+ this.log("error", `Error in event handler for ${event.type}:`, error);
794
+ }
795
+ });
796
+ }
797
+ /**
798
+ * 订阅事件
799
+ */
800
+ on(type, handler) {
801
+ let handlers = this.handlers.get(type);
802
+ if (!handlers) {
803
+ handlers = /* @__PURE__ */ new Set();
804
+ this.handlers.set(type, handlers);
805
+ }
806
+ if (handlers.size >= (this.options.maxListeners ?? 100)) {
807
+ this.log("warn", `Max listeners (${this.options.maxListeners}) reached for event: ${type}`);
808
+ }
809
+ handlers.add(handler);
810
+ return () => {
811
+ handlers?.delete(handler);
812
+ if (handlers?.size === 0) {
813
+ this.handlers.delete(type);
814
+ }
815
+ };
816
+ }
817
+ /**
818
+ * 取消订阅
819
+ */
820
+ off(type, handler) {
821
+ const handlers = this.handlers.get(type);
822
+ if (handlers) {
823
+ handlers.delete(handler);
824
+ if (handlers.size === 0) {
825
+ this.handlers.delete(type);
826
+ }
827
+ }
828
+ }
829
+ /**
830
+ * 一次性订阅
831
+ */
832
+ once(type, handler) {
833
+ const wrappedHandler = (event) => {
834
+ unsubscribe();
835
+ handler(event);
836
+ };
837
+ const unsubscribe = this.on(type, wrappedHandler);
838
+ return unsubscribe;
839
+ }
840
+ /**
841
+ * 清除所有监听器
842
+ */
843
+ clear(type) {
844
+ if (type) {
845
+ this.handlers.delete(type);
846
+ } else {
847
+ this.handlers.clear();
848
+ }
849
+ }
850
+ /**
851
+ * 获取监听器数量
852
+ */
853
+ listenerCount(type) {
854
+ return this.handlers.get(type)?.size ?? 0;
855
+ }
856
+ /**
857
+ * 创建事件
858
+ */
859
+ static createEvent(type, data, traceId) {
860
+ return {
861
+ type,
862
+ data,
863
+ timestamp: Date.now(),
864
+ traceId
865
+ };
866
+ }
867
+ log(level, message, ...args) {
868
+ if (this.options.logger) {
869
+ this.options.logger[level](message, ...args);
870
+ } else if (this.options.debug) {
871
+ console[level](`[EventBus] ${message}`, ...args);
872
+ }
873
+ }
874
+ };
875
+
876
+ // src/events/action-bridge.ts
877
+ var ActionBridge = class {
878
+ constructor(options) {
879
+ this.debounceTimers = /* @__PURE__ */ new Map();
880
+ this.throttleTimers = /* @__PURE__ */ new Map();
881
+ this.options = options;
882
+ }
883
+ /**
884
+ * 处理组件事件
885
+ */
886
+ async handleEvent(event, eventData, context) {
887
+ if (event.enabled === false) {
888
+ return;
889
+ }
890
+ if (event.condition) {
891
+ this.log("debug", `Event condition: ${event.condition}`);
892
+ }
893
+ if (event.debounce && event.debounce > 0) {
894
+ await this.debounce(event.id, event.debounce);
895
+ }
896
+ if (event.throttle && event.throttle > 0) {
897
+ if (!this.throttle(event.id, event.throttle)) {
898
+ return;
899
+ }
900
+ }
901
+ for (const action of event.actions) {
902
+ if (action.enabled === false) continue;
903
+ try {
904
+ if (action.delay && action.delay > 0) {
905
+ await this.delay(action.delay);
906
+ }
907
+ const params = this.resolveParams(action.params, eventData, context);
908
+ await this.executeAction(action, params);
909
+ } catch (error) {
910
+ this.log("error", `Action ${action.type} failed:`, error);
911
+ }
912
+ }
913
+ }
914
+ /**
915
+ * 销毁
916
+ */
917
+ destroy() {
918
+ this.debounceTimers.forEach((timer) => clearTimeout(timer));
919
+ this.debounceTimers.clear();
920
+ this.throttleTimers.clear();
921
+ }
922
+ async executeAction(action, params) {
923
+ this.log("debug", `Executing action: ${action.type}`, params);
924
+ const result = await this.options.executor.execute(action.type, params);
925
+ this.log("debug", `Action ${action.type} completed:`, result);
926
+ }
927
+ resolveParams(params, eventData, context) {
928
+ return {
929
+ ...params,
930
+ __eventData: eventData,
931
+ __context: context
932
+ };
933
+ }
934
+ debounce(id, delay) {
935
+ return new Promise((resolve) => {
936
+ const existing = this.debounceTimers.get(id);
937
+ if (existing) {
938
+ clearTimeout(existing);
939
+ }
940
+ const timer = setTimeout(() => {
941
+ this.debounceTimers.delete(id);
942
+ resolve();
943
+ }, delay);
944
+ this.debounceTimers.set(id, timer);
945
+ });
946
+ }
947
+ throttle(id, interval) {
948
+ const now = Date.now();
949
+ const lastTime = this.throttleTimers.get(id) ?? 0;
950
+ if (now - lastTime < interval) {
951
+ return false;
952
+ }
953
+ this.throttleTimers.set(id, now);
954
+ return true;
955
+ }
956
+ delay(ms) {
957
+ return new Promise((resolve) => setTimeout(resolve, ms));
958
+ }
959
+ log(level, message, ...args) {
960
+ if (this.options.logger) {
961
+ this.options.logger[level](message, ...args);
962
+ } else if (this.options.debug) {
963
+ console[level](`[ActionBridge] ${message}`, ...args);
964
+ }
965
+ }
966
+ };
967
+
968
+ // src/expression/lexer.ts
969
+ var Lexer = class {
970
+ constructor(input) {
971
+ this.pos = 0;
972
+ this.tokens = [];
973
+ this.input = input;
974
+ }
975
+ /**
976
+ * 分析表达式,返回 Token 列表
977
+ */
978
+ tokenize() {
979
+ this.pos = 0;
980
+ this.tokens = [];
981
+ while (this.pos < this.input.length) {
982
+ this.skipWhitespace();
983
+ if (this.pos >= this.input.length) break;
984
+ const token = this.readToken();
985
+ if (token) {
986
+ this.tokens.push(token);
987
+ }
988
+ }
989
+ this.tokens.push({
990
+ type: "EOF",
991
+ value: null,
992
+ start: this.input.length,
993
+ end: this.input.length
994
+ });
995
+ return this.tokens;
996
+ }
997
+ readToken() {
998
+ const char = this.input[this.pos];
999
+ const start = this.pos;
1000
+ if (this.isDigit(char) || char === "-" && this.isDigit(this.peek(1))) {
1001
+ return this.readNumber();
1002
+ }
1003
+ if (char === '"' || char === "'") {
1004
+ return this.readString(char);
1005
+ }
1006
+ if (this.isIdentifierStart(char)) {
1007
+ return this.readIdentifier();
1008
+ }
1009
+ const operator = this.readOperator();
1010
+ if (operator) {
1011
+ return operator;
1012
+ }
1013
+ switch (char) {
1014
+ case ".":
1015
+ this.pos++;
1016
+ return { type: "DOT", value: ".", start, end: this.pos };
1017
+ case "[":
1018
+ this.pos++;
1019
+ return { type: "LBRACKET", value: "[", start, end: this.pos };
1020
+ case "]":
1021
+ this.pos++;
1022
+ return { type: "RBRACKET", value: "]", start, end: this.pos };
1023
+ case "(":
1024
+ this.pos++;
1025
+ return { type: "LPAREN", value: "(", start, end: this.pos };
1026
+ case ")":
1027
+ this.pos++;
1028
+ return { type: "RPAREN", value: ")", start, end: this.pos };
1029
+ case ",":
1030
+ this.pos++;
1031
+ return { type: "COMMA", value: ",", start, end: this.pos };
1032
+ case "?":
1033
+ this.pos++;
1034
+ return { type: "QUESTION", value: "?", start, end: this.pos };
1035
+ case ":":
1036
+ this.pos++;
1037
+ return { type: "COLON", value: ":", start, end: this.pos };
1038
+ }
1039
+ throw new Error(`Unexpected character '${char}' at position ${this.pos}`);
1040
+ }
1041
+ readNumber() {
1042
+ const start = this.pos;
1043
+ let value = "";
1044
+ if (this.input[this.pos] === "-") {
1045
+ value += "-";
1046
+ this.pos++;
1047
+ }
1048
+ while (this.isDigit(this.input[this.pos])) {
1049
+ value += this.input[this.pos];
1050
+ this.pos++;
1051
+ }
1052
+ if (this.input[this.pos] === "." && this.isDigit(this.peek(1))) {
1053
+ value += ".";
1054
+ this.pos++;
1055
+ while (this.isDigit(this.input[this.pos])) {
1056
+ value += this.input[this.pos];
1057
+ this.pos++;
1058
+ }
1059
+ }
1060
+ return {
1061
+ type: "NUMBER",
1062
+ value: parseFloat(value),
1063
+ start,
1064
+ end: this.pos
1065
+ };
1066
+ }
1067
+ readString(quote) {
1068
+ const start = this.pos;
1069
+ this.pos++;
1070
+ let value = "";
1071
+ while (this.pos < this.input.length && this.input[this.pos] !== quote) {
1072
+ if (this.input[this.pos] === "\\") {
1073
+ this.pos++;
1074
+ const escaped = this.input[this.pos];
1075
+ switch (escaped) {
1076
+ case "n":
1077
+ value += "\n";
1078
+ break;
1079
+ case "t":
1080
+ value += " ";
1081
+ break;
1082
+ case "r":
1083
+ value += "\r";
1084
+ break;
1085
+ case "\\":
1086
+ value += "\\";
1087
+ break;
1088
+ case '"':
1089
+ value += '"';
1090
+ break;
1091
+ case "'":
1092
+ value += "'";
1093
+ break;
1094
+ default:
1095
+ value += escaped;
1096
+ }
1097
+ } else {
1098
+ value += this.input[this.pos];
1099
+ }
1100
+ this.pos++;
1101
+ }
1102
+ if (this.input[this.pos] !== quote) {
1103
+ throw new Error(`Unterminated string at position ${start}`);
1104
+ }
1105
+ this.pos++;
1106
+ return {
1107
+ type: "STRING",
1108
+ value,
1109
+ start,
1110
+ end: this.pos
1111
+ };
1112
+ }
1113
+ readIdentifier() {
1114
+ const start = this.pos;
1115
+ let value = "";
1116
+ while (this.pos < this.input.length && this.isIdentifierChar(this.input[this.pos])) {
1117
+ value += this.input[this.pos];
1118
+ this.pos++;
1119
+ }
1120
+ if (value === "true") {
1121
+ return { type: "BOOLEAN", value: true, start, end: this.pos };
1122
+ }
1123
+ if (value === "false") {
1124
+ return { type: "BOOLEAN", value: false, start, end: this.pos };
1125
+ }
1126
+ if (value === "null") {
1127
+ return { type: "NULL", value: null, start, end: this.pos };
1128
+ }
1129
+ return { type: "IDENTIFIER", value, start, end: this.pos };
1130
+ }
1131
+ readOperator() {
1132
+ const start = this.pos;
1133
+ const twoChar = this.input.slice(this.pos, this.pos + 2);
1134
+ const oneChar = this.input[this.pos];
1135
+ const twoCharOps = ["==", "!=", ">=", "<=", "&&", "||", "??"];
1136
+ if (twoCharOps.includes(twoChar)) {
1137
+ this.pos += 2;
1138
+ return { type: "OPERATOR", value: twoChar, start, end: this.pos };
1139
+ }
1140
+ const oneCharOps = ["+", "-", "*", "/", "%", ">", "<", "!"];
1141
+ if (oneCharOps.includes(oneChar)) {
1142
+ this.pos++;
1143
+ return { type: "OPERATOR", value: oneChar, start, end: this.pos };
1144
+ }
1145
+ return null;
1146
+ }
1147
+ skipWhitespace() {
1148
+ while (this.pos < this.input.length && /\s/.test(this.input[this.pos])) {
1149
+ this.pos++;
1150
+ }
1151
+ }
1152
+ isDigit(char) {
1153
+ return /[0-9]/.test(char);
1154
+ }
1155
+ isIdentifierStart(char) {
1156
+ return /[a-zA-Z_$]/.test(char);
1157
+ }
1158
+ isIdentifierChar(char) {
1159
+ return /[a-zA-Z0-9_$]/.test(char);
1160
+ }
1161
+ peek(offset = 1) {
1162
+ return this.input[this.pos + offset] || "";
1163
+ }
1164
+ };
1165
+
1166
+ // src/expression/parser.ts
1167
+ var import_contracts_types2 = require("@djvlc/contracts-types");
1168
+ var PRECEDENCE = {
1169
+ "||": 1,
1170
+ "??": 1,
1171
+ "&&": 2,
1172
+ "==": 3,
1173
+ "!=": 3,
1174
+ "<": 4,
1175
+ ">": 4,
1176
+ "<=": 4,
1177
+ ">=": 4,
1178
+ "+": 5,
1179
+ "-": 5,
1180
+ "*": 6,
1181
+ "/": 6,
1182
+ "%": 6
1183
+ };
1184
+ var Parser = class {
1185
+ constructor(tokens) {
1186
+ this.pos = 0;
1187
+ this.tokens = tokens;
1188
+ }
1189
+ /**
1190
+ * 解析表达式
1191
+ */
1192
+ parse() {
1193
+ const result = this.parseExpression();
1194
+ if (this.current().type !== "EOF") {
1195
+ throw new Error(`Unexpected token '${this.current().value}' at position ${this.current().start}`);
1196
+ }
1197
+ return result;
1198
+ }
1199
+ parseExpression() {
1200
+ return this.parseTernary();
1201
+ }
1202
+ parseTernary() {
1203
+ const test = this.parseBinary(0);
1204
+ if (this.current().type === "QUESTION") {
1205
+ this.advance();
1206
+ const consequent = this.parseExpression();
1207
+ this.expect("COLON");
1208
+ const alternate = this.parseExpression();
1209
+ return {
1210
+ type: "conditional",
1211
+ test,
1212
+ consequent,
1213
+ alternate,
1214
+ raw: void 0
1215
+ };
1216
+ }
1217
+ return test;
1218
+ }
1219
+ parseBinary(minPrecedence) {
1220
+ let left = this.parseUnary();
1221
+ while (true) {
1222
+ const token = this.current();
1223
+ if (token.type !== "OPERATOR") break;
1224
+ const precedence = PRECEDENCE[token.value];
1225
+ if (precedence === void 0 || precedence < minPrecedence) break;
1226
+ this.advance();
1227
+ const right = this.parseBinary(precedence + 1);
1228
+ left = {
1229
+ type: "binary",
1230
+ operator: token.value,
1231
+ left,
1232
+ right,
1233
+ raw: void 0
1234
+ };
1235
+ }
1236
+ return left;
1237
+ }
1238
+ parseUnary() {
1239
+ const token = this.current();
1240
+ if (token.type === "OPERATOR" && (token.value === "!" || token.value === "-")) {
1241
+ this.advance();
1242
+ const argument = this.parseUnary();
1243
+ return {
1244
+ type: "unary",
1245
+ operator: token.value,
1246
+ argument,
1247
+ raw: void 0
1248
+ };
1249
+ }
1250
+ return this.parsePostfix();
1251
+ }
1252
+ parsePostfix() {
1253
+ let node = this.parsePrimary();
1254
+ while (true) {
1255
+ const token = this.current();
1256
+ if (token.type === "DOT") {
1257
+ this.advance();
1258
+ const propToken = this.expect("IDENTIFIER");
1259
+ node = {
1260
+ type: "member",
1261
+ object: node,
1262
+ property: propToken.value,
1263
+ computed: false,
1264
+ raw: void 0
1265
+ };
1266
+ } else if (token.type === "LBRACKET") {
1267
+ this.advance();
1268
+ const index = this.parseExpression();
1269
+ this.expect("RBRACKET");
1270
+ node = {
1271
+ type: "member",
1272
+ object: node,
1273
+ property: index,
1274
+ computed: true,
1275
+ raw: void 0
1276
+ };
1277
+ } else if (token.type === "LPAREN" && node.type === "identifier") {
1278
+ const callee = node.name;
1279
+ if (!import_contracts_types2.BUILTIN_FUNCTION_NAMES.has(callee)) {
1280
+ throw new Error(`Unknown function '${callee}' at position ${token.start}`);
1281
+ }
1282
+ this.advance();
1283
+ const args = this.parseArguments();
1284
+ this.expect("RPAREN");
1285
+ node = {
1286
+ type: "call",
1287
+ callee,
1288
+ arguments: args,
1289
+ raw: void 0
1290
+ };
1291
+ } else {
1292
+ break;
1293
+ }
1294
+ }
1295
+ return node;
1296
+ }
1297
+ parsePrimary() {
1298
+ const token = this.current();
1299
+ if (token.type === "NUMBER" || token.type === "STRING" || token.type === "BOOLEAN" || token.type === "NULL") {
1300
+ this.advance();
1301
+ return {
1302
+ type: "literal",
1303
+ value: token.value,
1304
+ start: token.start,
1305
+ end: token.end,
1306
+ raw: void 0
1307
+ };
1308
+ }
1309
+ if (token.type === "IDENTIFIER") {
1310
+ this.advance();
1311
+ return {
1312
+ type: "identifier",
1313
+ name: token.value,
1314
+ start: token.start,
1315
+ end: token.end,
1316
+ raw: void 0
1317
+ };
1318
+ }
1319
+ if (token.type === "LBRACKET") {
1320
+ return this.parseArray();
1321
+ }
1322
+ if (token.type === "LPAREN") {
1323
+ this.advance();
1324
+ const expr = this.parseExpression();
1325
+ this.expect("RPAREN");
1326
+ return expr;
1327
+ }
1328
+ throw new Error(`Unexpected token '${token.value}' at position ${token.start}`);
1329
+ }
1330
+ parseArray() {
1331
+ const start = this.current().start;
1332
+ this.advance();
1333
+ const elements = [];
1334
+ while (this.current().type !== "RBRACKET") {
1335
+ elements.push(this.parseExpression());
1336
+ if (this.current().type === "COMMA") {
1337
+ this.advance();
1338
+ } else {
1339
+ break;
1340
+ }
1341
+ }
1342
+ const end = this.current().end;
1343
+ this.expect("RBRACKET");
1344
+ return {
1345
+ type: "array",
1346
+ elements,
1347
+ start,
1348
+ end,
1349
+ raw: void 0
1350
+ };
1351
+ }
1352
+ parseArguments() {
1353
+ const args = [];
1354
+ if (this.current().type !== "RPAREN") {
1355
+ args.push(this.parseExpression());
1356
+ while (this.current().type === "COMMA") {
1357
+ this.advance();
1358
+ args.push(this.parseExpression());
1359
+ }
1360
+ }
1361
+ return args;
1362
+ }
1363
+ current() {
1364
+ return this.tokens[this.pos];
1365
+ }
1366
+ advance() {
1367
+ return this.tokens[this.pos++];
1368
+ }
1369
+ expect(type) {
1370
+ const token = this.current();
1371
+ if (token.type !== type) {
1372
+ throw new Error(`Expected '${type}' but got '${token.type}' at position ${token.start}`);
1373
+ }
1374
+ return this.advance();
1375
+ }
1376
+ };
1377
+
1378
+ // src/expression/functions.ts
1379
+ var builtinFunctions = {
1380
+ // ==================== 字符串函数 ====================
1381
+ len: (value) => {
1382
+ if (typeof value === "string" || Array.isArray(value)) {
1383
+ return value.length;
1384
+ }
1385
+ return 0;
1386
+ },
1387
+ trim: (str) => {
1388
+ return String(str ?? "").trim();
1389
+ },
1390
+ upper: (str) => {
1391
+ return String(str ?? "").toUpperCase();
1392
+ },
1393
+ lower: (str) => {
1394
+ return String(str ?? "").toLowerCase();
1395
+ },
1396
+ substr: (str, start, length) => {
1397
+ const s = String(str ?? "");
1398
+ const startNum = Number(start) || 0;
1399
+ const lengthNum = length !== void 0 ? Number(length) : void 0;
1400
+ return s.substring(startNum, lengthNum !== void 0 ? startNum + lengthNum : void 0);
1401
+ },
1402
+ concat: (...args) => {
1403
+ return args.map((a) => String(a ?? "")).join("");
1404
+ },
1405
+ replace: (str, search, replacement) => {
1406
+ return String(str ?? "").split(String(search)).join(String(replacement));
1407
+ },
1408
+ split: (str, separator) => {
1409
+ return String(str ?? "").split(String(separator));
1410
+ },
1411
+ join: (arr, separator) => {
1412
+ if (!Array.isArray(arr)) return "";
1413
+ return arr.join(separator !== void 0 ? String(separator) : ",");
1414
+ },
1415
+ startsWith: (str, search) => {
1416
+ return String(str ?? "").startsWith(String(search));
1417
+ },
1418
+ endsWith: (str, search) => {
1419
+ return String(str ?? "").endsWith(String(search));
1420
+ },
1421
+ contains: (str, search) => {
1422
+ return String(str ?? "").includes(String(search));
1423
+ },
1424
+ // ==================== 数字函数 ====================
1425
+ toNumber: (value) => {
1426
+ const num = Number(value);
1427
+ return isNaN(num) ? 0 : num;
1428
+ },
1429
+ toString: (value) => {
1430
+ return String(value ?? "");
1431
+ },
1432
+ toInt: (value) => {
1433
+ return Math.trunc(Number(value) || 0);
1434
+ },
1435
+ toFloat: (value) => {
1436
+ return parseFloat(String(value)) || 0;
1437
+ },
1438
+ round: (value, decimals) => {
1439
+ const num = Number(value) || 0;
1440
+ const dec = Number(decimals) || 0;
1441
+ const factor = Math.pow(10, dec);
1442
+ return Math.round(num * factor) / factor;
1443
+ },
1444
+ floor: (value) => {
1445
+ return Math.floor(Number(value) || 0);
1446
+ },
1447
+ ceil: (value) => {
1448
+ return Math.ceil(Number(value) || 0);
1449
+ },
1450
+ abs: (value) => {
1451
+ return Math.abs(Number(value) || 0);
1452
+ },
1453
+ min: (...args) => {
1454
+ const nums = args.map((a) => Number(a)).filter((n) => !isNaN(n));
1455
+ return nums.length > 0 ? Math.min(...nums) : 0;
1456
+ },
1457
+ max: (...args) => {
1458
+ const nums = args.map((a) => Number(a)).filter((n) => !isNaN(n));
1459
+ return nums.length > 0 ? Math.max(...nums) : 0;
1460
+ },
1461
+ sum: (arr) => {
1462
+ if (!Array.isArray(arr)) return 0;
1463
+ return arr.reduce((acc, val) => acc + (Number(val) || 0), 0);
1464
+ },
1465
+ avg: (arr) => {
1466
+ if (!Array.isArray(arr) || arr.length === 0) return 0;
1467
+ const sum = arr.reduce((acc, val) => acc + (Number(val) || 0), 0);
1468
+ return sum / arr.length;
1469
+ },
1470
+ random: () => {
1471
+ return Math.random();
1472
+ },
1473
+ randomInt: (min, max) => {
1474
+ const minNum = Math.ceil(Number(min) || 0);
1475
+ const maxNum = Math.floor(Number(max) || 100);
1476
+ return Math.floor(Math.random() * (maxNum - minNum + 1)) + minNum;
1477
+ },
1478
+ // ==================== 日期函数 ====================
1479
+ now: () => {
1480
+ return Date.now();
1481
+ },
1482
+ today: () => {
1483
+ return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1484
+ },
1485
+ dateFormat: (timestamp, format) => {
1486
+ const date = new Date(Number(timestamp) || Date.now());
1487
+ const fmt = String(format || "YYYY-MM-DD");
1488
+ const pad = (n) => n.toString().padStart(2, "0");
1489
+ return fmt.replace("YYYY", date.getFullYear().toString()).replace("MM", pad(date.getMonth() + 1)).replace("DD", pad(date.getDate())).replace("HH", pad(date.getHours())).replace("mm", pad(date.getMinutes())).replace("ss", pad(date.getSeconds()));
1490
+ },
1491
+ dateParse: (dateStr) => {
1492
+ const date = new Date(String(dateStr));
1493
+ return date.getTime();
1494
+ },
1495
+ year: (timestamp) => {
1496
+ return new Date(Number(timestamp) || Date.now()).getFullYear();
1497
+ },
1498
+ month: (timestamp) => {
1499
+ return new Date(Number(timestamp) || Date.now()).getMonth() + 1;
1500
+ },
1501
+ day: (timestamp) => {
1502
+ return new Date(Number(timestamp) || Date.now()).getDate();
1503
+ },
1504
+ addDays: (timestamp, days) => {
1505
+ const date = new Date(Number(timestamp) || Date.now());
1506
+ date.setDate(date.getDate() + (Number(days) || 0));
1507
+ return date.getTime();
1508
+ },
1509
+ diffDays: (timestamp1, timestamp2) => {
1510
+ const date1 = new Date(Number(timestamp1) || Date.now());
1511
+ const date2 = new Date(Number(timestamp2) || Date.now());
1512
+ const diffTime = Math.abs(date2.getTime() - date1.getTime());
1513
+ return Math.floor(diffTime / (1e3 * 60 * 60 * 24));
1514
+ },
1515
+ // ==================== 类型检查函数 ====================
1516
+ isNull: (value) => {
1517
+ return value === null || value === void 0;
1518
+ },
1519
+ isUndefined: (value) => {
1520
+ return value === void 0;
1521
+ },
1522
+ isEmpty: (value) => {
1523
+ if (value === null || value === void 0) return true;
1524
+ if (typeof value === "string") return value.length === 0;
1525
+ if (Array.isArray(value)) return value.length === 0;
1526
+ if (typeof value === "object") return Object.keys(value).length === 0;
1527
+ return false;
1528
+ },
1529
+ isArray: (value) => {
1530
+ return Array.isArray(value);
1531
+ },
1532
+ isObject: (value) => {
1533
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1534
+ },
1535
+ isString: (value) => {
1536
+ return typeof value === "string";
1537
+ },
1538
+ isNumber: (value) => {
1539
+ return typeof value === "number" && !isNaN(value);
1540
+ },
1541
+ isBoolean: (value) => {
1542
+ return typeof value === "boolean";
1543
+ },
1544
+ typeOf: (value) => {
1545
+ if (value === null) return "null";
1546
+ if (Array.isArray(value)) return "array";
1547
+ return typeof value;
1548
+ },
1549
+ // ==================== 默认值函数 ====================
1550
+ default: (value, defaultValue) => {
1551
+ return value ?? defaultValue;
1552
+ },
1553
+ coalesce: (...args) => {
1554
+ for (const arg of args) {
1555
+ if (arg !== null && arg !== void 0) return arg;
1556
+ }
1557
+ return null;
1558
+ },
1559
+ ifElse: (condition, trueValue, falseValue) => {
1560
+ return condition ? trueValue : falseValue;
1561
+ },
1562
+ // ==================== 数组函数 ====================
1563
+ first: (arr) => {
1564
+ if (!Array.isArray(arr)) return void 0;
1565
+ return arr[0];
1566
+ },
1567
+ last: (arr) => {
1568
+ if (!Array.isArray(arr)) return void 0;
1569
+ return arr[arr.length - 1];
1570
+ },
1571
+ at: (arr, index) => {
1572
+ if (!Array.isArray(arr)) return void 0;
1573
+ return arr[Number(index) || 0];
1574
+ },
1575
+ slice: (arr, start, end) => {
1576
+ if (!Array.isArray(arr)) return [];
1577
+ return arr.slice(Number(start) || 0, end !== void 0 ? Number(end) : void 0);
1578
+ },
1579
+ includes: (arr, value) => {
1580
+ if (!Array.isArray(arr)) return false;
1581
+ return arr.includes(value);
1582
+ },
1583
+ indexOf: (arr, value) => {
1584
+ if (!Array.isArray(arr)) return -1;
1585
+ return arr.indexOf(value);
1586
+ },
1587
+ reverse: (arr) => {
1588
+ if (!Array.isArray(arr)) return [];
1589
+ return [...arr].reverse();
1590
+ },
1591
+ sort: (arr) => {
1592
+ if (!Array.isArray(arr)) return [];
1593
+ return [...arr].sort();
1594
+ },
1595
+ unique: (arr) => {
1596
+ if (!Array.isArray(arr)) return [];
1597
+ return [...new Set(arr)];
1598
+ },
1599
+ flatten: (arr) => {
1600
+ if (!Array.isArray(arr)) return [];
1601
+ return arr.flat();
1602
+ },
1603
+ count: (arr) => {
1604
+ if (!Array.isArray(arr)) return 0;
1605
+ return arr.length;
1606
+ },
1607
+ // ==================== 对象函数 ====================
1608
+ get: (obj, path, defaultValue) => {
1609
+ if (obj === null || obj === void 0) return defaultValue;
1610
+ const pathStr = String(path);
1611
+ const parts = pathStr.split(".");
1612
+ let current = obj;
1613
+ for (const part of parts) {
1614
+ if (current === null || current === void 0) return defaultValue;
1615
+ current = current[part];
1616
+ }
1617
+ return current ?? defaultValue;
1618
+ },
1619
+ keys: (obj) => {
1620
+ if (typeof obj !== "object" || obj === null) return [];
1621
+ return Object.keys(obj);
1622
+ },
1623
+ values: (obj) => {
1624
+ if (typeof obj !== "object" || obj === null) return [];
1625
+ return Object.values(obj);
1626
+ },
1627
+ entries: (obj) => {
1628
+ if (typeof obj !== "object" || obj === null) return [];
1629
+ return Object.entries(obj);
1630
+ },
1631
+ has: (obj, key) => {
1632
+ if (typeof obj !== "object" || obj === null) return false;
1633
+ return String(key) in obj;
1634
+ },
1635
+ merge: (...objs) => {
1636
+ const result = {};
1637
+ for (const obj of objs) {
1638
+ if (typeof obj === "object" && obj !== null) {
1639
+ Object.assign(result, obj);
1640
+ }
1641
+ }
1642
+ return result;
1643
+ },
1644
+ // ==================== 逻辑函数 ====================
1645
+ and: (...args) => {
1646
+ return args.every((a) => Boolean(a));
1647
+ },
1648
+ or: (...args) => {
1649
+ return args.some((a) => Boolean(a));
1650
+ },
1651
+ not: (value) => {
1652
+ return !value;
1653
+ },
1654
+ eq: (a, b) => {
1655
+ return a === b;
1656
+ },
1657
+ ne: (a, b) => {
1658
+ return a !== b;
1659
+ },
1660
+ gt: (a, b) => {
1661
+ return Number(a) > Number(b);
1662
+ },
1663
+ gte: (a, b) => {
1664
+ return Number(a) >= Number(b);
1665
+ },
1666
+ lt: (a, b) => {
1667
+ return Number(a) < Number(b);
1668
+ },
1669
+ lte: (a, b) => {
1670
+ return Number(a) <= Number(b);
1671
+ },
1672
+ between: (value, min, max) => {
1673
+ const num = Number(value);
1674
+ return num >= Number(min) && num <= Number(max);
1675
+ },
1676
+ // ==================== 格式化函数 ====================
1677
+ formatNumber: (value, decimals) => {
1678
+ const num = Number(value) || 0;
1679
+ const dec = Number(decimals) ?? 0;
1680
+ return num.toLocaleString(void 0, {
1681
+ minimumFractionDigits: dec,
1682
+ maximumFractionDigits: dec
1683
+ });
1684
+ },
1685
+ formatCurrency: (value, currency) => {
1686
+ const num = Number(value) || 0;
1687
+ const cur = String(currency || "CNY");
1688
+ return num.toLocaleString("zh-CN", { style: "currency", currency: cur });
1689
+ },
1690
+ formatPercent: (value, decimals) => {
1691
+ const num = Number(value) || 0;
1692
+ const dec = Number(decimals) ?? 0;
1693
+ return (num * 100).toFixed(dec) + "%";
1694
+ },
1695
+ // ==================== JSON 函数 ====================
1696
+ jsonParse: (str) => {
1697
+ try {
1698
+ return JSON.parse(String(str));
1699
+ } catch {
1700
+ return null;
1701
+ }
1702
+ },
1703
+ jsonStringify: (value) => {
1704
+ return JSON.stringify(value);
1705
+ }
1706
+ };
1707
+
1708
+ // src/expression/evaluator.ts
1709
+ var Evaluator = class {
1710
+ constructor(options = {}) {
1711
+ this.depth = 0;
1712
+ this.startTime = 0;
1713
+ this.options = {
1714
+ maxDepth: 100,
1715
+ timeout: 1e3,
1716
+ debug: false,
1717
+ ...options
1718
+ };
1719
+ }
1720
+ /**
1721
+ * 求值表达式
1722
+ */
1723
+ evaluate(ast, context) {
1724
+ this.depth = 0;
1725
+ this.startTime = Date.now();
1726
+ try {
1727
+ const value = this.evaluateNode(ast, context);
1728
+ return { value };
1729
+ } catch (error) {
1730
+ return {
1731
+ value: void 0,
1732
+ error: error instanceof Error ? error : new Error(String(error))
1733
+ };
1734
+ }
1735
+ }
1736
+ evaluateNode(node, context) {
1737
+ this.checkLimits();
1738
+ switch (node.type) {
1739
+ case "literal":
1740
+ return this.evaluateLiteral(node);
1741
+ case "identifier":
1742
+ return this.evaluateIdentifier(node, context);
1743
+ case "member":
1744
+ return this.evaluateMember(node, context);
1745
+ case "call":
1746
+ return this.evaluateCall(node, context);
1747
+ case "binary":
1748
+ return this.evaluateBinary(node, context);
1749
+ case "unary":
1750
+ return this.evaluateUnary(node, context);
1751
+ case "conditional":
1752
+ return this.evaluateConditional(node, context);
1753
+ case "array":
1754
+ return this.evaluateArray(node, context);
1755
+ default:
1756
+ throw new ExpressionError("", `Unknown node type: ${node.type}`);
1757
+ }
1758
+ }
1759
+ evaluateLiteral(node) {
1760
+ return node.value;
1761
+ }
1762
+ evaluateIdentifier(node, context) {
1763
+ const name = node.name;
1764
+ switch (name) {
1765
+ case "state":
1766
+ return context.state;
1767
+ case "query":
1768
+ return context.query;
1769
+ case "context":
1770
+ return context.context;
1771
+ case "event":
1772
+ return context.event;
1773
+ case "item":
1774
+ return context.item;
1775
+ case "index":
1776
+ return context.index;
1777
+ default:
1778
+ throw new ExpressionError(
1779
+ "",
1780
+ `Unknown variable '${name}'`,
1781
+ node.start !== void 0 && node.end !== void 0 ? { start: node.start, end: node.end } : void 0
1782
+ );
1783
+ }
1784
+ }
1785
+ evaluateMember(node, context) {
1786
+ const object = this.evaluateNode(node.object, context);
1787
+ if (object === null || object === void 0) {
1788
+ return void 0;
1789
+ }
1790
+ let property;
1791
+ if (node.computed) {
1792
+ const propValue = this.evaluateNode(node.property, context);
1793
+ property = propValue;
1794
+ } else {
1795
+ property = node.property;
1796
+ }
1797
+ if (typeof object === "object" && object !== null) {
1798
+ return object[property];
1799
+ }
1800
+ return void 0;
1801
+ }
1802
+ evaluateCall(node, context) {
1803
+ const fn = builtinFunctions[node.callee];
1804
+ if (!fn) {
1805
+ throw new ExpressionError("", `Unknown function '${node.callee}'`);
1806
+ }
1807
+ const args = node.arguments.map((arg) => this.evaluateNode(arg, context));
1808
+ return fn(...args);
1809
+ }
1810
+ evaluateBinary(node, context) {
1811
+ const op = node.operator;
1812
+ if (op === "&&") {
1813
+ const left2 = this.evaluateNode(node.left, context);
1814
+ if (!left2) return left2;
1815
+ return this.evaluateNode(node.right, context);
1816
+ }
1817
+ if (op === "||") {
1818
+ const left2 = this.evaluateNode(node.left, context);
1819
+ if (left2) return left2;
1820
+ return this.evaluateNode(node.right, context);
1821
+ }
1822
+ if (op === "??") {
1823
+ const left2 = this.evaluateNode(node.left, context);
1824
+ if (left2 !== null && left2 !== void 0) return left2;
1825
+ return this.evaluateNode(node.right, context);
1826
+ }
1827
+ const left = this.evaluateNode(node.left, context);
1828
+ const right = this.evaluateNode(node.right, context);
1829
+ switch (op) {
1830
+ case "+":
1831
+ if (typeof left === "string" || typeof right === "string") {
1832
+ return String(left) + String(right);
1833
+ }
1834
+ return left + right;
1835
+ case "-":
1836
+ return left - right;
1837
+ case "*":
1838
+ return left * right;
1839
+ case "/":
1840
+ return left / right;
1841
+ case "%":
1842
+ return left % right;
1843
+ case "==":
1844
+ return left === right;
1845
+ case "!=":
1846
+ return left !== right;
1847
+ case "<":
1848
+ return left < right;
1849
+ case ">":
1850
+ return left > right;
1851
+ case "<=":
1852
+ return left <= right;
1853
+ case ">=":
1854
+ return left >= right;
1855
+ default:
1856
+ throw new ExpressionError("", `Unknown operator '${op}'`);
1857
+ }
1858
+ }
1859
+ evaluateUnary(node, context) {
1860
+ const argument = this.evaluateNode(node.argument, context);
1861
+ switch (node.operator) {
1862
+ case "!":
1863
+ return !argument;
1864
+ case "-":
1865
+ return -argument;
1866
+ default:
1867
+ throw new ExpressionError("", `Unknown unary operator '${node.operator}'`);
1868
+ }
1869
+ }
1870
+ evaluateConditional(node, context) {
1871
+ const test = this.evaluateNode(node.test, context);
1872
+ if (test) {
1873
+ return this.evaluateNode(node.consequent, context);
1874
+ }
1875
+ return this.evaluateNode(node.alternate, context);
1876
+ }
1877
+ evaluateArray(node, context) {
1878
+ return node.elements.map((el) => this.evaluateNode(el, context));
1879
+ }
1880
+ checkLimits() {
1881
+ this.depth++;
1882
+ if (this.depth > (this.options.maxDepth ?? 100)) {
1883
+ throw new ExpressionError("", "Maximum recursion depth exceeded");
1884
+ }
1885
+ const elapsed = Date.now() - this.startTime;
1886
+ if (elapsed > (this.options.timeout ?? 1e3)) {
1887
+ throw new ExpressionError("", "Expression evaluation timeout");
1888
+ }
1889
+ }
1890
+ };
1891
+
1892
+ // src/expression/engine.ts
1893
+ var ExpressionEngine = class {
1894
+ constructor(options = {}) {
1895
+ this.astCache = /* @__PURE__ */ new Map();
1896
+ this.options = {
1897
+ cacheAST: true,
1898
+ maxCacheSize: 1e3,
1899
+ ...options
1900
+ };
1901
+ this.evaluator = new Evaluator(options);
1902
+ }
1903
+ /**
1904
+ * 求值表达式
1905
+ */
1906
+ evaluate(expression, context) {
1907
+ try {
1908
+ const ast = this.parse(expression);
1909
+ return this.evaluator.evaluate(ast, context);
1910
+ } catch (error) {
1911
+ return {
1912
+ value: void 0,
1913
+ error: error instanceof Error ? error : new Error(String(error))
1914
+ };
1915
+ }
1916
+ }
1917
+ /**
1918
+ * 求值表达式并返回值
1919
+ * 出错时返回 fallback 值
1920
+ */
1921
+ evaluateWithFallback(expression, context, fallback) {
1922
+ const result = this.evaluate(expression, context);
1923
+ if (result.error) {
1924
+ this.log("warn", `Expression evaluation failed: ${result.error.message}`, expression);
1925
+ return fallback;
1926
+ }
1927
+ return result.value;
1928
+ }
1929
+ /**
1930
+ * 求值模板字符串
1931
+ * 支持 ${expression} 语法
1932
+ */
1933
+ evaluateTemplate(template, context) {
1934
+ return template.replace(/\$\{([^}]+)\}/g, (_, expr) => {
1935
+ const result = this.evaluate(expr.trim(), context);
1936
+ if (result.error) {
1937
+ this.log("warn", `Template expression failed: ${result.error.message}`, expr);
1938
+ return "";
1939
+ }
1940
+ return String(result.value ?? "");
1941
+ });
1942
+ }
1943
+ /**
1944
+ * 解析表达式为 AST
1945
+ */
1946
+ parse(expression) {
1947
+ if (this.options.cacheAST) {
1948
+ const cached = this.astCache.get(expression);
1949
+ if (cached) {
1950
+ return cached;
1951
+ }
1952
+ }
1953
+ try {
1954
+ const lexer = new Lexer(expression);
1955
+ const tokens = lexer.tokenize();
1956
+ const parser = new Parser(tokens);
1957
+ const ast = parser.parse();
1958
+ if (this.options.cacheAST) {
1959
+ if (this.astCache.size >= (this.options.maxCacheSize ?? 1e3)) {
1960
+ const keysToDelete = Array.from(this.astCache.keys()).slice(
1961
+ 0,
1962
+ Math.floor(this.astCache.size / 2)
1963
+ );
1964
+ keysToDelete.forEach((key) => this.astCache.delete(key));
1965
+ }
1966
+ this.astCache.set(expression, ast);
1967
+ }
1968
+ return ast;
1969
+ } catch (error) {
1970
+ throw new ExpressionError(
1971
+ expression,
1972
+ error instanceof Error ? error.message : "Parse error"
1973
+ );
1974
+ }
1975
+ }
1976
+ /**
1977
+ * 校验表达式
1978
+ */
1979
+ validate(expression) {
1980
+ const errors = [];
1981
+ const warnings = [];
1982
+ const referencedPaths = [];
1983
+ const calledFunctions = [];
1984
+ try {
1985
+ const ast = this.parse(expression);
1986
+ this.collectReferences(ast, referencedPaths, calledFunctions);
1987
+ return {
1988
+ valid: true,
1989
+ errors: [],
1990
+ warnings,
1991
+ referencedPaths,
1992
+ calledFunctions
1993
+ };
1994
+ } catch (error) {
1995
+ errors.push({
1996
+ type: "SYNTAX_ERROR",
1997
+ message: error instanceof Error ? error.message : "Parse error"
1998
+ });
1999
+ return {
2000
+ valid: false,
2001
+ errors,
2002
+ warnings,
2003
+ referencedPaths,
2004
+ calledFunctions
2005
+ };
2006
+ }
2007
+ }
2008
+ /**
2009
+ * 清除 AST 缓存
2010
+ */
2011
+ clearCache() {
2012
+ this.astCache.clear();
2013
+ }
2014
+ collectReferences(node, paths, functions) {
2015
+ switch (node.type) {
2016
+ case "identifier":
2017
+ paths.push(node.name);
2018
+ break;
2019
+ case "member": {
2020
+ const path = this.buildMemberPath(node);
2021
+ if (path) paths.push(path);
2022
+ break;
2023
+ }
2024
+ case "call":
2025
+ functions.push(node.callee);
2026
+ node.arguments.forEach((arg) => this.collectReferences(arg, paths, functions));
2027
+ break;
2028
+ case "binary":
2029
+ this.collectReferences(node.left, paths, functions);
2030
+ this.collectReferences(node.right, paths, functions);
2031
+ break;
2032
+ case "unary":
2033
+ this.collectReferences(node.argument, paths, functions);
2034
+ break;
2035
+ case "conditional":
2036
+ this.collectReferences(node.test, paths, functions);
2037
+ this.collectReferences(node.consequent, paths, functions);
2038
+ this.collectReferences(node.alternate, paths, functions);
2039
+ break;
2040
+ case "array":
2041
+ node.elements.forEach((el) => this.collectReferences(el, paths, functions));
2042
+ break;
2043
+ }
2044
+ }
2045
+ buildMemberPath(node) {
2046
+ if (node.type === "identifier") {
2047
+ return node.name;
2048
+ }
2049
+ if (node.type === "member" && !node.computed) {
2050
+ const objectPath = this.buildMemberPath(node.object);
2051
+ if (objectPath) {
2052
+ return `${objectPath}.${node.property}`;
2053
+ }
2054
+ }
2055
+ return null;
2056
+ }
2057
+ log(level, message, ...args) {
2058
+ if (this.options.logger) {
2059
+ this.options.logger[level](message, ...args);
2060
+ } else if (this.options.debug) {
2061
+ console[level](`[ExpressionEngine] ${message}`, ...args);
2062
+ }
2063
+ }
2064
+ };
2065
+
2066
+ // src/host-api/host-api-impl.ts
2067
+ var HostAPIImpl = class {
2068
+ constructor(options) {
2069
+ // ==================== 存储 ====================
2070
+ this.storage = {
2071
+ get: (key, options) => {
2072
+ const fullKey = this.getStorageKey(key, options?.namespace);
2073
+ const item = localStorage.getItem(fullKey);
2074
+ if (!item) return void 0;
2075
+ try {
2076
+ const parsed = JSON.parse(item);
2077
+ if (parsed.expires && Date.now() > parsed.expires) {
2078
+ localStorage.removeItem(fullKey);
2079
+ return void 0;
2080
+ }
2081
+ return parsed.value;
2082
+ } catch {
2083
+ return void 0;
2084
+ }
2085
+ },
2086
+ set: (key, value, options) => {
2087
+ const fullKey = this.getStorageKey(key, options?.namespace);
2088
+ const item = {
2089
+ value,
2090
+ expires: options?.ttl ? Date.now() + options.ttl * 1e3 : void 0
2091
+ };
2092
+ localStorage.setItem(fullKey, JSON.stringify(item));
2093
+ },
2094
+ remove: (key, options) => {
2095
+ const fullKey = this.getStorageKey(key, options?.namespace);
2096
+ localStorage.removeItem(fullKey);
2097
+ }
2098
+ };
2099
+ this.options = options;
2100
+ this.storageNamespace = `djvlc:${options.context.pageUid}`;
2101
+ }
2102
+ // ==================== 数据请求 ====================
2103
+ async requestData(queryId, params) {
2104
+ this.log("debug", `Requesting data: ${queryId}`, params);
2105
+ const startTime = performance.now();
2106
+ const context = this.options.context;
2107
+ try {
2108
+ const response = await fetch(`${this.options.apiBaseUrl}/data/query`, {
2109
+ method: "POST",
2110
+ headers: this.buildHeaders(),
2111
+ body: JSON.stringify({
2112
+ queryVersionId: queryId,
2113
+ params: params || {},
2114
+ context: {
2115
+ pageVersionId: context.pageVersionId,
2116
+ uid: context.userId
2117
+ }
2118
+ })
2119
+ });
2120
+ const result = await response.json();
2121
+ const duration = performance.now() - startTime;
2122
+ this.log("debug", `Data query completed in ${duration.toFixed(2)}ms`);
2123
+ if (result.success && result.data) {
2124
+ this.options.stateManager.setQuery(queryId, result.data);
2125
+ }
2126
+ return result;
2127
+ } catch (error) {
2128
+ this.log("error", `Data query failed: ${queryId}`, error);
2129
+ return {
2130
+ success: false,
2131
+ message: error instanceof Error ? error.message : "Query failed"
2132
+ };
2133
+ }
2134
+ }
2135
+ async executeAction(actionType, params) {
2136
+ this.log("debug", `Executing action: ${actionType}`, params);
2137
+ const startTime = performance.now();
2138
+ const context = this.options.context;
2139
+ try {
2140
+ const response = await fetch(`${this.options.apiBaseUrl}/actions/execute`, {
2141
+ method: "POST",
2142
+ headers: this.buildHeaders(),
2143
+ body: JSON.stringify({
2144
+ actionType,
2145
+ params: params || {},
2146
+ context: {
2147
+ pageVersionId: context.pageVersionId,
2148
+ uid: context.userId,
2149
+ deviceId: context.deviceId,
2150
+ channel: context.channel
2151
+ },
2152
+ idempotencyKey: this.generateIdempotencyKey(actionType, params)
2153
+ })
2154
+ });
2155
+ const result = await response.json();
2156
+ const duration = performance.now() - startTime;
2157
+ this.log("debug", `Action completed in ${duration.toFixed(2)}ms`);
2158
+ return result;
2159
+ } catch (error) {
2160
+ this.log("error", `Action failed: ${actionType}`, error);
2161
+ return {
2162
+ success: false,
2163
+ message: error instanceof Error ? error.message : "Action failed"
2164
+ };
2165
+ }
2166
+ }
2167
+ // ==================== 导航 ====================
2168
+ navigate(options) {
2169
+ this.log("debug", "Navigate:", options);
2170
+ this.track({
2171
+ eventName: "navigate",
2172
+ params: { url: options.url, newTab: options.newTab }
2173
+ });
2174
+ let url = options.url;
2175
+ if (options.params) {
2176
+ const searchParams = new URLSearchParams(options.params);
2177
+ url += (url.includes("?") ? "&" : "?") + searchParams.toString();
2178
+ }
2179
+ if (options.newTab) {
2180
+ window.open(url, "_blank");
2181
+ } else if (options.replace) {
2182
+ window.location.replace(url);
2183
+ } else {
2184
+ window.location.href = url;
2185
+ }
2186
+ }
2187
+ goBack() {
2188
+ this.log("debug", "Navigate back");
2189
+ window.history.back();
2190
+ }
2191
+ refresh() {
2192
+ this.log("debug", "Refresh page");
2193
+ window.location.reload();
2194
+ }
2195
+ // ==================== 对话框 ====================
2196
+ async openDialog(options) {
2197
+ this.log("debug", "Open dialog:", options);
2198
+ switch (options.type) {
2199
+ case "alert":
2200
+ window.alert(options.content || options.title);
2201
+ return true;
2202
+ case "confirm":
2203
+ return window.confirm(options.content || options.title);
2204
+ case "prompt":
2205
+ return window.prompt(options.content || options.title) || "";
2206
+ case "custom":
2207
+ this.log("warn", "Custom dialog not implemented");
2208
+ return false;
2209
+ default:
2210
+ return false;
2211
+ }
2212
+ }
2213
+ closeDialog() {
2214
+ this.log("debug", "Close dialog");
2215
+ }
2216
+ showToast(options) {
2217
+ this.log("debug", "Show toast:", options);
2218
+ const toast = document.createElement("div");
2219
+ toast.className = `djvlc-toast djvlc-toast-${options.type || "info"} djvlc-toast-${options.position || "top"}`;
2220
+ toast.textContent = options.message;
2221
+ Object.assign(toast.style, {
2222
+ position: "fixed",
2223
+ left: "50%",
2224
+ transform: "translateX(-50%)",
2225
+ padding: "12px 24px",
2226
+ borderRadius: "8px",
2227
+ color: "#fff",
2228
+ fontSize: "14px",
2229
+ zIndex: "10000",
2230
+ animation: "djvlc-toast-fade-in 0.3s ease"
2231
+ });
2232
+ const colors = {
2233
+ success: "#52c41a",
2234
+ error: "#ff4d4f",
2235
+ warning: "#faad14",
2236
+ info: "#1890ff"
2237
+ };
2238
+ toast.style.backgroundColor = colors[options.type || "info"];
2239
+ const positions = {
2240
+ top: { top: "20px" },
2241
+ center: { top: "50%", transform: "translate(-50%, -50%)" },
2242
+ bottom: { bottom: "20px" }
2243
+ };
2244
+ Object.assign(toast.style, positions[options.position || "top"]);
2245
+ document.body.appendChild(toast);
2246
+ setTimeout(() => {
2247
+ toast.style.animation = "djvlc-toast-fade-out 0.3s ease";
2248
+ setTimeout(() => toast.remove(), 300);
2249
+ }, options.duration || 3e3);
2250
+ }
2251
+ // ==================== 埋点 ====================
2252
+ track(event) {
2253
+ this.log("debug", "Track event:", event);
2254
+ const context = this.options.context;
2255
+ fetch(`${this.options.apiBaseUrl}/track`, {
2256
+ method: "POST",
2257
+ headers: this.buildHeaders(),
2258
+ body: JSON.stringify({
2259
+ eventName: event.eventName,
2260
+ params: event.params,
2261
+ timestamp: event.timestamp || Date.now(),
2262
+ context: {
2263
+ pageVersionId: context.pageVersionId,
2264
+ runtimeVersion: context.runtimeVersion,
2265
+ userId: context.userId,
2266
+ deviceId: context.deviceId,
2267
+ channel: context.channel
2268
+ }
2269
+ }),
2270
+ keepalive: true
2271
+ // 页面关闭时也能发送
2272
+ }).catch((error) => {
2273
+ this.log("warn", "Track failed:", error);
2274
+ });
2275
+ }
2276
+ // ==================== 剪贴板 ====================
2277
+ async copyToClipboard(text) {
2278
+ try {
2279
+ await navigator.clipboard.writeText(text);
2280
+ return { success: true, content: text };
2281
+ } catch (error) {
2282
+ return {
2283
+ success: false,
2284
+ error: error instanceof Error ? error.message : "Copy failed"
2285
+ };
2286
+ }
2287
+ }
2288
+ async readFromClipboard() {
2289
+ try {
2290
+ const text = await navigator.clipboard.readText();
2291
+ return { success: true, content: text };
2292
+ } catch (error) {
2293
+ return {
2294
+ success: false,
2295
+ error: error instanceof Error ? error.message : "Read failed"
2296
+ };
2297
+ }
2298
+ }
2299
+ // ==================== 状态管理 ====================
2300
+ getState(key) {
2301
+ return this.options.stateManager.getVariable(key);
2302
+ }
2303
+ setState(key, value) {
2304
+ this.options.stateManager.setVariable(key, value);
2305
+ }
2306
+ getVariable(name) {
2307
+ return this.options.stateManager.getVariable(name);
2308
+ }
2309
+ // ==================== 组件通信 ====================
2310
+ postMessage(componentId, message) {
2311
+ this.log("debug", `Post message to ${componentId}:`, message);
2312
+ const event = new CustomEvent(`djvlc:message:${componentId}`, {
2313
+ detail: { message, from: this.options.context.pageUid }
2314
+ });
2315
+ document.dispatchEvent(event);
2316
+ }
2317
+ broadcast(channel, message) {
2318
+ this.log("debug", `Broadcast to ${channel}:`, message);
2319
+ const event = new CustomEvent(`djvlc:broadcast:${channel}`, {
2320
+ detail: { message, from: this.options.context.pageUid }
2321
+ });
2322
+ document.dispatchEvent(event);
2323
+ }
2324
+ // ==================== 上下文信息 ====================
2325
+ getContext() {
2326
+ return { ...this.options.context };
2327
+ }
2328
+ // ==================== 私有方法 ====================
2329
+ buildHeaders() {
2330
+ const headers = {
2331
+ "Content-Type": "application/json",
2332
+ ...this.options.headers
2333
+ };
2334
+ if (this.options.authToken) {
2335
+ headers["Authorization"] = `Bearer ${this.options.authToken}`;
2336
+ }
2337
+ return headers;
2338
+ }
2339
+ getStorageKey(key, namespace) {
2340
+ return `${namespace || this.storageNamespace}:${key}`;
2341
+ }
2342
+ generateIdempotencyKey(actionType, params) {
2343
+ const timestamp = Date.now();
2344
+ const paramsStr = JSON.stringify(params || {});
2345
+ return `${actionType}:${timestamp}:${this.simpleHash(paramsStr)}`;
2346
+ }
2347
+ simpleHash(str) {
2348
+ let hash = 0;
2349
+ for (let i = 0; i < str.length; i++) {
2350
+ const char = str.charCodeAt(i);
2351
+ hash = (hash << 5) - hash + char;
2352
+ hash = hash & hash;
2353
+ }
2354
+ return Math.abs(hash).toString(36);
2355
+ }
2356
+ log(level, message, ...args) {
2357
+ if (this.options.logger) {
2358
+ this.options.logger[level](message, ...args);
2359
+ } else if (this.options.debug) {
2360
+ console[level](`[HostAPI] ${message}`, ...args);
2361
+ }
2362
+ }
2363
+ };
2364
+
2365
+ // src/security/security-manager.ts
2366
+ var SecurityManager = class {
2367
+ constructor(options = {}) {
2368
+ this.blockedComponentsMap = /* @__PURE__ */ new Map();
2369
+ this.blockedActionsSet = /* @__PURE__ */ new Set();
2370
+ this.options = {
2371
+ enableSRI: true,
2372
+ cdnDomains: [],
2373
+ apiDomains: [],
2374
+ blockedComponents: [],
2375
+ blockedActions: [],
2376
+ ...options
2377
+ };
2378
+ this.updateBlockedList(options.blockedComponents || [], options.blockedActions || []);
2379
+ }
2380
+ /**
2381
+ * 更新阻断列表
2382
+ */
2383
+ updateBlockedList(components, actions) {
2384
+ this.blockedComponentsMap.clear();
2385
+ components.forEach((c) => {
2386
+ this.blockedComponentsMap.set(`${c.name}@${c.version}`, c);
2387
+ });
2388
+ this.blockedActionsSet = new Set(actions);
2389
+ }
2390
+ /**
2391
+ * 检查组件是否被阻断
2392
+ */
2393
+ isComponentBlocked(name, version) {
2394
+ return this.blockedComponentsMap.has(`${name}@${version}`);
2395
+ }
2396
+ /**
2397
+ * 获取组件阻断信息
2398
+ */
2399
+ getBlockedInfo(name, version) {
2400
+ return this.blockedComponentsMap.get(`${name}@${version}`);
2401
+ }
2402
+ /**
2403
+ * 检查动作是否被阻断
2404
+ */
2405
+ isActionBlocked(actionType) {
2406
+ return this.blockedActionsSet.has(actionType);
2407
+ }
2408
+ /**
2409
+ * 验证组件完整性
2410
+ */
2411
+ async validateComponent(name, version, content, expectedIntegrity) {
2412
+ if (!this.options.enableSRI) return;
2413
+ const [algorithm, expectedHash] = expectedIntegrity.split("-");
2414
+ if (!algorithm || !expectedHash) {
2415
+ throw new IntegrityError(name, version, expectedIntegrity, "Invalid format");
2416
+ }
2417
+ const actualHash = await this.computeHash(content, algorithm);
2418
+ if (actualHash !== expectedHash) {
2419
+ throw new IntegrityError(name, version, expectedHash, actualHash);
2420
+ }
2421
+ }
2422
+ /**
2423
+ * 验证 URL 是否在白名单内
2424
+ */
2425
+ isAllowedUrl(url, type) {
2426
+ const domains = type === "cdn" ? this.options.cdnDomains : this.options.apiDomains;
2427
+ if (!domains || domains.length === 0) return true;
2428
+ try {
2429
+ const parsedUrl = new URL(url);
2430
+ return domains.some((domain) => {
2431
+ if (domain.startsWith("*.")) {
2432
+ const suffix = domain.slice(2);
2433
+ return parsedUrl.hostname.endsWith(suffix);
2434
+ }
2435
+ return parsedUrl.hostname === domain;
2436
+ });
2437
+ } catch {
2438
+ return false;
2439
+ }
2440
+ }
2441
+ /**
2442
+ * 生成 CSP 策略
2443
+ */
2444
+ generateCSPPolicy() {
2445
+ const cdnDomains = this.options.cdnDomains || [];
2446
+ const apiDomains = this.options.apiDomains || [];
2447
+ const scriptSrc = ["'self'", ...cdnDomains].join(" ");
2448
+ const connectSrc = ["'self'", ...apiDomains].join(" ");
2449
+ const styleSrc = ["'self'", "'unsafe-inline'", ...cdnDomains].join(" ");
2450
+ const imgSrc = ["'self'", "data:", "blob:", ...cdnDomains].join(" ");
2451
+ return [
2452
+ `default-src 'self'`,
2453
+ `script-src ${scriptSrc}`,
2454
+ `style-src ${styleSrc}`,
2455
+ `img-src ${imgSrc}`,
2456
+ `connect-src ${connectSrc}`,
2457
+ `font-src 'self' data: ${cdnDomains.join(" ")}`,
2458
+ `frame-ancestors 'self'`,
2459
+ `base-uri 'self'`,
2460
+ `form-action 'self'`
2461
+ ].join("; ");
2462
+ }
2463
+ /**
2464
+ * 应用 CSP 策略
2465
+ */
2466
+ applyCSP() {
2467
+ const meta = document.createElement("meta");
2468
+ meta.httpEquiv = "Content-Security-Policy";
2469
+ meta.content = this.generateCSPPolicy();
2470
+ document.head.appendChild(meta);
2471
+ }
2472
+ /**
2473
+ * 确保组件未被阻断
2474
+ */
2475
+ assertNotBlocked(name, version) {
2476
+ const blocked = this.getBlockedInfo(name, version);
2477
+ if (blocked) {
2478
+ throw new ComponentBlockedError(name, version, blocked.reason);
2479
+ }
2480
+ }
2481
+ /**
2482
+ * 计算哈希值
2483
+ */
2484
+ async computeHash(content, algorithm) {
2485
+ const encoder = new TextEncoder();
2486
+ const data = encoder.encode(content);
2487
+ const hashBuffer = await crypto.subtle.digest(
2488
+ algorithm.toUpperCase(),
2489
+ data
2490
+ );
2491
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
2492
+ return btoa(String.fromCharCode(...hashArray));
2493
+ }
2494
+ _log(level, message) {
2495
+ if (this.options.logger) {
2496
+ this.options.logger[level](message);
2497
+ }
2498
+ }
2499
+ // 使用 _log 避免 unused 错误,后续可按需启用日志
2500
+ get log() {
2501
+ return this._log.bind(this);
2502
+ }
2503
+ };
2504
+
2505
+ // src/telemetry/telemetry-manager.ts
2506
+ var TelemetryManager = class {
2507
+ constructor(options) {
2508
+ this.spans = /* @__PURE__ */ new Map();
2509
+ this.metrics = [];
2510
+ this.errors = [];
2511
+ this.options = {
2512
+ enabled: true,
2513
+ sampleRate: 1,
2514
+ ...options
2515
+ };
2516
+ this.traceId = this.generateId();
2517
+ this.shouldSample = Math.random() < (this.options.sampleRate ?? 1);
2518
+ }
2519
+ /**
2520
+ * 获取 Trace ID
2521
+ */
2522
+ getTraceId() {
2523
+ return this.traceId;
2524
+ }
2525
+ /**
2526
+ * 获取 W3C Trace Context 格式的 traceparent
2527
+ */
2528
+ getTraceparent() {
2529
+ return `00-${this.traceId}-${this.generateId().slice(0, 16)}-01`;
2530
+ }
2531
+ /**
2532
+ * 开始一个 Span
2533
+ */
2534
+ startSpan(name, parentSpanId, attributes) {
2535
+ const span = {
2536
+ spanId: this.generateId().slice(0, 16),
2537
+ traceId: this.traceId,
2538
+ parentSpanId,
2539
+ name,
2540
+ startTime: performance.now(),
2541
+ attributes
2542
+ };
2543
+ this.spans.set(span.spanId, span);
2544
+ this.log("debug", `Span started: ${name} (${span.spanId})`);
2545
+ return span;
2546
+ }
2547
+ /**
2548
+ * 结束一个 Span
2549
+ */
2550
+ endSpan(spanId, status = "ok") {
2551
+ const span = this.spans.get(spanId);
2552
+ if (span) {
2553
+ span.endTime = performance.now();
2554
+ span.status = status;
2555
+ this.log("debug", `Span ended: ${span.name} (${spanId}) - ${span.endTime - span.startTime}ms`);
2556
+ }
2557
+ }
2558
+ /**
2559
+ * 记录性能指标
2560
+ */
2561
+ recordMetric(type, name, duration, extra) {
2562
+ const metric = {
2563
+ type,
2564
+ name,
2565
+ duration,
2566
+ startTime: performance.now() - duration,
2567
+ extra
2568
+ };
2569
+ this.metrics.push(metric);
2570
+ this.log("debug", `Metric: ${type} - ${name} - ${duration}ms`);
2571
+ if (this.options.onMetric) {
2572
+ this.options.onMetric(metric);
2573
+ }
2574
+ }
2575
+ /**
2576
+ * 记录错误
2577
+ */
2578
+ recordError(error, context) {
2579
+ const runtimeError = {
2580
+ type: "LOAD_ERROR",
2581
+ message: error.message,
2582
+ stack: error instanceof Error ? error.stack : void 0,
2583
+ timestamp: Date.now(),
2584
+ traceId: this.traceId,
2585
+ ...context
2586
+ };
2587
+ this.errors.push(runtimeError);
2588
+ this.log("error", `Error recorded: ${error.message}`);
2589
+ if (this.shouldSample && this.options.enabled) {
2590
+ this.reportError(runtimeError);
2591
+ }
2592
+ }
2593
+ /**
2594
+ * 记录页面加载时间
2595
+ */
2596
+ recordPageLoad(duration, extra) {
2597
+ this.recordMetric("page_resolve", "page_load", duration, {
2598
+ pageVersionId: this.options.pageVersionId,
2599
+ ...extra
2600
+ });
2601
+ }
2602
+ /**
2603
+ * 记录组件加载时间
2604
+ */
2605
+ recordComponentLoad(name, version, duration, success) {
2606
+ this.recordMetric("component_load", `${name}@${version}`, duration, {
2607
+ success
2608
+ });
2609
+ }
2610
+ /**
2611
+ * 记录首次渲染时间
2612
+ */
2613
+ recordFirstRender(duration) {
2614
+ this.recordMetric("first_render", "first_render", duration, {
2615
+ pageVersionId: this.options.pageVersionId
2616
+ });
2617
+ }
2618
+ /**
2619
+ * 记录动作执行时间
2620
+ */
2621
+ recordActionExecute(actionType, duration, success) {
2622
+ this.recordMetric("action_execute", actionType, duration, { success });
2623
+ }
2624
+ /**
2625
+ * 记录查询执行时间
2626
+ */
2627
+ recordQueryExecute(queryId, duration, success, fromCache) {
2628
+ this.recordMetric("query_execute", queryId, duration, {
2629
+ success,
2630
+ fromCache
2631
+ });
2632
+ }
2633
+ /**
2634
+ * 获取所有指标
2635
+ */
2636
+ getMetrics() {
2637
+ return [...this.metrics];
2638
+ }
2639
+ /**
2640
+ * 获取所有 Span
2641
+ */
2642
+ getSpans() {
2643
+ return Array.from(this.spans.values());
2644
+ }
2645
+ /**
2646
+ * 清理数据
2647
+ */
2648
+ clear() {
2649
+ this.spans.clear();
2650
+ this.metrics = [];
2651
+ this.errors = [];
2652
+ }
2653
+ /**
2654
+ * 刷新上报
2655
+ */
2656
+ async flush() {
2657
+ if (!this.shouldSample || !this.options.enabled || !this.options.endpoint) {
2658
+ return;
2659
+ }
2660
+ const payload = {
2661
+ traceId: this.traceId,
2662
+ pageVersionId: this.options.pageVersionId,
2663
+ appId: this.options.appId,
2664
+ spans: this.getSpans(),
2665
+ metrics: this.metrics,
2666
+ errors: this.errors,
2667
+ timestamp: Date.now()
2668
+ };
2669
+ try {
2670
+ await fetch(this.options.endpoint, {
2671
+ method: "POST",
2672
+ headers: { "Content-Type": "application/json" },
2673
+ body: JSON.stringify(payload),
2674
+ keepalive: true
2675
+ });
2676
+ } catch (error) {
2677
+ this.log("warn", "Failed to flush telemetry:", error);
2678
+ }
2679
+ }
2680
+ reportError(error) {
2681
+ if (this.options.endpoint) {
2682
+ fetch(`${this.options.endpoint}/errors`, {
2683
+ method: "POST",
2684
+ headers: { "Content-Type": "application/json" },
2685
+ body: JSON.stringify(error),
2686
+ keepalive: true
2687
+ }).catch(() => {
2688
+ });
2689
+ }
2690
+ }
2691
+ generateId() {
2692
+ const array = new Uint8Array(16);
2693
+ crypto.getRandomValues(array);
2694
+ return Array.from(array, (b) => b.toString(16).padStart(2, "0")).join("");
2695
+ }
2696
+ log(level, message, ...args) {
2697
+ if (this.options.logger) {
2698
+ this.options.logger[level](message, ...args);
2699
+ } else if (this.options.debug) {
2700
+ console[level](`[Telemetry] ${message}`, ...args);
2701
+ }
2702
+ }
2703
+ };
2704
+
2705
+ // src/renderer/base-renderer.ts
2706
+ var import_contracts_types3 = require("@djvlc/contracts-types");
2707
+ var BaseRenderer = class {
2708
+ constructor(options) {
2709
+ this.container = null;
2710
+ this.renderedElements = /* @__PURE__ */ new Map();
2711
+ this.expressionContext = {
2712
+ state: {},
2713
+ query: {},
2714
+ context: {}
2715
+ };
2716
+ this.options = options;
2717
+ }
2718
+ /**
2719
+ * 初始化渲染器
2720
+ */
2721
+ init() {
2722
+ this.log("debug", "Renderer initialized");
2723
+ }
2724
+ /**
2725
+ * 渲染页面
2726
+ */
2727
+ render(schema, container) {
2728
+ this.container = container;
2729
+ this.log("debug", "Rendering page", schema.page.id);
2730
+ container.innerHTML = "";
2731
+ this.applyPageStyles(schema.page.canvas);
2732
+ const fragment = document.createDocumentFragment();
2733
+ for (const component of schema.components) {
2734
+ const element = this.renderComponent(component);
2735
+ if (element) {
2736
+ fragment.appendChild(element);
2737
+ }
2738
+ }
2739
+ container.appendChild(fragment);
2740
+ this.log("info", `Rendered ${schema.components.length} components`);
2741
+ }
2742
+ /**
2743
+ * 更新组件属性
2744
+ */
2745
+ updateComponent(componentId, props) {
2746
+ const element = this.renderedElements.get(componentId);
2747
+ if (!element) {
2748
+ this.log("warn", `Component not found: ${componentId}`);
2749
+ return;
2750
+ }
2751
+ this.applyProps(element, props);
2752
+ }
2753
+ /**
2754
+ * 更新表达式上下文
2755
+ */
2756
+ updateContext(context) {
2757
+ this.expressionContext = {
2758
+ ...this.expressionContext,
2759
+ ...context
2760
+ };
2761
+ }
2762
+ /**
2763
+ * 销毁渲染器
2764
+ */
2765
+ destroy() {
2766
+ this.renderedElements.forEach((element) => {
2767
+ element.remove();
2768
+ });
2769
+ this.renderedElements.clear();
2770
+ if (this.container) {
2771
+ this.container.innerHTML = "";
2772
+ }
2773
+ this.log("debug", "Renderer destroyed");
2774
+ }
2775
+ /**
2776
+ * 渲染单个组件
2777
+ */
2778
+ renderComponent(instance) {
2779
+ const { id, type, props, style, children, visible } = instance;
2780
+ if (visible === false) {
2781
+ return null;
2782
+ }
2783
+ try {
2784
+ const loadedComponent = this.options.components.get(type);
2785
+ let element;
2786
+ if (loadedComponent && customElements.get(type)) {
2787
+ element = document.createElement(type);
2788
+ } else {
2789
+ element = document.createElement("div");
2790
+ element.className = `djvlc-component djvlc-${type}`;
2791
+ if (!loadedComponent) {
2792
+ this.log("warn", `Component not loaded: ${type}`);
2793
+ element.textContent = `[Component: ${type}]`;
2794
+ }
2795
+ }
2796
+ element.setAttribute("data-component-id", id);
2797
+ element.setAttribute("data-component-type", type);
2798
+ const resolvedProps = this.resolveProps(props);
2799
+ this.applyProps(element, resolvedProps);
2800
+ if (style) {
2801
+ this.applyStyles(element, style);
2802
+ }
2803
+ this.options.injectHostApi(element, id);
2804
+ if (children && children.length > 0) {
2805
+ for (const child of children) {
2806
+ const childElement = this.renderComponent(child);
2807
+ if (childElement) {
2808
+ element.appendChild(childElement);
2809
+ }
2810
+ }
2811
+ }
2812
+ this.renderedElements.set(id, element);
2813
+ return element;
2814
+ } catch (error) {
2815
+ this.log("error", `Failed to render component: ${id}`, error);
2816
+ if (this.options.onRenderError) {
2817
+ return this.options.onRenderError(id, error);
2818
+ }
2819
+ const fallback = document.createElement("div");
2820
+ fallback.className = "djvlc-error-boundary";
2821
+ fallback.textContent = `Error rendering component: ${type}`;
2822
+ return fallback;
2823
+ }
2824
+ }
2825
+ /**
2826
+ * 解析 props 中的表达式
2827
+ */
2828
+ resolveProps(props) {
2829
+ const resolved = {};
2830
+ for (const [key, value] of Object.entries(props)) {
2831
+ if ((0, import_contracts_types3.isExpressionBinding)(value)) {
2832
+ const result = this.options.expressionEngine.evaluateWithFallback(
2833
+ value.expression,
2834
+ this.expressionContext,
2835
+ value.fallback
2836
+ );
2837
+ resolved[key] = result;
2838
+ } else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
2839
+ resolved[key] = this.resolveProps(value);
2840
+ } else {
2841
+ resolved[key] = value;
2842
+ }
2843
+ }
2844
+ return resolved;
2845
+ }
2846
+ /**
2847
+ * 应用 props 到元素
2848
+ */
2849
+ applyProps(element, props) {
2850
+ for (const [key, value] of Object.entries(props)) {
2851
+ if (value === null || value === void 0) continue;
2852
+ if (element.tagName.includes("-")) {
2853
+ element[key] = value;
2854
+ } else {
2855
+ if (typeof value === "boolean") {
2856
+ if (value) {
2857
+ element.setAttribute(key, "");
2858
+ } else {
2859
+ element.removeAttribute(key);
2860
+ }
2861
+ } else if (typeof value === "object") {
2862
+ element.setAttribute(key, JSON.stringify(value));
2863
+ } else {
2864
+ element.setAttribute(key, String(value));
2865
+ }
2866
+ }
2867
+ }
2868
+ }
2869
+ /**
2870
+ * 应用样式到元素
2871
+ */
2872
+ applyStyles(element, style) {
2873
+ for (const [key, value] of Object.entries(style)) {
2874
+ if (value === null || value === void 0) continue;
2875
+ let cssValue;
2876
+ if (typeof value === "number") {
2877
+ const unitless = ["zIndex", "opacity", "flex", "fontWeight"];
2878
+ cssValue = unitless.includes(key) ? String(value) : `${value}px`;
2879
+ } else {
2880
+ cssValue = String(value);
2881
+ }
2882
+ const cssKey = key.replace(/([A-Z])/g, "-$1").toLowerCase();
2883
+ element.style.setProperty(cssKey, cssValue);
2884
+ }
2885
+ }
2886
+ /**
2887
+ * 应用页面样式
2888
+ */
2889
+ applyPageStyles(canvas) {
2890
+ if (!this.container) return;
2891
+ this.container.style.width = `${canvas.width}px`;
2892
+ if (canvas.height) {
2893
+ this.container.style.minHeight = `${canvas.height}px`;
2894
+ }
2895
+ if (canvas.background) {
2896
+ this.container.style.background = canvas.background;
2897
+ }
2898
+ this.container.classList.add("djvlc-page", `djvlc-canvas-${canvas.type}`);
2899
+ }
2900
+ log(level, message, ...args) {
2901
+ if (this.options.logger) {
2902
+ this.options.logger[level](message, ...args);
2903
+ } else if (this.options.debug) {
2904
+ console[level](`[Renderer] ${message}`, ...args);
2905
+ }
2906
+ }
2907
+ };
2908
+
2909
+ // src/renderer/fallback-component.ts
2910
+ function registerFallbackComponents() {
2911
+ if (!customElements.get("djvlc-fallback")) {
2912
+ customElements.define(
2913
+ "djvlc-fallback",
2914
+ class extends HTMLElement {
2915
+ constructor() {
2916
+ super();
2917
+ const shadow = this.attachShadow({ mode: "open" });
2918
+ shadow.innerHTML = `
2919
+ <style>
2920
+ :host {
2921
+ display: block;
2922
+ padding: 16px;
2923
+ background: #fff2f0;
2924
+ border: 1px solid #ffccc7;
2925
+ border-radius: 4px;
2926
+ color: #ff4d4f;
2927
+ font-size: 14px;
2928
+ }
2929
+ .title {
2930
+ font-weight: 600;
2931
+ margin-bottom: 8px;
2932
+ }
2933
+ .message {
2934
+ color: #666;
2935
+ }
2936
+ </style>
2937
+ <div class="title">\u7EC4\u4EF6\u52A0\u8F7D\u5931\u8D25</div>
2938
+ <div class="message"><slot>\u8BF7\u5237\u65B0\u9875\u9762\u91CD\u8BD5</slot></div>
2939
+ `;
2940
+ }
2941
+ static get observedAttributes() {
2942
+ return ["message", "component-name"];
2943
+ }
2944
+ attributeChangedCallback(name, _oldValue, newValue) {
2945
+ if (name === "message" && this.shadowRoot) {
2946
+ const message = this.shadowRoot.querySelector(".message");
2947
+ if (message) message.textContent = newValue;
2948
+ }
2949
+ if (name === "component-name" && this.shadowRoot) {
2950
+ const title = this.shadowRoot.querySelector(".title");
2951
+ if (title) title.textContent = `\u7EC4\u4EF6 ${newValue} \u52A0\u8F7D\u5931\u8D25`;
2952
+ }
2953
+ }
2954
+ }
2955
+ );
2956
+ }
2957
+ if (!customElements.get("djvlc-blocked")) {
2958
+ customElements.define(
2959
+ "djvlc-blocked",
2960
+ class extends HTMLElement {
2961
+ constructor() {
2962
+ super();
2963
+ const shadow = this.attachShadow({ mode: "open" });
2964
+ shadow.innerHTML = `
2965
+ <style>
2966
+ :host {
2967
+ display: block;
2968
+ padding: 16px;
2969
+ background: #fffbe6;
2970
+ border: 1px solid #ffe58f;
2971
+ border-radius: 4px;
2972
+ color: #faad14;
2973
+ font-size: 14px;
2974
+ }
2975
+ .icon {
2976
+ margin-right: 8px;
2977
+ }
2978
+ </style>
2979
+ <span class="icon">\u26A0\uFE0F</span>
2980
+ <span>\u6B64\u7EC4\u4EF6\u5DF2\u88AB\u6682\u505C\u4F7F\u7528</span>
2981
+ `;
2982
+ }
2983
+ }
2984
+ );
2985
+ }
2986
+ if (!customElements.get("djvlc-error-boundary")) {
2987
+ customElements.define(
2988
+ "djvlc-error-boundary",
2989
+ class extends HTMLElement {
2990
+ constructor() {
2991
+ super();
2992
+ const shadow = this.attachShadow({ mode: "open" });
2993
+ shadow.innerHTML = `
2994
+ <style>
2995
+ :host {
2996
+ display: block;
2997
+ padding: 16px;
2998
+ background: #f5f5f5;
2999
+ border: 1px dashed #d9d9d9;
3000
+ border-radius: 4px;
3001
+ color: #999;
3002
+ font-size: 14px;
3003
+ text-align: center;
3004
+ }
3005
+ </style>
3006
+ <slot>\u6E32\u67D3\u51FA\u9519</slot>
3007
+ `;
3008
+ }
3009
+ }
3010
+ );
3011
+ }
3012
+ }
3013
+ function createFallbackElement(type, message, componentName) {
3014
+ const tagName = `djvlc-${type === "error" ? "error-boundary" : type}`;
3015
+ const element = document.createElement(tagName);
3016
+ if (message) {
3017
+ element.setAttribute("message", message);
3018
+ }
3019
+ if (componentName) {
3020
+ element.setAttribute("component-name", componentName);
3021
+ }
3022
+ return element;
3023
+ }
3024
+
3025
+ // src/runtime.ts
3026
+ function createRuntime(options) {
3027
+ return new DjvlcRuntime(options);
3028
+ }
3029
+ var DjvlcRuntime = class {
3030
+ constructor(options) {
3031
+ this.container = null;
3032
+ this.options = {
3033
+ channel: "prod",
3034
+ debug: false,
3035
+ enableSRI: true,
3036
+ ...options
3037
+ };
3038
+ this.logger = this.createLogger();
3039
+ this.stateManager = new StateManager();
3040
+ this.eventBus = new EventBus({ debug: options.debug, logger: this.logger });
3041
+ this.expressionEngine = new ExpressionEngine({ debug: options.debug, logger: this.logger });
3042
+ this.pageLoader = new PageLoader({
3043
+ apiBaseUrl: options.apiBaseUrl,
3044
+ channel: options.channel,
3045
+ authToken: options.authToken,
3046
+ previewToken: options.previewToken,
3047
+ headers: options.headers,
3048
+ logger: this.logger
3049
+ });
3050
+ this.componentLoader = new ComponentLoader({
3051
+ cdnBaseUrl: options.cdnBaseUrl,
3052
+ enableSRI: options.enableSRI,
3053
+ logger: this.logger
3054
+ });
3055
+ this.assetLoader = new AssetLoader({
3056
+ cdnHosts: [new URL(options.cdnBaseUrl).host],
3057
+ apiHosts: [new URL(options.apiBaseUrl).host]
3058
+ });
3059
+ this.securityManager = new SecurityManager({
3060
+ enableSRI: options.enableSRI,
3061
+ cdnDomains: [new URL(options.cdnBaseUrl).host],
3062
+ apiDomains: [new URL(options.apiBaseUrl).host],
3063
+ logger: this.logger
3064
+ });
3065
+ this.log("info", "Runtime created");
3066
+ }
3067
+ /**
3068
+ * 初始化运行时
3069
+ */
3070
+ async init() {
3071
+ this.log("info", "Initializing runtime");
3072
+ const startTime = performance.now();
3073
+ try {
3074
+ this.container = this.resolveContainer();
3075
+ this.assetLoader.preconnectAll();
3076
+ this.pageLoader.preconnect();
3077
+ registerFallbackComponents();
3078
+ this.stateManager.setPhase("resolving");
3079
+ const initTime = performance.now() - startTime;
3080
+ this.log("info", `Runtime initialized in ${initTime.toFixed(2)}ms`);
3081
+ } catch (error) {
3082
+ this.handleError(error);
3083
+ throw error;
3084
+ }
3085
+ }
3086
+ /**
3087
+ * 加载页面
3088
+ */
3089
+ async load() {
3090
+ this.log("info", "Loading page:", this.options.pageUid);
3091
+ const startTime = performance.now();
3092
+ try {
3093
+ this.stateManager.setPhase("resolving");
3094
+ const page = await this.pageLoader.resolve(this.options.pageUid, {
3095
+ uid: this.options.userId,
3096
+ deviceId: this.options.deviceId
3097
+ });
3098
+ this.stateManager.setPage(page);
3099
+ this.telemetryManager = new TelemetryManager({
3100
+ pageVersionId: page.pageVersionId,
3101
+ debug: this.options.debug,
3102
+ logger: this.logger,
3103
+ onMetric: this.options.onMetric
3104
+ });
3105
+ if (page.runtimeConfig) {
3106
+ this.securityManager.updateBlockedList(
3107
+ page.runtimeConfig.blockedComponents || [],
3108
+ page.runtimeConfig.killSwitches || []
3109
+ );
3110
+ this.componentLoader.updateBlockedList(
3111
+ page.runtimeConfig.blockedComponents?.map((c) => `${c.name}@${c.version}`) || []
3112
+ );
3113
+ }
3114
+ this.stateManager.setPhase("loading");
3115
+ this.componentLoader.preload(page.manifest.components);
3116
+ const componentResults = await this.componentLoader.loadAll(page.manifest);
3117
+ componentResults.forEach((result, key) => {
3118
+ this.stateManager.setComponentStatus(key, result);
3119
+ this.telemetryManager.recordComponentLoad(
3120
+ result.name,
3121
+ result.version,
3122
+ result.loadTime || 0,
3123
+ result.status === "loaded"
3124
+ );
3125
+ });
3126
+ this.initHostApi(page);
3127
+ this.initRenderer();
3128
+ const loadTime = performance.now() - startTime;
3129
+ this.telemetryManager.recordPageLoad(loadTime);
3130
+ this.log("info", `Page loaded in ${loadTime.toFixed(2)}ms`);
3131
+ this.emitEvent("page:loaded", { page, loadTime });
3132
+ return page;
3133
+ } catch (error) {
3134
+ this.stateManager.setPhase("error");
3135
+ this.handleError(error);
3136
+ throw error;
3137
+ }
3138
+ }
3139
+ /**
3140
+ * 渲染页面
3141
+ */
3142
+ async render() {
3143
+ const state = this.stateManager.getState();
3144
+ if (!state.page || !this.container) {
3145
+ throw new PageLoadError("Page not loaded");
3146
+ }
3147
+ this.log("info", "Rendering page");
3148
+ const startTime = performance.now();
3149
+ try {
3150
+ this.stateManager.setPhase("rendering");
3151
+ this.renderer.updateContext({
3152
+ state: state.variables,
3153
+ query: state.queries,
3154
+ context: {
3155
+ userId: this.options.userId,
3156
+ deviceId: this.options.deviceId,
3157
+ channel: this.options.channel,
3158
+ pageVersionId: state.page.pageVersionId
3159
+ }
3160
+ });
3161
+ this.renderer.render(state.page.pageJson, this.container);
3162
+ this.stateManager.setPhase("ready");
3163
+ const renderTime = performance.now() - startTime;
3164
+ this.telemetryManager.recordFirstRender(renderTime);
3165
+ this.log("info", `Page rendered in ${renderTime.toFixed(2)}ms`);
3166
+ } catch (error) {
3167
+ this.stateManager.setPhase("error");
3168
+ this.handleError(error);
3169
+ throw error;
3170
+ }
3171
+ }
3172
+ /**
3173
+ * 获取 Host API
3174
+ */
3175
+ getHostApi() {
3176
+ return this.hostApi;
3177
+ }
3178
+ /**
3179
+ * 获取当前状态
3180
+ */
3181
+ getState() {
3182
+ return this.stateManager.getState();
3183
+ }
3184
+ /**
3185
+ * 订阅状态变更
3186
+ */
3187
+ onStateChange(listener) {
3188
+ return this.stateManager.subscribe(listener);
3189
+ }
3190
+ /**
3191
+ * 订阅事件
3192
+ */
3193
+ on(type, handler) {
3194
+ return this.eventBus.on(type, handler);
3195
+ }
3196
+ /**
3197
+ * 更新组件
3198
+ */
3199
+ updateComponent(componentId, props) {
3200
+ this.renderer.updateComponent(componentId, props);
3201
+ }
3202
+ /**
3203
+ * 设置变量
3204
+ */
3205
+ setVariable(key, value) {
3206
+ this.stateManager.setVariable(key, value);
3207
+ const state = this.stateManager.getState();
3208
+ if (state.page && this.container) {
3209
+ this.renderer.updateContext({
3210
+ state: state.variables
3211
+ });
3212
+ }
3213
+ }
3214
+ /**
3215
+ * 刷新数据
3216
+ */
3217
+ async refreshData(queryId) {
3218
+ const result = await this.hostApi.requestData(queryId);
3219
+ if (result.success) {
3220
+ this.stateManager.setQuery(queryId, result.data);
3221
+ const state = this.stateManager.getState();
3222
+ this.renderer.updateContext({
3223
+ query: state.queries
3224
+ });
3225
+ }
3226
+ }
3227
+ /**
3228
+ * 销毁运行时
3229
+ */
3230
+ destroy() {
3231
+ this.log("info", "Destroying runtime");
3232
+ this.telemetryManager?.flush();
3233
+ this.renderer?.destroy();
3234
+ this.eventBus.clear();
3235
+ this.stateManager.setDestroyed();
3236
+ if (this.container) {
3237
+ this.container.innerHTML = "";
3238
+ }
3239
+ this.log("info", "Runtime destroyed");
3240
+ }
3241
+ // ==================== 私有方法 ====================
3242
+ resolveContainer() {
3243
+ const { container } = this.options;
3244
+ if (typeof container === "string") {
3245
+ const element = document.querySelector(container);
3246
+ if (!element) {
3247
+ throw new Error(`Container not found: ${container}`);
3248
+ }
3249
+ return element;
3250
+ }
3251
+ return container;
3252
+ }
3253
+ initHostApi(page) {
3254
+ const context = {
3255
+ pageUid: page.pageUid,
3256
+ pageVersionId: page.pageVersionId,
3257
+ runtimeVersion: "0.1.0",
3258
+ userId: this.options.userId,
3259
+ deviceId: this.options.deviceId,
3260
+ channel: this.options.channel,
3261
+ isEditMode: false,
3262
+ isPreviewMode: page.isPreview || false
3263
+ };
3264
+ this.hostApi = new HostAPIImpl({
3265
+ apiBaseUrl: this.options.apiBaseUrl,
3266
+ authToken: this.options.authToken,
3267
+ headers: this.options.headers,
3268
+ stateManager: this.stateManager,
3269
+ eventBus: this.eventBus,
3270
+ expressionEngine: this.expressionEngine,
3271
+ context,
3272
+ debug: this.options.debug,
3273
+ logger: this.logger
3274
+ });
3275
+ }
3276
+ initRenderer() {
3277
+ const components = /* @__PURE__ */ new Map();
3278
+ const state = this.stateManager.getState();
3279
+ state.components.forEach((result, key) => {
3280
+ if (result.status === "loaded" && result.component) {
3281
+ const [name, version] = key.split("@");
3282
+ components.set(name, {
3283
+ name,
3284
+ version,
3285
+ Component: result.component,
3286
+ loadTime: result.loadTime || 0
3287
+ });
3288
+ }
3289
+ });
3290
+ this.renderer = new BaseRenderer({
3291
+ expressionEngine: this.expressionEngine,
3292
+ components,
3293
+ injectHostApi: (element, componentId) => {
3294
+ element.hostApi = this.hostApi;
3295
+ element.componentId = componentId;
3296
+ },
3297
+ debug: this.options.debug,
3298
+ logger: this.logger,
3299
+ onRenderError: (componentId, error) => {
3300
+ this.log("error", `Render error in ${componentId}:`, error);
3301
+ this.emitEvent("component:error", { componentId, error: error.message });
3302
+ return createFallbackElement("error", error.message);
3303
+ }
3304
+ });
3305
+ this.renderer.init();
3306
+ }
3307
+ handleError(error) {
3308
+ const runtimeError = error instanceof DjvlcRuntimeError ? error : { type: "LOAD_ERROR", message: error.message, timestamp: Date.now() };
3309
+ this.stateManager.setError(runtimeError);
3310
+ this.telemetryManager?.recordError(error);
3311
+ this.emitEvent("page:error", { error: error.message });
3312
+ if (this.options.onError) {
3313
+ this.options.onError(runtimeError);
3314
+ }
3315
+ }
3316
+ emitEvent(type, data) {
3317
+ const event = EventBus.createEvent(type, data, this.telemetryManager?.getTraceId());
3318
+ this.eventBus.emit(event);
3319
+ if (this.options.onEvent) {
3320
+ this.options.onEvent(event);
3321
+ }
3322
+ }
3323
+ createLogger() {
3324
+ const prefix = "[DJVLC]";
3325
+ return {
3326
+ debug: (...args) => {
3327
+ if (this.options.debug) console.debug(prefix, ...args);
3328
+ },
3329
+ info: (...args) => console.info(prefix, ...args),
3330
+ warn: (...args) => console.warn(prefix, ...args),
3331
+ error: (...args) => console.error(prefix, ...args)
3332
+ };
3333
+ }
3334
+ log(level, message, ...args) {
3335
+ this.logger[level](message, ...args);
3336
+ }
3337
+ };
3338
+ // Annotate the CommonJS export names for ESM import in node:
3339
+ 0 && (module.exports = {
3340
+ ActionBridge,
3341
+ ActionError,
3342
+ AssetLoader,
3343
+ BaseRenderer,
3344
+ ComponentBlockedError,
3345
+ ComponentLoadError,
3346
+ ComponentLoader,
3347
+ DjvlcRuntime,
3348
+ DjvlcRuntimeError,
3349
+ Evaluator,
3350
+ EventBus,
3351
+ ExpressionEngine,
3352
+ ExpressionError,
3353
+ HostAPIImpl,
3354
+ IntegrityError,
3355
+ Lexer,
3356
+ PageLoadError,
3357
+ PageLoader,
3358
+ Parser,
3359
+ QueryError,
3360
+ RenderError,
3361
+ SecurityManager,
3362
+ StateManager,
3363
+ TelemetryManager,
3364
+ builtinFunctions,
3365
+ createFallbackElement,
3366
+ createRuntime,
3367
+ registerFallbackComponents
3368
+ });