@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.
Files changed (35) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +105 -0
  3. package/dist/CollabCloudBinding.d.ts +106 -0
  4. package/dist/CollabCloudBinding.js +431 -0
  5. package/dist/CollabCloudProjectEvent.d.ts +41 -0
  6. package/dist/CollabCloudProjectEvent.js +133 -0
  7. package/dist/CollabCloudProjectSnapshot.d.ts +42 -0
  8. package/dist/CollabCloudProjectSnapshot.js +281 -0
  9. package/dist/CollabConstants.d.ts +32 -0
  10. package/dist/CollabConstants.js +34 -0
  11. package/dist/CollabControlOperationCodecs.d.ts +28 -0
  12. package/dist/CollabControlOperationCodecs.js +63 -0
  13. package/dist/CollabError.d.ts +27 -0
  14. package/dist/CollabError.js +165 -0
  15. package/dist/CollabMarkdownProse.d.ts +1 -0
  16. package/dist/CollabMarkdownProse.js +57 -0
  17. package/dist/CollabMemberMentionParser.d.ts +6 -0
  18. package/dist/CollabMemberMentionParser.js +51 -0
  19. package/dist/CollabProtocol.d.ts +144 -0
  20. package/dist/CollabProtocol.js +67 -0
  21. package/dist/CollabRequestTicketRequestCodecs.d.ts +5 -0
  22. package/dist/CollabRequestTicketRequestCodecs.js +266 -0
  23. package/dist/CollabRequestTicketResponseCodecs.d.ts +15 -0
  24. package/dist/CollabRequestTicketResponseCodecs.js +368 -0
  25. package/dist/CollabTicketReferenceParser.d.ts +24 -0
  26. package/dist/CollabTicketReferenceParser.js +56 -0
  27. package/dist/CollabValidation.d.ts +5 -0
  28. package/dist/CollabValidation.js +26 -0
  29. package/dist/DevelopmentBootstrap.d.ts +199 -0
  30. package/dist/DevelopmentBootstrap.js +544 -0
  31. package/dist/index.d.ts +24 -0
  32. package/dist/index.js +67 -0
  33. package/dist/types.d.ts +138 -0
  34. package/dist/types.js +11 -0
  35. package/package.json +66 -0
@@ -0,0 +1,266 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.decodeCollabRequestTicketOperationRequest = decodeCollabRequestTicketOperationRequest;
4
+ const CollabConstants_1 = require("./CollabConstants");
5
+ const CollabError_1 = require("./CollabError");
6
+ const CollabValidation_1 = require("./CollabValidation");
7
+ function invalid(reason) {
8
+ return {
9
+ error: new CollabError_1.CollabError({
10
+ code: 'protocol-payload-invalid',
11
+ safeContext: { reason },
12
+ }),
13
+ status: 'invalid',
14
+ };
15
+ }
16
+ function isRecord(value) {
17
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
18
+ }
19
+ function hasExactKeys(value, required, optional = []) {
20
+ const allowed = new Set([...required, ...optional]);
21
+ return required.every(key => Object.hasOwn(value, key))
22
+ && Object.keys(value).every(key => allowed.has(key));
23
+ }
24
+ function isRevision(value, minimum = 0) {
25
+ return typeof value === 'number'
26
+ && Number.isSafeInteger(value)
27
+ && value >= minimum;
28
+ }
29
+ function isPageQuery(value, maxLimit = CollabConstants_1.COLLAB_LIMITS.maxCommentPageSize) {
30
+ return (value.cursor === undefined
31
+ || (typeof value.cursor === 'string'
32
+ && value.cursor.length > 0
33
+ && value.cursor.length <= CollabConstants_1.COLLAB_LIMITS.maxPageCursorUtf16))
34
+ && (value.limit === undefined
35
+ || (isRevision(value.limit, 1) && value.limit <= maxLimit));
36
+ }
37
+ function mutationContext(value) {
38
+ return (0, CollabValidation_1.isCollabProjectId)(value.projectId) && (0, CollabValidation_1.isCollabOpaqueId)(value.idempotencyKey);
39
+ }
40
+ function resolvingTickets(value) {
41
+ if (!Array.isArray(value) || value.length > CollabConstants_1.COLLAB_LIMITS.maxRequestTicketRelations)
42
+ return null;
43
+ const seen = new Set();
44
+ const result = [];
45
+ for (const entry of value) {
46
+ if (!isRecord(entry)
47
+ || !(0, CollabValidation_1.isCollabOpaqueId)(entry.ticketId)
48
+ || seen.has(entry.ticketId)
49
+ || !isRevision(entry.revision, 1))
50
+ return null;
51
+ seen.add(entry.ticketId);
52
+ result.push({ ticketId: entry.ticketId, revision: entry.revision });
53
+ }
54
+ return result;
55
+ }
56
+ function decodeRequestTicketRequest(operation, input) {
57
+ if (!isRecord(input))
58
+ return null;
59
+ switch (operation) {
60
+ case 'getRequest':
61
+ return (0, CollabValidation_1.isCollabProjectId)(input.projectId) && (0, CollabValidation_1.isCollabOpaqueId)(input.requestId)
62
+ ? { projectId: input.projectId, requestId: input.requestId }
63
+ : null;
64
+ case 'ensureMyRequest':
65
+ return mutationContext(input)
66
+ && (0, CollabValidation_1.isCollabGitOid)(input.expectedMainOid)
67
+ && (0, CollabValidation_1.isCollabGitOid)(input.headOid)
68
+ && typeof input.description === 'string'
69
+ && (0, CollabValidation_1.hasUtf8ByteLengthAtMost)(input.description, CollabConstants_1.COLLAB_LIMITS.maxRequestDescriptionBytes)
70
+ ? {
71
+ description: input.description,
72
+ expectedMainOid: input.expectedMainOid,
73
+ headOid: input.headOid,
74
+ idempotencyKey: input.idempotencyKey,
75
+ projectId: input.projectId,
76
+ }
77
+ : null;
78
+ case 'createComment':
79
+ return mutationContext(input)
80
+ && (0, CollabValidation_1.isCollabOpaqueId)(input.requestId)
81
+ && typeof input.body === 'string'
82
+ && (0, CollabValidation_1.hasUtf8ByteLengthAtMost)(input.body, CollabConstants_1.COLLAB_LIMITS.maxCommentBytes)
83
+ && input.anchor === undefined
84
+ ? {
85
+ body: input.body,
86
+ idempotencyKey: input.idempotencyKey,
87
+ projectId: input.projectId,
88
+ requestId: input.requestId,
89
+ }
90
+ : null;
91
+ case 'updateMyRequestMetadata':
92
+ return mutationContext(input)
93
+ && (0, CollabValidation_1.isCollabOpaqueId)(input.requestId)
94
+ && (0, CollabValidation_1.isCollabGitOid)(input.expectedHeadOid)
95
+ && isRevision(input.expectedRequestRevision)
96
+ && typeof input.description === 'string'
97
+ && (0, CollabValidation_1.hasUtf8ByteLengthAtMost)(input.description, CollabConstants_1.COLLAB_LIMITS.maxRequestDescriptionBytes)
98
+ ? {
99
+ description: input.description,
100
+ expectedHeadOid: input.expectedHeadOid,
101
+ expectedRequestRevision: input.expectedRequestRevision,
102
+ idempotencyKey: input.idempotencyKey,
103
+ projectId: input.projectId,
104
+ requestId: input.requestId,
105
+ }
106
+ : null;
107
+ case 'acceptRequest': {
108
+ const expectedResolvingTickets = resolvingTickets(input.expectedResolvingTickets);
109
+ return mutationContext(input)
110
+ && (0, CollabValidation_1.isCollabOpaqueId)(input.requestId)
111
+ && (0, CollabValidation_1.isCollabGitOid)(input.expectedMainOid)
112
+ && (0, CollabValidation_1.isCollabGitOid)(input.expectedHeadOid)
113
+ && isRevision(input.expectedRequestRevision)
114
+ && expectedResolvingTickets !== null
115
+ ? {
116
+ expectedHeadOid: input.expectedHeadOid,
117
+ expectedMainOid: input.expectedMainOid,
118
+ expectedRequestRevision: input.expectedRequestRevision,
119
+ expectedResolvingTickets,
120
+ idempotencyKey: input.idempotencyKey,
121
+ projectId: input.projectId,
122
+ requestId: input.requestId,
123
+ }
124
+ : null;
125
+ }
126
+ case 'listTickets':
127
+ return hasExactKeys(input, ['projectId', 'status'], ['cursor', 'limit'])
128
+ && (0, CollabValidation_1.isCollabProjectId)(input.projectId)
129
+ && (input.status === 'open' || input.status === 'closed' || input.status === 'all')
130
+ && isPageQuery(input, CollabConstants_1.COLLAB_LIMITS.maxTicketPageSize)
131
+ ? {
132
+ ...(input.cursor === undefined ? {} : { cursor: input.cursor }),
133
+ ...(input.limit === undefined ? {} : { limit: input.limit }),
134
+ projectId: input.projectId,
135
+ status: input.status,
136
+ }
137
+ : null;
138
+ case 'listRequestComments':
139
+ return hasExactKeys(input, ['projectId', 'requestId'], ['cursor', 'limit'])
140
+ && (0, CollabValidation_1.isCollabProjectId)(input.projectId)
141
+ && (0, CollabValidation_1.isCollabOpaqueId)(input.requestId)
142
+ && isPageQuery(input)
143
+ ? {
144
+ ...(input.cursor === undefined ? {} : { cursor: input.cursor }),
145
+ ...(input.limit === undefined ? {} : { limit: input.limit }),
146
+ projectId: input.projectId,
147
+ requestId: input.requestId,
148
+ }
149
+ : null;
150
+ case 'listTicketComments':
151
+ return hasExactKeys(input, ['projectId', 'ticketId'], ['cursor', 'limit'])
152
+ && (0, CollabValidation_1.isCollabProjectId)(input.projectId)
153
+ && (0, CollabValidation_1.isCollabOpaqueId)(input.ticketId)
154
+ && isPageQuery(input)
155
+ ? {
156
+ ...(input.cursor === undefined ? {} : { cursor: input.cursor }),
157
+ ...(input.limit === undefined ? {} : { limit: input.limit }),
158
+ projectId: input.projectId,
159
+ ticketId: input.ticketId,
160
+ }
161
+ : null;
162
+ case 'listTicketAcceptedRelations':
163
+ return hasExactKeys(input, ['projectId', 'ticketId'], ['cursor', 'limit'])
164
+ && (0, CollabValidation_1.isCollabProjectId)(input.projectId)
165
+ && (0, CollabValidation_1.isCollabOpaqueId)(input.ticketId)
166
+ && isPageQuery(input, CollabConstants_1.COLLAB_LIMITS.maxRelationsPerPage)
167
+ ? {
168
+ ...(input.cursor === undefined ? {} : { cursor: input.cursor }),
169
+ ...(input.limit === undefined ? {} : { limit: input.limit }),
170
+ projectId: input.projectId,
171
+ ticketId: input.ticketId,
172
+ }
173
+ : null;
174
+ case 'getTicket':
175
+ return hasExactKeys(input, ['projectId', 'ticketId'])
176
+ && (0, CollabValidation_1.isCollabProjectId)(input.projectId)
177
+ && (0, CollabValidation_1.isCollabOpaqueId)(input.ticketId)
178
+ ? { projectId: input.projectId, ticketId: input.ticketId }
179
+ : null;
180
+ case 'createTicket':
181
+ return hasExactKeys(input, ['body', 'idempotencyKey', 'projectId', 'title'])
182
+ && mutationContext(input)
183
+ && typeof input.body === 'string'
184
+ && typeof input.title === 'string'
185
+ && (0, CollabValidation_1.hasUtf8ByteLengthAtMost)(input.body, CollabConstants_1.COLLAB_LIMITS.maxTicketBodyBytes)
186
+ && input.title.length <= CollabConstants_1.COLLAB_LIMITS.maxTicketTitleUtf16
187
+ ? {
188
+ body: input.body,
189
+ idempotencyKey: input.idempotencyKey,
190
+ projectId: input.projectId,
191
+ title: input.title,
192
+ }
193
+ : null;
194
+ case 'updateTicketContent':
195
+ return hasExactKeys(input, [
196
+ 'body', 'expectedRevision', 'idempotencyKey', 'projectId', 'ticketId', 'title',
197
+ ])
198
+ && mutationContext(input)
199
+ && (0, CollabValidation_1.isCollabOpaqueId)(input.ticketId)
200
+ && isRevision(input.expectedRevision, 1)
201
+ && typeof input.body === 'string'
202
+ && typeof input.title === 'string'
203
+ && (0, CollabValidation_1.hasUtf8ByteLengthAtMost)(input.body, CollabConstants_1.COLLAB_LIMITS.maxTicketBodyBytes)
204
+ && input.title.length <= CollabConstants_1.COLLAB_LIMITS.maxTicketTitleUtf16
205
+ ? {
206
+ body: input.body,
207
+ expectedRevision: input.expectedRevision,
208
+ idempotencyKey: input.idempotencyKey,
209
+ projectId: input.projectId,
210
+ ticketId: input.ticketId,
211
+ title: input.title,
212
+ }
213
+ : null;
214
+ case 'createTicketComment':
215
+ return hasExactKeys(input, ['body', 'idempotencyKey', 'projectId', 'ticketId'])
216
+ && mutationContext(input)
217
+ && (0, CollabValidation_1.isCollabOpaqueId)(input.ticketId)
218
+ && typeof input.body === 'string'
219
+ && (0, CollabValidation_1.hasUtf8ByteLengthAtMost)(input.body, CollabConstants_1.COLLAB_LIMITS.maxTicketCommentBytes)
220
+ ? {
221
+ body: input.body,
222
+ idempotencyKey: input.idempotencyKey,
223
+ projectId: input.projectId,
224
+ ticketId: input.ticketId,
225
+ }
226
+ : null;
227
+ case 'closeTicket':
228
+ case 'reopenTicket':
229
+ return hasExactKeys(input, [
230
+ 'expectedRevision', 'idempotencyKey', 'projectId', 'ticketId',
231
+ ])
232
+ && mutationContext(input)
233
+ && (0, CollabValidation_1.isCollabOpaqueId)(input.ticketId)
234
+ && isRevision(input.expectedRevision, 1)
235
+ ? {
236
+ expectedRevision: input.expectedRevision,
237
+ idempotencyKey: input.idempotencyKey,
238
+ projectId: input.projectId,
239
+ ticketId: input.ticketId,
240
+ }
241
+ : null;
242
+ }
243
+ }
244
+ const INVALID_REASONS = {
245
+ acceptRequest: 'request-accept-payload-invalid',
246
+ closeTicket: 'ticket-mutation-payload-invalid',
247
+ createComment: 'request-comment-payload-invalid',
248
+ createTicket: 'ticket-create-payload-invalid',
249
+ createTicketComment: 'ticket-comment-payload-invalid',
250
+ ensureMyRequest: 'request-ensure-payload-invalid',
251
+ getRequest: 'request-read-payload-invalid',
252
+ getTicket: 'ticket-read-payload-invalid',
253
+ listRequestComments: 'request-comment-page-query-invalid',
254
+ listTicketAcceptedRelations: 'ticket-relation-page-query-invalid',
255
+ listTicketComments: 'ticket-comment-page-query-invalid',
256
+ listTickets: 'ticket-list-query-invalid',
257
+ reopenTicket: 'ticket-mutation-payload-invalid',
258
+ updateMyRequestMetadata: 'request-metadata-payload-invalid',
259
+ updateTicketContent: 'ticket-content-payload-invalid',
260
+ };
261
+ function decodeCollabRequestTicketOperationRequest(operation, input) {
262
+ const value = decodeRequestTicketRequest(operation, input);
263
+ return value
264
+ ? { status: 'ok', value: value }
265
+ : invalid(INVALID_REASONS[operation]);
266
+ }
@@ -0,0 +1,15 @@
1
+ import { type AcceptResponse, type CreateCommentResponse, type CreateTicketCommentResponse, type CreateTicketResponse, type EnsureMyRequestResponse, type TicketMutationResponse, type UpdateMyRequestMetadataResponse } from './CollabProtocol';
2
+ import { type CollabCommentPage, type CollabRequestDetail, type CollabTicketAcceptedRelationPage, type CollabTicketCommentPage, type CollabTicketDetail, type CollabTicketPage } from './types';
3
+ export declare function decodeEnsureMyRequestResponse(value: unknown): EnsureMyRequestResponse;
4
+ export declare function decodeRequestDetailResponse(value: unknown): CollabRequestDetail;
5
+ export declare function decodeCommentPageResponse(value: unknown): CollabCommentPage;
6
+ export declare function decodeTicketCommentPageResponse(value: unknown): CollabTicketCommentPage;
7
+ export declare function decodeTicketAcceptedRelationPageResponse(value: unknown): CollabTicketAcceptedRelationPage;
8
+ export declare function decodeCreateCommentResponse(value: unknown): CreateCommentResponse;
9
+ export declare function decodeAcceptResponse(value: unknown): AcceptResponse;
10
+ export declare function decodeTicketPageResponse(value: unknown): CollabTicketPage;
11
+ export declare function decodeTicketDetailResponse(value: unknown): CollabTicketDetail;
12
+ export declare function decodeCreateTicketResponse(value: unknown): CreateTicketResponse;
13
+ export declare function decodeTicketMutationResponse(value: unknown): TicketMutationResponse;
14
+ export declare function decodeTicketCommentResponse(value: unknown): CreateTicketCommentResponse;
15
+ export declare function decodeUpdateRequestMetadataResponse(value: unknown): UpdateMyRequestMetadataResponse;
@@ -0,0 +1,368 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.decodeEnsureMyRequestResponse = decodeEnsureMyRequestResponse;
4
+ exports.decodeRequestDetailResponse = decodeRequestDetailResponse;
5
+ exports.decodeCommentPageResponse = decodeCommentPageResponse;
6
+ exports.decodeTicketCommentPageResponse = decodeTicketCommentPageResponse;
7
+ exports.decodeTicketAcceptedRelationPageResponse = decodeTicketAcceptedRelationPageResponse;
8
+ exports.decodeCreateCommentResponse = decodeCreateCommentResponse;
9
+ exports.decodeAcceptResponse = decodeAcceptResponse;
10
+ exports.decodeTicketPageResponse = decodeTicketPageResponse;
11
+ exports.decodeTicketDetailResponse = decodeTicketDetailResponse;
12
+ exports.decodeCreateTicketResponse = decodeCreateTicketResponse;
13
+ exports.decodeTicketMutationResponse = decodeTicketMutationResponse;
14
+ exports.decodeTicketCommentResponse = decodeTicketCommentResponse;
15
+ exports.decodeUpdateRequestMetadataResponse = decodeUpdateRequestMetadataResponse;
16
+ const CollabConstants_1 = require("./CollabConstants");
17
+ const CollabError_1 = require("./CollabError");
18
+ const CollabValidation_1 = require("./CollabValidation");
19
+ function decodeError(field) {
20
+ return new CollabError_1.CollabError({
21
+ code: 'protocol-payload-invalid',
22
+ recoveryActions: ['retry'],
23
+ safeContext: { field },
24
+ });
25
+ }
26
+ function isRecord(value) {
27
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
28
+ }
29
+ function record(value, field) {
30
+ if (!isRecord(value))
31
+ throw decodeError(field);
32
+ return value;
33
+ }
34
+ function assertJsonUtf8ByteLengthAtMost(value, maximum, field) {
35
+ let serialized;
36
+ try {
37
+ serialized = JSON.stringify(value);
38
+ }
39
+ catch {
40
+ throw decodeError(field);
41
+ }
42
+ if (serialized === undefined
43
+ || !(0, CollabValidation_1.hasUtf8ByteLengthAtMost)(serialized, maximum)) {
44
+ throw decodeError(field);
45
+ }
46
+ }
47
+ function string(value, field, maxLength, validate, unit = 'utf16') {
48
+ const candidate = value[field];
49
+ if (typeof candidate !== 'string'
50
+ || candidate.length === 0
51
+ || (unit === 'utf8'
52
+ ? !(0, CollabValidation_1.hasUtf8ByteLengthAtMost)(candidate, maxLength)
53
+ : candidate.length > maxLength)
54
+ || (validate && !validate(candidate))) {
55
+ throw decodeError(field);
56
+ }
57
+ return candidate;
58
+ }
59
+ function text(value, field, maxLength, unit = 'utf16') {
60
+ const candidate = value[field];
61
+ if (typeof candidate !== 'string'
62
+ || (unit === 'utf8'
63
+ ? !(0, CollabValidation_1.hasUtf8ByteLengthAtMost)(candidate, maxLength)
64
+ : candidate.length > maxLength))
65
+ throw decodeError(field);
66
+ return candidate;
67
+ }
68
+ function timestamp(value, field) {
69
+ const candidate = string(value, field, 64);
70
+ if (Number.isNaN(Date.parse(candidate)) || new Date(candidate).toISOString() !== candidate) {
71
+ throw decodeError(field);
72
+ }
73
+ return candidate;
74
+ }
75
+ function optionalTimestamp(value, field) {
76
+ return value[field] === undefined ? undefined : timestamp(value, field);
77
+ }
78
+ function nonNegativeInteger(value, field) {
79
+ const candidate = value[field];
80
+ if (typeof candidate !== 'number'
81
+ || !Number.isSafeInteger(candidate)
82
+ || candidate < 0) {
83
+ throw decodeError(field);
84
+ }
85
+ return candidate;
86
+ }
87
+ function boundedNonNegativeInteger(value, field, maximum) {
88
+ const candidate = nonNegativeInteger(value, field);
89
+ if (candidate > maximum)
90
+ throw decodeError(field);
91
+ return candidate;
92
+ }
93
+ function positiveInteger(value, field) {
94
+ const candidate = nonNegativeInteger(value, field);
95
+ if (candidate < 1)
96
+ throw decodeError(field);
97
+ return candidate;
98
+ }
99
+ function requestTicketRelation(value) {
100
+ const source = record(value, 'request.ticketRelations');
101
+ const kind = source.kind;
102
+ const state = source.state;
103
+ if ((kind !== 'references' && kind !== 'resolves')
104
+ || (state !== 'pending' && state !== 'accepted')) {
105
+ throw decodeError('request.ticketRelations');
106
+ }
107
+ return {
108
+ commitOid: string(source, 'commitOid', 64, CollabValidation_1.isCollabGitOid),
109
+ id: string(source, 'id', 128, CollabValidation_1.isCollabOpaqueId),
110
+ kind,
111
+ state,
112
+ ticketId: string(source, 'ticketId', 128, CollabValidation_1.isCollabOpaqueId),
113
+ ticketNumber: positiveInteger(source, 'ticketNumber'),
114
+ ticketRevision: positiveInteger(source, 'ticketRevision'),
115
+ ticketTitle: string(source, 'ticketTitle', CollabConstants_1.COLLAB_LIMITS.maxTicketTitleUtf16),
116
+ };
117
+ }
118
+ function changeRequest(value) {
119
+ const source = record(value, 'request');
120
+ const status = source.status;
121
+ if ((status !== 'open' && status !== 'merged' && status !== 'discarded')
122
+ || !Array.isArray(source.ticketRelations)
123
+ || source.ticketRelations.length > CollabConstants_1.COLLAB_LIMITS.maxRequestTicketRelations) {
124
+ throw decodeError('request.status');
125
+ }
126
+ const mergedOid = source.mergedOid === undefined
127
+ ? undefined
128
+ : string(source, 'mergedOid', 64, CollabValidation_1.isCollabGitOid);
129
+ return {
130
+ commentCount: boundedNonNegativeInteger(source, 'commentCount', CollabConstants_1.COLLAB_LIMITS.maxRequestComments),
131
+ createdAt: timestamp(source, 'createdAt'),
132
+ description: text(source, 'description', CollabConstants_1.COLLAB_LIMITS.maxRequestDescriptionBytes, 'utf8'),
133
+ firstBaseOid: string(source, 'firstBaseOid', 64, CollabValidation_1.isCollabGitOid),
134
+ id: string(source, 'id', 128, CollabValidation_1.isCollabOpaqueId),
135
+ latestHeadOid: string(source, 'latestHeadOid', 64, CollabValidation_1.isCollabGitOid),
136
+ memberId: string(source, 'memberId', 64, CollabValidation_1.isCollabMemberId),
137
+ ...(mergedOid ? { mergedOid } : {}),
138
+ revision: nonNegativeInteger(source, 'revision'),
139
+ status,
140
+ ticketRelations: source.ticketRelations.map(requestTicketRelation),
141
+ updatedAt: timestamp(source, 'updatedAt'),
142
+ };
143
+ }
144
+ function ticketSummary(value) {
145
+ const source = record(value, 'ticket');
146
+ const status = source.status;
147
+ const closedAt = optionalTimestamp(source, 'closedAt');
148
+ const closedByMemberId = source.closedByMemberId === undefined
149
+ ? undefined
150
+ : string(source, 'closedByMemberId', 64, CollabValidation_1.isCollabMemberId);
151
+ if ((status !== 'open' && status !== 'closed')
152
+ || (status === 'open' && (closedAt !== undefined || closedByMemberId !== undefined))
153
+ || (status === 'closed' && (closedAt === undefined || closedByMemberId === undefined))) {
154
+ throw decodeError('ticket.status');
155
+ }
156
+ return {
157
+ acceptedRelationCount: boundedNonNegativeInteger(source, 'acceptedRelationCount', CollabConstants_1.COLLAB_LIMITS.maxTicketAcceptedRelations),
158
+ authorMemberId: string(source, 'authorMemberId', 64, CollabValidation_1.isCollabMemberId),
159
+ ...(closedAt && closedByMemberId ? { closedAt, closedByMemberId } : {}),
160
+ commentCount: boundedNonNegativeInteger(source, 'commentCount', CollabConstants_1.COLLAB_LIMITS.maxTicketComments),
161
+ createdAt: timestamp(source, 'createdAt'),
162
+ id: string(source, 'id', 128, CollabValidation_1.isCollabOpaqueId),
163
+ number: positiveInteger(source, 'number'),
164
+ revision: positiveInteger(source, 'revision'),
165
+ status,
166
+ title: string(source, 'title', CollabConstants_1.COLLAB_LIMITS.maxTicketTitleUtf16),
167
+ updatedAt: timestamp(source, 'updatedAt'),
168
+ };
169
+ }
170
+ function ticketComment(value) {
171
+ const source = record(value, 'ticket.comment');
172
+ return {
173
+ authorMemberId: string(source, 'authorMemberId', 64, CollabValidation_1.isCollabMemberId),
174
+ body: string(source, 'body', CollabConstants_1.COLLAB_LIMITS.maxTicketCommentBytes, undefined, 'utf8'),
175
+ createdAt: timestamp(source, 'createdAt'),
176
+ id: string(source, 'id', 128, CollabValidation_1.isCollabOpaqueId),
177
+ ticketId: string(source, 'ticketId', 128, CollabValidation_1.isCollabOpaqueId),
178
+ };
179
+ }
180
+ function acceptedTicketRelation(value) {
181
+ const source = record(value, 'ticket.acceptedRelation');
182
+ const kind = source.kind;
183
+ if (kind !== 'references' && kind !== 'resolves') {
184
+ throw decodeError('ticket.acceptedRelation.kind');
185
+ }
186
+ return {
187
+ acceptedAt: timestamp(source, 'acceptedAt'),
188
+ acceptedMergeOid: string(source, 'acceptedMergeOid', 64, CollabValidation_1.isCollabGitOid),
189
+ commitOid: string(source, 'commitOid', 64, CollabValidation_1.isCollabGitOid),
190
+ id: string(source, 'id', 128, CollabValidation_1.isCollabOpaqueId),
191
+ kind,
192
+ requestId: string(source, 'requestId', 128, CollabValidation_1.isCollabOpaqueId),
193
+ };
194
+ }
195
+ function pageCursor(source) {
196
+ return source.nextCursor === undefined
197
+ ? undefined
198
+ : string(source, 'nextCursor', CollabConstants_1.COLLAB_LIMITS.maxPageCursorUtf16);
199
+ }
200
+ function commentPage(value) {
201
+ const source = record(value, 'commentPage');
202
+ assertJsonUtf8ByteLengthAtMost(source, CollabConstants_1.COLLAB_LIMITS.commentPageMaxUtf8Bytes, 'commentPage.bytes');
203
+ if (!Array.isArray(source.comments)
204
+ || source.comments.length > CollabConstants_1.COLLAB_LIMITS.maxCommentPageSize) {
205
+ throw decodeError('commentPage');
206
+ }
207
+ const nextCursor = pageCursor(source);
208
+ return {
209
+ comments: source.comments.map(comment),
210
+ ...(nextCursor ? { nextCursor } : {}),
211
+ };
212
+ }
213
+ function ticketCommentPage(value) {
214
+ const source = record(value, 'ticketCommentPage');
215
+ assertJsonUtf8ByteLengthAtMost(source, CollabConstants_1.COLLAB_LIMITS.commentPageMaxUtf8Bytes, 'ticketCommentPage.bytes');
216
+ if (!Array.isArray(source.comments)
217
+ || source.comments.length > CollabConstants_1.COLLAB_LIMITS.maxCommentPageSize) {
218
+ throw decodeError('ticketCommentPage');
219
+ }
220
+ const nextCursor = pageCursor(source);
221
+ return {
222
+ comments: source.comments.map(ticketComment),
223
+ ...(nextCursor ? { nextCursor } : {}),
224
+ };
225
+ }
226
+ function acceptedRelationPage(value) {
227
+ const source = record(value, 'ticketAcceptedRelationPage');
228
+ assertJsonUtf8ByteLengthAtMost(source, CollabConstants_1.COLLAB_LIMITS.relationPageMaxUtf8Bytes, 'ticketAcceptedRelationPage.bytes');
229
+ if (!Array.isArray(source.acceptedRelations)
230
+ || source.acceptedRelations.length > CollabConstants_1.COLLAB_LIMITS.maxRelationsPerPage) {
231
+ throw decodeError('ticketAcceptedRelationPage');
232
+ }
233
+ const nextCursor = pageCursor(source);
234
+ return {
235
+ acceptedRelations: source.acceptedRelations.map(acceptedTicketRelation),
236
+ ...(nextCursor ? { nextCursor } : {}),
237
+ };
238
+ }
239
+ function ticketDetail(value) {
240
+ const source = record(value, 'ticketDetail');
241
+ assertJsonUtf8ByteLengthAtMost(source, CollabConstants_1.COLLAB_LIMITS.detailMaxUtf8Bytes, 'ticketDetail.bytes');
242
+ if (!isRecord(source.comments)
243
+ || !isRecord(source.acceptedRelations)) {
244
+ throw decodeError('ticketDetail');
245
+ }
246
+ const decodedTicket = ticketSummary(source.ticket);
247
+ const comments = ticketCommentPage(source.comments);
248
+ if (comments.comments.some(commentValue => commentValue.ticketId !== decodedTicket.id)) {
249
+ throw decodeError('ticketDetail.comments');
250
+ }
251
+ const acceptedRelations = acceptedRelationPage(source.acceptedRelations);
252
+ return {
253
+ acceptedRelations,
254
+ body: string(source, 'body', CollabConstants_1.COLLAB_LIMITS.maxTicketBodyBytes, undefined, 'utf8'),
255
+ comments,
256
+ ticket: decodedTicket,
257
+ };
258
+ }
259
+ function comment(value) {
260
+ const source = record(value, 'comment');
261
+ return {
262
+ authorMemberId: string(source, 'authorMemberId', 64, CollabValidation_1.isCollabMemberId),
263
+ body: string(source, 'body', CollabConstants_1.COLLAB_LIMITS.maxCommentBytes, undefined, 'utf8'),
264
+ createdAt: timestamp(source, 'createdAt'),
265
+ id: string(source, 'id', 128, CollabValidation_1.isCollabOpaqueId),
266
+ requestId: string(source, 'requestId', 128, CollabValidation_1.isCollabOpaqueId),
267
+ };
268
+ }
269
+ function envelopeData(value) {
270
+ return value;
271
+ }
272
+ function decodeEnsureMyRequestResponse(value) {
273
+ const data = record(envelopeData(value), 'data');
274
+ return {
275
+ mainOid: string(data, 'mainOid', 64, CollabValidation_1.isCollabGitOid),
276
+ request: changeRequest(data.request),
277
+ };
278
+ }
279
+ function decodeRequestDetailResponse(value) {
280
+ const data = record(envelopeData(value), 'data');
281
+ assertJsonUtf8ByteLengthAtMost(data, CollabConstants_1.COLLAB_LIMITS.detailMaxUtf8Bytes, 'requestDetail.bytes');
282
+ if (!isRecord(data.comments)
283
+ || data.changedFiles !== undefined) {
284
+ throw decodeError('requestDetail');
285
+ }
286
+ const decodedRequest = changeRequest(data.request);
287
+ const reviewedHeadOid = string(data, 'reviewedHeadOid', 64, CollabValidation_1.isCollabGitOid);
288
+ const reviewCondition = data.reviewCondition;
289
+ const comments = commentPage(data.comments);
290
+ if (reviewedHeadOid !== decodedRequest.latestHeadOid
291
+ || !['clean', 'conflicting', 'stale'].includes(String(reviewCondition))
292
+ || comments.comments.some(item => item.requestId !== decodedRequest.id)) {
293
+ throw decodeError('requestDetail');
294
+ }
295
+ return {
296
+ comments,
297
+ currentMainOid: string(data, 'currentMainOid', 64, CollabValidation_1.isCollabGitOid),
298
+ request: decodedRequest,
299
+ reviewCondition: reviewCondition,
300
+ reviewedHeadOid,
301
+ };
302
+ }
303
+ function decodeCommentPageResponse(value) {
304
+ return commentPage(record(envelopeData(value), 'data'));
305
+ }
306
+ function decodeTicketCommentPageResponse(value) {
307
+ return ticketCommentPage(record(envelopeData(value), 'data'));
308
+ }
309
+ function decodeTicketAcceptedRelationPageResponse(value) {
310
+ return acceptedRelationPage(record(envelopeData(value), 'data'));
311
+ }
312
+ function decodeCreateCommentResponse(value) {
313
+ const data = record(envelopeData(value), 'data');
314
+ const decodedRequest = changeRequest(data.request);
315
+ const decodedComment = comment(data.comment);
316
+ if (decodedComment.requestId !== decodedRequest.id
317
+ || decodedRequest.commentCount < 1) {
318
+ throw decodeError('commentResponse');
319
+ }
320
+ return { comment: decodedComment, request: decodedRequest };
321
+ }
322
+ function decodeAcceptResponse(value) {
323
+ const data = record(envelopeData(value), 'data');
324
+ const mainOid = string(data, 'mainOid', 64, CollabValidation_1.isCollabGitOid);
325
+ const mergeCommitOid = string(data, 'mergeCommitOid', 64, CollabValidation_1.isCollabGitOid);
326
+ const decodedRequest = changeRequest(data.request);
327
+ if (mergeCommitOid !== mainOid
328
+ || decodedRequest.status !== 'merged'
329
+ || decodedRequest.mergedOid !== mainOid) {
330
+ throw decodeError('acceptResponse');
331
+ }
332
+ return { mainOid, mergeCommitOid, request: decodedRequest };
333
+ }
334
+ function decodeTicketPageResponse(value) {
335
+ const data = record(envelopeData(value), 'data');
336
+ assertJsonUtf8ByteLengthAtMost(data, CollabConstants_1.COLLAB_LIMITS.ticketPageMaxUtf8Bytes, 'ticketPage.bytes');
337
+ if (!Array.isArray(data.tickets)
338
+ || data.tickets.length > CollabConstants_1.COLLAB_LIMITS.maxTicketPageSize)
339
+ throw decodeError('ticketPage');
340
+ const nextCursor = data.nextCursor === undefined
341
+ ? undefined
342
+ : string(data, 'nextCursor', 512);
343
+ return {
344
+ ...(nextCursor ? { nextCursor } : {}),
345
+ tickets: data.tickets.map(ticketSummary),
346
+ };
347
+ }
348
+ function decodeTicketDetailResponse(value) {
349
+ return ticketDetail(envelopeData(value));
350
+ }
351
+ function decodeCreateTicketResponse(value) {
352
+ return { ticket: ticketDetail(record(envelopeData(value), 'data').ticket) };
353
+ }
354
+ function decodeTicketMutationResponse(value) {
355
+ return { ticket: ticketSummary(record(envelopeData(value), 'data').ticket) };
356
+ }
357
+ function decodeTicketCommentResponse(value) {
358
+ const data = record(envelopeData(value), 'data');
359
+ const decodedTicket = ticketSummary(data.ticket);
360
+ const decodedComment = ticketComment(data.comment);
361
+ if (decodedComment.ticketId !== decodedTicket.id) {
362
+ throw decodeError('ticketCommentResponse');
363
+ }
364
+ return { comment: decodedComment, ticket: decodedTicket };
365
+ }
366
+ function decodeUpdateRequestMetadataResponse(value) {
367
+ return { request: changeRequest(record(envelopeData(value), 'data').request) };
368
+ }
@@ -0,0 +1,24 @@
1
+ import type { CollabParsedTicketReference, CollabTicketCommitRelationKind } from './types';
2
+ export type CollabTicketReferenceParseFailureReason = 'description-too-large' | 'ticket-number-out-of-range';
3
+ export type CollabTicketReferenceParseResult = {
4
+ status: 'ok';
5
+ references: readonly CollabParsedTicketReference[];
6
+ } | {
7
+ status: 'invalid';
8
+ reason: CollabTicketReferenceParseFailureReason;
9
+ };
10
+ export interface CollabTicketReferenceToken {
11
+ readonly from: number;
12
+ readonly kind: CollabTicketCommitRelationKind;
13
+ readonly ticketNumber: number;
14
+ readonly to: number;
15
+ }
16
+ export type CollabTicketReferenceScanResult = {
17
+ readonly status: 'ok';
18
+ readonly tokens: readonly CollabTicketReferenceToken[];
19
+ } | {
20
+ readonly reason: 'ticket-number-out-of-range';
21
+ readonly status: 'invalid';
22
+ };
23
+ export declare function parseCollabTicketReferences(description: string): CollabTicketReferenceParseResult;
24
+ export declare function scanCollabTicketReferences(description: string): CollabTicketReferenceScanResult;