@lark-base-open/base-database-cli 1.1.0 → 1.3.7
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/CHANGELOG.md +86 -0
- package/LICENSE +6 -0
- package/README.md +55 -31
- package/dist/ai-functions.d.ts +42 -0
- package/dist/ai-functions.d.ts.map +1 -0
- package/dist/ai-functions.js +327 -0
- package/dist/ai-functions.js.map +1 -0
- package/dist/archive.d.ts +21 -0
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +65 -10
- package/dist/archive.js.map +1 -1
- package/dist/auth.d.ts +8 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +60 -6
- package/dist/auth.js.map +1 -1
- package/dist/check.d.ts +74 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +283 -0
- package/dist/check.js.map +1 -0
- package/dist/dependency-policy.d.ts +10 -0
- package/dist/dependency-policy.d.ts.map +1 -0
- package/dist/dependency-policy.js +162 -0
- package/dist/dependency-policy.js.map +1 -0
- package/dist/deploy.d.ts +1 -0
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +122 -175
- package/dist/deploy.js.map +1 -1
- package/dist/environment-variables.d.ts +52 -0
- package/dist/environment-variables.d.ts.map +1 -0
- package/dist/environment-variables.js +563 -0
- package/dist/environment-variables.js.map +1 -0
- package/dist/environment.d.ts +1 -1
- package/dist/environment.js +1 -1
- package/dist/environment.js.map +1 -1
- package/dist/function-input-types.d.ts +10 -0
- package/dist/function-input-types.d.ts.map +1 -0
- package/dist/function-input-types.js +160 -0
- package/dist/function-input-types.js.map +1 -0
- package/dist/function-schema.d.ts.map +1 -1
- package/dist/function-schema.js +89 -36
- package/dist/function-schema.js.map +1 -1
- package/dist/init-field-retry.d.ts +17 -0
- package/dist/init-field-retry.d.ts.map +1 -0
- package/dist/init-field-retry.js +127 -0
- package/dist/init-field-retry.js.map +1 -0
- package/dist/init-journal.d.ts +98 -0
- package/dist/init-journal.d.ts.map +1 -0
- package/dist/init-journal.js +492 -0
- package/dist/init-journal.js.map +1 -0
- package/dist/init-schema.d.ts +8 -0
- package/dist/init-schema.d.ts.map +1 -0
- package/dist/init-schema.js +423 -0
- package/dist/init-schema.js.map +1 -0
- package/dist/init.d.ts +14 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +886 -243
- package/dist/init.js.map +1 -1
- package/dist/lark-cli-error.d.ts +46 -0
- package/dist/lark-cli-error.d.ts.map +1 -0
- package/dist/lark-cli-error.js +171 -0
- package/dist/lark-cli-error.js.map +1 -0
- package/dist/lark-cli.d.ts +2 -1
- package/dist/lark-cli.d.ts.map +1 -1
- package/dist/lark-cli.js +292 -29
- package/dist/lark-cli.js.map +1 -1
- package/dist/mutation-schema.d.ts +146 -0
- package/dist/mutation-schema.d.ts.map +1 -0
- package/dist/mutation-schema.js +153 -0
- package/dist/mutation-schema.js.map +1 -0
- package/dist/mutation.d.ts +10 -0
- package/dist/mutation.d.ts.map +1 -0
- package/dist/mutation.js +131 -0
- package/dist/mutation.js.map +1 -0
- package/dist/npm.d.ts +1 -0
- package/dist/npm.d.ts.map +1 -1
- package/dist/npm.js +2 -2
- package/dist/npm.js.map +1 -1
- package/dist/preview-receipt.d.ts.map +1 -1
- package/dist/preview-receipt.js +15 -5
- package/dist/preview-receipt.js.map +1 -1
- package/dist/process.d.ts +3 -1
- package/dist/process.d.ts.map +1 -1
- package/dist/process.js +16 -4
- package/dist/process.js.map +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +241 -42
- package/dist/program.js.map +1 -1
- package/dist/project-build.d.ts +25 -0
- package/dist/project-build.d.ts.map +1 -0
- package/dist/project-build.js +229 -0
- package/dist/project-build.js.map +1 -0
- package/dist/project-context.d.ts +344 -0
- package/dist/project-context.d.ts.map +1 -0
- package/dist/project-context.js +485 -0
- package/dist/project-context.js.map +1 -0
- package/dist/project-layout.d.ts +5 -0
- package/dist/project-layout.d.ts.map +1 -0
- package/dist/project-layout.js +24 -0
- package/dist/project-layout.js.map +1 -0
- package/dist/public-sql-error.d.ts +25 -0
- package/dist/public-sql-error.d.ts.map +1 -0
- package/dist/public-sql-error.js +81 -0
- package/dist/public-sql-error.js.map +1 -0
- package/dist/pull.d.ts.map +1 -1
- package/dist/pull.js +54 -15
- package/dist/pull.js.map +1 -1
- package/dist/site-readme.d.ts +3 -0
- package/dist/site-readme.d.ts.map +1 -0
- package/dist/site-readme.js +83 -0
- package/dist/site-readme.js.map +1 -0
- package/dist/workspace-fingerprint.d.ts +21 -1
- package/dist/workspace-fingerprint.d.ts.map +1 -1
- package/dist/workspace-fingerprint.js +81 -2
- package/dist/workspace-fingerprint.js.map +1 -1
- package/package.json +2 -1
- package/skills/lark-base-database/SKILL.md +70 -53
- package/skills/lark-base-database/references/ai-function.md +471 -0
- package/skills/lark-base-database/references/base-capability-routing.md +3 -3
- package/skills/lark-base-database/references/database-function-limitations.md +46 -0
- package/skills/lark-base-database/references/database-function.md +349 -0
- package/skills/lark-base-database/references/design/components/actions-fields.md +16 -0
- package/skills/lark-base-database/references/design/components/code/controls.css +122 -0
- package/skills/lark-base-database/references/design/components/code/controls.tsx +73 -0
- package/skills/lark-base-database/references/design/components/code/date-picker.tsx +42 -0
- package/skills/lark-base-database/references/design/components/code/floating.tsx +41 -0
- package/skills/lark-base-database/references/design/components/code/overlays.tsx +19 -0
- package/skills/lark-base-database/references/design/components/code/select.tsx +34 -0
- package/skills/lark-base-database/references/design/components/code/styles.d.ts +1 -0
- package/skills/lark-base-database/references/design/components/code/theme.css +49 -0
- package/skills/lark-base-database/references/design/components/code/usage.tsx +38 -0
- package/skills/lark-base-database/references/design/components/date-time-number.md +9 -0
- package/skills/lark-base-database/references/design/components/feedback-navigation.md +9 -0
- package/skills/lark-base-database/references/design/components/index.md +18 -0
- package/skills/lark-base-database/references/design/components/overlays.md +9 -0
- package/skills/lark-base-database/references/design/components/patterns/flat-business.md +7 -0
- package/skills/lark-base-database/references/design/components/selection.md +24 -0
- package/skills/lark-base-database/references/design/components.md +91 -0
- package/skills/lark-base-database/references/design/composition.md +90 -0
- package/skills/lark-base-database/references/design/control-recipes.md +107 -0
- package/skills/lark-base-database/references/design/examples.md +172 -0
- package/skills/lark-base-database/references/design/foundations.md +126 -0
- package/skills/lark-base-database/references/design/review.md +82 -0
- package/skills/lark-base-database/references/design/scenes/analysis-report.md +66 -0
- package/skills/lark-base-database/references/design/scenes/brand-showcase.md +70 -0
- package/skills/lark-base-database/references/design/scenes/business-operations.md +85 -0
- package/skills/lark-base-database/references/design/scenes/campaign-report.md +66 -0
- package/skills/lark-base-database/references/design/scenes/dashboard.md +77 -0
- package/skills/lark-base-database/references/design/scenes/festive-campaign.md +69 -0
- package/skills/lark-base-database/references/design/theme.md +27 -0
- package/skills/lark-base-database/references/design/typography.md +56 -0
- package/skills/lark-base-database/references/development.md +165 -32
- package/skills/lark-base-database/references/environment-variables.md +55 -0
- package/skills/lark-base-database/references/faas.md +41 -48
- package/skills/lark-base-database/references/file-transfer.md +25 -25
- package/skills/lark-base-database/references/frontend-design-guide.md +33 -77
- package/skills/lark-base-database/references/mutations.md +295 -0
- package/skills/lark-base-database/references/permission-design.md +24 -24
- package/skills/lark-base-database/references/release.md +54 -27
- package/skills/lark-base-database/references/style-guide.md +10 -67
- package/skills/lark-base-database/references/verification.md +17 -13
- package/skills/lark-base-database/references/workspace.md +71 -22
- package/templates/database-starter/README.md +34 -4
- package/templates/database-starter/database-functions/README.md +8 -0
- package/templates/database-starter/mutations/README.md +19 -0
- package/templates/database-starter/page/README.md +25 -19
- package/templates/database-starter/page/package-lock.json +8 -16
- package/templates/database-starter/page/package.json +2 -3
- package/templates/database-starter/page/src/App.tsx +23 -3
- package/templates/database-starter/page/src/components/UserSelectorEditor/index.tsx +32 -4
- package/templates/database-starter/page/src/components/UserSelectorPanel/index.tsx +104 -34
- package/templates/database-starter/page/src/components/UserSelectorPanel/user-search.test.ts +55 -11
- package/templates/database-starter/page/src/components/UserSelectorPanel/user-search.ts +68 -25
- package/templates/database-starter/page/src/components/database-runtime-ready.tsx +1 -1
- package/templates/database-starter/page/src/components/database-user-menu/index.tsx +4 -7
- package/templates/database-starter/page/src/components/feedback/page-state.tsx +40 -5
- package/templates/database-starter/page/src/components/floating-button/assets/edit-active.svg +3 -0
- package/templates/database-starter/page/src/components/floating-button/assets/edit-default.svg +3 -0
- package/templates/database-starter/page/src/components/floating-button/index.tsx +75 -112
- package/templates/database-starter/page/src/components/floating-button/utils/project-meta.test.ts +107 -37
- package/templates/database-starter/page/src/components/floating-button/utils/project-meta.ts +13 -50
- package/templates/database-starter/page/src/components/ui-language.test.tsx +101 -0
- package/templates/database-starter/page/src/examples/ai-function/ImageTaskOperation.tsx +202 -0
- package/templates/database-starter/page/src/examples/ai-function/README.md +14 -0
- package/templates/database-starter/page/src/examples/ai-function/TextStreamOperation.tsx +184 -0
- package/templates/database-starter/page/src/examples/ai-function/ai-copy.ts +173 -0
- package/templates/database-starter/page/src/examples/ai-function/ai-delta.test.ts +30 -0
- package/templates/database-starter/page/src/examples/ai-function/ai-delta.ts +26 -0
- package/templates/database-starter/page/src/examples/ai-function/ai-language.test.tsx +86 -0
- package/templates/database-starter/page/src/examples/ai-function/ai-media-panel.test.ts +201 -0
- package/templates/database-starter/page/src/examples/ai-function/ai-media-panel.tsx +382 -0
- package/templates/database-starter/page/src/lib/attachment-image.tsx +144 -0
- package/templates/database-starter/page/src/lib/database-function-inputs.generated.ts +4 -0
- package/templates/database-starter/page/src/lib/database-runtime-attachment.test.ts +180 -76
- package/templates/database-starter/page/src/lib/database-runtime.test.ts +130 -10
- package/templates/database-starter/page/src/lib/database-runtime.ts +484 -51
- package/templates/database-starter/page/src/lib/date-time.ts +61 -0
- package/templates/database-starter/page/src/lib/ui-language.ts +86 -0
- package/templates/database-starter/page/src/main.tsx +1 -1
- package/templates/database-starter/page/tsconfig.json +2 -2
- package/templates/database-starter/page/vite.config.ts +1 -2
- package/templates/database-starter/prettierignore +1 -1
- package/templates/database-starter/server-functions/README.md +2 -2
- package/templates/database-starter/server-functions/package-lock.json +4 -4
- package/templates/database-starter/server-functions/package.json +1 -1
- package/dist/agent-guide.d.ts +0 -19
- package/dist/agent-guide.d.ts.map +0 -1
- package/dist/agent-guide.js +0 -31
- package/dist/agent-guide.js.map +0 -1
- package/dist/database-skill.d.ts +0 -24
- package/dist/database-skill.d.ts.map +0 -1
- package/dist/database-skill.js +0 -151
- package/dist/database-skill.js.map +0 -1
- package/skills/lark-base-database/references/base-sql-unsupported.md +0 -42
- package/skills/lark-base-database/references/database-capability-skill.md +0 -40
- package/skills/lark-base-database/references/sql-function.md +0 -289
- package/templates/database-capability/SKILL.md.tmpl +0 -58
- package/templates/database-starter/page/src/components/floating-button/chatbot-entry.test.ts +0 -23
- package/templates/database-starter/page/src/components/floating-button/chatbot-entry.tsx +0 -873
- package/templates/database-starter/page/src/components/floating-button/local-chat-panel.test.ts +0 -25
- package/templates/database-starter/page/src/components/floating-button/local-chat-panel.tsx +0 -197
- package/templates/database-starter/page/src/components/floating-button/utils/env.ts +0 -18
- package/templates/database-starter/sql/README.md +0 -8
|
@@ -1,9 +1,41 @@
|
|
|
1
1
|
import {
|
|
2
|
+
callSiteAIFunction as callRuntimeAIFunction,
|
|
3
|
+
createSiteAIFunctionTask as createRuntimeAIFunctionTask,
|
|
4
|
+
getSiteAIFunctionTask as getRuntimeAIFunctionTask,
|
|
5
|
+
invokeSiteAIFunctionStream as invokeRuntimeAIFunctionStream,
|
|
2
6
|
invokeHttp,
|
|
3
7
|
siteRuntime,
|
|
4
8
|
fetch as siteRuntimeFetch,
|
|
9
|
+
type CallSiteAIFunctionParams as RuntimeCallSiteAIFunctionParams,
|
|
10
|
+
type InvokeSiteAIFunctionStreamParams as RuntimeInvokeSiteAIFunctionStreamParams,
|
|
11
|
+
type SiteAIFunctionResult,
|
|
12
|
+
type SiteAIFunctionStreamHandle,
|
|
13
|
+
type SiteAIStreamDelta,
|
|
14
|
+
type SiteAIImageAsset,
|
|
15
|
+
type SiteAIImageGenerationInput,
|
|
16
|
+
type SiteAIVideoTask,
|
|
5
17
|
type SiteProjectEnv,
|
|
6
18
|
} from '@lark-base-open/base-site-channel/runtime'
|
|
19
|
+
import type { DatabaseFunctionInputMap } from './database-function-inputs.generated'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* alpha.17 的运行时代码已经会把 responseType 透传给宿主,但其公开 d.ts 仍停留在文本请求,
|
|
23
|
+
* 且没有声明顶层 signal。这里局部扩宽这一个二进制请求,避免生成工程为了通过 tsc 删除真实
|
|
24
|
+
* 运行所需字段;SDK 声明补齐后可以直接移除此兼容层。
|
|
25
|
+
*/
|
|
26
|
+
type BinarySiteHttpRequest = {
|
|
27
|
+
init: {
|
|
28
|
+
method: 'GET'
|
|
29
|
+
responseType: 'arrayBuffer'
|
|
30
|
+
timeoutMs: number
|
|
31
|
+
}
|
|
32
|
+
signal?: AbortSignal
|
|
33
|
+
url: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const invokeBinaryHttp = invokeHttp as unknown as (
|
|
37
|
+
request: BinarySiteHttpRequest,
|
|
38
|
+
) => Promise<Response>
|
|
7
39
|
|
|
8
40
|
/** 内置 Runtime Function(function.runtime.*,由宿主固定分发)。
|
|
9
41
|
* 随 channel 包发布,直接透传官方实现,不在 starter 侧包装。 */
|
|
@@ -21,7 +53,7 @@ export interface DatabaseContext {
|
|
|
21
53
|
token: string
|
|
22
54
|
}
|
|
23
55
|
|
|
24
|
-
/** 附件在底表里的位置。四个值都来自 catalog.json 与
|
|
56
|
+
/** 附件在底表里的位置。四个值都来自 catalog.json 与 Database Function 结果。 */
|
|
25
57
|
export interface DatabaseAttachmentRef {
|
|
26
58
|
tableId: string
|
|
27
59
|
fieldId: string
|
|
@@ -32,6 +64,71 @@ export interface DatabaseAttachmentRef {
|
|
|
32
64
|
/** 产物环境沿用 site.v1 协议的规范类型,避免两侧各自声明后漂移。 */
|
|
33
65
|
export type DatabaseEnv = SiteProjectEnv
|
|
34
66
|
|
|
67
|
+
export type SiteAIMessageContentPart =
|
|
68
|
+
| { text: string; type: 'text' }
|
|
69
|
+
| { detail?: 'high' | 'low' | 'xhigh'; type: 'image'; url: string }
|
|
70
|
+
| { fps?: number; type: 'video'; url: string }
|
|
71
|
+
| { type: 'audio'; url: string }
|
|
72
|
+
| { filename?: string; type: 'file'; url: string }
|
|
73
|
+
|
|
74
|
+
/** AI Function 对话消息。所有 content 都使用非空 parts 数组;System Prompt 由 Function 配置管理。
|
|
75
|
+
* 元组式 `[X, ...X[]]` 保证空数组无法通过 TS 编译,避免把空 content 推迟到运行态服务端校验。 */
|
|
76
|
+
export type SiteAIMessage =
|
|
77
|
+
| {
|
|
78
|
+
content: [SiteAIMessageContentPart, ...SiteAIMessageContentPart[]]
|
|
79
|
+
role: 'user'
|
|
80
|
+
}
|
|
81
|
+
| {
|
|
82
|
+
content: [
|
|
83
|
+
Extract<SiteAIMessageContentPart, { type: 'text' }>,
|
|
84
|
+
...Array<Extract<SiteAIMessageContentPart, { type: 'text' }>>,
|
|
85
|
+
]
|
|
86
|
+
role: 'assistant'
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
type WithSiteAIMessages<T> = T extends unknown
|
|
90
|
+
? Omit<T, 'messages'> & { messages?: SiteAIMessage[] }
|
|
91
|
+
: never
|
|
92
|
+
|
|
93
|
+
export type CallSiteAIFunctionParams =
|
|
94
|
+
WithSiteAIMessages<RuntimeCallSiteAIFunctionParams>
|
|
95
|
+
|
|
96
|
+
export type InvokeSiteAIFunctionStreamParams = Omit<
|
|
97
|
+
RuntimeInvokeSiteAIFunctionStreamParams,
|
|
98
|
+
'messages'
|
|
99
|
+
> & { messages: SiteAIMessage[] }
|
|
100
|
+
|
|
101
|
+
/** 单帧增量与可取消的流句柄。 */
|
|
102
|
+
export type { SiteAIStreamDelta, SiteAIFunctionStreamHandle }
|
|
103
|
+
|
|
104
|
+
/** AI Function 可判别联合返回与媒体任务态,供页面按 type 分支渲染。 */
|
|
105
|
+
export type SiteAIMediaTask =
|
|
106
|
+
| SiteAIVideoTask
|
|
107
|
+
| {
|
|
108
|
+
capabilityType?: 'image'
|
|
109
|
+
errCode?: string
|
|
110
|
+
errMessage?: string
|
|
111
|
+
imageAssets?: SiteAIImageAsset[]
|
|
112
|
+
progress?: number
|
|
113
|
+
requestId: string
|
|
114
|
+
status: 'cancelled' | 'failed' | 'queued' | 'running' | 'succeeded'
|
|
115
|
+
taskId: string
|
|
116
|
+
type: 'image_task'
|
|
117
|
+
}
|
|
118
|
+
export type {
|
|
119
|
+
SiteAIFunctionResult,
|
|
120
|
+
SiteAIImageGenerationInput,
|
|
121
|
+
SiteAIVideoTask,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const MEDIA_TASK_STATUSES = new Set([
|
|
125
|
+
'cancelled',
|
|
126
|
+
'failed',
|
|
127
|
+
'queued',
|
|
128
|
+
'running',
|
|
129
|
+
'succeeded',
|
|
130
|
+
])
|
|
131
|
+
|
|
35
132
|
/** Function 调用的统一响应包裹结构,与运行态接口一致。 */
|
|
36
133
|
export interface DatabaseFunctionResponse<Data = unknown> {
|
|
37
134
|
code: number
|
|
@@ -46,10 +143,8 @@ const ARTIFACT_PATH_PATTERN = /^\/?(preview\/)?app\/([^/]+)(\/|$)/
|
|
|
46
143
|
const FUNCTION_ID_PATTERN = /^[A-Za-z0-9_-]+$/
|
|
47
144
|
|
|
48
145
|
const DATABASE_RUNTIME_GATEWAY_PREFIX = '/space/api/bitable/site/v1'
|
|
49
|
-
const ATTACHMENT_COVER_PATH = '/space/api/box/stream/download/v2/cover/'
|
|
50
146
|
const ATTACHMENT_DOWNLOAD_URL_PATH =
|
|
51
147
|
'/space/api/box/file/temporary_download_url'
|
|
52
|
-
const ATTACHMENT_COVER_SIZES = [120, 360, 720, 1280] as const
|
|
53
148
|
const PREVIEW_ENV: DatabaseEnv = 'preview'
|
|
54
149
|
|
|
55
150
|
let databaseContextPromise: Promise<DatabaseContext> | null = null
|
|
@@ -87,6 +182,242 @@ function inferGatewayOrigin(): string {
|
|
|
87
182
|
}
|
|
88
183
|
}
|
|
89
184
|
|
|
185
|
+
/** 按 functionKey 调用 AI Function,返回可判别联合结果(text/image_task/video_task)。
|
|
186
|
+
*
|
|
187
|
+
* 页面用 switch(result.type) 分支渲染:text 为聚合文本,image_task/video_task
|
|
188
|
+
* 需再用 getSiteAIFunctionTask 轮询。传 messages 走文本/多模态理解,
|
|
189
|
+
* 传 input 创建媒体任务;随后用 getSiteAIFunctionTask 轮询。
|
|
190
|
+
*/
|
|
191
|
+
export async function callSiteAIFunction(
|
|
192
|
+
params: CallSiteAIFunctionParams,
|
|
193
|
+
): Promise<SiteAIFunctionResult> {
|
|
194
|
+
const result = await callRuntimeAIFunction(
|
|
195
|
+
params as unknown as RuntimeCallSiteAIFunctionParams,
|
|
196
|
+
)
|
|
197
|
+
if (params.input && !params.messages) {
|
|
198
|
+
return normalizeSiteAIMediaTask(result)
|
|
199
|
+
}
|
|
200
|
+
return result
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** 显式流式调用 AI Function(text/vision):逐帧消费增量,卸载或取消时 destroy。 */
|
|
204
|
+
export function invokeSiteAIFunctionStream(
|
|
205
|
+
params: InvokeSiteAIFunctionStreamParams,
|
|
206
|
+
): SiteAIFunctionStreamHandle {
|
|
207
|
+
return invokeRuntimeAIFunctionStream(
|
|
208
|
+
params as unknown as RuntimeInvokeSiteAIFunctionStreamParams,
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function readString(
|
|
213
|
+
source: Record<string, unknown>,
|
|
214
|
+
names: string[],
|
|
215
|
+
): string | undefined {
|
|
216
|
+
for (const name of names) {
|
|
217
|
+
const value = source[name]
|
|
218
|
+
if (typeof value === 'string' && value.trim()) {
|
|
219
|
+
return value
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return undefined
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function readNumber(
|
|
226
|
+
source: Record<string, unknown>,
|
|
227
|
+
names: string[],
|
|
228
|
+
): number | undefined {
|
|
229
|
+
for (const name of names) {
|
|
230
|
+
const value = source[name]
|
|
231
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
232
|
+
return value
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return undefined
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function readCapabilityType(
|
|
239
|
+
source: Record<string, unknown>,
|
|
240
|
+
): SiteAIMediaTask['capabilityType'] {
|
|
241
|
+
const value = readString(source, [
|
|
242
|
+
'capabilityType',
|
|
243
|
+
'capability_type',
|
|
244
|
+
'CapabilityType',
|
|
245
|
+
])
|
|
246
|
+
return value === 'image' || value === 'video' ? value : undefined
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function normalizeImageAssets(value: unknown): SiteAIImageAsset[] | undefined {
|
|
250
|
+
if (!Array.isArray(value)) {
|
|
251
|
+
return undefined
|
|
252
|
+
}
|
|
253
|
+
return value.filter(isRecord).map((asset) => {
|
|
254
|
+
const size = readString(asset, ['size', 'Size'])
|
|
255
|
+
const token = readString(asset, ['token', 'Token'])
|
|
256
|
+
const url = readString(asset, ['url', 'URL'])
|
|
257
|
+
return {
|
|
258
|
+
safetyStatus:
|
|
259
|
+
readString(asset, ['safetyStatus', 'safety_status', 'SafetyStatus']) ??
|
|
260
|
+
'',
|
|
261
|
+
...(size ? { size } : {}),
|
|
262
|
+
...(token ? { token } : {}),
|
|
263
|
+
...(url ? { url } : {}),
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function normalizeVideoAsset(value: unknown): SiteAIVideoTask['videoAsset'] {
|
|
269
|
+
if (!isRecord(value)) {
|
|
270
|
+
return undefined
|
|
271
|
+
}
|
|
272
|
+
const duration =
|
|
273
|
+
value.durationSeconds ?? value.duration_seconds ?? value.DurationSeconds
|
|
274
|
+
const token = readString(value, ['token', 'Token'])
|
|
275
|
+
const url = readString(value, ['url', 'URL'])
|
|
276
|
+
return {
|
|
277
|
+
...(typeof duration === 'number' ? { durationSeconds: duration } : {}),
|
|
278
|
+
...(token ? { token } : {}),
|
|
279
|
+
...(url ? { url } : {}),
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function mediaTaskError(
|
|
284
|
+
message: string,
|
|
285
|
+
code = 'INVALID_AI_FUNCTION_TASK',
|
|
286
|
+
): Error {
|
|
287
|
+
const error = new Error(message) as Error & { code: string }
|
|
288
|
+
error.name = 'SiteAIFunctionTaskError'
|
|
289
|
+
error.code = code
|
|
290
|
+
return error
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** 校验并归一化 AI Function 媒体任务响应。
|
|
294
|
+
*
|
|
295
|
+
* Channel SDK 新旧版本、OpenAPI 和父页面代理可能返回裸 task 或
|
|
296
|
+
* `{code,msg,data}` 包裹;页面只接受含合法 taskId/type/status 的任务。
|
|
297
|
+
*/
|
|
298
|
+
export function normalizeSiteAIMediaTask(value: unknown): SiteAIMediaTask {
|
|
299
|
+
if (!isRecord(value)) {
|
|
300
|
+
throw mediaTaskError('AI Function task response has an invalid structure')
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const envelopeError = readEnvelopeError(value)
|
|
304
|
+
if (envelopeError) {
|
|
305
|
+
throw mediaTaskError(envelopeError.message, envelopeError.code)
|
|
306
|
+
}
|
|
307
|
+
if ('data' in value && value.data !== undefined) {
|
|
308
|
+
return normalizeSiteAIMediaTask(value.data)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const type = readString(value, ['type', 'Type'])
|
|
312
|
+
const taskId = readString(value, ['taskId', 'task_id', 'TaskID'])
|
|
313
|
+
const status = value.status ?? value.Status
|
|
314
|
+
if (
|
|
315
|
+
(type !== 'image_task' && type !== 'video_task') ||
|
|
316
|
+
!taskId ||
|
|
317
|
+
typeof status !== 'string' ||
|
|
318
|
+
!MEDIA_TASK_STATUSES.has(status)
|
|
319
|
+
) {
|
|
320
|
+
throw mediaTaskError('AI Function task response has an invalid structure')
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const imageAssets = normalizeImageAssets(
|
|
324
|
+
value.imageAssets ?? value.image_assets ?? value.ImageAssets,
|
|
325
|
+
)
|
|
326
|
+
const videoAsset = normalizeVideoAsset(
|
|
327
|
+
value.videoAsset ?? value.video_asset ?? value.VideoAsset,
|
|
328
|
+
)
|
|
329
|
+
const progress = readNumber(value, ['progress', 'Progress'])
|
|
330
|
+
const capabilityType = readCapabilityType(value)
|
|
331
|
+
const errCode = readString(value, ['errCode', 'err_code', 'ErrCode'])
|
|
332
|
+
const errMessage = readString(value, [
|
|
333
|
+
'errMessage',
|
|
334
|
+
'err_message',
|
|
335
|
+
'ErrMessage',
|
|
336
|
+
])
|
|
337
|
+
return {
|
|
338
|
+
...(capabilityType ? { capabilityType } : {}),
|
|
339
|
+
...(errCode ? { errCode } : {}),
|
|
340
|
+
...(errMessage ? { errMessage } : {}),
|
|
341
|
+
...(imageAssets ? { imageAssets } : {}),
|
|
342
|
+
...(progress !== undefined ? { progress } : {}),
|
|
343
|
+
...(videoAsset ? { videoAsset } : {}),
|
|
344
|
+
requestId:
|
|
345
|
+
readString(value, ['requestId', 'request_id', 'RequestID']) ?? '',
|
|
346
|
+
status: status as SiteAIMediaTask['status'],
|
|
347
|
+
taskId,
|
|
348
|
+
type,
|
|
349
|
+
} as SiteAIMediaTask
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function readEnvelopeError(
|
|
353
|
+
value: Record<string, unknown>,
|
|
354
|
+
): { code: string; message: string } | undefined {
|
|
355
|
+
const directCode = value.code ?? value.Code
|
|
356
|
+
if (
|
|
357
|
+
(typeof directCode === 'number' && directCode !== 0) ||
|
|
358
|
+
(typeof directCode === 'string' && directCode && directCode !== '0')
|
|
359
|
+
) {
|
|
360
|
+
return {
|
|
361
|
+
code: String(directCode),
|
|
362
|
+
message:
|
|
363
|
+
readString(value, ['msg', 'message', 'Message']) ??
|
|
364
|
+
'Failed to create the AI Function task',
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const baseResp = value.BaseResp ?? value.base_resp ?? value.baseResp
|
|
369
|
+
if (!isRecord(baseResp)) {
|
|
370
|
+
return undefined
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const statusCode =
|
|
374
|
+
baseResp.StatusCode ?? baseResp.status_code ?? baseResp.statusCode
|
|
375
|
+
if (
|
|
376
|
+
(typeof statusCode === 'number' && statusCode !== 0) ||
|
|
377
|
+
(typeof statusCode === 'string' && statusCode && statusCode !== '0')
|
|
378
|
+
) {
|
|
379
|
+
return {
|
|
380
|
+
code: String(statusCode),
|
|
381
|
+
message:
|
|
382
|
+
readString(baseResp, [
|
|
383
|
+
'StatusMessage',
|
|
384
|
+
'status_message',
|
|
385
|
+
'statusMessage',
|
|
386
|
+
]) ?? 'Failed to create the AI Function task',
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return undefined
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export function hasRunnableMediaTask(
|
|
393
|
+
task: SiteAIMediaTask,
|
|
394
|
+
): task is SiteAIMediaTask & { taskId: string } {
|
|
395
|
+
return (
|
|
396
|
+
Boolean(task.taskId.trim()) &&
|
|
397
|
+
(task.status === 'queued' || task.status === 'running')
|
|
398
|
+
)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** 提交文生图或文生视频任务,返回封装后的 taskId 与初始状态。 */
|
|
402
|
+
export async function createSiteAIFunctionTask(
|
|
403
|
+
params: CallSiteAIFunctionParams,
|
|
404
|
+
): Promise<SiteAIMediaTask> {
|
|
405
|
+
return normalizeSiteAIMediaTask(await createRuntimeAIFunctionTask(params))
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** 轮询文生图或文生视频任务进度与最终资源。 */
|
|
409
|
+
export async function getSiteAIFunctionTask(
|
|
410
|
+
functionKey: string,
|
|
411
|
+
taskId: string,
|
|
412
|
+
): Promise<SiteAIMediaTask> {
|
|
413
|
+
if (!taskId.trim()) {
|
|
414
|
+
throw mediaTaskError('AI Function task ID must not be empty')
|
|
415
|
+
}
|
|
416
|
+
return normalizeSiteAIMediaTask(
|
|
417
|
+
await getRuntimeAIFunctionTask(functionKey, taskId),
|
|
418
|
+
)
|
|
419
|
+
}
|
|
420
|
+
|
|
90
421
|
/** 从产物托管路径推断当前应用 token 与运行环境。
|
|
91
422
|
*
|
|
92
423
|
* 这是宿主未下发上下文时的兜底:BaseApp 形态下 site 与 BaseApp 共用同一个 token,
|
|
@@ -172,7 +503,10 @@ export async function resolveDatabaseContext(): Promise<DatabaseContext> {
|
|
|
172
503
|
host = readHostDatabaseContext(await siteRuntime.getRuntimeContext())
|
|
173
504
|
} catch (error) {
|
|
174
505
|
// 宿主未实现或握手尚未完成时退回路径推断,不阻断业务。
|
|
175
|
-
console.warn(
|
|
506
|
+
console.warn(
|
|
507
|
+
'Could not read the host Runtime context; inferring it from the artifact path',
|
|
508
|
+
error,
|
|
509
|
+
)
|
|
176
510
|
}
|
|
177
511
|
|
|
178
512
|
const context: DatabaseContext = {
|
|
@@ -183,7 +517,7 @@ export async function resolveDatabaseContext(): Promise<DatabaseContext> {
|
|
|
183
517
|
|
|
184
518
|
if (!context.token) {
|
|
185
519
|
console.error(
|
|
186
|
-
'
|
|
520
|
+
'Cannot determine the app token: the host did not provide siteToken and the current path does not match an artifact path',
|
|
187
521
|
)
|
|
188
522
|
}
|
|
189
523
|
|
|
@@ -206,7 +540,6 @@ function hasCompleteRef(ref: DatabaseAttachmentRef): boolean {
|
|
|
206
540
|
)
|
|
207
541
|
}
|
|
208
542
|
|
|
209
|
-
/** baseAppToken 把附件鉴权绑定到应用;省略会退回底表权限判定并导致访客被拒。 */
|
|
210
543
|
function buildAttachmentExtra(
|
|
211
544
|
context: DatabaseContext,
|
|
212
545
|
ref: DatabaseAttachmentRef,
|
|
@@ -217,40 +550,76 @@ function buildAttachmentExtra(
|
|
|
217
550
|
attachments: {
|
|
218
551
|
[ref.fieldId]: { [ref.recordId]: [ref.fileToken] },
|
|
219
552
|
},
|
|
220
|
-
|
|
553
|
+
siteToken: context.token,
|
|
554
|
+
siteEnv: context.env,
|
|
221
555
|
},
|
|
222
556
|
})
|
|
223
557
|
}
|
|
224
558
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
559
|
+
function attachmentRequestError(operation: string, cause: unknown): Error {
|
|
560
|
+
// Errors from the SDK can carry actionable host guidance and a distinct type.
|
|
561
|
+
if (cause instanceof Error) return cause
|
|
562
|
+
const rawCode = isRecord(cause) ? cause.code : undefined
|
|
563
|
+
const code =
|
|
564
|
+
typeof rawCode === 'string' || typeof rawCode === 'number'
|
|
565
|
+
? String(rawCode)
|
|
566
|
+
: ''
|
|
567
|
+
const summary =
|
|
568
|
+
code === 'REQUEST_TIMEOUT' || code === '10005'
|
|
569
|
+
? `${operation} timed out`
|
|
570
|
+
: `${operation} failed`
|
|
571
|
+
return Object.assign(
|
|
572
|
+
new Error(`${summary}${code ? ` (code ${code})` : ''}`),
|
|
573
|
+
{
|
|
574
|
+
cause,
|
|
575
|
+
...(code ? { code } : {}),
|
|
576
|
+
},
|
|
577
|
+
)
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/** 宿主用当前访问者身份同源取图,返回可创建本地 URL 的图片字节。
|
|
581
|
+
* 调用方在替换或卸载时取消请求并释放自己创建的 Blob URL。
|
|
229
582
|
*/
|
|
230
|
-
export function
|
|
231
|
-
context: DatabaseContext,
|
|
583
|
+
export async function fetchAttachmentCover(
|
|
232
584
|
ref: DatabaseAttachmentRef,
|
|
233
585
|
size = 360,
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
586
|
+
options: { signal?: AbortSignal } = {},
|
|
587
|
+
): Promise<Blob> {
|
|
588
|
+
options.signal?.throwIfAborted()
|
|
589
|
+
if (!hasCompleteRef(ref) || !Number.isFinite(size) || size <= 0) {
|
|
590
|
+
throw new Error('Attachment location or image size is invalid')
|
|
237
591
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const
|
|
592
|
+
const context = await resolveDatabaseContext()
|
|
593
|
+
options.signal?.throwIfAborted()
|
|
594
|
+
if (!context.token)
|
|
595
|
+
throw new Error('Cannot load the attachment image without an app token')
|
|
596
|
+
const matchedSize =
|
|
597
|
+
[120, 360, 720, 1280].find((candidate) => candidate >= size) ?? 1280
|
|
598
|
+
const query = new URLSearchParams({
|
|
243
599
|
extra: buildAttachmentExtra(context, ref),
|
|
244
|
-
width: String(
|
|
245
|
-
height: String(
|
|
600
|
+
width: String(matchedSize),
|
|
601
|
+
height: String(matchedSize),
|
|
246
602
|
policy: 'allow_up',
|
|
247
603
|
fallback_source: '1',
|
|
248
604
|
})
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
)
|
|
605
|
+
const response = await invokeBinaryHttp({
|
|
606
|
+
url: `/space/api/box/stream/download/v2/cover/${encodeURIComponent(ref.fileToken)}?${query}`,
|
|
607
|
+
init: { method: 'GET', responseType: 'arrayBuffer', timeoutMs: 45_000 },
|
|
608
|
+
signal: options.signal,
|
|
609
|
+
}).catch((cause: unknown) => {
|
|
610
|
+
throw attachmentRequestError('Attachment image request', cause)
|
|
611
|
+
})
|
|
612
|
+
if (!response.ok) {
|
|
613
|
+
const logId = response.headers.get('x-tt-logid')
|
|
614
|
+
throw new Error(
|
|
615
|
+
`Failed to load attachment image: HTTP ${response.status}${logId ? `, LogID ${logId}` : ''}`,
|
|
616
|
+
)
|
|
617
|
+
}
|
|
618
|
+
const blob = await response.blob()
|
|
619
|
+
if (!blob.size || !blob.type.startsWith('image/')) {
|
|
620
|
+
throw new Error('The host did not return a valid image')
|
|
621
|
+
}
|
|
622
|
+
return blob
|
|
254
623
|
}
|
|
255
624
|
|
|
256
625
|
/** 经父页面 bridge 取短时效自证链接;原文件由浏览器下载,不进入页面 JavaScript。
|
|
@@ -263,11 +632,15 @@ export async function fetchAttachmentDownloadUrl(
|
|
|
263
632
|
ref: DatabaseAttachmentRef,
|
|
264
633
|
): Promise<string> {
|
|
265
634
|
if (!context.token) {
|
|
266
|
-
throw new Error(
|
|
635
|
+
throw new Error(
|
|
636
|
+
'Cannot get an attachment download URL without an app token',
|
|
637
|
+
)
|
|
267
638
|
}
|
|
268
639
|
|
|
269
640
|
if (!hasCompleteRef(ref)) {
|
|
270
|
-
throw new Error(
|
|
641
|
+
throw new Error(
|
|
642
|
+
'Cannot get an attachment download URL: the attachment location is incomplete',
|
|
643
|
+
)
|
|
271
644
|
}
|
|
272
645
|
|
|
273
646
|
const response = await siteRuntimeFetch(ATTACHMENT_DOWNLOAD_URL_PATH, {
|
|
@@ -277,20 +650,48 @@ export async function fetchAttachmentDownloadUrl(
|
|
|
277
650
|
}),
|
|
278
651
|
headers: { 'content-type': 'application/json' },
|
|
279
652
|
method: 'POST',
|
|
653
|
+
}).catch((cause: unknown) => {
|
|
654
|
+
throw attachmentRequestError('Attachment download URL request', cause)
|
|
280
655
|
})
|
|
281
656
|
|
|
657
|
+
const logId = response.headers?.get('x-tt-logid')?.trim()
|
|
658
|
+
let body: unknown
|
|
659
|
+
try {
|
|
660
|
+
body = await response.json()
|
|
661
|
+
} catch {
|
|
662
|
+
throw new Error(
|
|
663
|
+
response.ok
|
|
664
|
+
? `Attachment download URL response is not valid JSON${logId ? `, LogID ${logId}` : ''}`
|
|
665
|
+
: `Failed to get attachment download URL: HTTP ${response.status}${logId ? `, LogID ${logId}` : ''}`,
|
|
666
|
+
)
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const code = isRecord(body) ? body.code : undefined
|
|
670
|
+
if (
|
|
671
|
+
(typeof code === 'number' && code !== 0) ||
|
|
672
|
+
(typeof code === 'string' && code.trim() !== '' && code.trim() !== '0')
|
|
673
|
+
) {
|
|
674
|
+
const error = new Error(
|
|
675
|
+
`Attachment service could not provide a download URL (code ${String(code)}${logId ? `, LogID ${logId}` : ''})`,
|
|
676
|
+
)
|
|
677
|
+
throw Object.assign(error, { code: String(code), cause: body })
|
|
678
|
+
}
|
|
679
|
+
|
|
282
680
|
if (!response.ok) {
|
|
283
|
-
throw new Error(
|
|
681
|
+
throw new Error(
|
|
682
|
+
`Failed to get attachment download URL: HTTP ${response.status}${logId ? `, LogID ${logId}` : ''}`,
|
|
683
|
+
)
|
|
284
684
|
}
|
|
285
685
|
|
|
286
|
-
const body: unknown = await response.json()
|
|
287
686
|
const url =
|
|
288
687
|
isRecord(body) && isRecord(body.data) && typeof body.data.url === 'string'
|
|
289
688
|
? body.data.url.trim()
|
|
290
689
|
: ''
|
|
291
690
|
|
|
292
691
|
if (!url) {
|
|
293
|
-
throw new Error(
|
|
692
|
+
throw new Error(
|
|
693
|
+
`Attachment download URL response is missing data.url${logId ? `, LogID ${logId}` : ''}`,
|
|
694
|
+
)
|
|
294
695
|
}
|
|
295
696
|
|
|
296
697
|
return url
|
|
@@ -299,19 +700,11 @@ export async function fetchAttachmentDownloadUrl(
|
|
|
299
700
|
/** 构造运行态读取应用信息的统一网关地址。
|
|
300
701
|
*
|
|
301
702
|
* @param context 当前应用上下文。
|
|
302
|
-
* @param withChatbot 是否附带项目绑定的 chatbot 列表。
|
|
303
703
|
* @returns GetInvokeProject 的同源相对路径。
|
|
304
704
|
*/
|
|
305
|
-
export function buildDatabaseInvokeGetPath(
|
|
306
|
-
context: DatabaseContext,
|
|
307
|
-
withChatbot: boolean,
|
|
308
|
-
): string {
|
|
705
|
+
export function buildDatabaseInvokeGetPath(context: DatabaseContext): string {
|
|
309
706
|
const searchParams = new URLSearchParams()
|
|
310
707
|
|
|
311
|
-
if (withChatbot) {
|
|
312
|
-
searchParams.set('withChatbot', 'true')
|
|
313
|
-
}
|
|
314
|
-
|
|
315
708
|
if (context.env === PREVIEW_ENV) {
|
|
316
709
|
searchParams.set('env', PREVIEW_ENV)
|
|
317
710
|
}
|
|
@@ -337,7 +730,7 @@ export function buildDatabaseInvokeFunctionPath(
|
|
|
337
730
|
): string {
|
|
338
731
|
if (!FUNCTION_ID_PATTERN.test(functionId)) {
|
|
339
732
|
throw new TypeError(
|
|
340
|
-
`functionId ${functionId}
|
|
733
|
+
`functionId ${functionId} does not match the required pattern ${String(FUNCTION_ID_PATTERN)}`,
|
|
341
734
|
)
|
|
342
735
|
}
|
|
343
736
|
|
|
@@ -353,23 +746,31 @@ export function buildDatabaseInvokeFunctionPath(
|
|
|
353
746
|
/** 调用应用 Function。
|
|
354
747
|
*
|
|
355
748
|
* 请求经父页面同源代理发往统一网关;整个请求体即 payload,业务参数包在 params 下。
|
|
356
|
-
*
|
|
357
|
-
* cloneBaseAppHeaders 会剥掉入站的 x-project-env 后按归一化 env 重写,
|
|
358
|
-
* 再带同名 Header 属于重复下发。
|
|
749
|
+
* 目标环境由 query 参数 env 指定,不重复添加环境 Header。
|
|
359
750
|
* 4xx/5xx 直接抛错,业务码由调用方按 operation 契约校验一次顶层 code。
|
|
360
751
|
*
|
|
361
752
|
* @param functionId Base Database Function ID。
|
|
362
|
-
* @param params 函数入参:
|
|
753
|
+
* @param params 函数入参:Database Function 用数组,FaaS 用业务对象。
|
|
363
754
|
* @returns 统一包裹的函数响应。
|
|
364
755
|
*/
|
|
756
|
+
export type DatabaseFunctionInvocation<Inputs extends object> = [
|
|
757
|
+
Extract<keyof Inputs, string>,
|
|
758
|
+
] extends [never]
|
|
759
|
+
? [functionId: never, params: never]
|
|
760
|
+
: {
|
|
761
|
+
[FunctionId in Extract<keyof Inputs, string>]: [
|
|
762
|
+
functionId: FunctionId,
|
|
763
|
+
params: Inputs[FunctionId],
|
|
764
|
+
]
|
|
765
|
+
}[Extract<keyof Inputs, string>]
|
|
766
|
+
|
|
365
767
|
export async function invokeDatabaseFunction<Data = unknown>(
|
|
366
|
-
functionId:
|
|
367
|
-
params?: unknown,
|
|
768
|
+
...[functionId, params]: DatabaseFunctionInvocation<DatabaseFunctionInputMap>
|
|
368
769
|
): Promise<DatabaseFunctionResponse<Data>> {
|
|
369
770
|
const context = await resolveDatabaseContext()
|
|
370
771
|
|
|
371
772
|
if (!context.token) {
|
|
372
|
-
throw new Error(
|
|
773
|
+
throw new Error(`Cannot invoke Function ${functionId} without an app token`)
|
|
373
774
|
}
|
|
374
775
|
|
|
375
776
|
const response = await invokeHttp({
|
|
@@ -382,8 +783,40 @@ export async function invokeDatabaseFunction<Data = unknown>(
|
|
|
382
783
|
})
|
|
383
784
|
|
|
384
785
|
if (!response.ok) {
|
|
385
|
-
throw new Error(
|
|
786
|
+
throw new Error(
|
|
787
|
+
`Function ${functionId} request failed: HTTP ${response.status}`,
|
|
788
|
+
)
|
|
386
789
|
}
|
|
387
790
|
|
|
388
791
|
return (await response.json()) as DatabaseFunctionResponse<Data>
|
|
389
792
|
}
|
|
793
|
+
|
|
794
|
+
/** 每组参数应用同一份已发布 Mutation 声明;总记录数最多 200。 */
|
|
795
|
+
export async function invokeDatabaseMutationBatch<Data = unknown>(
|
|
796
|
+
functionId: string,
|
|
797
|
+
parameterGroups: unknown[][],
|
|
798
|
+
): Promise<DatabaseFunctionResponse<Data>> {
|
|
799
|
+
if (
|
|
800
|
+
!Array.isArray(parameterGroups) ||
|
|
801
|
+
parameterGroups.length < 1 ||
|
|
802
|
+
parameterGroups.length > 200 ||
|
|
803
|
+
parameterGroups.some((params) => !Array.isArray(params))
|
|
804
|
+
) {
|
|
805
|
+
throw new TypeError('Mutation batch requires 1 to 200 parameter arrays')
|
|
806
|
+
}
|
|
807
|
+
const context = await resolveDatabaseContext()
|
|
808
|
+
if (!context.token) throw new Error('应用 token 未确定,无法调用 Mutation')
|
|
809
|
+
const response = await invokeHttp({
|
|
810
|
+
url: buildDatabaseInvokeFunctionPath(context, functionId),
|
|
811
|
+
init: {
|
|
812
|
+
body: JSON.stringify({
|
|
813
|
+
batch: parameterGroups.map((params) => ({ params })),
|
|
814
|
+
}),
|
|
815
|
+
headers: { 'content-type': 'application/json' },
|
|
816
|
+
method: 'POST',
|
|
817
|
+
},
|
|
818
|
+
})
|
|
819
|
+
if (!response.ok)
|
|
820
|
+
throw new Error(`Mutation 调用失败:HTTP ${response.status}`)
|
|
821
|
+
return (await response.json()) as DatabaseFunctionResponse<Data>
|
|
822
|
+
}
|