@77-zhike/app-sdk 0.2.0-d7b1.2 → 0.2.0-d7b1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +281 -0
- package/package.json +17 -3
- package/src/cli/api-client.js +4 -0
- package/src/cli/browser-auth.js +64 -18
- package/src/cli/credential-store.js +21 -0
- package/src/cli/development-logs.js +5 -2
- package/src/cli/index.js +59 -42
- package/src/cli/local-commands.js +6 -3
- package/src/cli/logs.js +56 -0
- package/src/cli/project-init.js +111 -179
- package/src/cli/windows-credential-bridge.ps1 +131 -0
- package/src/cli/windows-credential-store.js +112 -0
- package/src/client/analysis.d.ts +4 -0
- package/src/client/analysis.js +12 -0
- package/src/client/destinations.d.ts +25 -0
- package/src/client/destinations.js +70 -0
- package/src/client/documents.d.ts +21 -0
- package/src/client/documents.js +39 -0
- package/src/client/external-url.js +9 -0
- package/src/client/index.d.ts +225 -3
- package/src/client/index.js +75 -3
- package/src/client/interactions.d.ts +23 -3
- package/src/client/interactions.js +53 -3
- package/src/client/points.generated.js +56 -0
- package/src/config.js +11 -7
- package/src/forms/array-contract.d.ts +32 -0
- package/src/forms/array-contract.js +138 -0
- package/src/forms/components.generated.js +2 -2
- package/src/forms/factory.js +21 -7
- package/src/forms/field-array.d.ts +16 -0
- package/src/forms/field-array.js +52 -0
- package/src/forms/index.d.ts +74 -3
- package/src/forms/index.js +2 -0
- package/src/forms/options.d.ts +7 -2
- package/src/forms/options.js +8 -1
- package/src/forms/record-options.d.ts +28 -0
- package/src/forms/record-options.js +92 -0
- package/src/forms/schema.d.ts +73 -9
- package/src/forms/schema.js +128 -0
- package/src/forms/state.d.ts +30 -0
- package/src/forms/state.js +82 -0
- package/src/forms/validation.d.ts +9 -0
- package/src/forms/validation.js +81 -0
- package/src/forms/values.d.ts +5 -0
- package/src/forms/values.js +38 -0
- package/src/jsx-dev-runtime.d.ts +2 -0
- package/src/jsx-dev-runtime.js +1 -0
- package/src/jsx-runtime.d.ts +2 -0
- package/src/jsx-runtime.js +1 -0
- package/src/server/agents/index.d.ts +62 -0
- package/src/server/agents/index.js +40 -0
- package/src/server/index.d.ts +60 -5
- package/src/server/index.js +43 -0
- package/src/server/steps/index.d.ts +71 -0
- package/src/server/steps/index.js +15 -0
- package/src/server/triggers/index.d.ts +68 -0
- package/src/server/triggers/index.js +27 -0
- package/src/server/workflow-blocks/index.d.ts +72 -0
- package/src/server/workflow-blocks/index.js +32 -0
- package/src/toolchain/agents/discovery.js +126 -0
- package/src/toolchain/agents/exposure.js +31 -0
- package/src/toolchain/agents/manifest.js +84 -0
- package/src/toolchain/ai-capabilities.js +69 -0
- package/src/toolchain/allowed-packages.json +7 -0
- package/src/toolchain/dependency-policy.js +181 -0
- package/src/toolchain/index.js +0 -88
- package/src/toolchain/real-build.js +135 -27
- package/src/toolchain/source-discovery.js +330 -81
- package/src/toolchain/workflow-blocks/discovery.js +133 -0
- package/src/toolchain/workflow-steps/manifest.js +99 -0
- package/src/toolchain/workflow-triggers/manifest.js +47 -0
- package/src/ui/components.generated.js +4 -1
- package/src/ui/conversation.d.ts +81 -0
- package/src/ui/conversation.js +82 -0
- package/src/ui/data-table-model.d.ts +14 -0
- package/src/ui/data-table-model.js +98 -0
- package/src/ui/data-table.d.ts +70 -0
- package/src/ui/data-table.js +74 -0
- package/src/ui/events.d.ts +10 -0
- package/src/ui/factory.js +87 -3
- package/src/ui/index.d.ts +351 -8
- package/src/ui/index.js +1 -1
- package/src/ui/jsx.generated.d.ts +359 -0
- package/src/ui/native-dom.d.ts +49 -0
- package/src/ui/native-dom.js +44 -0
- package/src/ui/scroll-viewport.d.ts +22 -0
- package/src/ui/scroll-viewport.js +23 -0
- package/src/ui/slider.d.ts +27 -0
- package/src/ui/slider.js +26 -0
- package/src/ui/style.d.ts +5 -0
- package/src/ui/style.generated.js +2 -0
- package/src/ui/style.js +15 -0
- package/src/ui/text-selection.d.ts +22 -0
- package/src/ui/text-selection.js +23 -0
- package/src/ui/validation.js +86 -0
- package/src/cli/validate-bundle.js +0 -36
package/src/client/index.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { ComponentType } from "react";
|
|
2
2
|
import type { AppIconName } from "../ui/index.js";
|
|
3
3
|
|
|
4
|
+
/** SDK Record 扩展明确开放的产品对象;任务、跟进记录和笔记不属于此合同。 */
|
|
5
|
+
export type RecordExtensionObject =
|
|
6
|
+
| "accounts"
|
|
7
|
+
| "contacts"
|
|
8
|
+
| "opportunities"
|
|
9
|
+
| `custom_${string}`;
|
|
10
|
+
|
|
4
11
|
/** Settings 只声明组件,表单状态不代表持久化配置。 */
|
|
5
12
|
export interface SettingsExtensionDefinition {
|
|
6
13
|
key: string;
|
|
@@ -14,7 +21,7 @@ export interface RecordActionContext {
|
|
|
14
21
|
/** 当前记录的公开 ID。 */
|
|
15
22
|
recordId: string;
|
|
16
23
|
/** 当前对象的公开 API slug。 */
|
|
17
|
-
object:
|
|
24
|
+
object: RecordExtensionObject;
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
/** 在通用记录详情菜单中贡献一个操作。 */
|
|
@@ -27,15 +34,161 @@ export interface RecordActionDefinition {
|
|
|
27
34
|
/** 统一 SDK 图标目录中的名称。 */
|
|
28
35
|
icon?: AppIconName;
|
|
29
36
|
/** 省略时匹配所有能提供公开身份的记录对象。 */
|
|
30
|
-
objects?:
|
|
37
|
+
objects?: RecordExtensionObject | readonly RecordExtensionObject[];
|
|
31
38
|
/** 用户触发操作时执行;成功无需返回值。 */
|
|
32
39
|
onTrigger(context: RecordActionContext): void | Promise<void>;
|
|
33
40
|
component?: never;
|
|
34
41
|
handler?: never;
|
|
35
42
|
}
|
|
43
|
+
|
|
44
|
+
/** 当前对象列表提供的公开对象身份;不包含查询、权限或原生 mutation 身份。 */
|
|
45
|
+
export interface ObjectActionContext {
|
|
46
|
+
/** 当前对象的公开 API slug。 */
|
|
47
|
+
object: RecordExtensionObject;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 在对象列表右上角菜单中贡献一个操作。 */
|
|
51
|
+
export interface ObjectActionDefinition {
|
|
52
|
+
/** App 内唯一 lowercase kebab-case key。 */
|
|
53
|
+
key: string;
|
|
54
|
+
type: "object-action";
|
|
55
|
+
/** 菜单中展示的操作名称。 */
|
|
56
|
+
label: string;
|
|
57
|
+
/** 统一 SDK 图标目录中的名称。 */
|
|
58
|
+
icon?: AppIconName;
|
|
59
|
+
/** 省略时匹配所有能提供公开身份的对象列表。 */
|
|
60
|
+
objects?: RecordExtensionObject | readonly RecordExtensionObject[];
|
|
61
|
+
/** 用户触发操作时执行;成功无需返回值。 */
|
|
62
|
+
onTrigger(context: ObjectActionContext): void | Promise<void>;
|
|
63
|
+
component?: never;
|
|
64
|
+
handler?: never;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** 当前显式选择快照;只暴露公开对象和 1..100 个 Record ID。 */
|
|
68
|
+
export interface BulkRecordActionContext {
|
|
69
|
+
/** 当前对象的公开 API slug。 */
|
|
70
|
+
object: RecordExtensionObject;
|
|
71
|
+
/** 点击瞬间冻结的公开 Record ID,保持当前页可见顺序。 */
|
|
72
|
+
recordIds: readonly string[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** 在对象列表选择模式中贡献一个批量操作。 */
|
|
76
|
+
export interface BulkRecordActionDefinition {
|
|
77
|
+
/** App 内唯一 lowercase kebab-case key。 */
|
|
78
|
+
key: string;
|
|
79
|
+
type: "bulk-record-action";
|
|
80
|
+
/** 菜单中展示的操作名称。 */
|
|
81
|
+
label: string;
|
|
82
|
+
/** 统一 SDK 图标目录中的名称。 */
|
|
83
|
+
icon?: AppIconName;
|
|
84
|
+
/** 省略时匹配所有支持显式多选的公开对象列表。 */
|
|
85
|
+
objects?: RecordExtensionObject | readonly RecordExtensionObject[];
|
|
86
|
+
/** 用户触发操作时执行;成功无需返回值。 */
|
|
87
|
+
onTrigger(context: BulkRecordActionContext): void | Promise<void>;
|
|
88
|
+
component?: never;
|
|
89
|
+
handler?: never;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Record Page 组件获得的公开记录身份;不能作为服务端授权凭据。 */
|
|
93
|
+
export interface RecordComponentProps {
|
|
94
|
+
/** 当前对象的公开 API slug。 */
|
|
95
|
+
readonly object: RecordExtensionObject;
|
|
96
|
+
/** 当前记录的公开 ID。 */
|
|
97
|
+
readonly recordId: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** 在记录概览的重点信息卡片容器中贡献一个小卡片 Widget。 */
|
|
101
|
+
export interface RecordWidgetDefinition {
|
|
102
|
+
/** App 内唯一 lowercase kebab-case key。 */
|
|
103
|
+
key: string;
|
|
104
|
+
type: "record-widget";
|
|
105
|
+
/** Widget 标题,trim 后最多 80 个 Unicode code points。 */
|
|
106
|
+
label: string;
|
|
107
|
+
/** 省略时匹配客户、联系人、商机和全部自定义对象。 */
|
|
108
|
+
objects?: readonly RecordExtensionObject[];
|
|
109
|
+
/** 纯 React Widget 正文,只接收公开记录身份;卡片外框由 Host 提供。 */
|
|
110
|
+
Widget: ComponentType<RecordComponentProps>;
|
|
111
|
+
component?: never;
|
|
112
|
+
handler?: never;
|
|
113
|
+
onTrigger?: never;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** 为 Record Page 贡献一个可由布局放入 Section 区域的受管组件。 */
|
|
117
|
+
export interface RecordSectionDefinition {
|
|
118
|
+
/** App 内唯一 lowercase kebab-case key。 */
|
|
119
|
+
key: string;
|
|
120
|
+
type: "record-section";
|
|
121
|
+
/** Section 标题,trim 后最多 80 个 Unicode code points。 */
|
|
122
|
+
label: string;
|
|
123
|
+
/** 省略时匹配客户、联系人、商机和全部自定义对象。 */
|
|
124
|
+
objects?: readonly RecordExtensionObject[];
|
|
125
|
+
/** 纯 React Section 正文,只接收公开记录身份;标题与外框由 Host 提供。 */
|
|
126
|
+
Section: ComponentType<RecordComponentProps>;
|
|
127
|
+
component?: never;
|
|
128
|
+
handler?: never;
|
|
129
|
+
onTrigger?: never;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** 在记录详情的产品 Tab 区域中贡献一个 Tab。 */
|
|
133
|
+
export interface RecordTabDefinition {
|
|
134
|
+
/** App 内唯一 lowercase kebab-case key。 */
|
|
135
|
+
key: string;
|
|
136
|
+
type: "record-tab";
|
|
137
|
+
/** Tab 标题,trim 后最多 80 个 Unicode code points。 */
|
|
138
|
+
label: string;
|
|
139
|
+
/** 省略时匹配客户、联系人、商机和全部自定义对象。 */
|
|
140
|
+
objects?: readonly RecordExtensionObject[];
|
|
141
|
+
/** 纯 React Tab component,只接收公开记录身份。 */
|
|
142
|
+
Tab: ComponentType<RecordComponentProps>;
|
|
143
|
+
component?: never;
|
|
144
|
+
handler?: never;
|
|
145
|
+
onTrigger?: never;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface PageProps {
|
|
149
|
+
/** 当前地址的业务输入,缺省为空对象;不代表记录访问权。 */
|
|
150
|
+
readonly params: import('./destinations.js').PageParams;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Workspace Page 不接收宿主路由或身份;导航只能通过 SDK 能力发起。 */
|
|
154
|
+
export interface PageDefinition {
|
|
155
|
+
/** Apps 菜单和 Page 标题使用的名称。 */
|
|
156
|
+
name: string;
|
|
157
|
+
/** Apps 导航图标,必须来自 SDK 统一图标目录;省略时保留宿主默认图标。 */
|
|
158
|
+
icon?: AppIconName;
|
|
159
|
+
/** 有值的 Page 优先按数值排序;缺失时按 slug 稳定排序。 */
|
|
160
|
+
order?: number;
|
|
161
|
+
/** Page 自身的产品布局语义,不选择 Dialog、Sheet 或 Sidebar。 */
|
|
162
|
+
variant: "full" | "centered";
|
|
163
|
+
/** 缺省可见;只影响 Apps 菜单,不改变页面授权或可访问性。 */
|
|
164
|
+
showInNavigation?: boolean;
|
|
165
|
+
/** 默认显示宿主标题栏;false 时由 Page 自行提供标题。移动端仍保留返回导航。 */
|
|
166
|
+
showHeader?: boolean;
|
|
167
|
+
/** 缺省 false,按内容自然滚动;true 由宿主提供扣除导航后的确定高度,内部滚动由 Page 布局负责。 */
|
|
168
|
+
fillHeight?: boolean;
|
|
169
|
+
/** 纯 React Page component,只接收有界业务参数。 */
|
|
170
|
+
Page: ComponentType<PageProps>;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export type DefinedPage = Readonly<PageDefinition & { type: "page" }>;
|
|
174
|
+
/** 结果来自同次已校验 Action;身份与导航 owner 由 Host 绑定。 */
|
|
175
|
+
export interface ToolRendererProps<Result = unknown> {
|
|
176
|
+
readonly result: Result;
|
|
177
|
+
readonly actionKey: string;
|
|
178
|
+
}
|
|
179
|
+
export interface ToolRendererDefinition<Result = unknown> {
|
|
180
|
+
key: string;
|
|
181
|
+
Renderer: ComponentType<ToolRendererProps<Result>>;
|
|
182
|
+
}
|
|
183
|
+
export function defineToolRenderer<Result>(definition: ToolRendererDefinition<Result>): Readonly<ToolRendererDefinition<Result> & { type: 'tool-renderer' }>;
|
|
36
184
|
export type FrontendExtensionDefinition =
|
|
37
185
|
| SettingsExtensionDefinition
|
|
38
|
-
| RecordActionDefinition
|
|
186
|
+
| RecordActionDefinition
|
|
187
|
+
| ObjectActionDefinition
|
|
188
|
+
| BulkRecordActionDefinition
|
|
189
|
+
| RecordWidgetDefinition
|
|
190
|
+
| RecordSectionDefinition
|
|
191
|
+
| RecordTabDefinition;
|
|
39
192
|
|
|
40
193
|
export function defineFrontendExtension(
|
|
41
194
|
definition: SettingsExtensionDefinition,
|
|
@@ -43,10 +196,79 @@ export function defineFrontendExtension(
|
|
|
43
196
|
export function defineFrontendExtension(
|
|
44
197
|
definition: RecordActionDefinition,
|
|
45
198
|
): Readonly<RecordActionDefinition>;
|
|
199
|
+
export function defineFrontendExtension(
|
|
200
|
+
definition: ObjectActionDefinition,
|
|
201
|
+
): Readonly<ObjectActionDefinition>;
|
|
202
|
+
export function defineFrontendExtension(
|
|
203
|
+
definition: BulkRecordActionDefinition,
|
|
204
|
+
): Readonly<BulkRecordActionDefinition>;
|
|
205
|
+
export function defineFrontendExtension(
|
|
206
|
+
definition: RecordWidgetDefinition,
|
|
207
|
+
): Readonly<RecordWidgetDefinition>;
|
|
208
|
+
export function defineFrontendExtension(
|
|
209
|
+
definition: RecordSectionDefinition,
|
|
210
|
+
): Readonly<RecordSectionDefinition>;
|
|
211
|
+
export function defineFrontendExtension(
|
|
212
|
+
definition: RecordTabDefinition,
|
|
213
|
+
): Readonly<RecordTabDefinition>;
|
|
214
|
+
|
|
215
|
+
/** 声明一个由约定目录提供 slug 的 Workspace Page。 */
|
|
216
|
+
export function definePage(definition: PageDefinition): DefinedPage;
|
|
46
217
|
|
|
47
218
|
export type { AppIconName } from "../ui/index.js";
|
|
219
|
+
export { Destinations } from './destinations.js';
|
|
220
|
+
export type { AppDestination, AppPageDestination, RecordDestination, PageParams } from './destinations.js';
|
|
48
221
|
export type { ToastOptions, ConfirmOptions } from './interactions.js';
|
|
49
222
|
/** 受管结果提示;不接收 HTML,回调完成后可继续显示,Workspace 会话结束时清理。 */
|
|
50
223
|
export function showToast(options: import('./interactions.js').ToastOptions): Promise<void>;
|
|
51
224
|
/** 用户取消返回 false;所属页面关闭时拒绝,不能当作确认。 */
|
|
52
225
|
export function confirm(options: import('./interactions.js').ConfirmOptions): Promise<boolean>;
|
|
226
|
+
/** Page view 可用;RecordAction invocation 仅可打开同 App Page。Dialog 不可直接导航。 */
|
|
227
|
+
export function navigate(destination: import('./destinations.js').AppDestination): Promise<void>;
|
|
228
|
+
|
|
229
|
+
export interface DialogProps {
|
|
230
|
+
/** 只关闭当前 Dialog;重复调用无效果,不代表保存或回滚。 */
|
|
231
|
+
hideDialog(): void;
|
|
232
|
+
}
|
|
233
|
+
export interface DialogOptions {
|
|
234
|
+
/** 宿主固定标题,去空白后 1~200 字符。 */
|
|
235
|
+
title: string;
|
|
236
|
+
/** 默认保持原宽度;wide 为宽版,content 按内容固有宽度展开并受视口约束。Mobile 均为全屏 Sheet。 */
|
|
237
|
+
size?: 'default' | 'wide' | 'content';
|
|
238
|
+
Dialog: ComponentType<DialogProps>;
|
|
239
|
+
}
|
|
240
|
+
/** 从有效 Action/UI 回调打开;普通关闭完成,失效/失败拒绝,不支持嵌套。 */
|
|
241
|
+
export function showDialog(options: DialogOptions): Promise<void>;
|
|
242
|
+
/**
|
|
243
|
+
* 由 Web Host 在新页签打开绝对 HTTPS URL;不返回窗口或远端加载状态。
|
|
244
|
+
* @platform Web
|
|
245
|
+
*/
|
|
246
|
+
export function openExternalUrl(url: string): Promise<void>;
|
|
247
|
+
|
|
248
|
+
/** 当前用户在当前 Workspace 内的成员快照。 */
|
|
249
|
+
export interface CurrentUser {
|
|
250
|
+
/** Workspace 成员 ID;不是平台全局 User ID。 */
|
|
251
|
+
workspaceMemberId: string;
|
|
252
|
+
displayName: string;
|
|
253
|
+
primaryEmailAddress: string | null;
|
|
254
|
+
avatarUrl: string | null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** 同步读取当前前端 Workspace 会话的成员快照。 */
|
|
258
|
+
export function getCurrentUser(): Readonly<CurrentUser>;
|
|
259
|
+
|
|
260
|
+
export type { AnalysisRequest } from './analysis.js';
|
|
261
|
+
/** 当前打开页面成员的获权分析;仅 App Page 可用,不获取登录凭据。 */
|
|
262
|
+
export function queryAnalysis(request: import('./analysis.js').AnalysisRequest): Promise<import('../ui/index.js').JsonValue>;
|
|
263
|
+
export type { OpenAssistantOptions } from './interactions.js';
|
|
264
|
+
/** 打开侧边助手;首次发言才调用模型,context 仅作为新会话初始背景。 */
|
|
265
|
+
export function openAssistant(options: import('./interactions.js').OpenAssistantOptions): Promise<void>;
|
|
266
|
+
|
|
267
|
+
export type { RestrictedInputSchema, PreparePdfTemplateInput, OpenPdfDocumentInput, PreparedPdfTemplate, OpenPdfDocumentResult } from './documents.js';
|
|
268
|
+
/** Desktop制作;取消返回null。缺能力或旧Host返回runtime_document_unsupported。 */
|
|
269
|
+
export function preparePdfTemplate(input: import('./documents.js').PreparePdfTemplateInput): Promise<import('./documents.js').PreparedPdfTemplate | null>;
|
|
270
|
+
/** Desktop/H5认证文件交付;started只表示已交浏览器,不证明磁盘保存。 */
|
|
271
|
+
export function openPdfDocument(input: import('./documents.js').OpenPdfDocumentInput): Promise<import('./documents.js').OpenPdfDocumentResult>;
|
|
272
|
+
|
|
273
|
+
/** 以当前成员权限打开稳定报告产物;宿主控制预览,不执行App提供的HTML。 */
|
|
274
|
+
export function openArtifact(input: import('./interactions.js').OpenArtifactOptions): Promise<void>;
|
package/src/client/index.js
CHANGED
|
@@ -1,22 +1,94 @@
|
|
|
1
1
|
import { frontendPoint } from './points.generated.js';
|
|
2
|
+
import { sdkIconNames } from '../ui/icons.generated.js';
|
|
3
|
+
export { Destinations } from './destinations.js';
|
|
2
4
|
const FRONTEND_EXTENSION_MARKER = Symbol.for("zhike.frontend-extension.v1");
|
|
5
|
+
const PAGE_VARIANTS = new Set(["full", "centered"]);
|
|
6
|
+
const PAGE_ICONS = new Set(sdkIconNames);
|
|
7
|
+
|
|
8
|
+
export function defineToolRenderer(definition) {
|
|
9
|
+
if (!definition || typeof definition !== 'object' || Array.isArray(definition)
|
|
10
|
+
|| Object.keys(definition).some(key => !['key', 'Renderer'].includes(key))
|
|
11
|
+
|| typeof definition.key !== 'string' || !/^[a-z][a-z0-9-]{0,63}$/.test(definition.key)
|
|
12
|
+
|| typeof definition.Renderer !== 'function') throw new TypeError('tool_renderer_definition_invalid');
|
|
13
|
+
return Object.freeze({ ...definition, type: 'tool-renderer' });
|
|
14
|
+
}
|
|
3
15
|
|
|
4
16
|
export function defineFrontendExtension(definition) {
|
|
5
17
|
if (!definition || typeof definition !== "object" || Array.isArray(definition)) {
|
|
6
18
|
throw new TypeError("Frontend Extension definition 必须是对象");
|
|
7
19
|
}
|
|
8
20
|
const point = frontendPoint(definition.type);
|
|
9
|
-
if (!point) {
|
|
21
|
+
if (!point || definition.type === "page") {
|
|
10
22
|
throw new TypeError("Frontend Extension type 不受支持");
|
|
11
23
|
}
|
|
12
|
-
const property = point.kind === "view" ? "component" : (point.callbackName || "handler");
|
|
13
|
-
const forbidden = point.kind === "view"
|
|
24
|
+
const property = point.kind === "view" ? (point.componentName || "component") : (point.callbackName || "handler");
|
|
25
|
+
const forbidden = point.kind === "view"
|
|
26
|
+
? ["component", "Widget", "Section", "Tab", "Page", "handler", "onTrigger"].filter(name => name !== property)
|
|
27
|
+
: ["component", "Widget", "Section", "Tab", "Page", ...(property === "handler" ? ["onTrigger"] : ["handler"])];
|
|
14
28
|
if (typeof definition[property] !== "function" || forbidden.some(name => name in definition)) {
|
|
15
29
|
throw new TypeError(`Frontend Extension 必须只提供 ${property} 函数`);
|
|
16
30
|
}
|
|
17
31
|
return Object.freeze({ ...definition, [FRONTEND_EXTENSION_MARKER]: true });
|
|
18
32
|
}
|
|
19
33
|
|
|
34
|
+
export function definePage(definition) {
|
|
35
|
+
if (!definition || typeof definition !== "object" || Array.isArray(definition)) {
|
|
36
|
+
throw new TypeError("Page definition 必须是对象");
|
|
37
|
+
}
|
|
38
|
+
const keys = Object.keys(definition).sort();
|
|
39
|
+
const allowed = new Set(["Page", "name", "icon", "order", "variant", "showInNavigation", "showHeader", "fillHeight"]);
|
|
40
|
+
if (keys.some(key => !allowed.has(key))) {
|
|
41
|
+
throw new TypeError("Page definition 包含不支持的字段");
|
|
42
|
+
}
|
|
43
|
+
const name = typeof definition.name === "string" ? definition.name.trim() : "";
|
|
44
|
+
if ([...name].length === 0 || [...name].length > 80 || /\p{Cc}/u.test(name)) {
|
|
45
|
+
throw new TypeError("Page name 必须是 1~80 个非控制字符");
|
|
46
|
+
}
|
|
47
|
+
if (!PAGE_VARIANTS.has(definition.variant)) {
|
|
48
|
+
throw new TypeError("Page variant 必须是 full 或 centered");
|
|
49
|
+
}
|
|
50
|
+
if (definition.icon !== undefined && !PAGE_ICONS.has(definition.icon)) {
|
|
51
|
+
throw new TypeError('Page icon 必须来自 SDK 统一图标目录');
|
|
52
|
+
}
|
|
53
|
+
if (definition.order !== undefined && !Number.isSafeInteger(definition.order)) {
|
|
54
|
+
throw new TypeError("Page order 必须是安全整数");
|
|
55
|
+
}
|
|
56
|
+
if (typeof definition.Page !== "function") {
|
|
57
|
+
throw new TypeError("Page definition 必须提供 React Page component");
|
|
58
|
+
}
|
|
59
|
+
if (definition.fillHeight !== undefined && typeof definition.fillHeight !== 'boolean') {
|
|
60
|
+
throw new TypeError('Page fillHeight 必须是布尔值');
|
|
61
|
+
}
|
|
62
|
+
if (definition.showHeader !== undefined && typeof definition.showHeader !== 'boolean') {
|
|
63
|
+
throw new TypeError('Page showHeader 必须是布尔值');
|
|
64
|
+
}
|
|
65
|
+
if (definition.showInNavigation !== undefined && typeof definition.showInNavigation !== 'boolean') {
|
|
66
|
+
throw new TypeError('Page showInNavigation 必须是布尔值');
|
|
67
|
+
}
|
|
68
|
+
return Object.freeze({
|
|
69
|
+
Page: definition.Page,
|
|
70
|
+
name,
|
|
71
|
+
...(definition.icon === undefined ? {} : { icon: definition.icon }),
|
|
72
|
+
...(definition.order === undefined ? {} : { order: definition.order }),
|
|
73
|
+
type: "page",
|
|
74
|
+
variant: definition.variant,
|
|
75
|
+
...(definition.showHeader === undefined ? {} : { showHeader: definition.showHeader }),
|
|
76
|
+
...(definition.fillHeight === undefined ? {} : { fillHeight: definition.fillHeight }),
|
|
77
|
+
...(definition.showInNavigation === undefined ? {} : { showInNavigation: definition.showInNavigation }),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
20
81
|
// 受管 Guest 注入实际请求出口;脱离前端运行环境不能调用产品交互。
|
|
21
82
|
export function showToast() { return Promise.reject(new Error('runtime_host_interaction_unavailable')); }
|
|
22
83
|
export function confirm() { return Promise.reject(new Error('runtime_host_interaction_unavailable')); }
|
|
84
|
+
export function navigate() { return Promise.reject(new Error('runtime_host_interaction_unavailable')); }
|
|
85
|
+
export function showDialog() { return Promise.reject(new Error('runtime_host_interaction_unavailable')); }
|
|
86
|
+
export function openExternalUrl() { return Promise.reject(new Error('runtime_host_interaction_unavailable')); }
|
|
87
|
+
export function getCurrentUser() { throw new Error('current_user_unavailable'); }
|
|
88
|
+
|
|
89
|
+
export function queryAnalysis() { return Promise.reject(new Error('runtime_analysis_unavailable')); }
|
|
90
|
+
export function openAssistant() { return Promise.reject(new Error('runtime_host_interaction_unavailable')); }
|
|
91
|
+
export function preparePdfTemplate() { return Promise.reject(new Error('runtime_document_unsupported')); }
|
|
92
|
+
export function openPdfDocument() { return Promise.reject(new Error('runtime_document_unsupported')); }
|
|
93
|
+
|
|
94
|
+
export function openArtifact() { return Promise.reject(new Error('runtime_host_interaction_unavailable')); }
|
|
@@ -1,8 +1,28 @@
|
|
|
1
|
+
export interface OpenArtifactOptions {
|
|
2
|
+
/** 私有报告的稳定ID;不承载身份或授予读取权限。 */
|
|
3
|
+
artifactId: string;
|
|
4
|
+
}
|
|
5
|
+
export interface OpenAssistantOptions {
|
|
6
|
+
/** App 内的场景名;同 App/场景跨页面续聊,Host 自动拼接 App 前缀。 */
|
|
7
|
+
scenario: string;
|
|
8
|
+
/** 新对话的初始业务背景,建议使用 Markdown,最多 32 KiB UTF-8;重开不会覆盖已有背景。 */
|
|
9
|
+
context: string;
|
|
10
|
+
/** 助手面板标题,1~200字符;省略时显示“智客”,不参与会话定位。 */
|
|
11
|
+
title?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface OpenExternalOptions { href: string }
|
|
1
14
|
export interface ToastOptions { message: string; tone?: 'info' | 'success' | 'error' }
|
|
2
15
|
export interface ConfirmOptions { title: string; description?: string; confirmLabel?: string; cancelLabel?: string }
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
16
|
+
export interface OpenExternalUrlOptions { url: string }
|
|
17
|
+
export interface NavigateOptions { destination: import('./destinations.js').AppDestination }
|
|
18
|
+
export type HostInteraction = {operation:'openArtifact';input:OpenArtifactOptions} | {operation:'preparePdfTemplate';input:import('./documents.js').PreparePdfTemplateInput} | {operation:'openPdfDocument';input:import('./documents.js').OpenPdfDocumentInput} | {operation:'openAssistant';input:OpenAssistantOptions} | {operation:'showToast';input:ToastOptions} | {operation:'confirm';input:ConfirmOptions} | {operation:'navigate';input:NavigateOptions} | {operation:'openExternal';input:OpenExternalOptions} | {operation:'openExternalUrl';input:OpenExternalUrlOptions};
|
|
19
|
+
export function assertHostInteraction(operation: unknown, input: unknown): Readonly<OpenArtifactOptions | ToastOptions | ConfirmOptions | NavigateOptions | OpenExternalOptions | OpenExternalUrlOptions | OpenAssistantOptions>;
|
|
20
|
+
export function createClientInteractions(invoke: (operation: string, input: OpenArtifactOptions | ToastOptions | ConfirmOptions | NavigateOptions | OpenExternalOptions | OpenExternalUrlOptions | OpenAssistantOptions) => Promise<unknown>): {
|
|
21
|
+
openArtifact(input: OpenArtifactOptions): Promise<void>;
|
|
22
|
+
openAssistant(input: OpenAssistantOptions): Promise<void>;
|
|
6
23
|
showToast(input: ToastOptions): Promise<void>;
|
|
7
24
|
confirm(input: ConfirmOptions): Promise<boolean>;
|
|
25
|
+
openExternalUrl(url: string): Promise<void>;
|
|
26
|
+
openExternal(href: string): Promise<boolean>;
|
|
27
|
+
navigate(destination: import('./destinations.js').AppDestination): Promise<void>;
|
|
8
28
|
};
|
|
@@ -1,16 +1,54 @@
|
|
|
1
|
+
import { assertDocumentInput } from './documents.js';
|
|
2
|
+
import { assertExternalUrl } from './external-url.js';
|
|
3
|
+
import { assertAppDestination } from './destinations.js';
|
|
4
|
+
|
|
1
5
|
const fail = () => { throw new Error('runtime_interaction_invalid'); };
|
|
2
6
|
const text = (value, max, required = false) => value === undefined ? !required : typeof value === 'string' && value.length <= max && (!required || value.length > 0);
|
|
7
|
+
const externalUrl = value => {
|
|
8
|
+
if (!text(value,8192,true) || value.trim() !== value || /[\u0000-\u001f\u007f]/.test(value)) return false;
|
|
9
|
+
try {
|
|
10
|
+
const parsed = new URL(value);
|
|
11
|
+
return parsed.protocol === 'https:' && parsed.hostname.length > 0 && !parsed.username && !parsed.password;
|
|
12
|
+
} catch { return false; }
|
|
13
|
+
};
|
|
3
14
|
export function assertHostInteraction(operation, input) {
|
|
15
|
+
if (operation === 'openArtifact') {
|
|
16
|
+
if (!input || typeof input !== 'object' || Array.isArray(input) || Object.keys(input).length !== 1
|
|
17
|
+
|| typeof input.artifactId !== 'string' || !/^[A-Za-z0-9]{26}$/.test(input.artifactId)) fail();
|
|
18
|
+
return Object.freeze({ artifactId: input.artifactId });
|
|
19
|
+
}
|
|
20
|
+
if (['preparePdfTemplate', 'openPdfDocument'].includes(operation)) return assertDocumentInput(operation, input);
|
|
4
21
|
if (!input || typeof input !== 'object' || Array.isArray(input)) fail();
|
|
5
|
-
const allowed = operation === 'showToast' ? ['message','tone'] : operation === 'confirm' ? ['title','description','confirmLabel','cancelLabel'] : [];
|
|
22
|
+
const allowed = operation === 'openAssistant' ? ['scenario','context','title'] : operation === 'showToast' ? ['message','tone'] : operation === 'confirm' ? ['title','description','confirmLabel','cancelLabel'] : operation === 'navigate' ? ['destination'] : operation === 'openExternal' ? ['href'] : operation === 'openExternalUrl' ? ['url'] : [];
|
|
6
23
|
if (!allowed.length || Object.keys(input).some(key=>!allowed.includes(key))) fail();
|
|
7
|
-
if (operation === '
|
|
24
|
+
if (operation === 'openAssistant') {
|
|
25
|
+
if (typeof input.scenario !== 'string' || !/^[a-z][a-z0-9-]{0,63}$/.test(input.scenario)
|
|
26
|
+
|| typeof input.context !== 'string' || !input.context.trim()
|
|
27
|
+
|| new TextEncoder().encode(input.context).byteLength > 32768
|
|
28
|
+
|| !text(input.title,200) || (input.title !== undefined && !input.title.trim())) fail();
|
|
29
|
+
} else if (operation === 'showToast') {
|
|
8
30
|
if (!text(input.message,500,true) || (input.tone !== undefined && !['info','success','error'].includes(input.tone))) fail();
|
|
9
|
-
} else if (
|
|
31
|
+
} else if (operation === 'confirm') {
|
|
32
|
+
if (!text(input.title,200,true) || !text(input.description,2000) || !text(input.confirmLabel,60) || !text(input.cancelLabel,60)) fail();
|
|
33
|
+
} else if (operation === 'openExternalUrl') {
|
|
34
|
+
if (!externalUrl(input.url)) fail();
|
|
35
|
+
} else if (operation === 'openExternal') {
|
|
36
|
+
try { return Object.freeze({ href: assertExternalUrl(input.href) }); } catch { fail(); }
|
|
37
|
+
} else {
|
|
38
|
+
try { assertAppDestination(input.destination); } catch { fail(); }
|
|
39
|
+
}
|
|
10
40
|
return Object.freeze(Object.fromEntries(Object.entries(input).filter(([,value])=>value!==undefined)));
|
|
11
41
|
}
|
|
12
42
|
export function createClientInteractions(invoke) {
|
|
13
43
|
return Object.freeze({
|
|
44
|
+
openArtifact(input) {
|
|
45
|
+
const request = assertHostInteraction('openArtifact',input);
|
|
46
|
+
return invoke('openArtifact',request).then(value=>{if(value!==null)fail();});
|
|
47
|
+
},
|
|
48
|
+
openAssistant(input) {
|
|
49
|
+
const request = assertHostInteraction('openAssistant',input);
|
|
50
|
+
return invoke('openAssistant',request).then(value=>{if(value!==null)fail();});
|
|
51
|
+
},
|
|
14
52
|
showToast(input) {
|
|
15
53
|
const request = assertHostInteraction('showToast',input);
|
|
16
54
|
return invoke('showToast',request).then(value=>{if(value!==null)fail();});
|
|
@@ -19,5 +57,17 @@ export function createClientInteractions(invoke) {
|
|
|
19
57
|
const request = assertHostInteraction('confirm',input);
|
|
20
58
|
return invoke('confirm',request).then(value=>{if(typeof value!=='boolean')fail();return value;});
|
|
21
59
|
},
|
|
60
|
+
openExternalUrl(url) {
|
|
61
|
+
const request = assertHostInteraction('openExternalUrl',{url});
|
|
62
|
+
return invoke('openExternalUrl',request).then(value=>{if(value!==null)fail();});
|
|
63
|
+
},
|
|
64
|
+
openExternal(href) {
|
|
65
|
+
const request = assertHostInteraction('openExternal', {href});
|
|
66
|
+
return invoke('openExternal',request).then(value=>{if(typeof value!=='boolean')fail();return value;});
|
|
67
|
+
},
|
|
68
|
+
navigate(destination) {
|
|
69
|
+
const request = assertHostInteraction('navigate',{destination});
|
|
70
|
+
return invoke('navigate',request).then(value=>{if(value!==null)fail();});
|
|
71
|
+
},
|
|
22
72
|
});
|
|
23
73
|
}
|
|
@@ -14,6 +14,62 @@ export const frontendPoints = Object.freeze([
|
|
|
14
14
|
"definitionName": "RecordActionDefinition",
|
|
15
15
|
"callbackName": "onTrigger",
|
|
16
16
|
"maxPerApp": 100
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"key": "app.object-actions",
|
|
20
|
+
"kind": "action",
|
|
21
|
+
"legacyType": "object-action",
|
|
22
|
+
"definitionName": "ObjectActionDefinition",
|
|
23
|
+
"callbackName": "onTrigger",
|
|
24
|
+
"maxPerApp": 100
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"key": "app.bulk-record-actions",
|
|
28
|
+
"kind": "action",
|
|
29
|
+
"legacyType": "bulk-record-action",
|
|
30
|
+
"definitionName": "BulkRecordActionDefinition",
|
|
31
|
+
"callbackName": "onTrigger",
|
|
32
|
+
"maxPerApp": 100
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"key": "app.record-page-widgets",
|
|
36
|
+
"kind": "view",
|
|
37
|
+
"legacyType": "record-widget",
|
|
38
|
+
"definitionName": "RecordWidgetDefinition",
|
|
39
|
+
"componentName": "Widget",
|
|
40
|
+
"maxPerApp": 100
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"key": "app.record-sections",
|
|
44
|
+
"kind": "view",
|
|
45
|
+
"legacyType": "record-section",
|
|
46
|
+
"definitionName": "RecordSectionDefinition",
|
|
47
|
+
"componentName": "Section",
|
|
48
|
+
"maxPerApp": 100
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"key": "app.record-page-tabs",
|
|
52
|
+
"kind": "view",
|
|
53
|
+
"legacyType": "record-tab",
|
|
54
|
+
"definitionName": "RecordTabDefinition",
|
|
55
|
+
"componentName": "Tab",
|
|
56
|
+
"maxPerApp": 100
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"key": "app.workspace-pages",
|
|
60
|
+
"kind": "view",
|
|
61
|
+
"legacyType": "page",
|
|
62
|
+
"definitionName": "PageDefinition",
|
|
63
|
+
"componentName": "Page",
|
|
64
|
+
"maxPerApp": 100
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"key": "app.tool-renderers",
|
|
68
|
+
"kind": "view",
|
|
69
|
+
"legacyType": "tool-renderer",
|
|
70
|
+
"definitionName": "ToolRendererDefinition",
|
|
71
|
+
"componentName": "Renderer",
|
|
72
|
+
"maxPerApp": 64
|
|
17
73
|
}
|
|
18
74
|
].map(Object.freeze));
|
|
19
75
|
export const frontendPoint = type => frontendPoints.find(point => point.legacyType === type);
|
package/src/config.js
CHANGED
|
@@ -3,6 +3,9 @@ import { readFileSync } from "node:fs";
|
|
|
3
3
|
import { ZhikeCliError } from "./errors.js";
|
|
4
4
|
|
|
5
5
|
export const CLI_CLIENT_ID = "zhike-cli";
|
|
6
|
+
export const DEFAULT_DEVELOPER_API_URL = "https://build-clm.77hub.com";
|
|
7
|
+
export const DEFAULT_DEVELOPER_CONSOLE_URL = "https://build-clm.77hub.com";
|
|
8
|
+
export const DEFAULT_WORKSPACE_APP_URL = "https://app-clm.77hub.com";
|
|
6
9
|
const metadata = JSON.parse(
|
|
7
10
|
readFileSync(new URL("../package.json", import.meta.url), "utf8"),
|
|
8
11
|
);
|
|
@@ -38,26 +41,27 @@ export function normalizeOrigin(value, fieldName) {
|
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
export function loadCliConfig(environment = process.env) {
|
|
41
|
-
const
|
|
42
|
-
(name) => typeof environment[name]
|
|
44
|
+
const provided = CLI_ORIGIN_VARIABLES.filter(
|
|
45
|
+
(name) => typeof environment[name] === "string" && environment[name].trim(),
|
|
43
46
|
);
|
|
44
|
-
if (
|
|
47
|
+
if (provided.length > 0 && provided.length < CLI_ORIGIN_VARIABLES.length) {
|
|
48
|
+
const missing = CLI_ORIGIN_VARIABLES.filter((name) => !provided.includes(name));
|
|
45
49
|
throw new ZhikeCliError(
|
|
46
50
|
"zhike_environment_required",
|
|
47
|
-
|
|
51
|
+
`覆盖平台环境时必须同时提供:${missing.join(", ")}`,
|
|
48
52
|
);
|
|
49
53
|
}
|
|
50
54
|
return {
|
|
51
55
|
apiBaseUrl: normalizeOrigin(
|
|
52
|
-
environment.ZHIKE_DEVELOPER_API_URL,
|
|
56
|
+
environment.ZHIKE_DEVELOPER_API_URL || DEFAULT_DEVELOPER_API_URL,
|
|
53
57
|
"ZHIKE_DEVELOPER_API_URL",
|
|
54
58
|
),
|
|
55
59
|
consoleBaseUrl: normalizeOrigin(
|
|
56
|
-
environment.ZHIKE_DEVELOPER_CONSOLE_URL,
|
|
60
|
+
environment.ZHIKE_DEVELOPER_CONSOLE_URL || DEFAULT_DEVELOPER_CONSOLE_URL,
|
|
57
61
|
"ZHIKE_DEVELOPER_CONSOLE_URL",
|
|
58
62
|
),
|
|
59
63
|
workspaceBaseUrl: normalizeOrigin(
|
|
60
|
-
environment.ZHIKE_WORKSPACE_APP_URL,
|
|
64
|
+
environment.ZHIKE_WORKSPACE_APP_URL || DEFAULT_WORKSPACE_APP_URL,
|
|
61
65
|
"ZHIKE_WORKSPACE_APP_URL",
|
|
62
66
|
),
|
|
63
67
|
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const ARRAY_LIMITS: Readonly<{ arrays: number; rowFields: number; rows: number; instances: number; valueBytes: number; messageBytes: number }>;
|
|
2
|
+
export function validFieldKey(key: unknown): boolean;
|
|
3
|
+
export function formBytes(value: unknown): number;
|
|
4
|
+
export function formObject(value: unknown, keys: string[]): void;
|
|
5
|
+
export interface FieldAddress { field: string; array?: string; index?: number }
|
|
6
|
+
export interface ArrayBinding { name: string; rowKey: string; formInstanceId: string; structureRevision: number }
|
|
7
|
+
export type ArrayAction = { kind: 'append'; value: Record<string, unknown> } | { kind: 'insertBefore'; beforeKey: string; value: Record<string, unknown> } | { kind: 'remove'; rowKey: string } | { kind: 'move'; rowKey: string; before: string | null };
|
|
8
|
+
export interface ArrayOperation { formId: string; formInstanceId: string; arrayName: string; baseStructureRevision: number; action: ArrayAction }
|
|
9
|
+
export interface ArraySnapshot {
|
|
10
|
+
formId: string; name: string; formInstanceId: string; revision: number; structureRevision: number;
|
|
11
|
+
rows: { key: string; index: number; value: Record<string, unknown>; errors: Record<string, string>; validatingFields: Record<string, boolean> }[];
|
|
12
|
+
submitting: boolean; canAppend: boolean; error: string | null;
|
|
13
|
+
}
|
|
14
|
+
export type ErrorTree<T = string> = Record<string, T | (Record<string, T> | null)[]>;
|
|
15
|
+
export function parseFieldPath(path: string): FieldAddress;
|
|
16
|
+
export function fieldAtPath<F>(fields: Record<string, F>, path: string): F;
|
|
17
|
+
export function valueAtPath(values: Record<string, unknown>, path: string): unknown;
|
|
18
|
+
export function setPathValue(values: Record<string, unknown>, path: string, value: unknown): void;
|
|
19
|
+
export function rulePath(path: string): string;
|
|
20
|
+
export function leafPaths(fields: Record<string, unknown>, values: Record<string, unknown>): string[];
|
|
21
|
+
export function rhfPath(path: string): string;
|
|
22
|
+
export function publicPath(path: string): string;
|
|
23
|
+
export function assertFieldValue(value: unknown, field: unknown, draft?: boolean): void;
|
|
24
|
+
export function assertArrayCapacity(fields: Record<string, unknown>, values: Record<string, unknown>, draft?: boolean): void;
|
|
25
|
+
export function normalizeFormValues<T extends Record<string, unknown>>(fields: Record<string, unknown>, raw: T): T;
|
|
26
|
+
export function assertErrorTree(tree: unknown, fields: Record<string, unknown>, values?: Record<string, unknown>, boolean?: boolean, maxText?: number): void;
|
|
27
|
+
export function assertArrayErrors(errors: unknown, fields: Record<string, unknown>, maxText?: number): void;
|
|
28
|
+
export function flattenErrors<T = string>(tree?: ErrorTree<T>): Record<string, T>;
|
|
29
|
+
export function nestErrors<T = string>(flat: Record<string, T>): ErrorTree<T>;
|
|
30
|
+
export function assertArrayBinding(binding: unknown): ArrayBinding;
|
|
31
|
+
export function assertArrayOperation(input: unknown): ArrayOperation;
|
|
32
|
+
export function assertArraySnapshot(snapshot: unknown, formId: string, name: string, fields: Record<string, unknown>): ArraySnapshot;
|