@claudian-collab/protocol 1.0.0
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/LICENSE +21 -0
- package/README.md +105 -0
- package/dist/CollabCloudBinding.d.ts +106 -0
- package/dist/CollabCloudBinding.js +431 -0
- package/dist/CollabCloudProjectEvent.d.ts +41 -0
- package/dist/CollabCloudProjectEvent.js +133 -0
- package/dist/CollabCloudProjectSnapshot.d.ts +42 -0
- package/dist/CollabCloudProjectSnapshot.js +281 -0
- package/dist/CollabConstants.d.ts +32 -0
- package/dist/CollabConstants.js +34 -0
- package/dist/CollabControlOperationCodecs.d.ts +28 -0
- package/dist/CollabControlOperationCodecs.js +63 -0
- package/dist/CollabError.d.ts +27 -0
- package/dist/CollabError.js +165 -0
- package/dist/CollabMarkdownProse.d.ts +1 -0
- package/dist/CollabMarkdownProse.js +57 -0
- package/dist/CollabMemberMentionParser.d.ts +6 -0
- package/dist/CollabMemberMentionParser.js +51 -0
- package/dist/CollabProtocol.d.ts +144 -0
- package/dist/CollabProtocol.js +67 -0
- package/dist/CollabRequestTicketRequestCodecs.d.ts +5 -0
- package/dist/CollabRequestTicketRequestCodecs.js +266 -0
- package/dist/CollabRequestTicketResponseCodecs.d.ts +15 -0
- package/dist/CollabRequestTicketResponseCodecs.js +368 -0
- package/dist/CollabTicketReferenceParser.d.ts +24 -0
- package/dist/CollabTicketReferenceParser.js +56 -0
- package/dist/CollabValidation.d.ts +5 -0
- package/dist/CollabValidation.js +26 -0
- package/dist/DevelopmentBootstrap.d.ts +199 -0
- package/dist/DevelopmentBootstrap.js +544 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +67 -0
- package/dist/types.d.ts +138 -0
- package/dist/types.js +11 -0
- package/package.json +66 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const COLLAB_ERROR_CODES: readonly ["protocol-version-unsupported", "protocol-payload-invalid", "project-not-found", "quota-exceeded", "authentication-failed", "authorization-denied", "membership-revoked", "stale-main", "stale-request-head", "personal-ref-diverged", "content-conflict", "description-required", "request-not-open", "request-head-not-pushed", "ticket-not-found", "resolving-ticket-reference-not-found", "ticket-not-open", "stale-ticket", "stale-request-metadata", "authority-not-synchronized", "idempotency-conflict", "acceptance-recovery-required", "authority-integrity-error", "operation-timeout", "operation-failed"];
|
|
2
|
+
export type CollabErrorCode = typeof COLLAB_ERROR_CODES[number];
|
|
3
|
+
export type CollabErrorGroup = 'setup' | 'path' | 'authorization' | 'state' | 'integrity' | 'operation';
|
|
4
|
+
declare const COLLAB_RECOVERY_ACTIONS: readonly ["retry", "review-conflicts", "request-access"];
|
|
5
|
+
export type CollabRecoveryAction = typeof COLLAB_RECOVERY_ACTIONS[number];
|
|
6
|
+
export type CollabDiagnosticValue = string | number | boolean | null | readonly CollabDiagnosticValue[] | {
|
|
7
|
+
readonly [key: string]: CollabDiagnosticValue;
|
|
8
|
+
};
|
|
9
|
+
export type CollabDiagnosticContext = Readonly<Record<string, CollabDiagnosticValue>>;
|
|
10
|
+
export declare function sanitizeCollabDiagnosticContext(context?: Readonly<Record<string, unknown>>): CollabDiagnosticContext;
|
|
11
|
+
export declare function collabErrorGroup(code: CollabErrorCode): CollabErrorGroup;
|
|
12
|
+
export interface CollabErrorOptions {
|
|
13
|
+
code: CollabErrorCode;
|
|
14
|
+
safeContext?: Readonly<Record<string, unknown>>;
|
|
15
|
+
recoveryActions?: readonly CollabRecoveryAction[];
|
|
16
|
+
cause?: unknown;
|
|
17
|
+
}
|
|
18
|
+
export declare class CollabError extends Error {
|
|
19
|
+
readonly code: CollabErrorCode;
|
|
20
|
+
readonly group: CollabErrorGroup;
|
|
21
|
+
readonly safeContext: CollabDiagnosticContext;
|
|
22
|
+
readonly recoveryActions: readonly CollabRecoveryAction[];
|
|
23
|
+
readonly cause?: unknown;
|
|
24
|
+
constructor(options: CollabErrorOptions);
|
|
25
|
+
toJSON(): Readonly<Record<string, unknown>>;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CollabError = exports.COLLAB_ERROR_CODES = void 0;
|
|
4
|
+
exports.sanitizeCollabDiagnosticContext = sanitizeCollabDiagnosticContext;
|
|
5
|
+
exports.collabErrorGroup = collabErrorGroup;
|
|
6
|
+
exports.COLLAB_ERROR_CODES = Object.freeze([
|
|
7
|
+
'protocol-version-unsupported',
|
|
8
|
+
'protocol-payload-invalid',
|
|
9
|
+
'project-not-found',
|
|
10
|
+
'quota-exceeded',
|
|
11
|
+
'authentication-failed',
|
|
12
|
+
'authorization-denied',
|
|
13
|
+
'membership-revoked',
|
|
14
|
+
'stale-main',
|
|
15
|
+
'stale-request-head',
|
|
16
|
+
'personal-ref-diverged',
|
|
17
|
+
'content-conflict',
|
|
18
|
+
'description-required',
|
|
19
|
+
'request-not-open',
|
|
20
|
+
'request-head-not-pushed',
|
|
21
|
+
'ticket-not-found',
|
|
22
|
+
'resolving-ticket-reference-not-found',
|
|
23
|
+
'ticket-not-open',
|
|
24
|
+
'stale-ticket',
|
|
25
|
+
'stale-request-metadata',
|
|
26
|
+
'authority-not-synchronized',
|
|
27
|
+
'idempotency-conflict',
|
|
28
|
+
'acceptance-recovery-required',
|
|
29
|
+
'authority-integrity-error',
|
|
30
|
+
'operation-timeout',
|
|
31
|
+
'operation-failed',
|
|
32
|
+
]);
|
|
33
|
+
const COLLAB_RECOVERY_ACTIONS = Object.freeze([
|
|
34
|
+
'retry',
|
|
35
|
+
'review-conflicts',
|
|
36
|
+
'request-access',
|
|
37
|
+
]);
|
|
38
|
+
const COLLAB_ERROR_CODE_SET = new Set(exports.COLLAB_ERROR_CODES);
|
|
39
|
+
const COLLAB_RECOVERY_ACTION_SET = new Set(COLLAB_RECOVERY_ACTIONS);
|
|
40
|
+
const SAFE_CONTEXT_RULES = Object.freeze({
|
|
41
|
+
endpoint: 'path',
|
|
42
|
+
exitCode: 'number',
|
|
43
|
+
field: 'token',
|
|
44
|
+
kind: 'token',
|
|
45
|
+
limit: 'number',
|
|
46
|
+
operation: 'token',
|
|
47
|
+
operationId: 'id',
|
|
48
|
+
path: 'path',
|
|
49
|
+
projectId: 'id',
|
|
50
|
+
quota: 'token',
|
|
51
|
+
reason: 'token',
|
|
52
|
+
receivedVersion: 'number',
|
|
53
|
+
recordKind: 'token',
|
|
54
|
+
retiredAt: 'timestamp',
|
|
55
|
+
side: 'token',
|
|
56
|
+
status: 'token',
|
|
57
|
+
supportedVersion: 'number',
|
|
58
|
+
ticketNumber: 'number',
|
|
59
|
+
});
|
|
60
|
+
const SAFE_TOKEN_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
61
|
+
const SAFE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|
|
62
|
+
function sanitizeAllowedValue(rule, value) {
|
|
63
|
+
switch (rule) {
|
|
64
|
+
case 'number':
|
|
65
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
66
|
+
case 'path':
|
|
67
|
+
return typeof value === 'string' ? '[PATH]' : null;
|
|
68
|
+
case 'timestamp':
|
|
69
|
+
return typeof value === 'string'
|
|
70
|
+
&& !Number.isNaN(Date.parse(value))
|
|
71
|
+
&& new Date(value).toISOString() === value
|
|
72
|
+
? value
|
|
73
|
+
: null;
|
|
74
|
+
case 'id':
|
|
75
|
+
return typeof value === 'string' && SAFE_ID_PATTERN.test(value) ? value : null;
|
|
76
|
+
case 'token':
|
|
77
|
+
return typeof value === 'string' && SAFE_TOKEN_PATTERN.test(value) ? value : null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function sanitizeCollabDiagnosticContext(context = {}) {
|
|
81
|
+
const result = {};
|
|
82
|
+
for (const [key, value] of Object.entries(context)) {
|
|
83
|
+
const rule = SAFE_CONTEXT_RULES[key];
|
|
84
|
+
if (!rule)
|
|
85
|
+
continue;
|
|
86
|
+
const sanitized = sanitizeAllowedValue(rule, value);
|
|
87
|
+
if (sanitized !== null)
|
|
88
|
+
result[key] = sanitized;
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
function collabErrorGroup(code) {
|
|
93
|
+
switch (code) {
|
|
94
|
+
case 'protocol-version-unsupported':
|
|
95
|
+
case 'protocol-payload-invalid':
|
|
96
|
+
return 'setup';
|
|
97
|
+
case 'project-not-found':
|
|
98
|
+
case 'quota-exceeded':
|
|
99
|
+
return 'path';
|
|
100
|
+
case 'authentication-failed':
|
|
101
|
+
case 'authorization-denied':
|
|
102
|
+
case 'membership-revoked':
|
|
103
|
+
return 'authorization';
|
|
104
|
+
case 'stale-main':
|
|
105
|
+
case 'stale-request-head':
|
|
106
|
+
case 'personal-ref-diverged':
|
|
107
|
+
case 'content-conflict':
|
|
108
|
+
case 'description-required':
|
|
109
|
+
case 'request-not-open':
|
|
110
|
+
case 'request-head-not-pushed':
|
|
111
|
+
case 'ticket-not-found':
|
|
112
|
+
case 'resolving-ticket-reference-not-found':
|
|
113
|
+
case 'ticket-not-open':
|
|
114
|
+
case 'stale-ticket':
|
|
115
|
+
case 'stale-request-metadata':
|
|
116
|
+
case 'authority-not-synchronized':
|
|
117
|
+
case 'idempotency-conflict':
|
|
118
|
+
return 'state';
|
|
119
|
+
case 'acceptance-recovery-required':
|
|
120
|
+
case 'authority-integrity-error':
|
|
121
|
+
return 'integrity';
|
|
122
|
+
case 'operation-timeout':
|
|
123
|
+
case 'operation-failed':
|
|
124
|
+
return 'operation';
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
class CollabError extends Error {
|
|
128
|
+
code;
|
|
129
|
+
group;
|
|
130
|
+
safeContext;
|
|
131
|
+
recoveryActions;
|
|
132
|
+
constructor(options) {
|
|
133
|
+
if (!COLLAB_ERROR_CODE_SET.has(options.code)) {
|
|
134
|
+
throw new TypeError('Unsupported Collab error code');
|
|
135
|
+
}
|
|
136
|
+
if (!(options.recoveryActions ?? []).every(action => COLLAB_RECOVERY_ACTION_SET.has(action))) {
|
|
137
|
+
throw new TypeError('Unsupported Collab recovery action');
|
|
138
|
+
}
|
|
139
|
+
super(`collab.error.${options.code}`);
|
|
140
|
+
this.name = 'CollabError';
|
|
141
|
+
this.code = options.code;
|
|
142
|
+
this.group = collabErrorGroup(options.code);
|
|
143
|
+
this.safeContext = Object.freeze(sanitizeCollabDiagnosticContext(options.safeContext));
|
|
144
|
+
this.recoveryActions = Object.freeze([...(options.recoveryActions ?? [])]);
|
|
145
|
+
if (options.cause !== undefined) {
|
|
146
|
+
Object.defineProperty(this, 'cause', {
|
|
147
|
+
configurable: false,
|
|
148
|
+
enumerable: false,
|
|
149
|
+
value: options.cause,
|
|
150
|
+
writable: false,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
toJSON() {
|
|
155
|
+
return {
|
|
156
|
+
name: this.name,
|
|
157
|
+
code: this.code,
|
|
158
|
+
group: this.group,
|
|
159
|
+
message: this.message,
|
|
160
|
+
safeContext: this.safeContext,
|
|
161
|
+
recoveryActions: this.recoveryActions,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
exports.CollabError = CollabError;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function maskCollabMarkdownProse(markdown: string): string;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.maskCollabMarkdownProse = maskCollabMarkdownProse;
|
|
4
|
+
const markdown_1 = require("@lezer/markdown");
|
|
5
|
+
const MASKED_MARKDOWN_NODES = new Set([
|
|
6
|
+
'CodeBlock',
|
|
7
|
+
'Comment',
|
|
8
|
+
'CommentBlock',
|
|
9
|
+
'FencedCode',
|
|
10
|
+
'HTMLBlock',
|
|
11
|
+
'HTMLTag',
|
|
12
|
+
'Image',
|
|
13
|
+
'InlineCode',
|
|
14
|
+
'LinkLabel',
|
|
15
|
+
'LinkReference',
|
|
16
|
+
'LinkTitle',
|
|
17
|
+
'ProcessingInstruction',
|
|
18
|
+
'ProcessingInstructionBlock',
|
|
19
|
+
'URL',
|
|
20
|
+
]);
|
|
21
|
+
const BOUNDARY_MASKED_MARKDOWN_NODES = new Set(['Entity']);
|
|
22
|
+
function maskCollabMarkdownProse(markdown) {
|
|
23
|
+
const maskedRanges = [];
|
|
24
|
+
const escapes = [];
|
|
25
|
+
markdown_1.parser.parse(markdown).iterate({
|
|
26
|
+
enter(node) {
|
|
27
|
+
if (node.name === 'Escape') {
|
|
28
|
+
escapes.push({ from: node.from, to: node.to });
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
if (MASKED_MARKDOWN_NODES.has(node.name)
|
|
32
|
+
|| BOUNDARY_MASKED_MARKDOWN_NODES.has(node.name)) {
|
|
33
|
+
maskedRanges.push({
|
|
34
|
+
from: node.from,
|
|
35
|
+
preservesBoundary: BOUNDARY_MASKED_MARKDOWN_NODES.has(node.name),
|
|
36
|
+
to: node.to,
|
|
37
|
+
});
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
const output = markdown.split('');
|
|
44
|
+
for (const range of maskedRanges) {
|
|
45
|
+
for (let offset = range.from; offset < range.to; offset += 1) {
|
|
46
|
+
if (markdown[offset] !== '\n' && markdown[offset] !== '\r') {
|
|
47
|
+
output[offset] = range.preservesBoundary ? '_' : ' ';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
for (const escape of escapes) {
|
|
52
|
+
const escaped = markdown.slice(escape.from + 1, escape.to);
|
|
53
|
+
output[escape.from] = ' ';
|
|
54
|
+
output[escape.from + 1] = escaped === '@' || escaped === '#' ? '_' : escaped;
|
|
55
|
+
}
|
|
56
|
+
return output.join('');
|
|
57
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CollabMemberId } from './types';
|
|
2
|
+
export interface CollabMemberMentionTarget {
|
|
3
|
+
readonly displayName: string;
|
|
4
|
+
readonly memberId: CollabMemberId;
|
|
5
|
+
}
|
|
6
|
+
export declare function parseCollabMemberMentions(markdown: string, targets: readonly CollabMemberMentionTarget[]): readonly CollabMemberId[];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseCollabMemberMentions = parseCollabMemberMentions;
|
|
4
|
+
const CollabMarkdownProse_1 = require("./CollabMarkdownProse");
|
|
5
|
+
const MENTION_ADJACENT_CHARACTER = /[\p{L}\p{N}_@-]/u;
|
|
6
|
+
function parseCollabMemberMentions(markdown, targets) {
|
|
7
|
+
const candidates = mentionCandidates(targets);
|
|
8
|
+
if (candidates.length === 0)
|
|
9
|
+
return [];
|
|
10
|
+
const prose = (0, CollabMarkdownProse_1.maskCollabMarkdownProse)(markdown);
|
|
11
|
+
const memberIds = new Set();
|
|
12
|
+
let searchFrom = 0;
|
|
13
|
+
while (searchFrom < prose.length) {
|
|
14
|
+
const mentionStart = prose.indexOf('@', searchFrom);
|
|
15
|
+
if (mentionStart < 0)
|
|
16
|
+
break;
|
|
17
|
+
searchFrom = mentionStart + 1;
|
|
18
|
+
const preceding = prose[mentionStart - 1];
|
|
19
|
+
if (preceding !== undefined && MENTION_ADJACENT_CHARACTER.test(preceding))
|
|
20
|
+
continue;
|
|
21
|
+
const candidate = candidates.find(entry => {
|
|
22
|
+
if (!prose.startsWith(entry.name, mentionStart + 1))
|
|
23
|
+
return false;
|
|
24
|
+
const following = prose[mentionStart + entry.name.length + 1];
|
|
25
|
+
return following === undefined || !MENTION_ADJACENT_CHARACTER.test(following);
|
|
26
|
+
});
|
|
27
|
+
if (!candidate)
|
|
28
|
+
continue;
|
|
29
|
+
memberIds.add(candidate.memberId);
|
|
30
|
+
searchFrom = mentionStart + candidate.name.length + 1;
|
|
31
|
+
}
|
|
32
|
+
return [...memberIds];
|
|
33
|
+
}
|
|
34
|
+
function mentionCandidates(targets) {
|
|
35
|
+
const byName = new Map();
|
|
36
|
+
for (const target of targets) {
|
|
37
|
+
const name = target.displayName.trim();
|
|
38
|
+
if (name.length === 0)
|
|
39
|
+
continue;
|
|
40
|
+
const existing = byName.get(name);
|
|
41
|
+
if (existing === undefined) {
|
|
42
|
+
byName.set(name, { memberId: target.memberId, name });
|
|
43
|
+
}
|
|
44
|
+
else if (existing?.memberId !== target.memberId) {
|
|
45
|
+
byName.set(name, null);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return [...byName.values()]
|
|
49
|
+
.filter((candidate) => candidate !== null)
|
|
50
|
+
.sort((left, right) => right.name.length - left.name.length);
|
|
51
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { type CollabProtocolVersion } from './CollabConstants';
|
|
2
|
+
import { CollabError } from './CollabError';
|
|
3
|
+
import type { CollabChangeRequest, CollabComment, CollabCommentPage, CollabGitOid, CollabIdempotencyKey, CollabProjectId, CollabRequestDetail, CollabRequestId, CollabResolvingTicketExpectation, CollabTicketAcceptedRelationPage, CollabTicketComment, CollabTicketCommentPage, CollabTicketDetail, CollabTicketId, CollabTicketPage, CollabTicketStatus, CollabTicketSummary } from './types';
|
|
4
|
+
export interface CollabProtocolEnvelope<T> {
|
|
5
|
+
protocolVersion: CollabProtocolVersion;
|
|
6
|
+
requestId: string;
|
|
7
|
+
data: T;
|
|
8
|
+
}
|
|
9
|
+
export type CollabDecodeFailure = {
|
|
10
|
+
status: 'invalid';
|
|
11
|
+
error: CollabError;
|
|
12
|
+
} | {
|
|
13
|
+
status: 'unsupported-version';
|
|
14
|
+
receivedVersion: number;
|
|
15
|
+
error: CollabError;
|
|
16
|
+
};
|
|
17
|
+
export type CollabDecodeResult<T> = {
|
|
18
|
+
status: 'ok';
|
|
19
|
+
value: T;
|
|
20
|
+
} | CollabDecodeFailure;
|
|
21
|
+
export interface CollabMutationContext {
|
|
22
|
+
projectId: CollabProjectId;
|
|
23
|
+
idempotencyKey: CollabIdempotencyKey;
|
|
24
|
+
}
|
|
25
|
+
export interface GetRequestRequest {
|
|
26
|
+
projectId: CollabProjectId;
|
|
27
|
+
requestId: CollabRequestId;
|
|
28
|
+
}
|
|
29
|
+
export interface EnsureMyRequestRequest extends CollabMutationContext {
|
|
30
|
+
expectedMainOid: CollabGitOid;
|
|
31
|
+
headOid: CollabGitOid;
|
|
32
|
+
description: string;
|
|
33
|
+
}
|
|
34
|
+
export interface EnsureMyRequestResponse {
|
|
35
|
+
mainOid: CollabGitOid;
|
|
36
|
+
request: CollabChangeRequest;
|
|
37
|
+
}
|
|
38
|
+
export interface CreateCommentRequest extends CollabMutationContext {
|
|
39
|
+
requestId: CollabRequestId;
|
|
40
|
+
body: string;
|
|
41
|
+
}
|
|
42
|
+
export interface CreateCommentResponse {
|
|
43
|
+
comment: CollabComment;
|
|
44
|
+
request: CollabChangeRequest;
|
|
45
|
+
}
|
|
46
|
+
export interface ListTicketsRequest {
|
|
47
|
+
projectId: CollabProjectId;
|
|
48
|
+
status: CollabTicketStatus | 'all';
|
|
49
|
+
cursor?: string;
|
|
50
|
+
limit?: number;
|
|
51
|
+
}
|
|
52
|
+
export interface ListRequestCommentsRequest {
|
|
53
|
+
projectId: CollabProjectId;
|
|
54
|
+
requestId: CollabRequestId;
|
|
55
|
+
cursor?: string;
|
|
56
|
+
limit?: number;
|
|
57
|
+
}
|
|
58
|
+
export interface ListTicketCommentsRequest {
|
|
59
|
+
projectId: CollabProjectId;
|
|
60
|
+
ticketId: CollabTicketId;
|
|
61
|
+
cursor?: string;
|
|
62
|
+
limit?: number;
|
|
63
|
+
}
|
|
64
|
+
export interface ListTicketAcceptedRelationsRequest {
|
|
65
|
+
projectId: CollabProjectId;
|
|
66
|
+
ticketId: CollabTicketId;
|
|
67
|
+
cursor?: string;
|
|
68
|
+
limit?: number;
|
|
69
|
+
}
|
|
70
|
+
export interface GetTicketRequest {
|
|
71
|
+
projectId: CollabProjectId;
|
|
72
|
+
ticketId: CollabTicketId;
|
|
73
|
+
}
|
|
74
|
+
export interface CreateTicketRequest extends CollabMutationContext {
|
|
75
|
+
title: string;
|
|
76
|
+
body: string;
|
|
77
|
+
}
|
|
78
|
+
export interface CreateTicketResponse {
|
|
79
|
+
ticket: CollabTicketDetail;
|
|
80
|
+
}
|
|
81
|
+
export interface UpdateTicketContentRequest extends CollabMutationContext {
|
|
82
|
+
ticketId: CollabTicketId;
|
|
83
|
+
expectedRevision: number;
|
|
84
|
+
title: string;
|
|
85
|
+
body: string;
|
|
86
|
+
}
|
|
87
|
+
export interface CreateTicketCommentRequest extends CollabMutationContext {
|
|
88
|
+
ticketId: CollabTicketId;
|
|
89
|
+
body: string;
|
|
90
|
+
}
|
|
91
|
+
export interface CreateTicketCommentResponse {
|
|
92
|
+
comment: CollabTicketComment;
|
|
93
|
+
ticket: CollabTicketSummary;
|
|
94
|
+
}
|
|
95
|
+
export interface ChangeTicketStatusRequest extends CollabMutationContext {
|
|
96
|
+
ticketId: CollabTicketId;
|
|
97
|
+
expectedRevision: number;
|
|
98
|
+
}
|
|
99
|
+
export interface TicketMutationResponse {
|
|
100
|
+
ticket: CollabTicketSummary;
|
|
101
|
+
}
|
|
102
|
+
export interface UpdateMyRequestMetadataRequest extends CollabMutationContext {
|
|
103
|
+
requestId: CollabRequestId;
|
|
104
|
+
expectedHeadOid: CollabGitOid;
|
|
105
|
+
expectedRequestRevision: number;
|
|
106
|
+
description: string;
|
|
107
|
+
}
|
|
108
|
+
export interface UpdateMyRequestMetadataResponse {
|
|
109
|
+
request: CollabChangeRequest;
|
|
110
|
+
}
|
|
111
|
+
export interface AcceptRequest extends CollabMutationContext {
|
|
112
|
+
requestId: CollabRequestId;
|
|
113
|
+
expectedMainOid: CollabGitOid;
|
|
114
|
+
expectedHeadOid: CollabGitOid;
|
|
115
|
+
expectedRequestRevision: number;
|
|
116
|
+
expectedResolvingTickets: readonly CollabResolvingTicketExpectation[];
|
|
117
|
+
}
|
|
118
|
+
export interface AcceptResponse {
|
|
119
|
+
request: CollabChangeRequest;
|
|
120
|
+
mainOid: CollabGitOid;
|
|
121
|
+
mergeCommitOid: CollabGitOid;
|
|
122
|
+
}
|
|
123
|
+
export interface CollabControlOperationDefinition<Request, Response> {
|
|
124
|
+
request: Request;
|
|
125
|
+
response: Response;
|
|
126
|
+
}
|
|
127
|
+
export interface CollabControlOperationMap {
|
|
128
|
+
getRequest: CollabControlOperationDefinition<GetRequestRequest, CollabRequestDetail>;
|
|
129
|
+
listRequestComments: CollabControlOperationDefinition<ListRequestCommentsRequest, CollabCommentPage>;
|
|
130
|
+
ensureMyRequest: CollabControlOperationDefinition<EnsureMyRequestRequest, EnsureMyRequestResponse>;
|
|
131
|
+
createComment: CollabControlOperationDefinition<CreateCommentRequest, CreateCommentResponse>;
|
|
132
|
+
listTickets: CollabControlOperationDefinition<ListTicketsRequest, CollabTicketPage>;
|
|
133
|
+
getTicket: CollabControlOperationDefinition<GetTicketRequest, CollabTicketDetail>;
|
|
134
|
+
listTicketComments: CollabControlOperationDefinition<ListTicketCommentsRequest, CollabTicketCommentPage>;
|
|
135
|
+
listTicketAcceptedRelations: CollabControlOperationDefinition<ListTicketAcceptedRelationsRequest, CollabTicketAcceptedRelationPage>;
|
|
136
|
+
createTicket: CollabControlOperationDefinition<CreateTicketRequest, CreateTicketResponse>;
|
|
137
|
+
updateTicketContent: CollabControlOperationDefinition<UpdateTicketContentRequest, TicketMutationResponse>;
|
|
138
|
+
createTicketComment: CollabControlOperationDefinition<CreateTicketCommentRequest, CreateTicketCommentResponse>;
|
|
139
|
+
closeTicket: CollabControlOperationDefinition<ChangeTicketStatusRequest, TicketMutationResponse>;
|
|
140
|
+
reopenTicket: CollabControlOperationDefinition<ChangeTicketStatusRequest, TicketMutationResponse>;
|
|
141
|
+
updateMyRequestMetadata: CollabControlOperationDefinition<UpdateMyRequestMetadataRequest, UpdateMyRequestMetadataResponse>;
|
|
142
|
+
acceptRequest: CollabControlOperationDefinition<AcceptRequest, AcceptResponse>;
|
|
143
|
+
}
|
|
144
|
+
export declare function decodeCollabProtocolEnvelope<T = unknown>(input: unknown): CollabDecodeResult<CollabProtocolEnvelope<T>>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.decodeCollabProtocolEnvelope = decodeCollabProtocolEnvelope;
|
|
4
|
+
const CollabConstants_1 = require("./CollabConstants");
|
|
5
|
+
const CollabError_1 = require("./CollabError");
|
|
6
|
+
function isRecord(value) {
|
|
7
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
function hasExactKeys(record, required) {
|
|
10
|
+
const allowed = new Set(required);
|
|
11
|
+
return required.every(key => Object.hasOwn(record, key))
|
|
12
|
+
&& Object.keys(record).every(key => allowed.has(key));
|
|
13
|
+
}
|
|
14
|
+
function requiredString(record, field) {
|
|
15
|
+
const value = record[field];
|
|
16
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
17
|
+
}
|
|
18
|
+
function invalidPayload(field) {
|
|
19
|
+
return {
|
|
20
|
+
status: 'invalid',
|
|
21
|
+
error: new CollabError_1.CollabError({
|
|
22
|
+
code: 'protocol-payload-invalid',
|
|
23
|
+
safeContext: { field },
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function unsupportedVersion(receivedVersion) {
|
|
28
|
+
return {
|
|
29
|
+
status: 'unsupported-version',
|
|
30
|
+
receivedVersion,
|
|
31
|
+
error: new CollabError_1.CollabError({
|
|
32
|
+
code: 'protocol-version-unsupported',
|
|
33
|
+
safeContext: {
|
|
34
|
+
receivedVersion,
|
|
35
|
+
supportedVersion: CollabConstants_1.COLLAB_PROTOCOL_VERSION,
|
|
36
|
+
},
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function decodeVersion(record) {
|
|
41
|
+
const version = record.protocolVersion;
|
|
42
|
+
if (typeof version !== 'number' || !Number.isInteger(version)) {
|
|
43
|
+
return invalidPayload('protocolVersion');
|
|
44
|
+
}
|
|
45
|
+
if (version !== CollabConstants_1.COLLAB_PROTOCOL_VERSION)
|
|
46
|
+
return unsupportedVersion(version);
|
|
47
|
+
return { status: 'ok', value: CollabConstants_1.COLLAB_PROTOCOL_VERSION };
|
|
48
|
+
}
|
|
49
|
+
function decodeCollabProtocolEnvelope(input) {
|
|
50
|
+
if (!isRecord(input) || !hasExactKeys(input, ['protocolVersion', 'requestId', 'data'])) {
|
|
51
|
+
return invalidPayload('envelope');
|
|
52
|
+
}
|
|
53
|
+
const version = decodeVersion(input);
|
|
54
|
+
if (version.status !== 'ok')
|
|
55
|
+
return version;
|
|
56
|
+
const requestId = requiredString(input, 'requestId');
|
|
57
|
+
if (!requestId)
|
|
58
|
+
return invalidPayload('requestId');
|
|
59
|
+
return {
|
|
60
|
+
status: 'ok',
|
|
61
|
+
value: {
|
|
62
|
+
data: input.data,
|
|
63
|
+
protocolVersion: version.value,
|
|
64
|
+
requestId,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CollabControlOperationMap, CollabDecodeResult } from './CollabProtocol';
|
|
2
|
+
export type CollabRequestTicketOperation = 'acceptRequest' | 'closeTicket' | 'createComment' | 'createTicket' | 'createTicketComment' | 'ensureMyRequest' | 'getRequest' | 'getTicket' | 'listRequestComments' | 'listTicketAcceptedRelations' | 'listTicketComments' | 'listTickets' | 'reopenTicket' | 'updateMyRequestMetadata' | 'updateTicketContent';
|
|
3
|
+
type OperationRequest<Operation extends CollabRequestTicketOperation> = CollabControlOperationMap[Operation]['request'];
|
|
4
|
+
export declare function decodeCollabRequestTicketOperationRequest<Operation extends CollabRequestTicketOperation>(operation: Operation, input: unknown): CollabDecodeResult<OperationRequest<Operation>>;
|
|
5
|
+
export {};
|