@univerjs-pro/collaboration-comment-service 1.0.0-beta.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.
package/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # @univerjs-pro/collaboration-comment-service
2
+
3
+ English | [简体中文](./README.zh-CN.md)
4
+
5
+ The domain Service for Univer Sheet/Doc Thread Comments. When using Univer Comment Client, `UniverCommentEndpoint` calls it according to the frontend Comment protocol:
6
+
7
+ ```text
8
+ Transport
9
+ → UniverCommentEndpoint
10
+ → UniverCommentService
11
+ → Comment Database Adapter
12
+ ```
13
+
14
+ It manages comment bodies, replies, edits, solve/reopen, deletion, user-profile enrichment, middleware, and post-commit events. Root anchors that move with Sheet/Doc structure remain in the main collaboration snapshot/changeset plane.
15
+
16
+ ## Installation and creation
17
+
18
+ ```bash
19
+ pnpm add \
20
+ @univerjs-pro/collaboration-comment-service \
21
+ @univerjs-pro/collaboration-comment-endpoint \
22
+ @univerjs-pro/collaboration-comment-database-sqlite
23
+ ```
24
+
25
+ ```ts
26
+ const commentDatabase = new SQLiteCommentDatabaseAdapter({
27
+ filename: './data/collaboration.sqlite',
28
+ });
29
+ const commentService = new UniverCommentService({
30
+ database: commentDatabase,
31
+ userProvider: {
32
+ async getUsers(userIDs, { customData }) {
33
+ return applicationUsers.findMany(
34
+ userIDs,
35
+ customData.tenantID
36
+ );
37
+ },
38
+ },
39
+ });
40
+ ```
41
+
42
+ User Provider only enriches list responses and add/reply realtime updates with names and avatars. A missing user or Provider failure does not roll back a Comment data operation.
43
+
44
+ Next, create `UniverCommentEndpoint` with this Service and register Comment Endpoint and the main `UniverCollabEndpoint` on the same Transport.
45
+
46
+ ## Comment middleware
47
+
48
+ Comment Service has no built-in owner, editor, or author ACL. Install middleware for all six actions according to application policy:
49
+
50
+ | Action | When it runs | Visible fields | Retry semantics | Typical uses |
51
+ | --- | --- | --- | --- | --- |
52
+ | `addComment` | After input normalization, before writing a root comment | `userID`, `memberID`, `customData`, `request` | Once per call; Adapter does not trigger retries | Unit Comment ACL, tenant, audit |
53
+ | `listComments` | After input normalization, before reading the database | `userID`, `customData`, `request` | Once per call | Unit read ACL, query audit |
54
+ | `replyComment` | After input normalization, before writing a reply | `userID`, `memberID`, `customData`, `request` | Once per call | Unit Comment ACL, rate limits |
55
+ | `setThreadSolved` | After input normalization, before writing state | `userID`, `memberID`, `customData`, `request` | Once per call | Solve/reopen permission |
56
+ | `editComment` | After input normalization, before atomic author/open-state checks | `userID`, `memberID`, `customData`, `request` | Once per call | Unit Comment ACL; Service still checks author |
57
+ | `deleteComment` | After resolving the target, before deletion with a concurrency-version check | `userID`, `memberID`, `customData`, `request`, readonly `target` | Once per call; no automatic retry after concurrent target changes | Author, Unit owner, or administrator delete policy |
58
+
59
+ ```ts
60
+ commentService.use('deleteComment', async (ctx, next) => {
61
+ const allowed =
62
+ ctx.target.authorUserID === ctx.userID ||
63
+ await acl.isUnitOwner(ctx.userID, ctx.request.unitID);
64
+ if (!allowed) {
65
+ throw new CollabError('PERMISSION_DENIED', 'Delete denied');
66
+ }
67
+ await next();
68
+ });
69
+ ```
70
+
71
+ The `deleteComment` context already contains the resolved target author and concurrency version. `listComments` should check Unit read access; all other actions should check Comment write access.
72
+
73
+ ## Advanced integration: call Comment Service directly
74
+
75
+ Reading comments needs only the current business user. Writes also need the current online collaboration member's `memberID`:
76
+
77
+ ```ts
78
+ const result = await commentService.listComments(
79
+ { unitID, threadIDs },
80
+ { userID: user.userId, customData: { tenantID } }
81
+ );
82
+
83
+ const thread = await commentService.addComment(
84
+ { unitID, content: 'Looks good', mentions: [] },
85
+ { userID: user.userId, memberID, customData: { tenantID } }
86
+ );
87
+ ```
88
+
89
+ When a browser uses Univer Comment Client, `UniverCommentEndpoint` normally gets `memberID` from the established online Session; applications do not need to generate one in product HTTP APIs. Only background programs that call a write method directly need to supply their own `memberID`.
90
+
91
+ ## Consistency and disposal
92
+
93
+ Comment bodies belong in Comment Database Adapter, not the core Collaboration Adapter. Realtime `comment_update` delivery is not guaranteed for every update, and send failure cannot roll back committed data. Clients can recover by listing comments again.
94
+
95
+ ```ts
96
+ await commentService.dispose();
97
+ await commentDatabase.dispose();
98
+ ```
99
+
100
+ The Service does not dispose an externally injected Database Adapter or User Provider.
@@ -0,0 +1,110 @@
1
+ # @univerjs-pro/collaboration-comment-service
2
+
3
+ [English](./README.md) | [简体中文](./README.zh-CN.md)
4
+
5
+ Univer Sheet/Doc Thread Comment 的领域 Service。使用 Univer Comment Client 时,
6
+ `UniverCommentEndpoint` 会根据前端 Comment 协议调用它:
7
+
8
+ ```text
9
+ Transport
10
+ → UniverCommentEndpoint
11
+ → UniverCommentService
12
+ → Comment Database Adapter
13
+ ```
14
+
15
+ 它管理评论正文、回复、编辑、solve/reopen、删除、用户资料补全、middleware 和提交后
16
+ event。会随 Sheet/Doc 结构变化的 root anchor 仍保存在主协同 snapshot/changeset 平面。
17
+
18
+ ## 安装与创建
19
+
20
+ ```bash
21
+ pnpm add \
22
+ @univerjs-pro/collaboration-comment-service \
23
+ @univerjs-pro/collaboration-comment-endpoint \
24
+ @univerjs-pro/collaboration-comment-database-sqlite
25
+ ```
26
+
27
+ ```ts
28
+ const commentDatabase = new SQLiteCommentDatabaseAdapter({
29
+ filename: './data/collaboration.sqlite',
30
+ });
31
+ const commentService = new UniverCommentService({
32
+ database: commentDatabase,
33
+ userProvider: {
34
+ async getUsers(userIDs, { customData }) {
35
+ return applicationUsers.findMany(
36
+ userIDs,
37
+ customData.tenantID
38
+ );
39
+ },
40
+ },
41
+ });
42
+ ```
43
+
44
+ User Provider 只为 list 响应和 add/reply 实时更新补全姓名、头像。用户缺失或 Provider
45
+ 失败不会回滚 Comment 数据操作。
46
+
47
+ 接着用这个 Service 创建 `UniverCommentEndpoint`,并把 Comment Endpoint 与主
48
+ `UniverCollabEndpoint` 注册到同一个 Transport。
49
+
50
+ ## Comment middleware
51
+
52
+ Comment Service 不内置 owner、editor 或 author ACL。六个 action 都应按应用策略安装
53
+ middleware:
54
+
55
+ | Action | 触发时机 | 可见字段 | 重试语义 | 典型用途 |
56
+ | --- | --- | --- | --- | --- |
57
+ | `addComment` | 输入规范化后、根评论写入前 | `userID`、`memberID`、`customData`、`request` | 每次调用一次;Adapter 不触发重试 | Unit Comment ACL、tenant、审计 |
58
+ | `listComments` | 输入规范化后、读库前 | `userID`、`customData`、`request` | 每次调用一次 | Unit 读取 ACL、查询审计 |
59
+ | `replyComment` | 输入规范化后、回复写入前 | `userID`、`memberID`、`customData`、`request` | 每次调用一次 | Unit Comment ACL、限流 |
60
+ | `setThreadSolved` | 输入规范化后、状态写库前 | `userID`、`memberID`、`customData`、`request` | 每次调用一次 | solve/reopen 权限 |
61
+ | `editComment` | 输入规范化后、作者与 open 状态原子检查前 | `userID`、`memberID`、`customData`、`request` | 每次调用一次 | Unit Comment ACL;Service 仍检查作者 |
62
+ | `deleteComment` | 目标已读取、执行带并发版本检查的删除前 | `userID`、`memberID`、`customData`、`request`、只读 `target` | 每次调用一次;目标被并发修改时不自动重试 | 作者、Unit owner 或管理员删除策略 |
63
+
64
+ ```ts
65
+ commentService.use('deleteComment', async (ctx, next) => {
66
+ const allowed =
67
+ ctx.target.authorUserID === ctx.userID ||
68
+ await acl.isUnitOwner(ctx.userID, ctx.request.unitID);
69
+ if (!allowed) {
70
+ throw new CollabError('PERMISSION_DENIED', 'Delete denied');
71
+ }
72
+ await next();
73
+ });
74
+ ```
75
+
76
+ `deleteComment` context 已解析目标作者和并发版本。`listComments` 应检查 Unit 读取权限,
77
+ 其余 action 应检查 Comment 写权限。
78
+
79
+ ## 高级集成:直接调用 Comment Service API
80
+
81
+ 读取评论只需要当前业务用户;写评论还需要本次在线协同成员的 `memberID`:
82
+
83
+ ```ts
84
+ const result = await commentService.listComments(
85
+ { unitID, threadIDs },
86
+ { userID: user.userId, customData: { tenantID } }
87
+ );
88
+
89
+ const thread = await commentService.addComment(
90
+ { unitID, content: 'Looks good', mentions: [] },
91
+ { userID: user.userId, memberID, customData: { tenantID } }
92
+ );
93
+ ```
94
+
95
+ 浏览器使用 Univer Comment Client 时,通常由 `UniverCommentEndpoint` 从已建立的在线
96
+ Session 取得 `memberID`,应用不需要在产品 HTTP API 中自行生成它。只有后台程序直接调用
97
+ 写方法时,才需要自己提供一个 `memberID`。
98
+
99
+ ## 一致性与释放
100
+
101
+ 评论正文属于 Comment Database Adapter,不应写入 core Collaboration Adapter。实时
102
+ `comment_update` 不保证每条实时更新都送达,发送失败不能回滚已提交数据;客户端可通过
103
+ list 恢复。
104
+
105
+ ```ts
106
+ await commentService.dispose();
107
+ await commentDatabase.dispose();
108
+ ```
109
+
110
+ Service 不释放外部注入的 Database Adapter 或 User Provider。
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ Object['defineProperty'](exports,Symbol['toStringTag'],{'value':'Module'});let e=require('node:crypto'),t=require('@univerjs-pro/collaboration-service'),n=require('@univerjs/protocol');var r=class{['_middlewares']=new Map();['use'](_0x41a999,_0x26faf2){let _0x53e809=this['_middlewares']['get'](_0x41a999)??[],_0x44c0b3=_0x26faf2;_0x53e809['push'](_0x44c0b3),this['_middlewares']['set'](_0x41a999,_0x53e809);let _0x3b8669=!0x1;return{'dispose':()=>{if(_0x3b8669)return;_0x3b8669=!0x0;let _0x26c1cc=_0x53e809['indexOf'](_0x44c0b3);_0x26c1cc>=0x0&&_0x53e809['splice'](_0x26c1cc,0x1);}};}async['run'](_0x1022b7,_0x17088e,_0x296479){let _0x210544=[...this['_middlewares']['get'](_0x1022b7)??[]],_0x222b4e=async _0x11f7fa=>{let _0x4223da=_0x210544[_0x11f7fa];if(!_0x4223da)return _0x296479();let _0x1fc573=!0x1,_0x401a03,_0x340c24=()=>{if(_0x1fc573)throw Error('Middleware\x20for\x20action\x20'+_0x1022b7+'\x20called\x20next()\x20more\x20than\x20once');return _0x1fc573=!0x0,_0x401a03=_0x222b4e(_0x11f7fa+0x1),_0x401a03;};try{await _0x4223da(_0x17088e,_0x340c24);}catch(_0x5ca069){throw _0x401a03&&await _0x401a03['catch'](()=>void 0x0),_0x5ca069;}if(!_0x1fc573)throw Error('Middleware\x20for\x20action\x20'+_0x1022b7+'\x20completed\x20without\x20calling\x20next()');await _0x401a03;};await _0x222b4e(0x0);}['clear'](){this['_middlewares']['clear']();}},i=class{['_middleware']=new r();['_listeners']=new Map();['_calls']=new Set();['_database'];['_userProvider'];['_state']='running';['_disposePromise'];constructor(_0xca0c96){if(!_0xca0c96?.['database'])throw TypeError('UniverCommentService\x20requires\x20a\x20database');this['_database']=_0xca0c96['database'],this['_userProvider']=_0xca0c96['userProvider'];}['use'](_0x15e91e,_0x29e1f6){return this['_assertRunning'](),this['_middleware']['use'](_0x15e91e,_0x29e1f6);}['on'](_0x16517b,_0x6b8e48){this['_assertRunning']();let _0x2f0ded=this['_listeners']['get'](_0x16517b)??new Set(),_0x139ae8=_0x6b8e48;_0x2f0ded['add'](_0x139ae8),this['_listeners']['set'](_0x16517b,_0x2f0ded);let _0x478f34=!0x1;return{'dispose':()=>{_0x478f34||(_0x478f34=!0x0,_0x2f0ded['delete'](_0x139ae8));}};}['addComment'](_0x172957,_0x432cd5){return this['_call'](async()=>{let _0xffc9e9=s(_0x432cd5);m(_0x172957['unitID']),h(_0x172957['content']);let _0x1caca9=a(_0x172957),_0x232258={..._0xffc9e9,'request':_0x1caca9},_0xf4669e;return await this['_middleware']['run']('addComment',_0x232258,async()=>{let _0xd8bd13=await this['_databaseCall'](()=>this['_database']['createRoot']({'unitID':_0x172957['unitID'],'threadID':(0x0,e['randomUUID'])(),'authorUserID':_0xffc9e9['userID'],'content':_0x172957['content'],'mentions':[..._0x172957['mentions']]},_0x232258));if(_0xd8bd13['status']==='duplicate')throw _('Comment\x20ID\x20already\x20exists');_0xf4669e=u([_0xd8bd13['record']]),await this['_emitWrite'](_0xffc9e9,_0x1caca9,await this['_createUpdate'](n['CommentUpdateEventType']['Add'],_0xd8bd13['record'],_0xffc9e9,_0x1caca9));}),_0xf4669e;});}['listComments'](_0x3807e9,_0x419d9e){return this['_call'](async()=>{let _0x2e6674=o(_0x419d9e);m(_0x3807e9['unitID']),_0x3807e9['threadIDs']['forEach'](_0x2e6a91=>g('threadID',_0x2e6a91));let _0x38a622=a(_0x3807e9),_0x112fb0={..._0x2e6674,'request':_0x38a622},_0x26ee7d;return await this['_middleware']['run']('listComments',_0x112fb0,async()=>{let _0x2e008b=d(await this['_databaseCall'](()=>this['_database']['listThreads']({'unitID':_0x3807e9['unitID'],'threadIDs':[...new Set(_0x3807e9['threadIDs'])]},_0x112fb0))),_0x3823cf=Object['values'](_0x2e008b)['flatMap'](_0x2993f2=>_0x2993f2['replies']['map'](_0x43eccb=>_0x43eccb['userId']));_0x26ee7d={'comments':_0x2e008b,'users':await this['_resolveUsers'](_0x3823cf,_0x2e6674,_0x38a622)};}),_0x26ee7d;});}['replyComment'](_0x6565e9,_0x20972e){return this['_call'](async()=>{let _0xde85b3=s(_0x20972e);m(_0x6565e9['unitID']),g('threadID',_0x6565e9['threadID']),h(_0x6565e9['content']);let _0x2738ec=a(_0x6565e9),_0x13b3f1={..._0xde85b3,'request':_0x2738ec},_0x89fb8c;return await this['_middleware']['run']('replyComment',_0x13b3f1,async()=>{let _0x1eb5a1=await this['_databaseCall'](()=>this['_database']['createReply']({'unitID':_0x6565e9['unitID'],'threadID':_0x6565e9['threadID'],'replyID':(0x0,e['randomUUID'])(),'authorUserID':_0xde85b3['userID'],'content':_0x6565e9['content'],'mentions':[..._0x6565e9['mentions']]},_0x13b3f1));if(_0x1eb5a1['status']==='not-found'||_0x1eb5a1['status']==='closed')throw v('Open\x20Comment\x20thread\x20was\x20not\x20found');if(_0x1eb5a1['status']==='duplicate')throw _('Comment\x20ID\x20already\x20exists');_0x89fb8c=l(_0x1eb5a1['record']),await this['_emitWrite'](_0xde85b3,_0x2738ec,await this['_createUpdate'](n['CommentUpdateEventType']['Reply'],_0x1eb5a1['record'],_0xde85b3,_0x2738ec));}),_0x89fb8c;});}['editComment'](_0x43c613,_0x260368){return this['_call'](async()=>{let _0x49d10f=s(_0x260368);p(_0x43c613),h(_0x43c613['content']);let _0xd15291=a(_0x43c613),_0x5117ff={..._0x49d10f,'request':_0xd15291};await this['_middleware']['run']('editComment',_0x5117ff,async()=>{let _0x33dd42=await this['_databaseCall'](()=>this['_database']['editComment']({'unitID':_0x43c613['unitID'],'threadID':_0x43c613['threadID'],'replyID':_0x43c613['replyID'],'expectedAuthorUserID':_0x49d10f['userID'],'content':_0x43c613['content'],'mentions':[..._0x43c613['mentions']]},_0x5117ff));if(_0x33dd42['status']==='author-mismatch')throw y('Only\x20the\x20Comment\x20author\x20can\x20edit\x20it');if(_0x33dd42['status']==='not-found'||_0x33dd42['status']==='closed')throw v('Open\x20Comment\x20was\x20not\x20found');_0x33dd42['status']==='edited'&&await this['_emitWrite'](_0x49d10f,_0xd15291,f(n['CommentUpdateEventType']['Edit'],_0x33dd42['record'],_0x49d10f));});});}['setThreadSolved'](_0xe84e65,_0x17e60e){return this['_call'](async()=>{let _0x4b1c43=s(_0x17e60e);if(m(_0xe84e65['unitID']),g('threadID',_0xe84e65['threadID']),_0xe84e65['solved']!==n['CommentSolvedStatus']['OpenOrReOpen']&&_0xe84e65['solved']!==n['CommentSolvedStatus']['Solved'])throw _('solved\x20must\x20be\x20OpenOrReOpen\x20or\x20Solved');let _0x5099ae=a(_0xe84e65),_0x1f3f46={..._0x4b1c43,'request':_0x5099ae};await this['_middleware']['run']('setThreadSolved',_0x1f3f46,async()=>{let _0xca9bec=await this['_databaseCall'](()=>this['_database']['setThreadSolved'](_0xe84e65,_0x1f3f46));if(_0xca9bec['status']==='not-found')throw v('Comment\x20thread\x20was\x20not\x20found');_0xca9bec['status']==='updated'&&await this['_emitWrite'](_0x4b1c43,_0x5099ae,{'memberId':_0x4b1c43['memberID'],'userId':_0x4b1c43['userID'],'unitId':_0xe84e65['unitID'],'threadId':_0xe84e65['threadID'],'type':n['CommentUpdateEventType']['Solve'],'solved':_0xe84e65['solved']});});});}['deleteComment'](_0x38c792,_0x1a1fce){return this['_call'](async()=>{let _0x341009=s(_0x1a1fce);m(_0x38c792['unitID']),g('threadID',_0x38c792['threadID']),_0x38c792['replyID']!==void 0x0&&g('replyID',_0x38c792['replyID']);let _0xd9ce50=_0x38c792['replyID']===_0x38c792['threadID']?void 0x0:_0x38c792['replyID'],_0xe97052=a({'unitID':_0x38c792['unitID'],'threadID':_0x38c792['threadID'],..._0xd9ce50===void 0x0?{}:{'replyID':_0xd9ce50}}),_0x513ed0={..._0x341009,'request':_0xe97052},_0x2db13b=await this['_databaseCall'](()=>this['_database']['getComment']({'unitID':_0x38c792['unitID'],'threadID':_0x38c792['threadID'],'replyID':_0xd9ce50??_0x38c792['threadID']},_0x513ed0));if(!_0x2db13b)throw v('Comment\x20was\x20not\x20found');await this['_middleware']['run']('deleteComment',{..._0x513ed0,'request':_0xe97052,'target':{'kind':_0xd9ce50===void 0x0?'root':'reply','threadID':_0x2db13b['threadID'],'replyID':_0x2db13b['replyID'],'authorUserID':_0x2db13b['authorUserID'],'generation':_0x2db13b['generation']}},async()=>{let _0x4a328e=await this['_databaseCall'](()=>this['_database']['deleteComment']({'unitID':_0x38c792['unitID'],'threadID':_0x38c792['threadID'],..._0xd9ce50===void 0x0?{}:{'replyID':_0xd9ce50},'expectedGeneration':_0x2db13b['generation']},_0x513ed0));if(_0x4a328e['status']==='not-found')throw v('Comment\x20was\x20not\x20found');if(_0x4a328e['status']==='generation-mismatch')throw _('Comment\x20changed\x20before\x20deletion');await this['_emitWrite'](_0x341009,_0xe97052,{'memberId':_0x341009['memberID'],'userId':_0x341009['userID'],'unitId':_0x38c792['unitID'],'threadId':_0x38c792['threadID'],'type':n['CommentUpdateEventType']['Delete'],..._0xd9ce50===void 0x0?{}:{'replyId':_0xd9ce50}});});});}['dispose'](){return this['_disposePromise']?this['_disposePromise']:(this['_state']='disposing',this['_disposePromise']=((async()=>{await Promise['allSettled']([...this['_calls']]),this['_middleware']['clear'](),this['_listeners']['clear'](),this['_state']='disposed';})()),this['_disposePromise']);}async['_createUpdate'](_0x15b966,_0x17be5b,_0x19bfc5,_0x5dd925){let _0x613f06=await this['_resolveUsers']([_0x17be5b['authorUserID']],_0x19bfc5,_0x5dd925);return{...f(_0x15b966,_0x17be5b,_0x19bfc5),..._0x613f06[_0x17be5b['authorUserID']]?{'user':_0x613f06[_0x17be5b['authorUserID']]}:{}};}async['_resolveUsers'](_0x2436ce,_0x4c37d2,_0x3cf2f5){let _0x8b5428=[...new Set(_0x2436ce['filter'](Boolean))];if(!this['_userProvider']||_0x8b5428['length']===0x0)return{};let _0x4d1fb0;try{_0x4d1fb0=await this['_userProvider']['getUsers'](_0x8b5428,{..._0x4c37d2,'request':_0x3cf2f5});}catch{return{};}let _0x14fd1f=new Set(_0x8b5428),_0x449ff1={};for(let _0x13ab26 of _0x4d1fb0)_0x13ab26?.['userID']&&_0x14fd1f['has'](_0x13ab26['userID'])&&(_0x449ff1[_0x13ab26['userID']]=_0x13ab26);return _0x449ff1;}async['_emitWrite'](_0xa4c496,_0x4ef1f7,_0x3deb8a){let _0x35285f={'userID':_0xa4c496['userID'],'memberID':_0xa4c496['memberID'],'customData':_0xa4c496['customData'],'request':_0x4ef1f7,'update':_0x3deb8a,'committedAt':Date['now']()};for(let _0x3d6f0c of[...this['_listeners']['get']('commentCommitted')??[]])try{await _0x3d6f0c(_0x35285f);}catch{}}['_call'](_0x4d6bf7){this['_assertRunning']();let _0x435768=_0x4d6bf7();return this['_calls']['add'](_0x435768),_0x435768['finally'](()=>this['_calls']['delete'](_0x435768))['catch'](()=>void 0x0),_0x435768;}async['_databaseCall'](_0x16dc40){try{return await _0x16dc40();}catch(_0x7d928f){throw _0x7d928f instanceof t['CollabError']?_0x7d928f:new t['CollabError']('ADAPTER_FAILURE','Comment\x20Database\x20Adapter\x20failed',{'retryable':!0x0,'cause':_0x7d928f});}}['_assertRunning'](){if(this['_state']!=='running')throw new t['CollabError']('INTERNAL_ERROR','Comment\x20Service\x20is\x20'+this['_state'],{'retryable':!0x0});}};function a(_0xb6355e){return{..._0xb6355e};}function o(_0x161e73){return c(_0x161e73),{'userID':_0x161e73['userID'],'customData':_0x161e73['customData']??Object['create'](null)};}function s(_0x5b59a4){return c(_0x5b59a4),g('memberID',_0x5b59a4['memberID']),{'userID':_0x5b59a4['userID'],'memberID':_0x5b59a4['memberID'],'customData':_0x5b59a4['customData']??Object['create'](null)};}function c(_0x554cc0){if(!_0x554cc0||typeof _0x554cc0!='object')throw _('context\x20must\x20be\x20an\x20object');if(g('userID',_0x554cc0['userID']),_0x554cc0['customData']!==void 0x0&&(_0x554cc0['customData']===null||typeof _0x554cc0['customData']!='object'||Array['isArray'](_0x554cc0['customData'])))throw _('customData\x20must\x20be\x20an\x20object');}function l(_0x5a7f5d){return{'threadId':_0x5a7f5d['threadID'],'replyId':_0x5a7f5d['replyID'],'content':_0x5a7f5d['content'],'userId':_0x5a7f5d['authorUserID'],'createTimestamp':_0x5a7f5d['createdAt']};}function u(_0x53a0c6){let _0x394b93=_0x53a0c6['find'](_0x4ca53d=>_0x4ca53d['replyID']===_0x4ca53d['threadID']);return{'threadId':_0x394b93['threadID'],'solved':_0x394b93['solved'],'replies':_0x53a0c6['map'](l)};}function d(_0x33afe0){let _0x47d27d=new Map();for(let _0x481c79 of _0x33afe0){let _0x7b7f77=_0x47d27d['get'](_0x481c79['threadID'])??[];_0x7b7f77['push'](_0x481c79),_0x47d27d['set'](_0x481c79['threadID'],_0x7b7f77);}let _0x1e3b23={};for(let [_0x2a7759,_0x31e54e]of _0x47d27d)_0x31e54e['find'](_0x497acb=>_0x497acb['replyID']===_0x2a7759)&&(_0x1e3b23[_0x2a7759]=u([..._0x31e54e]['sort']((_0x18210b,_0x16db3a)=>_0x18210b['replyID']===_0x2a7759?-0x1:_0x16db3a['replyID']===_0x2a7759?0x1:_0x18210b['createdAt']-_0x16db3a['createdAt']||_0x18210b['replyID']['localeCompare'](_0x16db3a['replyID']))));return _0x1e3b23;}function f(_0x3c899b,_0x463523,_0x2c51f5){return{'memberId':_0x2c51f5['memberID'],'userId':_0x2c51f5['userID'],'unitId':_0x463523['unitID'],'threadId':_0x463523['threadID'],'type':_0x3c899b,'replyId':_0x463523['replyID'],'content':_0x463523['content'],'createTimestamp':String(_0x463523['createdAt'])};}function p(_0x395ca4){m(_0x395ca4['unitID']),g('threadID',_0x395ca4['threadID']),g('replyID',_0x395ca4['replyID']);}function m(_0x1a1d71){g('unitID',_0x1a1d71);}function h(_0xc9b880){if(typeof _0xc9b880!='string'||_0xc9b880['length']===0x0)throw _('content\x20must\x20be\x20a\x20non-empty\x20string');}function g(_0x47c90c,_0x57bbae){if(typeof _0x57bbae!='string'||_0x57bbae['length']===0x0)throw _(_0x47c90c+'\x20must\x20be\x20a\x20non-empty\x20string');}function _(_0x2ff552){return new t['CollabError']('INVALID_REQUEST',_0x2ff552);}function v(_0x1bab49){return new t['CollabError']('UNIT_NOT_FOUND',_0x1bab49);}function y(_0x431000){return new t['CollabError']('PERMISSION_DENIED',_0x431000);}exports['UniverCommentService']=i;
@@ -0,0 +1,253 @@
1
+ import { CollabContext, CollabMemberContext, CustomData, Disposable } from "@univerjs-pro/collaboration-service";
2
+ import { CommentSolvedStatus, ICommentUpdate, IReply, IThread, IUser } from "@univerjs/protocol";
3
+
4
+ //#region src/middleware.d.ts
5
+ interface DeleteCommentMiddlewareTarget {
6
+ readonly kind: "root" | "reply";
7
+ readonly threadID: string;
8
+ readonly replyID: string;
9
+ readonly authorUserID: string;
10
+ readonly generation: string;
11
+ }
12
+ interface BaseCommentMiddlewareContext<TRequest> {
13
+ readonly userID: string;
14
+ readonly customData: CustomData;
15
+ readonly request: TRequest;
16
+ }
17
+ interface MemberCommentMiddlewareContext<TRequest> extends BaseCommentMiddlewareContext<TRequest> {
18
+ readonly memberID: string;
19
+ }
20
+ type AddCommentMiddlewareContext = MemberCommentMiddlewareContext<AddCommentRequest>;
21
+ type ListCommentsMiddlewareContext = BaseCommentMiddlewareContext<ListCommentsRequest>;
22
+ type ReplyCommentMiddlewareContext = MemberCommentMiddlewareContext<ReplyCommentRequest>;
23
+ type SetThreadSolvedMiddlewareContext = MemberCommentMiddlewareContext<SetThreadSolvedRequest>;
24
+ type EditCommentMiddlewareContext = MemberCommentMiddlewareContext<EditCommentRequest>;
25
+ interface DeleteCommentMiddlewareContext extends MemberCommentMiddlewareContext<DeleteCommentRequest> {
26
+ readonly target: DeleteCommentMiddlewareTarget;
27
+ }
28
+ type CommentMiddlewareNext = () => Promise<void>;
29
+ type CommentMiddleware<TContext> = (context: TContext, next: CommentMiddlewareNext) => void | Promise<void>;
30
+ type CommentMiddlewareAction = "addComment" | "listComments" | "replyComment" | "setThreadSolved" | "editComment" | "deleteComment";
31
+ interface CommentMiddlewareMap {
32
+ readonly addComment: CommentMiddleware<AddCommentMiddlewareContext>;
33
+ readonly listComments: CommentMiddleware<ListCommentsMiddlewareContext>;
34
+ readonly replyComment: CommentMiddleware<ReplyCommentMiddlewareContext>;
35
+ readonly setThreadSolved: CommentMiddleware<SetThreadSolvedMiddlewareContext>;
36
+ readonly editComment: CommentMiddleware<EditCommentMiddlewareContext>;
37
+ readonly deleteComment: CommentMiddleware<DeleteCommentMiddlewareContext>;
38
+ }
39
+ //#endregion
40
+ //#region src/types.d.ts
41
+ interface AddCommentInput {
42
+ readonly unitID: string;
43
+ readonly content: string;
44
+ readonly mentions: readonly string[];
45
+ }
46
+ interface ListCommentsInput {
47
+ readonly unitID: string;
48
+ readonly threadIDs: readonly string[];
49
+ }
50
+ interface ReplyCommentInput {
51
+ readonly unitID: string;
52
+ readonly threadID: string;
53
+ readonly content: string;
54
+ readonly mentions: readonly string[];
55
+ }
56
+ interface SetThreadSolvedInput {
57
+ readonly unitID: string;
58
+ readonly threadID: string;
59
+ readonly solved: CommentSolvedStatus;
60
+ }
61
+ interface EditCommentInput {
62
+ readonly unitID: string;
63
+ readonly threadID: string;
64
+ readonly replyID: string;
65
+ readonly content: string;
66
+ readonly mentions: readonly string[];
67
+ }
68
+ interface DeleteCommentInput {
69
+ readonly unitID: string;
70
+ readonly threadID: string;
71
+ readonly replyID?: string;
72
+ }
73
+ interface AddCommentRequest extends AddCommentInput {}
74
+ interface ListCommentsRequest extends ListCommentsInput {}
75
+ interface ReplyCommentRequest extends ReplyCommentInput {}
76
+ interface SetThreadSolvedRequest extends SetThreadSolvedInput {}
77
+ interface EditCommentRequest extends EditCommentInput {}
78
+ interface DeleteCommentRequest extends DeleteCommentInput {}
79
+ type CommentRequest = AddCommentRequest | ListCommentsRequest | ReplyCommentRequest | SetThreadSolvedRequest | EditCommentRequest | DeleteCommentRequest;
80
+ type CommentWriteRequest = Exclude<CommentRequest, ListCommentsRequest>;
81
+ interface CommentListResult {
82
+ readonly comments: Readonly<Record<string, IThread>>;
83
+ readonly users: Readonly<Record<string, IUser>>;
84
+ }
85
+ interface CommentUserProviderContext {
86
+ readonly userID: string;
87
+ readonly customData: CustomData;
88
+ readonly request: CommentRequest;
89
+ }
90
+ interface ICommentUserProvider {
91
+ getUsers(userIDs: readonly string[], context: CommentUserProviderContext): Promise<readonly IUser[]>;
92
+ }
93
+ interface CommentCommittedEvent {
94
+ readonly userID: string;
95
+ readonly memberID: string;
96
+ readonly customData: CustomData;
97
+ readonly request: CommentWriteRequest;
98
+ readonly update: ICommentUpdate;
99
+ readonly committedAt: number;
100
+ }
101
+ interface CommentServiceEventMap {
102
+ readonly commentCommitted: CommentCommittedEvent;
103
+ }
104
+ type CommentServiceListener<K extends keyof CommentServiceEventMap> = (event: CommentServiceEventMap[K]) => void | Promise<void>;
105
+ interface IUniverCommentService {
106
+ addComment(input: AddCommentInput, context: CollabMemberContext): Promise<IThread>;
107
+ listComments(input: ListCommentsInput, context: CollabContext): Promise<CommentListResult>;
108
+ replyComment(input: ReplyCommentInput, context: CollabMemberContext): Promise<IReply>;
109
+ setThreadSolved(input: SetThreadSolvedInput, context: CollabMemberContext): Promise<void>;
110
+ editComment(input: EditCommentInput, context: CollabMemberContext): Promise<void>;
111
+ deleteComment(input: DeleteCommentInput, context: CollabMemberContext): Promise<void>;
112
+ use<K extends CommentMiddlewareAction>(action: K, middleware: CommentMiddlewareMap[K]): Disposable;
113
+ on<K extends keyof CommentServiceEventMap>(event: K, listener: CommentServiceListener<K>): Disposable;
114
+ dispose(): Promise<void>;
115
+ }
116
+ //#endregion
117
+ //#region src/database-adapter.d.ts
118
+ interface CommentRecord {
119
+ readonly unitID: string;
120
+ readonly threadID: string;
121
+ readonly replyID: string;
122
+ readonly authorUserID: string;
123
+ readonly content: string;
124
+ readonly mentions: readonly string[];
125
+ readonly solved: CommentSolvedStatus;
126
+ readonly createdAt: number;
127
+ readonly updatedAt: number;
128
+ readonly generation: string;
129
+ }
130
+ interface CommentDatabaseContext<TRequest extends CommentRequest = CommentRequest> {
131
+ readonly userID: string;
132
+ readonly customData: CustomData;
133
+ readonly request: TRequest;
134
+ }
135
+ interface CommentMemberDatabaseContext<TRequest extends CommentRequest = CommentRequest> extends CommentDatabaseContext<TRequest> {
136
+ readonly memberID: string;
137
+ }
138
+ interface CreateRootDatabaseInput {
139
+ readonly unitID: string;
140
+ readonly threadID: string;
141
+ readonly authorUserID: string;
142
+ readonly content: string;
143
+ readonly mentions: readonly string[];
144
+ }
145
+ interface CreateReplyDatabaseInput extends CreateRootDatabaseInput {
146
+ readonly replyID: string;
147
+ }
148
+ interface GetCommentDatabaseInput {
149
+ readonly unitID: string;
150
+ readonly threadID: string;
151
+ readonly replyID: string;
152
+ }
153
+ interface ListThreadsDatabaseInput {
154
+ readonly unitID: string;
155
+ readonly threadIDs: readonly string[];
156
+ }
157
+ interface EditCommentDatabaseInput extends GetCommentDatabaseInput {
158
+ readonly expectedAuthorUserID: string;
159
+ readonly content: string;
160
+ readonly mentions: readonly string[];
161
+ }
162
+ interface SetThreadSolvedDatabaseInput {
163
+ readonly unitID: string;
164
+ readonly threadID: string;
165
+ readonly solved: CommentSolvedStatus;
166
+ }
167
+ interface DeleteCommentDatabaseInput {
168
+ readonly unitID: string;
169
+ readonly threadID: string;
170
+ readonly replyID?: string;
171
+ readonly expectedGeneration: string;
172
+ }
173
+ type CreateCommentDatabaseResult = {
174
+ readonly status: "created";
175
+ readonly record: CommentRecord;
176
+ } | {
177
+ readonly status: "duplicate";
178
+ };
179
+ type CreateReplyDatabaseResult = CreateCommentDatabaseResult | {
180
+ readonly status: "not-found";
181
+ } | {
182
+ readonly status: "closed";
183
+ };
184
+ type EditCommentDatabaseResult = {
185
+ readonly status: "edited";
186
+ readonly record: CommentRecord;
187
+ } | {
188
+ readonly status: "unchanged";
189
+ readonly record: CommentRecord;
190
+ } | {
191
+ readonly status: "not-found";
192
+ } | {
193
+ readonly status: "closed";
194
+ } | {
195
+ readonly status: "author-mismatch";
196
+ };
197
+ type SetThreadSolvedDatabaseResult = {
198
+ readonly status: "updated";
199
+ } | {
200
+ readonly status: "unchanged";
201
+ } | {
202
+ readonly status: "not-found";
203
+ };
204
+ type DeleteCommentDatabaseResult = {
205
+ readonly status: "deleted";
206
+ } | {
207
+ readonly status: "not-found";
208
+ } | {
209
+ readonly status: "generation-mismatch";
210
+ };
211
+ interface ICommentDatabaseAdapter {
212
+ createRoot(input: CreateRootDatabaseInput, context: CommentMemberDatabaseContext): Promise<CreateCommentDatabaseResult>;
213
+ createReply(input: CreateReplyDatabaseInput, context: CommentMemberDatabaseContext): Promise<CreateReplyDatabaseResult>;
214
+ getComment(input: GetCommentDatabaseInput, context: CommentMemberDatabaseContext): Promise<CommentRecord | null>;
215
+ listThreads(input: ListThreadsDatabaseInput, context: CommentDatabaseContext): Promise<readonly CommentRecord[]>;
216
+ editComment(input: EditCommentDatabaseInput, context: CommentMemberDatabaseContext): Promise<EditCommentDatabaseResult>;
217
+ setThreadSolved(input: SetThreadSolvedDatabaseInput, context: CommentMemberDatabaseContext): Promise<SetThreadSolvedDatabaseResult>;
218
+ deleteComment(input: DeleteCommentDatabaseInput, context: CommentMemberDatabaseContext): Promise<DeleteCommentDatabaseResult>;
219
+ dispose(): Promise<void>;
220
+ }
221
+ //#endregion
222
+ //#region src/service.d.ts
223
+ interface UniverCommentServiceOptions {
224
+ readonly database: ICommentDatabaseAdapter;
225
+ readonly userProvider?: ICommentUserProvider;
226
+ }
227
+ declare class UniverCommentService implements IUniverCommentService {
228
+ private readonly _middleware;
229
+ private readonly _listeners;
230
+ private readonly _calls;
231
+ private readonly _database;
232
+ private readonly _userProvider;
233
+ private _state;
234
+ private _disposePromise;
235
+ constructor(options: UniverCommentServiceOptions);
236
+ use<K extends CommentMiddlewareAction>(action: K, middleware: CommentMiddlewareMap[K]): Disposable;
237
+ on<K extends keyof CommentServiceEventMap>(event: K, listener: CommentServiceListener<K>): Disposable;
238
+ addComment(input: AddCommentInput, context: CollabMemberContext): Promise<IThread>;
239
+ listComments(input: ListCommentsInput, context: CollabContext): Promise<CommentListResult>;
240
+ replyComment(input: ReplyCommentInput, context: CollabMemberContext): Promise<IReply>;
241
+ editComment(input: EditCommentInput, context: CollabMemberContext): Promise<void>;
242
+ setThreadSolved(input: SetThreadSolvedInput, context: CollabMemberContext): Promise<void>;
243
+ deleteComment(input: DeleteCommentInput, context: CollabMemberContext): Promise<void>;
244
+ dispose(): Promise<void>;
245
+ private _createUpdate;
246
+ private _resolveUsers;
247
+ private _emitWrite;
248
+ private _call;
249
+ private _databaseCall;
250
+ private _assertRunning;
251
+ }
252
+ //#endregion
253
+ export { type AddCommentInput, type AddCommentMiddlewareContext, type AddCommentRequest, type CommentCommittedEvent, type CommentDatabaseContext, type CommentListResult, type CommentMemberDatabaseContext, type CommentMiddleware, type CommentMiddlewareAction, type CommentMiddlewareMap, type CommentMiddlewareNext, type CommentRecord, type CommentRequest, type CommentServiceEventMap, type CommentUserProviderContext, type CommentWriteRequest, type CreateCommentDatabaseResult, type CreateReplyDatabaseInput, type CreateReplyDatabaseResult, type CreateRootDatabaseInput, type DeleteCommentDatabaseInput, type DeleteCommentDatabaseResult, type DeleteCommentInput, type DeleteCommentMiddlewareContext, type DeleteCommentMiddlewareTarget, type DeleteCommentRequest, type EditCommentDatabaseInput, type EditCommentDatabaseResult, type EditCommentInput, type EditCommentMiddlewareContext, type EditCommentRequest, type GetCommentDatabaseInput, type ICommentDatabaseAdapter, type ICommentUserProvider, type IUniverCommentService, type ListCommentsInput, type ListCommentsMiddlewareContext, type ListCommentsRequest, type ListThreadsDatabaseInput, type ReplyCommentInput, type ReplyCommentMiddlewareContext, type ReplyCommentRequest, type SetThreadSolvedDatabaseInput, type SetThreadSolvedDatabaseResult, type SetThreadSolvedInput, type SetThreadSolvedMiddlewareContext, type SetThreadSolvedRequest, UniverCommentService, type UniverCommentServiceOptions };
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ function _0x1f9f(_0x268e59,_0x5e3af7){_0x268e59=_0x268e59-0x1a2;const _0x19b024=_0x19b0();let _0x1f9fda=_0x19b024[_0x268e59];return _0x1f9fda;}function _0x19b0(){const _0x14919f=['447223rJWOfH','7249332ONRbVt','5160951nAbyUl','37125TDMBsm','18446IQLlnF','66oJOUPi','457295ovMZAO','173TFXlAx','1021056zkYQIo'];_0x19b0=function(){return _0x14919f;};return _0x19b0();}(function(_0x178f51,_0x4cd2e3){const _0x5f32e9=_0x1f9f,_0x2d3b7a=_0x178f51();while(!![]){try{const _0x4253b2=-parseInt(_0x5f32e9(0x1a4))/0x1*(parseInt(_0x5f32e9(0x1aa))/0x2)+parseInt(_0x5f32e9(0x1a8))/0x3+parseInt(_0x5f32e9(0x1a7))/0x4+parseInt(_0x5f32e9(0x1a3))/0x5*(-parseInt(_0x5f32e9(0x1a2))/0x6)+-parseInt(_0x5f32e9(0x1a6))/0x7+parseInt(_0x5f32e9(0x1a5))/0x8+parseInt(_0x5f32e9(0x1a9))/0x9;if(_0x4253b2===_0x4cd2e3)break;else _0x2d3b7a['push'](_0x2d3b7a['shift']());}catch(_0x248efe){_0x2d3b7a['push'](_0x2d3b7a['shift']());}}}(_0x19b0,0xf3dea));import _0xe5d1ba from'./index.cjs';const facadeExport0=_0xe5d1ba['UniverCommentService'];export{facadeExport0 as UniverCommentService};
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@univerjs-pro/collaboration-comment-service",
3
+ "type": "module",
4
+ "version": "1.0.0-beta.2",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.cjs"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "dependencies": {
19
+ "@univerjs-pro/collaboration-service": "1.0.0-beta.2",
20
+ "@univerjs/protocol": "1.0.0-beta.2"
21
+ }
22
+ }