zentao-api 0.6.4 → 0.6.6

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.
@@ -4,13 +4,9 @@
4
4
  // - ./define —— 定义/写入模块(defineModules / defineModuleActions / resetModuleDefinitions)。
5
5
  // - ./override —— 内置覆盖/扩展(基于 define,随 SDK 发布并在加载/重置时自动应用)。
6
6
  // - ./query —— 获取模块信息(getModule / getModuleAction / getModuleNames / isModuleName)。
7
- import { BUILTIN_MODULES } from './generated.js';
8
7
  import { applyBuiltinOverrides } from './override.js';
9
8
  import { setPostResetHook } from './registry-store.js';
10
- export { BUILTIN_MODULES };
11
- export const MODULES = BUILTIN_MODULES;
12
9
  export { defineModules, defineModuleActions, extendModuleAction, resetModuleDefinitions, } from './define.js';
13
- export { applyBuiltinOverrides };
14
10
  // 内置覆盖接线:避免 define ↔ override 之间的循环依赖。
15
11
  // - 加载时应用一次;
16
12
  // - 注册为 store 的「重置后钩子」,使 resetModuleDefinitions 还原内置基线后自动重新应用。
@@ -162,7 +162,7 @@ function buildQuery(action, params) {
162
162
  value = params.page;
163
163
  }
164
164
  if (value === undefined) {
165
- value = param.defaultValue ?? param.options?.[0]?.value;
165
+ value = param.defaultValue;
166
166
  }
167
167
  if (value === undefined && param.required) {
168
168
  throw new ZentaoError('E_MISSING_PARAM', { param: param.name });
@@ -1,6 +1,6 @@
1
1
  import { ZentaoError } from '../misc/errors.js';
2
2
  import { isNodeRuntime } from '../misc/environment.js';
3
- import { normalizeSiteUrl } from '../utils/index.js';
3
+ import { isRecord, normalizeSiteUrl } from '../utils/index.js';
4
4
  /**
5
5
  * 浏览器环境下用于在 `localStorage` 中保存 profile 数据的 key。
6
6
  *
@@ -8,9 +8,6 @@ import { normalizeSiteUrl } from '../utils/index.js';
8
8
  */
9
9
  export const ZENTAO_PROFILES_STORAGE_KEY = 'ZENTAO_PROFILES';
10
10
  const PROFILE_FILE_PARTS = ['.config', 'zentao', 'zentao.json'];
11
- function isRecord(value) {
12
- return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
13
- }
14
11
  function cloneJson(value) {
15
12
  if (value === undefined)
16
13
  return value;
@@ -31,13 +31,21 @@ export type RequestResultFor<Name extends BuiltinRequestName> = ActionMetaOf<Nam
31
31
  * 对 `update` 动作,当 `options.autoFill` 或全局 `autoFill` 为真时,会先 GET 当前对象,
32
32
  * 用现值补齐用户未显式传入的 body 字段后再 PUT,避免禅道覆盖未提交字段。详见 {@link RequestOptions.autoFill}。
33
33
  *
34
- * @typeParam T 期望的 `data` 字段类型;不传时为 `unknown`,调用方需要自行收窄。
34
+ * @typeParam T 归一化响应中期望的 `data` 字段类型;不传时为 `unknown`,调用方需要自行收窄。
35
35
  * @param name - 请求名,例如 `product`、`product/list` 或 `product/1`。
36
36
  * @param params - 请求参数。
37
37
  * @param options - 请求选项。
38
- * @returns 归一化后的禅道 API 响应。
38
+ * @returns 默认返回归一化后的禅道 API 响应;`options.raw` 为 true 时返回未经归一化的原始响应。
39
39
  * @throws {ZentaoError} 传输层错误、参数缺失或 `throwOnFail` 启用时的业务失败。
40
40
  */
41
- export declare function request<Name extends BuiltinRequestName>(name: Name, params?: RequestParamsFor<Name>, options?: RequestOptions): Promise<ResponseData<RequestResultFor<Name>>>;
42
- export declare function request<T = unknown>(name: string, params?: Record<string, unknown>, options?: RequestOptions): Promise<ResponseData<T>>;
41
+ export declare function request(name: string, params: Record<string, unknown> | undefined, options: RequestOptions & {
42
+ raw: true;
43
+ }): Promise<unknown>;
44
+ export declare function request<Name extends BuiltinRequestName>(name: Name, params?: RequestParamsFor<Name>, options?: RequestOptions & {
45
+ raw?: false;
46
+ }): Promise<ResponseData<RequestResultFor<Name>>>;
47
+ export declare function request<T = unknown>(name: string, params?: Record<string, unknown>, options?: RequestOptions & {
48
+ raw?: false;
49
+ }): Promise<ResponseData<T>>;
50
+ export declare function request(name: string, params?: Record<string, unknown>, options?: RequestOptions): Promise<unknown>;
43
51
  export {};
@@ -50,7 +50,7 @@ function getExplicitDataKeys(data) {
50
50
  /**
51
51
  * 在执行 `update` 动作前,用当前对象的现值填充用户未显式传入的 body 字段。
52
52
  *
53
- * 仅当模块存在 `type: 'get'` 动作且 update 动作声明了对象类型 body schema 时生效;
53
+ * 仅当模块存在与 update 路径相同的 `type: 'get'` 动作,且 update 动作声明了对象类型 body schema 时生效;
54
54
  * 否则原样返回参数。GET 返回失败状态时会抛出 `E_API_FAILED`,避免继续发送未补齐的 PUT;
55
55
  * GET 成功但返回非对象时跳过填充,交由后续 PUT 正常处理。
56
56
  *
@@ -59,7 +59,7 @@ function getExplicitDataKeys(data) {
59
59
  */
60
60
  async function autoFillUpdateParams(module, action, params, options) {
61
61
  const properties = action.requestBody?.schema?.properties;
62
- const getAction = module.actions.find((candidate) => candidate.type === 'get');
62
+ const getAction = module.actions.find((candidate) => candidate.type === 'get' && candidate.path === action.path);
63
63
  if (!properties || !getAction)
64
64
  return params;
65
65
  const current = (await request(`${module.name}/${getAction.name}`, params, {
@@ -23,12 +23,12 @@ export interface ClientRequestOptions {
23
23
  body?: unknown;
24
24
  /** 请求体序列化方式。默认 `json`;传入 `FormData` 等原生 body 时会自动按 `raw` 处理。 */
25
25
  bodyType?: ClientRequestBodyType;
26
- /** 响应体解析方式。默认 `auto`,会优先尝试 JSON,失败后回落为文本。 */
26
+ /** 响应体解析方式。默认 `auto`,会优先尝试 JSON,失败后回落为文本;`response` 会让 signal 和 timeout 继续控制响应体。 */
27
27
  responseType?: ClientResponseType;
28
28
  /** 额外请求头;会与 SDK 自动注入的 `Token` / `Content-Type` 合并。 */
29
29
  headers?: HeadersInit;
30
- /** URL 查询参数;`undefined` 值会被跳过。 */
31
- query?: Record<string, string | number | boolean | undefined>;
30
+ /** URL 查询参数;对象和数组使用 deepObject 风格序列化,`undefined` 值会被跳过。 */
31
+ query?: Record<string, unknown>;
32
32
  /** 外部取消信号;会与 SDK 自身的超时控制合并。 */
33
33
  signal?: AbortSignal;
34
34
  /** 单次请求超时时间,优先级高于全局和客户端默认值。 */
@@ -27,9 +27,13 @@ export interface ModuleActionParam {
27
27
  /** 未显式传入时使用的默认值。 */
28
28
  defaultValue?: unknown;
29
29
  /** 参数值类型,用于基础类型转换。 */
30
- type?: 'string' | 'number' | 'boolean';
30
+ type?: 'string' | 'number' | 'boolean' | 'array' | 'object';
31
31
  /** OpenAPI schema format,例如 `binary` 或 `int32`。 */
32
32
  format?: string;
33
+ /** OpenAPI 查询参数序列化样式,例如 `deepObject`。 */
34
+ style?: string;
35
+ /** OpenAPI 查询参数是否展开序列化。 */
36
+ explode?: boolean;
33
37
  /** 参数可选值。 */
34
38
  options?: readonly ModuleActionParamOption[];
35
39
  }
@@ -147,7 +151,7 @@ export interface ModuleActionRequest {
147
151
  /** 已替换路径参数后的 API 路径。 */
148
152
  path: string;
149
153
  /** 已组装的查询参数。 */
150
- query?: Record<string, string | number>;
154
+ query?: Record<string, unknown>;
151
155
  /** 已组装的请求体。 */
152
156
  data?: Record<string, unknown>;
153
157
  /** 从 `id` 或 `{module}ID` 推断出的对象 ID。 */
@@ -49,6 +49,7 @@ export interface RequestOptions extends ProcessListOptions {
49
49
  * 动作 body schema 中声明的字段用现值补齐,再发起 PUT,避免禅道用空值覆盖未提交字段。
50
50
  * 因此只需传想修改的字段即可。仅对 `type: 'update'` 且模块存在 `type: 'get'` 动作时生效。
51
51
  *
52
+ * 只有模块中存在与 update 路径相同的 GET 动作时才会预取;否则直接发送更新请求。
52
53
  * 不传时回落到全局 `autoFill`,默认 false。
53
54
  */
54
55
  autoFill?: boolean;
@@ -1,3 +1,2 @@
1
- export { snapshotToHtml } from './html.js';
2
1
  export { snapshotToMarkdown } from './markdown.js';
3
- export type { BlockRenderer, BlockSnapshot, ConvertOptions, DeltaInsert, DocSnapshot, DocSnapshotMeta, DocumentReference, HtmlOptions, JsonRecord, MarkdownOptions, OutputFormat, RenderBlockContext, SliceSnapshot, Snapshot, SnapshotEnvelope, SnapshotInput, TextSnapshot, UnknownBlockStrategy, } from './types.js';
2
+ export type { BlockRenderer, BlockSnapshot, ConvertOptions, DeltaInsert, DocSnapshot, DocSnapshotMeta, DocumentReference, JsonRecord, MarkdownOptions, RenderBlockContext, SliceSnapshot, Snapshot, SnapshotEnvelope, SnapshotInput, TextSnapshot, UnknownBlockStrategy, } from './types.js';
@@ -1,2 +1 @@
1
- export { snapshotToHtml } from './html.js';
2
1
  export { snapshotToMarkdown } from './markdown.js';
@@ -1,3 +1,2 @@
1
1
  import type { BlockSnapshot, ConvertOptions } from './types.js';
2
2
  export declare function deltaToMarkdown(value: unknown, options: ConvertOptions, block?: BlockSnapshot): string;
3
- export declare function deltaToHtml(value: unknown, options: ConvertOptions, block?: BlockSnapshot): string;
@@ -1,4 +1,4 @@
1
- import { deltaFrom, escapeMarkdownMath, escapeHtml, escapeHtmlAttribute, isRecord, resolveDocumentUrl, safeCssColor, safeFontSize, sanitizeUrl, toRecord, toStringValue, } from './shared.js';
1
+ import { deltaFrom, escapeMarkdownMath, resolveDocumentUrl, sanitizeUrl, toRecord, toStringValue, } from './shared.js';
2
2
  function escapeMarkdownText(value) {
3
3
  return value
4
4
  .replace(/\\/g, '\\\\')
@@ -48,9 +48,8 @@ function renderMarkdownDelta(delta, options, block) {
48
48
  const latex = toStringValue(attributes.latex);
49
49
  const reference = referenceFrom(delta);
50
50
  const rawLink = toStringValue(attributes.link);
51
- // The existing Markdown adapter has no holder matcher and therefore keeps
52
- // the underlying insert (normally a single space). HTML has a dedicated
53
- // readable holder representation below.
51
+ // The Markdown adapter has no holder matcher and therefore keeps the
52
+ // underlying insert (normally a single space).
54
53
  let raw = mention ?? delta.insert;
55
54
  const linkIsPlainText = !reference && (raw === '' || raw === rawLink);
56
55
  if (!reference && raw === '' && rawLink)
@@ -89,80 +88,3 @@ export function deltaToMarkdown(value, options, block) {
89
88
  .map(delta => renderMarkdownDelta(delta, options, block))
90
89
  .join('');
91
90
  }
92
- function htmlDataAttributes(data) {
93
- if (!isRecord(data))
94
- return '';
95
- return ` data-holder-data="${escapeHtmlAttribute(JSON.stringify(data))}"`;
96
- }
97
- function renderHtmlDelta(delta, options, block) {
98
- if (typeof delta.insert !== 'string')
99
- return '';
100
- const attributes = toRecord(delta.attributes);
101
- const holder = toRecord(attributes.holder);
102
- if (Object.keys(holder).length) {
103
- const text = toStringValue(holder.text) || toStringValue(holder.name) || delta.insert;
104
- const id = toStringValue(holder.id);
105
- const name = toStringValue(holder.name);
106
- const hint = toStringValue(holder.hint);
107
- return `<code class="affine-zui-holder"${id ? ` data-holder-id="${escapeHtmlAttribute(id)}"` : ''}${name ? ` data-holder-name="${escapeHtmlAttribute(name)}"` : ''}${hint ? ` title="${escapeHtmlAttribute(hint)}"` : ''}${htmlDataAttributes(holder.data)}>${escapeHtml(text)}</code>`;
108
- }
109
- const mention = toRecord(attributes.mention);
110
- if (Object.keys(mention).length) {
111
- const label = toStringValue(mention.label) || toStringValue(mention.name);
112
- const id = toStringValue(mention.id);
113
- const type = toStringValue(mention.type);
114
- return `<span class="affine-zui-mention-label"${id ? ` data-id="${escapeHtmlAttribute(id)}"` : ''}${type ? ` data-type="${escapeHtmlAttribute(type)}"` : ''}>@${escapeHtml(label)}</span>`;
115
- }
116
- const latex = toStringValue(attributes.latex);
117
- if (latex) {
118
- return `<span class="affine-inline-latex" data-latex="${escapeHtmlAttribute(latex)}">${escapeHtml(latex)}</span>`;
119
- }
120
- const reference = referenceFrom(delta);
121
- const visibleText = reference
122
- ? options.resolveDocTitle?.(reference.pageId) ||
123
- delta.insert.trim() ||
124
- reference.pageId
125
- : delta.insert;
126
- let output = escapeHtml(visibleText).replace(/\n/g, '<br>');
127
- if (attributes.bold)
128
- output = `<strong>${output}</strong>`;
129
- if (attributes.italic)
130
- output = `<em>${output}</em>`;
131
- if (attributes.strike)
132
- output = `<del>${output}</del>`;
133
- if (attributes.underline)
134
- output = `<u>${output}</u>`;
135
- if (attributes.code)
136
- output = `<code>${output}</code>`;
137
- if (attributes.sub)
138
- output = `<sub>${output}</sub>`;
139
- if (attributes.sup)
140
- output = `<sup>${output}</sup>`;
141
- const styles = [];
142
- const color = safeCssColor(attributes.color);
143
- const background = safeCssColor(attributes.background);
144
- const fontSize = safeFontSize(attributes.fontSize);
145
- if (color)
146
- styles.push(`color:${color}`);
147
- if (background)
148
- styles.push(`background-color:${background}`);
149
- if (fontSize)
150
- styles.push(`font-size:${fontSize}`);
151
- if (styles.length)
152
- output = `<span style="${styles.join(';')}">${output}</span>`;
153
- let url = sanitizeUrl(attributes.link);
154
- if (reference) {
155
- url = resolveDocumentUrl(options, {
156
- ...reference,
157
- title: options.resolveDocTitle?.(reference.pageId),
158
- }, block);
159
- }
160
- return url
161
- ? `<a href="${escapeHtmlAttribute(url)}">${output}</a>`
162
- : output;
163
- }
164
- export function deltaToHtml(value, options, block) {
165
- return deltaFrom(value)
166
- .map(delta => renderHtmlDelta(delta, options, block))
167
- .join('');
168
- }
@@ -22,9 +22,6 @@ export declare function validateSnapshotTree(blocks: readonly BlockSnapshot[], o
22
22
  export declare function deltaFrom(value: unknown): DeltaInsert[];
23
23
  export declare function plainText(value: unknown): string;
24
24
  export declare function pageTitleDelta(normalized: NormalizedSnapshot): DeltaInsert[];
25
- export declare function pageTitleText(normalized: NormalizedSnapshot): string;
26
- export declare function escapeHtml(value: unknown): string;
27
- export declare function escapeHtmlAttribute(value: unknown): string;
28
25
  export declare function sanitizeUrl(value: unknown, kind?: 'link' | 'image'): string;
29
26
  export declare function resolveAssetUrl(options: ConvertOptions, block: BlockSnapshot, kind: 'image' | 'attachment'): string;
30
27
  export declare function resolveZuiImageUrl(options: ConvertOptions, block: BlockSnapshot): string;
@@ -33,5 +30,3 @@ export declare function formatFileSize(value: unknown): string;
33
30
  export declare function isEdgelessOnly(block: BlockSnapshot): boolean;
34
31
  export declare function safeLanguage(value: unknown): string;
35
32
  export declare function escapeMarkdownMath(value: unknown): string;
36
- export declare function safeCssColor(value: unknown): string;
37
- export declare function safeFontSize(value: unknown): string;
@@ -160,23 +160,6 @@ export function pageTitleDelta(normalized) {
160
160
  const metaTitle = normalized.meta?.title;
161
161
  return typeof metaTitle === 'string' ? [{ insert: metaTitle }] : [];
162
162
  }
163
- export function pageTitleText(normalized) {
164
- const metaTitle = normalized.meta?.title;
165
- if (typeof metaTitle === 'string')
166
- return metaTitle;
167
- return plainText(toRecord(normalized.root?.props).title);
168
- }
169
- export function escapeHtml(value) {
170
- return String(value ?? '')
171
- .replace(/&/g, '&amp;')
172
- .replace(/</g, '&lt;')
173
- .replace(/>/g, '&gt;');
174
- }
175
- export function escapeHtmlAttribute(value) {
176
- return escapeHtml(value)
177
- .replace(/"/g, '&quot;')
178
- .replace(/'/g, '&#39;');
179
- }
180
163
  export function sanitizeUrl(value, kind = 'link') {
181
164
  if (typeof value !== 'string')
182
165
  return '';
@@ -286,20 +269,3 @@ export function escapeMarkdownMath(value) {
286
269
  return `${prefix}${target.replace(/:|&(?:#0*58|#x0*3a|colon);/i, '%3A')}`;
287
270
  });
288
271
  }
289
- export function safeCssColor(value) {
290
- if (typeof value !== 'string')
291
- return '';
292
- const color = value.trim();
293
- if (/^(#[\da-f]{3,8}|(?:rgb|hsl)a?\([\d\s.,%+-]+\)|var\(--[\w-]+\)|[a-z]+)$/i.test(color)) {
294
- return color;
295
- }
296
- return '';
297
- }
298
- export function safeFontSize(value) {
299
- if (typeof value !== 'string')
300
- return '';
301
- const size = value.trim();
302
- return /^(?:\d+(?:\.\d+)?(?:px|em|rem|%|pt)|var\(--[\w-]+\))$/i.test(size)
303
- ? size
304
- : '';
305
- }
@@ -37,7 +37,6 @@ export interface SnapshotEnvelope {
37
37
  snapshot: Snapshot | readonly BlockSnapshot[];
38
38
  }
39
39
  export type SnapshotInput = Snapshot | readonly BlockSnapshot[] | SnapshotEnvelope | string;
40
- export type OutputFormat = 'markdown' | 'html';
41
40
  export type UnknownBlockStrategy = 'children' | 'omit' | 'throw';
42
41
  export interface DocumentReference {
43
42
  pageId: string;
@@ -45,7 +44,7 @@ export interface DocumentReference {
45
44
  title?: string;
46
45
  }
47
46
  export interface RenderBlockContext {
48
- format: OutputFormat;
47
+ format: 'markdown';
49
48
  renderChildren(children?: readonly BlockSnapshot[]): string;
50
49
  renderInline(text: unknown): string;
51
50
  }
@@ -73,14 +72,3 @@ export interface ConvertOptions {
73
72
  unknownBlock?: UnknownBlockStrategy;
74
73
  }
75
74
  export type MarkdownOptions = ConvertOptions;
76
- export interface HtmlOptions extends ConvertOptions {
77
- /**
78
- * Defaults to true for a DocSnapshot/page root and false for block/slice input.
79
- */
80
- fullDocument?: boolean;
81
- /**
82
- * Render stored HTML from affine:embed-zui-html or zui-custom as trusted HTML.
83
- * Disabled by default.
84
- */
85
- allowUnsafeHtml?: boolean;
86
- }
package/dist/version.js CHANGED
@@ -1,5 +1,5 @@
1
- const fallbackBuild = "2026-08-19T02:49:40.949Z";
2
- const fallbackVersion = "0.6.4";
1
+ const fallbackBuild = "2026-08-29T09:25:28.910Z";
2
+ const fallbackVersion = "0.6.6";
3
3
  /**
4
4
  * 构建标识,由构建脚本通过 `__ZENTAO_API_BUILD__` 注入。
5
5
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zentao-api",
3
- "version": "0.6.4",
3
+ "version": "0.6.6",
4
4
  "type": "module",
5
5
  "description": "Browser and Node.js SDK for ZenTao API",
6
6
  "license": "MIT",
@@ -47,6 +47,7 @@
47
47
  "typecheck": "tsc --noEmit",
48
48
  "typecheck:tests": "tsc -p tsconfig.test.json --noEmit",
49
49
  "registry:check": "bun run scripts/update-registry.ts --check",
50
+ "registry:audit": "bun run .agents/skills/update-openapi-registry/scripts/audit-generated-registry.ts",
50
51
  "build": "rm -rf dist && tsc -p tsconfig.json && bun run scripts/build-browser.ts",
51
52
  "smoke:node": "node scripts/smoke-node.mjs",
52
53
  "smoke:browser": "bun run scripts/smoke-browser-bundler.ts",
@@ -57,16 +58,16 @@
57
58
  "docs:dev": "bun run docs:generate && vitepress dev docs",
58
59
  "docs:build": "bun run docs:generate && vitepress build docs",
59
60
  "docs:preview": "bun run docs:build && vitepress preview docs",
60
- "check": "bun run test:coverage && bun run typecheck && bun run typecheck:tests && bun run registry:check && bun run build && bun run smoke:node && bun run smoke:browser && bun run smoke:package",
61
+ "check": "bun run test:coverage && bun run typecheck && bun run typecheck:tests && bun run registry:check && bun run registry:audit && bun run build && bun run smoke:node && bun run smoke:browser && bun run smoke:package",
61
62
  "prepublishOnly": "bun run check"
62
63
  },
63
64
  "devDependencies": {
64
- "@types/bun": "^1.3.13",
65
- "@types/node": "^24.10.0",
66
- "typedoc": "^0.28.19",
67
- "typedoc-plugin-markdown": "^4.11.0",
68
- "typedoc-vitepress-theme": "^1.1.2",
69
- "typescript": "^5.7.0",
65
+ "@types/bun": "^1.4.0",
66
+ "@types/node": "^24.13.3",
67
+ "typedoc": "^0.28.20",
68
+ "typedoc-plugin-markdown": "^4.12.0",
69
+ "typedoc-vitepress-theme": "^1.1.3",
70
+ "typescript": "^5.9.3",
70
71
  "vitepress": "^1.6.4"
71
72
  },
72
73
  "engines": {
@@ -1,3 +0,0 @@
1
- import type { HtmlOptions, SnapshotInput } from './types.js';
2
- /** Convert a BlockSuite 0.19.x snapshot to semantic, escaped HTML. */
3
- export declare function snapshotToHtml(input: SnapshotInput, options?: HtmlOptions): string;