@xihe-lab/tapd-core 2.0.0-rc.1 → 2.0.0-rc.2

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.
Files changed (94) hide show
  1. package/README.md +22 -0
  2. package/dist/registry/richtext-manifests.generated.d.ts +1 -1
  3. package/dist/registry/richtext-manifests.generated.d.ts.map +1 -1
  4. package/dist/registry/richtext-manifests.generated.js +1 -1
  5. package/dist/registry/richtext-manifests.generated.js.map +1 -1
  6. package/dist/registry/richtext.d.ts +9 -0
  7. package/dist/registry/richtext.d.ts.map +1 -1
  8. package/dist/registry/richtext.js +33 -15
  9. package/dist/registry/richtext.js.map +1 -1
  10. package/dist/registry/richtext.test.js +70 -7
  11. package/dist/registry/richtext.test.js.map +1 -1
  12. package/dist/richtext/attachment-anchor.d.ts +58 -0
  13. package/dist/richtext/attachment-anchor.d.ts.map +1 -0
  14. package/dist/richtext/attachment-anchor.js +221 -0
  15. package/dist/richtext/attachment-anchor.js.map +1 -0
  16. package/dist/richtext/attachment-anchor.test.d.ts +2 -0
  17. package/dist/richtext/attachment-anchor.test.d.ts.map +1 -0
  18. package/dist/richtext/attachment-anchor.test.js +149 -0
  19. package/dist/richtext/attachment-anchor.test.js.map +1 -0
  20. package/dist/richtext/escape.d.ts +7 -0
  21. package/dist/richtext/escape.d.ts.map +1 -0
  22. package/dist/richtext/escape.js +60 -0
  23. package/dist/richtext/escape.js.map +1 -0
  24. package/dist/richtext/index.d.ts +15 -0
  25. package/dist/richtext/index.d.ts.map +1 -0
  26. package/dist/richtext/index.js +35 -0
  27. package/dist/richtext/index.js.map +1 -0
  28. package/dist/richtext/mention.d.ts +49 -0
  29. package/dist/richtext/mention.d.ts.map +1 -0
  30. package/dist/richtext/mention.js +179 -0
  31. package/dist/richtext/mention.js.map +1 -0
  32. package/dist/richtext/mention.test.d.ts +2 -0
  33. package/dist/richtext/mention.test.d.ts.map +1 -0
  34. package/dist/richtext/mention.test.js +123 -0
  35. package/dist/richtext/mention.test.js.map +1 -0
  36. package/dist/richtext/pipeline.d.ts +5 -0
  37. package/dist/richtext/pipeline.d.ts.map +1 -0
  38. package/dist/richtext/pipeline.js +199 -0
  39. package/dist/richtext/pipeline.js.map +1 -0
  40. package/dist/richtext/round-trip.test.d.ts +2 -0
  41. package/dist/richtext/round-trip.test.d.ts.map +1 -0
  42. package/dist/richtext/round-trip.test.js +105 -0
  43. package/dist/richtext/round-trip.test.js.map +1 -0
  44. package/dist/sdk-routes.d.ts.map +1 -1
  45. package/dist/sdk-routes.js +4 -0
  46. package/dist/sdk-routes.js.map +1 -1
  47. package/dist/tapd-client.d.ts +45 -0
  48. package/dist/tapd-client.d.ts.map +1 -1
  49. package/dist/tapd-client.js +74 -0
  50. package/dist/tapd-client.js.map +1 -1
  51. package/dist/tools/attachment.d.ts +47 -0
  52. package/dist/tools/attachment.d.ts.map +1 -1
  53. package/dist/tools/attachment.js +247 -1
  54. package/dist/tools/attachment.js.map +1 -1
  55. package/dist/tools/attachment.test.d.ts +2 -0
  56. package/dist/tools/attachment.test.d.ts.map +1 -0
  57. package/dist/tools/attachment.test.js +432 -0
  58. package/dist/tools/attachment.test.js.map +1 -0
  59. package/dist/tools/bug.d.ts.map +1 -1
  60. package/dist/tools/bug.js +3 -0
  61. package/dist/tools/bug.js.map +1 -1
  62. package/dist/tools/comment.d.ts.map +1 -1
  63. package/dist/tools/comment.js +3 -0
  64. package/dist/tools/comment.js.map +1 -1
  65. package/dist/tools/index.d.ts.map +1 -1
  66. package/dist/tools/index.js +2 -0
  67. package/dist/tools/index.js.map +1 -1
  68. package/dist/tools/media.d.ts +9 -0
  69. package/dist/tools/media.d.ts.map +1 -0
  70. package/dist/tools/media.js +45 -0
  71. package/dist/tools/media.js.map +1 -0
  72. package/dist/tools/richtext-wiring.test.d.ts +2 -0
  73. package/dist/tools/richtext-wiring.test.d.ts.map +1 -0
  74. package/dist/tools/richtext-wiring.test.js +302 -0
  75. package/dist/tools/richtext-wiring.test.js.map +1 -0
  76. package/dist/tools/story.d.ts.map +1 -1
  77. package/dist/tools/story.js +3 -0
  78. package/dist/tools/story.js.map +1 -1
  79. package/dist/tools/task.d.ts.map +1 -1
  80. package/dist/tools/task.js +3 -0
  81. package/dist/tools/task.js.map +1 -1
  82. package/dist/tools/utility.d.ts.map +1 -1
  83. package/dist/tools/utility.js +133 -12
  84. package/dist/tools/utility.js.map +1 -1
  85. package/dist/tools/wiki.d.ts.map +1 -1
  86. package/dist/tools/wiki.js +20 -5
  87. package/dist/tools/wiki.js.map +1 -1
  88. package/dist/types.d.ts +5 -0
  89. package/dist/types.d.ts.map +1 -1
  90. package/package.json +8 -5
  91. package/dist/tools/zz-acceptance-echo.d.ts +0 -3
  92. package/dist/tools/zz-acceptance-echo.d.ts.map +0 -1
  93. package/dist/tools/zz-acceptance-echo.js +0 -9
  94. package/dist/tools/zz-acceptance-echo.js.map +0 -1
@@ -0,0 +1,123 @@
1
+ // mention 语义单测:生成/解析/扫描/边界误伤(FSD §8)/双向镜像。tsx 直跑,不进 package.json test 脚本(接线归集成者)。
2
+ import assert from 'node:assert/strict';
3
+ import { atWhoNodeToMd, escapeAtLiteralsForMd, findMentions, isAtWhoNode, isAtWhoTag, mentionHtmlToMd, mentionMdToHtml, renderMentionHtml, } from './mention.js';
4
+ const AT_WHO_XUZHAO = '<b class="at-who" contenteditable="false" data-userid="徐昭" data-type="user">@徐昭</b>';
5
+ const checks = [
6
+ ['renderMentionHtml: 逐字段复刻 at-who 模板(e2e 金样)', () => {
7
+ assert.equal(renderMentionHtml('徐昭'), AT_WHO_XUZHAO);
8
+ }],
9
+ ['renderMentionHtml: 昵称含 HTML 特殊字符分别转义 attr/文本位', () => {
10
+ const html = renderMentionHtml('a"b<c>');
11
+ assert.ok(html.includes('data-userid="a&quot;b&lt;c&gt;"'));
12
+ assert.ok(html.endsWith('>@a"b&lt;c&gt;</b>'));
13
+ assert.ok(html.startsWith('<b class="at-who" contenteditable="false" '));
14
+ }],
15
+ ['renderMentionHtml: 空昵称抛 TypeError', () => {
16
+ assert.throws(() => renderMentionHtml(''), TypeError);
17
+ }],
18
+ ['findMentions: 基本命中与区间索引', () => {
19
+ assert.deepEqual(findMentions('请 @徐昭 尽快确认'), [{ name: '徐昭', start: 2, end: 5 }]);
20
+ assert.deepEqual(findMentions('no mention here'), []);
21
+ }],
22
+ ['findMentions: 右边界贪吃 + 多命中', () => {
23
+ assert.deepEqual(findMentions('@bob_dev.'), [{ name: 'bob_dev', start: 0, end: 8 }]);
24
+ assert.deepEqual(findMentions('@徐昭,你好'), [{ name: '徐昭', start: 0, end: 3 }]);
25
+ assert.deepEqual(findMentions('找 @张三 和 @Alice_1 一起'), [
26
+ { name: '张三', start: 2, end: 5 },
27
+ { name: 'Alice_1', start: 8, end: 16 },
28
+ ]);
29
+ }],
30
+ ['findMentions: FSD §8 误伤对策——邮箱/工单号/紧贴中文不命中', () => {
31
+ assert.deepEqual(findMentions('联系 user@example.com 获取'), []);
32
+ assert.deepEqual(findMentions('工单号 T-100@QA 由 support@test 处理'), []);
33
+ assert.deepEqual(findMentions('联系@徐昭 谢谢'), []); // @ 前紧贴中文(保守侧不标记;R4 服务端仍识别)
34
+ assert.deepEqual(findMentions('版本 v1.2@release'), []);
35
+ }],
36
+ ['findMentions: @@ 转义与孤立 @ 不命中', () => {
37
+ assert.deepEqual(findMentions('@@徐昭'), []);
38
+ assert.deepEqual(findMentions('行尾单独 @ 保持'), []);
39
+ assert.deepEqual(findMentions('@'), []);
40
+ assert.deepEqual(findMentions('@ 上路'), []);
41
+ }],
42
+ ['findMentions: 数字昵称与起始边界', () => {
43
+ assert.deepEqual(findMentions('@123 上线'), [{ name: '123', start: 0, end: 4 }]);
44
+ assert.deepEqual(findMentions('( @张三 )'), [{ name: '张三', start: 2, end: 5 }]);
45
+ }],
46
+ ['mentionMdToHtml: 昵称 → at-who,其余原样', () => {
47
+ assert.equal(mentionMdToHtml('请 @徐昭 今天内确认'), `请 ${AT_WHO_XUZHAO} 今天内确认`);
48
+ }],
49
+ ['mentionMdToHtml: @@ → 字面 @ 不触发 mention;@@@ 组合行为', () => {
50
+ assert.equal(mentionMdToHtml('@@徐昭'), '@徐昭');
51
+ assert.equal(mentionMdToHtml('@@@徐昭'), `@${AT_WHO_XUZHAO}`);
52
+ assert.equal(mentionMdToHtml('@@@@'), '@@');
53
+ }],
54
+ ['mentionMdToHtml: 疑似 html 标签区域 @ 语义跳过(透传)', () => {
55
+ assert.equal(mentionMdToHtml('a <b>x@y</b> @徐昭'), `a <b>x@y</b> ${AT_WHO_XUZHAO}`);
56
+ }],
57
+ ['mentionHtmlToMd: at-who → @昵称,禁止降级 **粗体**(R7 回归)', () => {
58
+ const md = mentionHtmlToMd(`<p>${AT_WHO_XUZHAO} 请查收</p>`);
59
+ assert.equal(md, '<p>@徐昭 请查收</p>');
60
+ assert.ok(!md.includes('**'));
61
+ }],
62
+ ['mentionHtmlToMd: 非 at-who 的 <b> 原样(不吞不转)', () => {
63
+ assert.equal(mentionHtmlToMd('<b>user@x</b>'), '<b>user@x</b>');
64
+ }],
65
+ ['mentionHtmlToMd: 文本字面 @ 按 mention 规则保护为 @@;不命中者原样', () => {
66
+ assert.equal(mentionHtmlToMd('<p>@x user@y.com @@z</p>'), '<p>@@x user@y.com @@@@z</p>');
67
+ }],
68
+ ['escapeAtLiteralsForMd: 与 md 方向规则严格镜像', () => {
69
+ assert.equal(escapeAtLiteralsForMd('@徐昭'), '@@徐昭');
70
+ assert.equal(escapeAtLiteralsForMd('user@example.com'), 'user@example.com');
71
+ assert.equal(escapeAtLiteralsForMd('@@'), '@@@@');
72
+ }],
73
+ ['isAtWhoTag / 节点辅助: class 识别与 data-userid 优先', () => {
74
+ assert.equal(isAtWhoTag(AT_WHO_XUZHAO), true);
75
+ assert.equal(isAtWhoTag('<b class="tapd-editor__bold">@徐昭</b>'), false);
76
+ const node = {
77
+ nodeName: 'B',
78
+ getAttribute: (name) => (name === 'class' ? 'at-who' : name === 'data-userid' ? '徐昭' : null),
79
+ };
80
+ assert.equal(isAtWhoNode(node), true);
81
+ assert.equal(atWhoNodeToMd(node), '@徐昭');
82
+ }],
83
+ ['节点辅助: data-userid 缺失回退文本', () => {
84
+ const node = {
85
+ nodeName: 'b',
86
+ getAttribute: (name) => (name === 'class' ? 'at-who' : null),
87
+ textContent: '@李四',
88
+ };
89
+ assert.equal(isAtWhoNode(node), true);
90
+ assert.equal(atWhoNodeToMd(node), '@李四');
91
+ }],
92
+ ['mention 双向镜像: md→html→md 恒等(多边界样本)', () => {
93
+ const samples = [
94
+ '请 @徐昭 今天内确认,抄送 @bob_dev。',
95
+ '@张三',
96
+ 'user@example.com 与 T-100@QA 与 @@李四 与 @',
97
+ '(@王五)结束。',
98
+ ];
99
+ for (const md of samples) {
100
+ assert.equal(mentionHtmlToMd(mentionMdToHtml(md)), md);
101
+ }
102
+ }],
103
+ ];
104
+ let failed = 0;
105
+ for (const [name, check] of checks) {
106
+ try {
107
+ check();
108
+ console.log(`PASS ${name}`);
109
+ }
110
+ catch (error) {
111
+ failed += 1;
112
+ console.error(`FAIL ${name}`);
113
+ console.error(` ${error instanceof Error ? error.message : String(error)}`);
114
+ }
115
+ }
116
+ if (failed > 0) {
117
+ console.error(`\n${failed}/${checks.length} failed`);
118
+ process.exitCode = 1;
119
+ }
120
+ else {
121
+ console.log(`\n${checks.length}/${checks.length} passed`);
122
+ }
123
+ //# sourceMappingURL=mention.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mention.test.js","sourceRoot":"","sources":["../../src/richtext/mention.test.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EACL,aAAa,EACb,qBAAqB,EACrB,YAAY,EACZ,WAAW,EACX,UAAU,EACV,eAAe,EACf,eAAe,EACf,iBAAiB,GAClB,MAAM,cAAc,CAAC;AAEtB,MAAM,aAAa,GAAG,qFAAqF,CAAC;AAE5G,MAAM,MAAM,GAA2B;IACrC,CAAC,4CAA4C,EAAE,GAAG,EAAE;YAClD,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,CAAC;QACvD,CAAC,CAAC;IAEF,CAAC,+CAA+C,EAAE,GAAG,EAAE;YACrD,MAAM,IAAI,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;YACzC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,iCAAiC,CAAC,CAAC,CAAC;YAC5D,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC,CAAC;YAC/C,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,4CAA4C,CAAC,CAAC,CAAC;QAC3E,CAAC,CAAC;IAEF,CAAC,mCAAmC,EAAE,GAAG,EAAE;YACzC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC;QACxD,CAAC,CAAC;IAEF,CAAC,yBAAyB,EAAE,GAAG,EAAE;YAC/B,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,YAAY,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YACjF,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,iBAAiB,CAAC,EAAE,EAAE,CAAC,CAAC;QACxD,CAAC,CAAC;IAEF,CAAC,2BAA2B,EAAE,GAAG,EAAE;YACjC,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YACrF,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAC7E,MAAM,CAAC,SAAS,CACd,YAAY,CAAC,qBAAqB,CAAC,EACnC;gBACE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE;gBAChC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;aACvC,CACF,CAAC;QACJ,CAAC,CAAC;IAEF,CAAC,2CAA2C,EAAE,GAAG,EAAE;YACjD,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,wBAAwB,CAAC,EAAE,EAAE,CAAC,CAAC;YAC7D,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,gCAAgC,CAAC,EAAE,EAAE,CAAC,CAAC;YACrE,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,4BAA4B;YAC5E,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,iBAAiB,CAAC,EAAE,EAAE,CAAC,CAAC;QACxD,CAAC,CAAC;IAEF,CAAC,8BAA8B,EAAE,GAAG,EAAE;YACpC,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;YAC3C,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC;YAChD,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;YACxC,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;QAC7C,CAAC,CAAC;IAEF,CAAC,yBAAyB,EAAE,GAAG,EAAE;YAC/B,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAC/E,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAChF,CAAC,CAAC;IAEF,CAAC,mCAAmC,EAAE,GAAG,EAAE;YACzC,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,aAAa,CAAC,EAAE,KAAK,aAAa,QAAQ,CAAC,CAAC;QAC3E,CAAC,CAAC;IAEF,CAAC,iDAAiD,EAAE,GAAG,EAAE;YACvD,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC;YAC7C,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,OAAO,CAAC,EAAE,IAAI,aAAa,EAAE,CAAC,CAAC;YAC5D,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC;QAC9C,CAAC,CAAC;IAEF,CAAC,0CAA0C,EAAE,GAAG,EAAE;YAChD,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,kBAAkB,CAAC,EAAE,gBAAgB,aAAa,EAAE,CAAC,CAAC;QACrF,CAAC,CAAC;IAEF,CAAC,kDAAkD,EAAE,GAAG,EAAE;YACxD,MAAM,EAAE,GAAG,eAAe,CAAC,MAAM,aAAa,UAAU,CAAC,CAAC;YAC1D,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC;YACnC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;QAChC,CAAC,CAAC;IAEF,CAAC,0CAA0C,EAAE,GAAG,EAAE;YAChD,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC,CAAC;QAClE,CAAC,CAAC;IAEF,CAAC,mDAAmD,EAAE,GAAG,EAAE;YACzD,MAAM,CAAC,KAAK,CACV,eAAe,CAAC,0BAA0B,CAAC,EAC3C,6BAA6B,CAC9B,CAAC;QACJ,CAAC,CAAC;IAEF,CAAC,sCAAsC,EAAE,GAAG,EAAE;YAC5C,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC;YACnD,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC,CAAC;YAC5E,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;QACpD,CAAC,CAAC;IAEF,CAAC,6CAA6C,EAAE,GAAG,EAAE;YACnD,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,IAAI,CAAC,CAAC;YAC9C,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,sCAAsC,CAAC,EAAE,KAAK,CAAC,CAAC;YACxE,MAAM,IAAI,GAAG;gBACX,QAAQ,EAAE,GAAG;gBACb,YAAY,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,KAAK,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;aACrG,CAAC;YACF,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;YACtC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC;QAC3C,CAAC,CAAC;IAEF,CAAC,0BAA0B,EAAE,GAAG,EAAE;YAChC,MAAM,IAAI,GAAG;gBACX,QAAQ,EAAE,GAAG;gBACb,YAAY,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;gBACpE,WAAW,EAAE,KAAK;aACnB,CAAC;YACF,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;YACtC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC;QAC3C,CAAC,CAAC;IAEF,CAAC,oCAAoC,EAAE,GAAG,EAAE;YAC1C,MAAM,OAAO,GAAG;gBACd,0BAA0B;gBAC1B,KAAK;gBACL,wCAAwC;gBACxC,UAAU;aACX,CAAC;YACF,KAAK,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;gBACzB,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACzD,CAAC;QACH,CAAC,CAAC;CACH,CAAC;AAEF,IAAI,MAAM,GAAG,CAAC,CAAC;AACf,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;IACnC,IAAI,CAAC;QACH,KAAK,EAAE,CAAC;QACR,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;IAC9B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,CAAC,CAAC;QACZ,OAAO,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;QAC9B,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC/E,CAAC;AACH,CAAC;AAED,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;IACf,OAAO,CAAC,KAAK,CAAC,KAAK,MAAM,IAAI,MAAM,CAAC,MAAM,SAAS,CAAC,CAAC;IACrD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC;KAAM,CAAC;IACN,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,SAAS,CAAC,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,5 @@
1
+ /** md → TAPD 富文本 html(@昵称/@@转义/attach: 附件引用/图片与普通 md 全兼容)。 */
2
+ export declare function mdToHtml(md: string): string;
3
+ /** TAPD 富文本 html → md(at-who → @昵称、附件锚点 → attach: 引用、/tfl/ 图片走 GFM 内建规则)。 */
4
+ export declare function htmlToMd(html: string): string;
5
+ //# sourceMappingURL=pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/richtext/pipeline.ts"],"names":[],"mappings":"AAoLA,8DAA8D;AAC9D,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE3C;AA+BD,6EAA6E;AAC7E,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7C"}
@@ -0,0 +1,199 @@
1
+ // TAPD 2.0 rc.2 P3 接线(需求 1139814312001001548)—— 富文本保真转换主管道。
2
+ //
3
+ // B 侧语义库(core/richtext,纯函数)在转换器「token / 规则级」接入(B 的接线警告):
4
+ // - md→html(markdown-it core 规则,token 级):
5
+ // * `attach:` 链接(link_open..link_close 纯文本运行)→ 整段替换为 R6 data 五件套锚点
6
+ // (html_inline token;html:false 只影响解析不影响程序化 token 的渲染);
7
+ // * `@昵称` / `@@` 语义只在 inline children 的 **text token** 内替换(mention/escape 各自成 token);
8
+ // * code_inline / fence / html_* / image(含 alt)/ 链接内部(含 URL)一律跳过;
9
+ // 裸 URL(https://.../@name)内的 @ 由「连续非空白段含 ://」护栏跳过。
10
+ // * 禁止对整段输入直接调 fragment 级函数上线(会误伤代码段与 URL)——本模块即正确接线层。
11
+ // - html→md(turndown 规则级):
12
+ // * at-who <b> → `@昵称`(永不降级 **粗体**,R7);附件锚点 <a> → `[📎 name](attach:ws/id)`;
13
+ // 识别失败(href 缺失等)保留原节点 HTML,绝不猜测改写。
14
+ // * 保真规则先于 GFM 注册(FSD §8):turndown addRule 为 unshift 语义(后注册者先匹配),
15
+ // 故在 use(gfm) 之后再 addRule 即获得最高匹配优先级,并有规则优先级测试固化。
16
+ // * 文本节点字面 @ 保护走 turndown 实例 escape 钩子(escapeAtLiteralsForMd;
17
+ // CODE 内文本 isCode 天然绕过 escape,代码段 @ 不被加倍)。
18
+ //
19
+ // 单一实例共享给 md_to_html/html_to_md 工具(tools/utility.ts)与写管道
20
+ // (registry/richtext.ts 的 transformWriteArgs/transformReadData)——FSD §3.7:
21
+ // 所有富文本写工具与 pm 层场景共用同一条管道,免费获得 @/附件/图片能力。
22
+ import MarkdownIt from 'markdown-it';
23
+ import TurndownService from 'turndown';
24
+ import { gfm } from 'turndown-plugin-gfm';
25
+ import { attachmentAnchorHtml, attachmentAnchorNodeToMd, atWhoNodeToMd, escapeAtLiteralsForMd, findMentions, isAttachmentAnchorNode, isAtWhoNode, renderMentionHtml, } from './index.js';
26
+ // ---------------------------------------------------------------------------
27
+ // md → html(markdown-it,token 级)
28
+ // ---------------------------------------------------------------------------
29
+ /** `attach:<ws>/<id>`(与 richtext md 扩展语法的 ws/id 文法一致:排除空白、()、/)。 */
30
+ const ATTACH_HREF_RE = /^attach:([^()\s/]+)\/([^()\s/]+)$/i;
31
+ /**
32
+ * 裸 URL 防误伤:mention 候选往前回溯同一连续非空白段,段内含 `://` 视为 URL 的一部分
33
+ * (如 `https://e.com/@zhang`)。md 链接语法 `[t](url)` 的 URL 在 token attrs 里天然不进本层。
34
+ */
35
+ function insideBareUrl(content, atIndex) {
36
+ let start = atIndex;
37
+ while (start > 0 && !/\s/.test(content[start - 1]))
38
+ start -= 1;
39
+ return content.slice(start, atIndex).includes('://');
40
+ }
41
+ /**
42
+ * 把一个 text token 的内容按 @ 语义拆分为 [text | html_inline] 串
43
+ * (镜像 richtext scanAtTokens 规则:@@ 优先、findMentions 已内建左边界判定)。
44
+ * 无任何语义变化时返回 null(调用方保留原 token,避免无谓重排)。
45
+ */
46
+ function splitTextTokenAtSemantics(TokenCtor, token) {
47
+ const content = token.content;
48
+ const mentions = findMentions(content);
49
+ if (mentions.length === 0 && !content.includes('@@'))
50
+ return null;
51
+ const mentionByStart = new Map(mentions.map(m => [m.start, m]));
52
+ const out = [];
53
+ let plain = '';
54
+ const flush = () => {
55
+ if (plain === '')
56
+ return;
57
+ const text = new TokenCtor('text', '', 0);
58
+ text.content = plain;
59
+ out.push(text);
60
+ plain = '';
61
+ };
62
+ let i = 0;
63
+ let changed = false;
64
+ while (i < content.length) {
65
+ if (content[i] === '@') {
66
+ if (content[i + 1] === '@') {
67
+ plain += '@'; // md 转义 @@ → 字面 @
68
+ changed = true;
69
+ i += 2;
70
+ continue;
71
+ }
72
+ const mention = mentionByStart.get(i);
73
+ if (mention && !insideBareUrl(content, i)) {
74
+ flush();
75
+ const html = new TokenCtor('html_inline', '', 0);
76
+ html.content = renderMentionHtml(mention.name);
77
+ out.push(html);
78
+ changed = true;
79
+ i = mention.end;
80
+ continue;
81
+ }
82
+ }
83
+ plain += content[i];
84
+ i += 1;
85
+ }
86
+ flush();
87
+ if (!changed || (out.length === 1 && out[0].type === 'text' && out[0].content === content))
88
+ return null;
89
+ return out;
90
+ }
91
+ /** inline children 内的 `attach:` 链接 → R6 锚点(整段替换为单个 html_inline token)。 */
92
+ function convertAttachmentLinks(TokenCtor, tokens) {
93
+ for (let i = 0; i < tokens.length; i++) {
94
+ const open = tokens[i];
95
+ if (open.type !== 'link_open')
96
+ continue;
97
+ const href = String(open.attrGet('href') ?? '');
98
+ const refMatch = ATTACH_HREF_RE.exec(href.trim());
99
+ if (!refMatch)
100
+ continue;
101
+ // link_open..link_close 之间必须是纯文本运行(含嵌套/换行则保守跳过,保留原链接)
102
+ let text = '';
103
+ let closeIdx = -1;
104
+ for (let j = i + 1; j < tokens.length; j++) {
105
+ const t = tokens[j];
106
+ if (t.type === 'link_close') {
107
+ closeIdx = j;
108
+ break;
109
+ }
110
+ if (t.type !== 'text')
111
+ break;
112
+ text += t.content;
113
+ }
114
+ if (closeIdx < 0)
115
+ continue;
116
+ const name = text.replace(/^\s*📎\s*/, '');
117
+ if (name === '')
118
+ continue;
119
+ let anchor;
120
+ try {
121
+ anchor = attachmentAnchorHtml({ workspaceId: refMatch[1], id: refMatch[2], name });
122
+ }
123
+ catch {
124
+ continue; // ws/id 含空白或非法字符 → 原样保留(绝不猜测改写)
125
+ }
126
+ const htmlToken = new TokenCtor('html_inline', '', 0);
127
+ htmlToken.content = anchor;
128
+ tokens.splice(i, closeIdx - i + 1, htmlToken);
129
+ }
130
+ }
131
+ /** inline children 内 text token 的 @ 语义替换(链接内部跳过;code/html/image token 天然跳过)。 */
132
+ function convertMentions(TokenCtor, tokens) {
133
+ let linkDepth = 0;
134
+ for (let i = 0; i < tokens.length; i++) {
135
+ const token = tokens[i];
136
+ if (token.type === 'link_open') {
137
+ linkDepth += 1;
138
+ continue;
139
+ }
140
+ if (token.type === 'link_close') {
141
+ linkDepth = Math.max(0, linkDepth - 1);
142
+ continue;
143
+ }
144
+ if (linkDepth > 0)
145
+ continue; // 链接文本内 @ 不转换(URL 已天然不在 text token)
146
+ if (token.type !== 'text')
147
+ continue;
148
+ const replacement = splitTextTokenAtSemantics(TokenCtor, token);
149
+ if (replacement) {
150
+ tokens.splice(i, 1, ...replacement);
151
+ i += replacement.length - 1;
152
+ }
153
+ }
154
+ }
155
+ function tapdRichtextCoreRule(state) {
156
+ const TokenCtor = state.Token;
157
+ for (const token of state.tokens) {
158
+ // fence/code_block/html_block 等 block token 内容不经过 inline 层,天然跳过
159
+ if (token.type !== 'inline' || !token.children)
160
+ continue;
161
+ convertAttachmentLinks(TokenCtor, token.children);
162
+ convertMentions(TokenCtor, token.children);
163
+ }
164
+ }
165
+ const markdownIt = new MarkdownIt({ html: false, breaks: true });
166
+ // push 到 core 链尾(text_join 之后):不再有规则会合并/重排我们的 token 切分
167
+ markdownIt.core.ruler.push('tapd_richtext_semantics', tapdRichtextCoreRule);
168
+ /** md → TAPD 富文本 html(@昵称/@@转义/attach: 附件引用/图片与普通 md 全兼容)。 */
169
+ export function mdToHtml(md) {
170
+ return markdownIt.render(md);
171
+ }
172
+ // ---------------------------------------------------------------------------
173
+ // html → md(turndown,规则级)
174
+ // ---------------------------------------------------------------------------
175
+ const turndown = new TurndownService({ headingStyle: 'atx', codeBlockStyle: 'fenced' });
176
+ turndown.use(gfm);
177
+ // 保真规则先于 GFM/内建规则匹配:addRule 是 unshift 语义(后注册者先匹配),
178
+ // 在 use(gfm) 之后注册即拿到最高优先级(FSD §8,规则优先级由测试固化)。
179
+ turndown.addRule('tapdAtWho', {
180
+ filter: node => isAtWhoNode(node),
181
+ replacement: (_content, node) => atWhoNodeToMd(node),
182
+ });
183
+ turndown.addRule('tapdAttachmentAnchor', {
184
+ filter: node => isAttachmentAnchorNode(node),
185
+ replacement: (_content, node) => {
186
+ const md = attachmentAnchorNodeToMd(node);
187
+ // 识别条件不满足(缺 href 等)→ 保留原节点 HTML,绝不猜测改写
188
+ return md ?? node.outerHTML ?? '';
189
+ },
190
+ });
191
+ // 文本节点字面 @ 保护(round-trip 稳定:命中 mention 规则的 @名字 加倍为 @@名字)。
192
+ // CODE 内文本 isCode 天然绕过 escape 钩子(turndown process 分支),代码段 @ 不被加倍。
193
+ const baseEscape = TurndownService.prototype.escape.bind(TurndownService.prototype);
194
+ turndown.escape = (text) => baseEscape(escapeAtLiteralsForMd(text));
195
+ /** TAPD 富文本 html → md(at-who → @昵称、附件锚点 → attach: 引用、/tfl/ 图片走 GFM 内建规则)。 */
196
+ export function htmlToMd(html) {
197
+ return turndown.turndown(html);
198
+ }
199
+ //# sourceMappingURL=pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/richtext/pipeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=round-trip.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"round-trip.test.d.ts","sourceRoot":"","sources":["../../src/richtext/round-trip.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,105 @@
1
+ // round-trip 契约测试:六类样本(@/附件/链接/图片/混合/纯文本)md→html→md 与原文等价(空白规约内)。
2
+ // 只覆盖纯函数语义层(本目录 fragment 函数),不经过工具/注册表转换主链路(接线归 Wave 2)。
3
+ import assert from 'node:assert/strict';
4
+ import { htmlToMdFragment, mdToHtmlFragment } from './index.js';
5
+ const WS = '39814312';
6
+ const AT = (name) => `<b class="at-who" contenteditable="false" data-userid="${name}" data-type="user">@${name}</b>`;
7
+ const ANCHOR = (id, name, fileType) => `<a data-is-tapd-attachment="true" data-can-preview="true" data-file-type="${fileType}" ` +
8
+ `data-name="${name}" target="_blank" rel="noopener" ` +
9
+ `href="/${WS}/attachments/preview_attachments/${id}/story_description_attachment">${name}</a>`;
10
+ // 空白规约:允许转换层的空白差异(本层本不应产生,防御性归一)
11
+ const normalize = (value) => value.replace(/\s+/g, ' ').trim();
12
+ const SAMPLES = [
13
+ {
14
+ name: '样本1 @(中英文昵称、CJK 标点边界)',
15
+ md: '请 @徐昭 今天内确认,备份联系 @bob_dev。',
16
+ html: `请 ${AT('徐昭')} 今天内确认,备份联系 ${AT('bob_dev')}。`,
17
+ },
18
+ {
19
+ name: '样本2 附件(pdf/text/other 映射)',
20
+ md: `评审材料 [📎 接口设计.pdf](attach:${WS}/1139814312001000001),日志 [📎 app.log](attach:${WS}/1139814312001000002)`,
21
+ html: `评审材料 ${ANCHOR('1139814312001000001', '接口设计.pdf', 'pdf')},日志 ${ANCHOR('1139814312001000002', 'app.log', 'other')}`,
22
+ },
23
+ {
24
+ name: '样本3 链接(语义层零干预透传,基础转换器职责)',
25
+ md: `需求详情见 [TAPD 1139814312001001548](https://www.tapd.cn/${WS}/prong/stories/view/1139814312001001548)`,
26
+ html: `需求详情见 [TAPD 1139814312001001548](https://www.tapd.cn/${WS}/prong/stories/view/1139814312001001548)`,
27
+ },
28
+ {
29
+ name: '样本4 图片(/tfl/ 远端路径,语义层零干预透传)',
30
+ md: '效果图 ![登录页](/tfl/captures/2026/09/login.png)',
31
+ html: '效果图 ![登录页](/tfl/captures/2026/09/login.png)',
32
+ },
33
+ {
34
+ name: '样本5 混合(@ + @@转义 + 附件@名 + 图片 + 链接 + 粗体)',
35
+ md: [
36
+ '# rc.2 评审',
37
+ '',
38
+ '负责人 @徐昭,抄送 @@everyone(字面量)。',
39
+ `材料 [📎 报表@v2.txt](attach:${WS}/1139814312001000003),截图 ![首页](/tfl/a.png)。`,
40
+ `线索 [需求](https://www.tapd.cn/${WS}/prong/stories/view/1139814312001001548) 与 **加粗**。`,
41
+ ].join('\n'),
42
+ html: [
43
+ '# rc.2 评审',
44
+ '',
45
+ `负责人 ${AT('徐昭')},抄送 @everyone(字面量)。`,
46
+ `材料 ${ANCHOR('1139814312001000003', '报表@v2.txt', 'text')},截图 ![首页](/tfl/a.png)。`,
47
+ `线索 [需求](https://www.tapd.cn/${WS}/prong/stories/view/1139814312001001548) 与 **加粗**。`,
48
+ ].join('\n'),
49
+ },
50
+ {
51
+ name: '样本6 纯文本(邮箱/工单号/孤立 @ 不误伤,@@转义不触发 mention,FSD §8)',
52
+ md: '版本 v1.2 已发布。联系邮箱 user@example.com;工单号 T-100@QA 由 support@test 处理;单独的 @ 保持原样,转义 @@support 也不触发 mention。',
53
+ html: '版本 v1.2 已发布。联系邮箱 user@example.com;工单号 T-100@QA 由 support@test 处理;单独的 @ 保持原样,转义 @support 也不触发 mention。',
54
+ },
55
+ ];
56
+ const checks = [
57
+ ...SAMPLES.map((sample) => [
58
+ `md→html: ${sample.name}`,
59
+ () => {
60
+ assert.equal(normalize(mdToHtmlFragment(sample.md)), normalize(sample.html));
61
+ },
62
+ ]),
63
+ ...SAMPLES.map((sample) => [
64
+ `html→md: ${sample.name}`,
65
+ () => {
66
+ assert.equal(normalize(htmlToMdFragment(sample.html)), normalize(sample.md));
67
+ },
68
+ ]),
69
+ ...SAMPLES.map((sample) => [
70
+ `round-trip: ${sample.name}`,
71
+ () => {
72
+ assert.equal(normalize(htmlToMdFragment(mdToHtmlFragment(sample.md))), normalize(sample.md));
73
+ // 二次往返幂等(转换稳定点)
74
+ const once = htmlToMdFragment(mdToHtmlFragment(sample.md));
75
+ assert.equal(normalize(htmlToMdFragment(mdToHtmlFragment(once))), normalize(once));
76
+ },
77
+ ]),
78
+ ['R7 回归: @昵称 产出 at-who 标记而非降级形态', () => {
79
+ const html = mdToHtmlFragment('呼叫 @徐昭');
80
+ assert.ok(html.includes('<b class="at-who" contenteditable="false" data-userid="徐昭" data-type="user">@徐昭</b>'));
81
+ const md = htmlToMdFragment('<p><b class="at-who" contenteditable="false" data-userid="徐昭" data-type="user">@徐昭</b></p>');
82
+ assert.equal(md, '<p>@徐昭</p>');
83
+ assert.ok(!md.includes('**'));
84
+ }],
85
+ ];
86
+ let failed = 0;
87
+ for (const [name, check] of checks) {
88
+ try {
89
+ check();
90
+ console.log(`PASS ${name}`);
91
+ }
92
+ catch (error) {
93
+ failed += 1;
94
+ console.error(`FAIL ${name}`);
95
+ console.error(` ${error instanceof Error ? error.message : String(error)}`);
96
+ }
97
+ }
98
+ if (failed > 0) {
99
+ console.error(`\n${failed}/${checks.length} failed`);
100
+ process.exitCode = 1;
101
+ }
102
+ else {
103
+ console.log(`\n${checks.length}/${checks.length} passed`);
104
+ }
105
+ //# sourceMappingURL=round-trip.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"round-trip.test.js","sourceRoot":"","sources":["../../src/richtext/round-trip.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"sdk-routes.d.ts","sourceRoot":"","sources":["../src/sdk-routes.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,GAAG,KAAK,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC;AAErD;;;;;GAKG;AACH,eAAO,MAAM,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAgLxD,CAAC"}
1
+ {"version":3,"file":"sdk-routes.d.ts","sourceRoot":"","sources":["../src/sdk-routes.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,GAAG,KAAK,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC;AAErD;;;;;GAKG;AACH,eAAO,MAAM,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAqLxD,CAAC"}
@@ -8,6 +8,10 @@ export const SDK_ROUTES = {
8
8
  // attachments
9
9
  'GET /attachments': 'getAttachments',
10
10
  'GET /attachments/down': 'downloadAttachment',
11
+ 'POST /open_app_obj_attachment': 'addOpenAppObjAttachment',
12
+ // files (upload channel — multipart via TapdClient.postFile)
13
+ 'POST /files/upload_attachment': 'uploadAttachment',
14
+ 'POST /files/upload_image': 'uploadImage',
11
15
  // board-cards
12
16
  'GET /board_cards': 'getBoardCards',
13
17
  // board-columns
@@ -1 +1 @@
1
- {"version":3,"file":"sdk-routes.js","sourceRoot":"","sources":["../src/sdk-routes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sdk-routes.js","sourceRoot":"","sources":["../src/sdk-routes.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,25 @@
1
+ /**
2
+ * TAPD API Client
3
+ *
4
+ * Supports dual authentication modes:
5
+ * - Basic Auth: using api_user + api_password
6
+ * - OAuth/Bearer Token: using access_token
7
+ *
8
+ * Routes requests through @opentapd/tapd-node-sdk when the method+path is
9
+ * listed in SDK_ROUTES, otherwise falls back to the custom fetch client.
10
+ * - GET SDK failures fall back to fetch (read-only, idempotent)
11
+ * - POST SDK failures throw directly (never fallback, avoid duplicate writes)
12
+ * - Set TAPD_SDK_DISABLED=1 to bypass the SDK entirely (rollback switch)
13
+ */
14
+ import { ReadStream } from 'node:fs';
15
+ /**
16
+ * A param value accepted by the multipart upload channel (TapdClient.postFile).
17
+ * The file itself MUST be an fs.ReadStream (i.e. fs.createReadStream):
18
+ * the SDK only switches to multipart/form-data for ReadStream/FILE values
19
+ * (node-sdk/src/sdk.js:159) — Buffers / Readable.from() silently degrade to
20
+ * plain form fields.
21
+ */
22
+ export type FileUploadParam = string | number | boolean | undefined | ReadStream;
1
23
  export declare class TapdClient {
2
24
  private authHeader;
3
25
  private baseUrl;
@@ -87,6 +109,29 @@ export declare class TapdClient {
87
109
  * POST request to TAPD API
88
110
  */
89
111
  post<T>(path: string, params?: Record<string, string | number | boolean | undefined>): Promise<T>;
112
+ /**
113
+ * POST a multipart (file upload) request — the streaming channel for upload
114
+ * endpoints (`/files/upload_attachment`, `/files/upload_image`, ...).
115
+ *
116
+ * Keeps registry.exec the single execution path (FSD §2 ruling: streaming
117
+ * calls never bypass TapdClient, so audit / timeout / read-only gating and
118
+ * auth stay unified).
119
+ *
120
+ * Routing mirrors route() POST semantics:
121
+ * - When `POST <path>` is mapped in SDK_ROUTES and the SDK is enabled, the
122
+ * request goes through the SDK, which builds multipart/form-data when any
123
+ * param value is an fs.ReadStream (node-sdk/src/sdk.js:159).
124
+ * - POST failures throw directly (never fall back / retry — a flaky network
125
+ * must not duplicate an upload).
126
+ * - Without an SDK route (or with TAPD_SDK_DISABLED=1 rollback switch on),
127
+ * falls back to a fetch-based multipart upload (Node 20+ global FormData).
128
+ */
129
+ postFile<T>(path: string, params: Record<string, FileUploadParam>): Promise<T>;
130
+ /**
131
+ * fetch-based multipart fallback (no SDK route / SDK disabled). Node 20+
132
+ * global fetch serializes FormData bodies (boundary included) automatically.
133
+ */
134
+ private postMultipartViaFetch;
90
135
  }
91
136
  /**
92
137
  * TAPD API response structure
@@ -1 +1 @@
1
- {"version":3,"file":"tapd-client.d.ts","sourceRoot":"","sources":["../src/tapd-client.ts"],"names":[],"mappings":"AAgBA,qBAAa,UAAU;IACrB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAoB;IACpC,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,OAAO,CAAC,WAAW,CAAC,CAAS;IAC7B,OAAO,CAAC,WAAW,CAAC,CAAS;IAC7B,OAAO,CAAC,GAAG,CAAC,CAAU;IACtB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyC;IACpE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwC;IAEhE,OAAO;IAgBP;;OAEG;IACH,MAAM,CAAC,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,UAAU;IAIzE;;OAEG;IACH,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,UAAU;IAWxF;;;OAGG;IACH,MAAM,CAAC,OAAO,IAAI,UAAU;IAmB5B;;OAEG;IACH,MAAM,CAAC,WAAW,IAAI,MAAM,GAAG,SAAS;IAIxC;;OAEG;IACH,MAAM,CAAC,qBAAqB,IAAI,MAAM,GAAG,SAAS;IAKlD;;OAEG;IACH,MAAM,CAAC,6BAA6B,IAAI,MAAM,GAAG,SAAS;IAI1D;;OAEG;IACH,MAAM,CAAC,4BAA4B,IAAI,MAAM,GAAG,SAAS;IAIzD;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,UAAO,GAAG,MAAM;IAc1F;;;;;;;KAOC;IACD;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,YAAY;IAe3B,OAAO,CAAC,MAAM;IAQd;;;;OAIG;IACH,OAAO,CAAC,MAAM;IAOd;;;OAGG;YACW,KAAK;IAyBb,OAAO,CAAC,CAAC,EACb,MAAM,EAAE,KAAK,GAAG,MAAM,EACtB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,GAC7D,OAAO,CAAC,CAAC,CAAC;IAwEb;;OAEG;IACG,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAI1F;;OAEG;IACG,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAGxG;AAED;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,CAAC,CAAC;IACR,IAAI,EAAE,MAAM,CAAC;CACd"}
1
+ {"version":3,"file":"tapd-client.d.ts","sourceRoot":"","sources":["../src/tapd-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAMrC;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,CAAC;AAEjF,qBAAa,UAAU;IACrB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAoB;IACpC,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,OAAO,CAAC,WAAW,CAAC,CAAS;IAC7B,OAAO,CAAC,WAAW,CAAC,CAAS;IAC7B,OAAO,CAAC,GAAG,CAAC,CAAU;IACtB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyC;IACpE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwC;IAEhE,OAAO;IAgBP;;OAEG;IACH,MAAM,CAAC,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,UAAU;IAIzE;;OAEG;IACH,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,UAAU;IAWxF;;;OAGG;IACH,MAAM,CAAC,OAAO,IAAI,UAAU;IAmB5B;;OAEG;IACH,MAAM,CAAC,WAAW,IAAI,MAAM,GAAG,SAAS;IAIxC;;OAEG;IACH,MAAM,CAAC,qBAAqB,IAAI,MAAM,GAAG,SAAS;IAKlD;;OAEG;IACH,MAAM,CAAC,6BAA6B,IAAI,MAAM,GAAG,SAAS;IAI1D;;OAEG;IACH,MAAM,CAAC,4BAA4B,IAAI,MAAM,GAAG,SAAS;IAIzD;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,UAAO,GAAG,MAAM;IAc1F;;;;;;;KAOC;IACD;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,YAAY;IAe3B,OAAO,CAAC,MAAM;IAQd;;;;OAIG;IACH,OAAO,CAAC,MAAM;IAOd;;;OAGG;YACW,KAAK;IAyBb,OAAO,CAAC,CAAC,EACb,MAAM,EAAE,KAAK,GAAG,MAAM,EACtB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,GAC7D,OAAO,CAAC,CAAC,CAAC;IAwEb;;OAEG;IACG,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAI1F;;OAEG;IACG,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAIvG;;;;;;;;;;;;;;;;OAgBG;IACG,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAiBpF;;;OAGG;YACW,qBAAqB;CAgCpC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,CAAC,CAAC;IACR,IAAI,EAAE,MAAM,CAAC;CACd"}