@pactor-app/runtime 0.1.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.js ADDED
@@ -0,0 +1,909 @@
1
+ // src/context/adapters.ts
2
+ function createDefaultPermissionAdapter() {
3
+ return { check: () => true };
4
+ }
5
+ function createDefaultRouterAdapter() {
6
+ return {
7
+ navigate(to) {
8
+ console.warn(
9
+ `[@pactor-app/runtime] RouterAdapter \u672A\u6CE8\u5165\uFF0Cnavigate("${to}") \u5DF2\u5FFD\u7565`
10
+ );
11
+ }
12
+ };
13
+ }
14
+ function createFetchHttpAdapter() {
15
+ return {
16
+ async request(config) {
17
+ const method = (config.method ?? "GET").toUpperCase();
18
+ let url = config.url;
19
+ if (config.params) {
20
+ const search = new URLSearchParams();
21
+ for (const [key, value] of Object.entries(config.params)) {
22
+ if (value !== void 0 && value !== null) {
23
+ search.set(key, String(value));
24
+ }
25
+ }
26
+ const query = search.toString();
27
+ if (query) {
28
+ url += (url.includes("?") ? "&" : "?") + query;
29
+ }
30
+ }
31
+ const headers = { ...config.headers };
32
+ let body;
33
+ if (config.body !== void 0) {
34
+ headers["content-type"] ?? (headers["content-type"] = "application/json");
35
+ body = JSON.stringify(config.body);
36
+ }
37
+ const response = await fetch(url, { method, headers, body });
38
+ if (!response.ok) {
39
+ throw new Error(
40
+ `[@pactor-app/runtime] http request failed: ${response.status} ${response.statusText} (${config.url})`
41
+ );
42
+ }
43
+ const text = await response.text();
44
+ if (!text) {
45
+ return void 0;
46
+ }
47
+ try {
48
+ return JSON.parse(text);
49
+ } catch {
50
+ return text;
51
+ }
52
+ }
53
+ };
54
+ }
55
+
56
+ // src/context/context.ts
57
+ import { Registry, createI18nContext } from "@pactor-app/core";
58
+
59
+ // src/utils/resolve.ts
60
+ import {
61
+ hasExpression,
62
+ resolveTemplate
63
+ } from "@pactor-app/expression";
64
+ function isPlainRecord(value) {
65
+ return typeof value === "object" && value !== null && !Array.isArray(value);
66
+ }
67
+ function resolveDeep(value, scope, options = {}) {
68
+ if (typeof value === "string") {
69
+ return hasExpression(value) ? resolveTemplate(value, scope) : value;
70
+ }
71
+ if (Array.isArray(value)) {
72
+ return value.map((item) => resolveDeep(item, scope, options));
73
+ }
74
+ if (isPlainRecord(value)) {
75
+ if (options.isOpaque?.(value)) {
76
+ return value;
77
+ }
78
+ const out = {};
79
+ for (const [key, item] of Object.entries(value)) {
80
+ out[key] = resolveDeep(item, scope, options);
81
+ }
82
+ return out;
83
+ }
84
+ return value;
85
+ }
86
+ function toError(cause) {
87
+ return cause instanceof Error ? cause : new Error(String(cause));
88
+ }
89
+
90
+ // src/datasource/data-store.ts
91
+ var DataStore = class {
92
+ constructor(deps) {
93
+ this.statuses = /* @__PURE__ */ new Map();
94
+ this.listeners = /* @__PURE__ */ new Set();
95
+ this.version = 0;
96
+ this.configs = deps.dataSources;
97
+ this.services = deps.services;
98
+ this.http = deps.http;
99
+ this.getScope = deps.getScope;
100
+ for (const name of Object.keys(deps.dataSources)) {
101
+ this.statuses.set(name, { loading: false });
102
+ }
103
+ }
104
+ /** 状态快照(未知名返回空状态,不抛错) */
105
+ get(name) {
106
+ return this.statuses.get(name) ?? { loading: false };
107
+ }
108
+ /** 同 `get(name)`,语义化别名 */
109
+ getStatus(name) {
110
+ return this.get(name);
111
+ }
112
+ getData(name) {
113
+ return this.get(name).data;
114
+ }
115
+ /** 各数据源当前数据,供表达式作用域的 `data` 键使用 */
116
+ getAllData() {
117
+ const out = {};
118
+ for (const [name, status] of this.statuses) {
119
+ out[name] = status.data;
120
+ }
121
+ return out;
122
+ }
123
+ subscribe(listener) {
124
+ this.listeners.add(listener);
125
+ return () => {
126
+ this.listeners.delete(listener);
127
+ };
128
+ }
129
+ /** 版本号,供 `useSyncExternalStore` 的 getSnapshot 使用 */
130
+ getVersion() {
131
+ return this.version;
132
+ }
133
+ /** 触发所有 `autoLoad: true` 的数据源(PageRuntime 初始化阶段调用) */
134
+ init() {
135
+ return Promise.all(this.autoLoadNames().map((name) => this.load(name)));
136
+ }
137
+ /**
138
+ * 加载指定数据源。状态流转:开始置 loading 并清空 error;
139
+ * 成功写入 data;失败写入 error(保留旧 data)。MUST NOT 抛出。
140
+ */
141
+ async load(name) {
142
+ const config = this.configs[name];
143
+ if (!config) {
144
+ throw new Error(`[@pactor-app/runtime] unknown dataSource: "${name}"`);
145
+ }
146
+ const previous = this.get(name);
147
+ this.update(name, { loading: true, error: void 0, data: previous.data });
148
+ try {
149
+ const data = await this.loadByType(config);
150
+ this.update(name, { loading: false, data, error: void 0 });
151
+ } catch (cause) {
152
+ this.update(name, {
153
+ loading: false,
154
+ error: toError(cause),
155
+ data: previous.data
156
+ });
157
+ }
158
+ }
159
+ /** 重新加载指定数据源(语义同 `load`,供模板与 Action 调用) */
160
+ refresh(name) {
161
+ return this.load(name);
162
+ }
163
+ /**
164
+ * reload 语义:指定名称时刷新该数据源;
165
+ * 缺省时刷新全部 autoLoad 数据源(供 Action 引擎 `reload` 动作调用)。
166
+ */
167
+ reload(name) {
168
+ if (name !== void 0) {
169
+ return this.load(name);
170
+ }
171
+ return Promise.all(this.autoLoadNames().map((n) => this.load(n)));
172
+ }
173
+ autoLoadNames() {
174
+ return Object.entries(this.configs).filter(([, config]) => config.autoLoad === true).map(([name]) => name);
175
+ }
176
+ async loadByType(config) {
177
+ switch (config.type) {
178
+ case "static":
179
+ return config.value;
180
+ case "service": {
181
+ const serviceName = config.service ?? "";
182
+ const handler = this.services.get(serviceName);
183
+ if (!handler) {
184
+ throw new Error(
185
+ `[@pactor-app/runtime] service not registered: "${serviceName}"`
186
+ );
187
+ }
188
+ const params = resolveDeep(config.params ?? {}, this.getScope());
189
+ return handler(params);
190
+ }
191
+ case "http": {
192
+ if (!config.request) {
193
+ throw new Error("[@pactor-app/runtime] http dataSource \u7F3A\u5C11 request \u914D\u7F6E");
194
+ }
195
+ const request = resolveDeep(
196
+ config.request,
197
+ this.getScope()
198
+ );
199
+ return this.http.request(request);
200
+ }
201
+ default:
202
+ throw new Error(
203
+ `[@pactor-app/runtime] unsupported dataSource type: "${config.type}"`
204
+ );
205
+ }
206
+ }
207
+ update(name, status) {
208
+ this.statuses.set(name, status);
209
+ this.version += 1;
210
+ for (const listener of [...this.listeners]) {
211
+ listener();
212
+ }
213
+ }
214
+ };
215
+
216
+ // src/context/scope.ts
217
+ function buildScope(context, local) {
218
+ return {
219
+ app: context.app,
220
+ page: context.page,
221
+ state: context.state.getState(),
222
+ form: context.state.get("form"),
223
+ data: context.data.getAllData(),
224
+ route: context.route,
225
+ user: context.user,
226
+ params: context.route.params,
227
+ actions: context.actionResults,
228
+ locale: context.locale,
229
+ t: context.i18n.t,
230
+ ...local
231
+ };
232
+ }
233
+
234
+ // src/context/state-store.ts
235
+ var StateStore = class {
236
+ constructor(initialState = {}) {
237
+ this.listeners = /* @__PURE__ */ new Set();
238
+ this.version = 0;
239
+ this.initialState = initialState;
240
+ this.state = initialState;
241
+ }
242
+ /** 当前完整 state 对象 */
243
+ getState() {
244
+ return this.state;
245
+ }
246
+ /** 按点路径读取,如 `get('user.name')`;路径不存在时返回 undefined */
247
+ get(path) {
248
+ let current = this.state;
249
+ for (const key of path.split(".")) {
250
+ if (typeof current !== "object" || current === null) {
251
+ return void 0;
252
+ }
253
+ current = current[key];
254
+ }
255
+ return current;
256
+ }
257
+ set(patchOrPath, value) {
258
+ if (typeof patchOrPath === "string") {
259
+ this.state = setPath(this.state, patchOrPath.split("."), value);
260
+ } else {
261
+ this.state = { ...this.state, ...patchOrPath };
262
+ }
263
+ this.notify();
264
+ }
265
+ /** 恢复为创建时的初始值 */
266
+ reset() {
267
+ this.state = this.initialState;
268
+ this.notify();
269
+ }
270
+ /** 订阅变化,返回退订函数 */
271
+ subscribe(listener) {
272
+ this.listeners.add(listener);
273
+ return () => {
274
+ this.listeners.delete(listener);
275
+ };
276
+ }
277
+ /**
278
+ * 单调递增的版本号,供 `useSyncExternalStore` 的 getSnapshot 使用
279
+ * (V1 简化策略:任一变化重渲染整棵 DSL 树)。
280
+ */
281
+ getVersion() {
282
+ return this.version;
283
+ }
284
+ notify() {
285
+ this.version += 1;
286
+ for (const listener of [...this.listeners]) {
287
+ listener();
288
+ }
289
+ }
290
+ };
291
+ function setPath(source, path, value) {
292
+ const [key, ...rest] = path;
293
+ if (key === void 0) {
294
+ return source;
295
+ }
296
+ const clone = { ...source };
297
+ if (rest.length === 0) {
298
+ clone[key] = value;
299
+ return clone;
300
+ }
301
+ const child = clone[key];
302
+ clone[key] = setPath(
303
+ typeof child === "object" && child !== null && !Array.isArray(child) ? child : {},
304
+ rest,
305
+ value
306
+ );
307
+ return clone;
308
+ }
309
+
310
+ // src/context/context.ts
311
+ function createRuntimeContext(options = {}) {
312
+ const state = new StateStore(options.state);
313
+ const services = options.services ?? new Registry("ServiceRegistry");
314
+ const components = options.components ?? new Registry("ComponentRegistry");
315
+ const actions = options.actions ?? new Registry("ActionRegistry");
316
+ const forms = options.forms ?? new Registry("FormRegistry");
317
+ const capabilities = options.capabilities ?? new Registry("CapabilityRegistry");
318
+ const permission = options.permission ?? createDefaultPermissionAdapter();
319
+ const router = options.router ?? createDefaultRouterAdapter();
320
+ const http = options.http ?? createFetchHttpAdapter();
321
+ const route = {
322
+ params: options.route?.params ?? {},
323
+ query: options.route?.query ?? {},
324
+ navigate: (to) => router.navigate(to)
325
+ };
326
+ let context;
327
+ const data = new DataStore({
328
+ dataSources: options.dataSources ?? {},
329
+ services,
330
+ http,
331
+ getScope: () => buildScope(context)
332
+ });
333
+ context = {
334
+ app: options.app ?? {},
335
+ page: options.page ?? {},
336
+ state,
337
+ data,
338
+ route,
339
+ user: options.user ?? {},
340
+ services,
341
+ components,
342
+ actions,
343
+ forms,
344
+ actionResults: {},
345
+ permission,
346
+ capabilities,
347
+ http,
348
+ locale: options.locale ?? "zh-CN",
349
+ i18n: createI18nContext({
350
+ locale: options.locale,
351
+ defaultLocale: options.defaultLocale,
352
+ messages: options.messages
353
+ })
354
+ };
355
+ return context;
356
+ }
357
+
358
+ // src/context/i18n-context.ts
359
+ import { createI18nContext as createI18nContext2 } from "@pactor-app/core";
360
+ import { createContext, useContext } from "react";
361
+ var I18nReactContext = createContext(null);
362
+ var I18nContextProvider = I18nReactContext.Provider;
363
+ var defaultI18n = createI18nContext2({
364
+ messages: {
365
+ "pactor.layout.expandSidebar": "\u5C55\u5F00\u4FA7\u8FB9\u680F",
366
+ "pactor.layout.collapseSidebar": "\u6536\u8D77\u4FA7\u8FB9\u680F",
367
+ "pactor.layout.closeTab": "\u5173\u95ED {{title}}",
368
+ "pactor.layout.breadcrumb": "\u9762\u5305\u5C51",
369
+ "pactor.table.loading": "\u52A0\u8F7D\u4E2D\u2026",
370
+ "pactor.table.empty": "\u6682\u65E0\u6570\u636E",
371
+ "pactor.form.required": "\u5FC5\u586B",
372
+ "pactor.select.clear": "\u6E05\u9664"
373
+ }
374
+ });
375
+ function useI18n() {
376
+ const value = useContext(I18nReactContext);
377
+ if (value === null) {
378
+ return defaultI18n;
379
+ }
380
+ return {
381
+ ...value,
382
+ t: (key, params) => {
383
+ const translated = value.t(key, params);
384
+ return translated === key ? defaultI18n.t(key, params) : translated;
385
+ }
386
+ };
387
+ }
388
+
389
+ // src/action/builtins.ts
390
+ function registerBuiltinActions(registry) {
391
+ const register = (name, handler) => {
392
+ if (!registry.has(name)) {
393
+ registry.register(name, handler);
394
+ }
395
+ };
396
+ register("setState", (action, ctx) => {
397
+ ctx.context.state.set(action.params ?? {});
398
+ return { ok: true };
399
+ });
400
+ register("resetState", (_action, ctx) => {
401
+ ctx.context.state.reset();
402
+ return { ok: true };
403
+ });
404
+ register("reload", async (action, ctx) => {
405
+ const target = action.params?.["dataSource"];
406
+ await ctx.context.data.reload(
407
+ typeof target === "string" ? target : void 0
408
+ );
409
+ return { ok: true };
410
+ });
411
+ register("request", async (action, ctx) => {
412
+ const params = action.params ?? {};
413
+ const { target, method, url, headers, body } = params;
414
+ if (typeof url !== "string" || !url) {
415
+ return {
416
+ ok: false,
417
+ error: new Error("[@pactor-app/runtime] request \u52A8\u4F5C\u7F3A\u5C11 url \u53C2\u6570")
418
+ };
419
+ }
420
+ try {
421
+ const data = await ctx.context.http.request({
422
+ method: typeof method === "string" ? method : void 0,
423
+ url,
424
+ params: isRecord(params["params"]) ? params["params"] : void 0,
425
+ headers: isStringRecord(headers) ? headers : void 0,
426
+ body
427
+ });
428
+ if (typeof target === "string" && target) {
429
+ const path = target.startsWith("state.") ? target.slice(6) : target;
430
+ ctx.context.state.set(path, data);
431
+ }
432
+ return { ok: true, data };
433
+ } catch (cause) {
434
+ return {
435
+ ok: false,
436
+ error: cause instanceof Error ? cause : new Error(String(cause))
437
+ };
438
+ }
439
+ });
440
+ register("sequence", async (action, ctx) => {
441
+ for (const child of asActions(action.params?.["actions"])) {
442
+ const result = await ctx.execute(child);
443
+ if (!result.ok) {
444
+ return result;
445
+ }
446
+ }
447
+ return { ok: true };
448
+ });
449
+ register("parallel", async (action, ctx) => {
450
+ const results = await Promise.all(
451
+ asActions(action.params?.["actions"]).map((child) => ctx.execute(child))
452
+ );
453
+ const failed = results.find((result) => !result.ok);
454
+ return failed ?? { ok: true, data: results };
455
+ });
456
+ register("if", async (action, ctx) => {
457
+ const branch = action.params?.["condition"] ? action.params?.["then"] : action.params?.["else"];
458
+ for (const child of asActions(branch)) {
459
+ const result = await ctx.execute(child);
460
+ if (!result.ok) {
461
+ return result;
462
+ }
463
+ }
464
+ return { ok: true };
465
+ });
466
+ register(
467
+ "delay",
468
+ (action) => new Promise((resolve) => {
469
+ const ms = Number(action.params?.["ms"] ?? 0);
470
+ setTimeout(() => resolve({ ok: true }), Number.isFinite(ms) ? ms : 0);
471
+ })
472
+ );
473
+ register("form.setValues", (action, ctx) => {
474
+ const formName = action.params?.["form"];
475
+ const name = typeof formName === "string" && formName !== "" ? formName : "default";
476
+ const handle = ctx.context.forms.get(name);
477
+ if (!handle) {
478
+ return {
479
+ ok: false,
480
+ error: new Error(
481
+ `[@pactor-app/runtime] form.setValues \u76EE\u6807\u8868\u5355\u672A\u6CE8\u518C: "${name}"`
482
+ )
483
+ };
484
+ }
485
+ const values = action.params?.["values"];
486
+ if (!isRecord(values)) {
487
+ return {
488
+ ok: false,
489
+ error: new Error("[@pactor-app/runtime] form.setValues \u7F3A\u5C11 params.values\uFF08object\uFF09")
490
+ };
491
+ }
492
+ handle.setValues(values);
493
+ return { ok: true };
494
+ });
495
+ }
496
+ function asActions(value) {
497
+ if (Array.isArray(value)) {
498
+ return value;
499
+ }
500
+ if (isRecord(value) && typeof value["action"] === "string") {
501
+ return [value];
502
+ }
503
+ return [];
504
+ }
505
+ function isRecord(value) {
506
+ return typeof value === "object" && value !== null && !Array.isArray(value);
507
+ }
508
+ function isStringRecord(value) {
509
+ return isRecord(value) && Object.values(value).every((v) => typeof v === "string");
510
+ }
511
+
512
+ // src/action/engine.ts
513
+ import {
514
+ resolveTemplate as resolveTemplate2
515
+ } from "@pactor-app/expression";
516
+ var ActionEngine = class {
517
+ constructor(context) {
518
+ this.context = context;
519
+ }
520
+ async execute(action, localScope) {
521
+ const scope = buildScope(this.context, localScope);
522
+ let result;
523
+ try {
524
+ if (action.condition !== void 0) {
525
+ const expression = typeof action.condition === "string" ? action.condition : action.condition.expression;
526
+ if (!resolveTemplate2(expression, scope)) {
527
+ result = { ok: true, skipped: true };
528
+ this.recordNamedResult(action, result);
529
+ return result;
530
+ }
531
+ }
532
+ if (action.permission !== void 0 && !this.context.permission.check(action.permission)) {
533
+ result = {
534
+ ok: false,
535
+ error: new Error(
536
+ `[@pactor-app/runtime] permission denied for action "${action.action}"`
537
+ )
538
+ };
539
+ this.recordNamedResult(action, result);
540
+ return result;
541
+ }
542
+ const handler = this.context.actions.get(action.action);
543
+ if (!handler) {
544
+ result = {
545
+ ok: false,
546
+ error: new Error(
547
+ `[@pactor-app/runtime] action not registered: "${action.action}"`
548
+ )
549
+ };
550
+ this.recordNamedResult(action, result);
551
+ return result;
552
+ }
553
+ const resolvedAction = {
554
+ ...action,
555
+ // 内嵌的子动作配置(sequence/parallel/if 的 actions/then/else)
556
+ // 与内嵌的 DslNode 子树(overlay.open 的 content 等,约定同
557
+ // Renderer 的 resolveProps)保持原样,由各自执行/渲染时
558
+ // 按当时的 state 与作用域求值
559
+ params: resolveDeep(action.params ?? {}, scope, {
560
+ isOpaque: (value) => typeof value["action"] === "string" || typeof value["type"] === "string"
561
+ })
562
+ };
563
+ const ctx = {
564
+ context: this.context,
565
+ scope,
566
+ // 子动作继承局部作用域(而非作用域快照),保证 state 始终取当前值
567
+ execute: (child) => this.execute(child, localScope)
568
+ };
569
+ result = await handler(resolvedAction, ctx);
570
+ } catch (cause) {
571
+ result = { ok: false, error: toError(cause) };
572
+ }
573
+ this.recordNamedResult(action, result);
574
+ return result;
575
+ }
576
+ /** 命名动作结果落盘:成功 → data;失败 → `{ ok: false, error }` */
577
+ recordNamedResult(action, result) {
578
+ if (action.id === void 0) {
579
+ return;
580
+ }
581
+ this.context.actionResults[action.id] = result.ok ? result.data : { ok: false, error: result.error };
582
+ }
583
+ /** 事件动作数组按声明顺序串行执行 */
584
+ async executeAll(actions, localScope) {
585
+ const list = Array.isArray(actions) ? actions : [actions];
586
+ const results = [];
587
+ for (const action of list) {
588
+ results.push(await this.execute(action, localScope));
589
+ }
590
+ return results;
591
+ }
592
+ };
593
+
594
+ // src/renderer/renderer.tsx
595
+ import {
596
+ resolveTemplate as resolveTemplate3
597
+ } from "@pactor-app/expression";
598
+ import {
599
+ createElement,
600
+ useMemo,
601
+ useSyncExternalStore as useSyncExternalStore2
602
+ } from "react";
603
+
604
+ // src/renderer/renderer-context.ts
605
+ import {
606
+ createContext as createContext2,
607
+ useContext as useContext2,
608
+ useSyncExternalStore
609
+ } from "react";
610
+ var RendererReactContext = createContext2(null);
611
+ var RendererContextProvider = RendererReactContext.Provider;
612
+ function useRenderer() {
613
+ const value = useContext2(RendererReactContext);
614
+ if (!value) {
615
+ throw new Error("[@pactor-app/runtime] useRenderer \u5FC5\u987B\u5728 <DslRenderer> \u5185\u4F7F\u7528");
616
+ }
617
+ return value;
618
+ }
619
+ function useRuntimeState(path) {
620
+ const { context } = useRenderer();
621
+ return useSyncExternalStore(
622
+ (onChange) => context.state.subscribe(onChange),
623
+ () => path === void 0 ? context.state.getState() : context.state.get(path)
624
+ );
625
+ }
626
+ function useDataSource(name) {
627
+ const { context } = useRenderer();
628
+ return useSyncExternalStore(
629
+ (onChange) => context.data.subscribe(onChange),
630
+ () => context.data.get(name)
631
+ );
632
+ }
633
+
634
+ // src/renderer/unsupported.tsx
635
+ import { jsxs } from "react/jsx-runtime";
636
+ function UnsupportedComponent({ type }) {
637
+ return /* @__PURE__ */ jsxs(
638
+ "div",
639
+ {
640
+ "data-pactor-unsupported": type,
641
+ style: {
642
+ border: "1px dashed #d97706",
643
+ borderRadius: 4,
644
+ padding: 8,
645
+ color: "#b45309",
646
+ fontSize: 12
647
+ },
648
+ children: [
649
+ "Unsupported component: ",
650
+ type
651
+ ]
652
+ }
653
+ );
654
+ }
655
+
656
+ // src/renderer/renderer.tsx
657
+ import { jsx, jsxs as jsxs2 } from "react/jsx-runtime";
658
+ function DslRenderer({ context, node, engine, children }) {
659
+ useSyncExternalStore2(
660
+ (onChange) => {
661
+ const offState = context.state.subscribe(onChange);
662
+ const offData = context.data.subscribe(onChange);
663
+ return () => {
664
+ offState();
665
+ offData();
666
+ };
667
+ },
668
+ () => `${context.state.getVersion()}:${context.data.getVersion()}`
669
+ );
670
+ const resolvedEngine = useMemo(
671
+ () => engine ?? new ActionEngine(context),
672
+ [engine, context]
673
+ );
674
+ const value = useMemo(() => {
675
+ const root = {
676
+ context,
677
+ engine: resolvedEngine,
678
+ scope: {},
679
+ renderChildren: (children2, extraScope) => renderChildrenOf(root, children2, extraScope)
680
+ };
681
+ return root;
682
+ }, [context, resolvedEngine]);
683
+ return /* @__PURE__ */ jsxs2(RendererContextProvider, { value, children: [
684
+ node ? renderNode(node, value, node.id ?? "root") : null,
685
+ children
686
+ ] });
687
+ }
688
+ function renderNode(node, value, key) {
689
+ const scope = buildScope(value.context, value.scope);
690
+ if (node.when !== void 0) {
691
+ const expression = typeof node.when === "string" ? node.when : node.when.expression;
692
+ let visible;
693
+ try {
694
+ visible = resolveTemplate3(expression, scope);
695
+ } catch (cause) {
696
+ console.warn(
697
+ `[@pactor-app/runtime] when \u8868\u8FBE\u5F0F\u6C42\u503C\u5931\u8D25\uFF08\u8282\u70B9 "${node.type}"\uFF09`,
698
+ cause
699
+ );
700
+ visible = false;
701
+ }
702
+ if (!visible) {
703
+ return null;
704
+ }
705
+ }
706
+ let deniedStrategy;
707
+ if (node.permission !== void 0 && !value.context.permission.check(node.permission)) {
708
+ deniedStrategy = resolveDeniedStrategy(node.permission);
709
+ if (deniedStrategy === "hidden") {
710
+ return null;
711
+ }
712
+ }
713
+ const entry = value.context.components.get(node.type);
714
+ const normalized = normalizeEntry(entry);
715
+ if (!normalized) {
716
+ return /* @__PURE__ */ jsx(UnsupportedComponent, { type: node.type }, key);
717
+ }
718
+ const props = resolveProps(node.props, scope);
719
+ if (node.events) {
720
+ for (const [name, actions] of Object.entries(node.events)) {
721
+ props[name] = (event) => {
722
+ void value.engine.executeAll(actions, { ...value.scope, event });
723
+ };
724
+ }
725
+ }
726
+ if (deniedStrategy === "disabled") {
727
+ props["disabled"] = true;
728
+ } else if (deniedStrategy === "readonly") {
729
+ props["readOnly"] = true;
730
+ }
731
+ if (normalized.withScope) {
732
+ return createElement(normalized.component, {
733
+ key,
734
+ ...props,
735
+ dslChildren: node.children
736
+ });
737
+ }
738
+ return createElement(
739
+ normalized.component,
740
+ { key, ...props },
741
+ renderChildrenOf(value, node.children)
742
+ );
743
+ }
744
+ function resolveDeniedStrategy(permission) {
745
+ if (typeof permission === "object" && !Array.isArray(permission)) {
746
+ return permission.denied ?? "hidden";
747
+ }
748
+ return "hidden";
749
+ }
750
+ function renderChildrenOf(value, children, extraScope) {
751
+ if (!children || children.length === 0) {
752
+ return null;
753
+ }
754
+ if (!extraScope) {
755
+ return children.map(
756
+ (child, index) => renderNode(child, value, child.id ?? index)
757
+ );
758
+ }
759
+ const scoped = {
760
+ context: value.context,
761
+ engine: value.engine,
762
+ scope: { ...value.scope, ...extraScope },
763
+ renderChildren: (nested, nestedScope) => renderChildrenOf(scoped, nested, nestedScope)
764
+ };
765
+ return /* @__PURE__ */ jsx(RendererContextProvider, { value: scoped, children: children.map(
766
+ (child, index) => renderNode(child, scoped, child.id ?? index)
767
+ ) });
768
+ }
769
+ function normalizeEntry(entry) {
770
+ if (!entry) {
771
+ return void 0;
772
+ }
773
+ if (typeof entry === "object" && "component" in entry) {
774
+ return entry;
775
+ }
776
+ return { component: entry };
777
+ }
778
+ function resolveProps(props, scope) {
779
+ if (!props) {
780
+ return {};
781
+ }
782
+ const out = {};
783
+ for (const [key, value] of Object.entries(props)) {
784
+ out[key] = typeof value === "string" && key.startsWith("on") ? value : resolveDeep(value, scope, { isOpaque: isEmbeddedDslNode });
785
+ }
786
+ return out;
787
+ }
788
+ function isEmbeddedDslNode(value) {
789
+ return typeof value.type === "string";
790
+ }
791
+
792
+ // src/page/page-runtime.tsx
793
+ import {
794
+ validatePageDsl
795
+ } from "@pactor-app/schema";
796
+ import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
797
+ var PageRuntimeError = class extends Error {
798
+ constructor(errors) {
799
+ super(
800
+ `Page DSL \u6821\u9A8C\u5931\u8D25\uFF08${errors.length} \u4E2A\u9519\u8BEF\uFF09\uFF1A${errors.map((error) => `${error.path}: ${error.message}`).join("\uFF1B")}`
801
+ );
802
+ this.name = "PageRuntimeError";
803
+ this.errors = errors;
804
+ }
805
+ };
806
+ function createPageRuntime(pageDsl, options = {}) {
807
+ const validation = validatePageDsl(pageDsl);
808
+ if (!validation.valid) {
809
+ throw new PageRuntimeError(validation.errors);
810
+ }
811
+ const context = createRuntimeContext({
812
+ ...options,
813
+ page: pageDsl.page,
814
+ state: pageDsl.state ?? options.state,
815
+ dataSources: pageDsl.dataSources ?? options.dataSources
816
+ });
817
+ registerBuiltinActions(context.actions);
818
+ const engine = new ActionEngine(context);
819
+ let destroyed = false;
820
+ const lifecycleListeners = /* @__PURE__ */ new Set();
821
+ const runLifecycle = async (name) => {
822
+ for (const listener of [...lifecycleListeners]) {
823
+ listener(name);
824
+ }
825
+ const actions = pageDsl.lifecycle?.[name];
826
+ if (actions) {
827
+ await engine.executeAll(actions);
828
+ }
829
+ };
830
+ const initialized = Promise.resolve().then(async () => {
831
+ const loading = context.data.init();
832
+ await runLifecycle("onInit");
833
+ await loading;
834
+ await runLifecycle("onLoad");
835
+ });
836
+ const triggerWhenAlive = async (name) => {
837
+ if (destroyed) {
838
+ return;
839
+ }
840
+ await runLifecycle(name);
841
+ };
842
+ return {
843
+ pageDsl,
844
+ context,
845
+ engine,
846
+ initialized,
847
+ get destroyed() {
848
+ return destroyed;
849
+ },
850
+ render() {
851
+ if (!pageDsl.body) {
852
+ return null;
853
+ }
854
+ return /* @__PURE__ */ jsx2(DslRenderer, { context, node: pageDsl.body, engine });
855
+ },
856
+ ready: () => triggerWhenAlive("onReady"),
857
+ show: () => triggerWhenAlive("onShow"),
858
+ hide: () => triggerWhenAlive("onHide"),
859
+ async destroy() {
860
+ if (destroyed) {
861
+ return;
862
+ }
863
+ destroyed = true;
864
+ await runLifecycle("onDestroy");
865
+ lifecycleListeners.clear();
866
+ for (const key of Object.keys(context.actionResults)) {
867
+ delete context.actionResults[key];
868
+ }
869
+ },
870
+ onLifecycle(listener) {
871
+ lifecycleListeners.add(listener);
872
+ return () => {
873
+ lifecycleListeners.delete(listener);
874
+ };
875
+ }
876
+ };
877
+ }
878
+ function PageRuntimeView({
879
+ runtime,
880
+ children
881
+ }) {
882
+ const body = runtime.pageDsl.body;
883
+ if (!body) {
884
+ return /* @__PURE__ */ jsx2(Fragment, { children: children ?? null });
885
+ }
886
+ return /* @__PURE__ */ jsx2(DslRenderer, { context: runtime.context, node: body, engine: runtime.engine, children });
887
+ }
888
+ export {
889
+ ActionEngine,
890
+ DataStore,
891
+ DslRenderer,
892
+ I18nContextProvider,
893
+ PageRuntimeError,
894
+ PageRuntimeView,
895
+ StateStore,
896
+ UnsupportedComponent,
897
+ buildScope,
898
+ createDefaultPermissionAdapter,
899
+ createDefaultRouterAdapter,
900
+ createFetchHttpAdapter,
901
+ createPageRuntime,
902
+ createRuntimeContext,
903
+ registerBuiltinActions,
904
+ resolveDeep,
905
+ useDataSource,
906
+ useI18n,
907
+ useRenderer,
908
+ useRuntimeState
909
+ };