@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,41 @@
|
|
|
1
|
+
import { COLLAB_PROTOCOL_VERSION } from './CollabConstants';
|
|
2
|
+
import type { CollabGitOid, CollabIsoTimestamp, CollabMemberId, CollabProjectId, CollabRequestId, CollabTicketId } from './types';
|
|
3
|
+
export declare const COLLAB_CLOUD_EVENT_KINDS: readonly ["membership.updated", "request.updated", "request.comment-added", "ticket.updated", "ticket.comment-added", "main.updated"];
|
|
4
|
+
export type CollabCloudEventKind = typeof COLLAB_CLOUD_EVENT_KINDS[number];
|
|
5
|
+
export interface CollabCloudEventPayloadMap {
|
|
6
|
+
readonly 'main.updated': {
|
|
7
|
+
readonly mainOid: CollabGitOid;
|
|
8
|
+
readonly requestId: CollabRequestId;
|
|
9
|
+
};
|
|
10
|
+
readonly 'membership.updated': {
|
|
11
|
+
readonly memberId: CollabMemberId;
|
|
12
|
+
};
|
|
13
|
+
readonly 'request.comment-added': {
|
|
14
|
+
readonly requestId: CollabRequestId;
|
|
15
|
+
};
|
|
16
|
+
readonly 'request.updated': {
|
|
17
|
+
readonly requestId: CollabRequestId;
|
|
18
|
+
};
|
|
19
|
+
readonly 'ticket.comment-added': {
|
|
20
|
+
readonly ticketId: CollabTicketId;
|
|
21
|
+
};
|
|
22
|
+
readonly 'ticket.updated': {
|
|
23
|
+
readonly ticketId: CollabTicketId;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export type CollabCloudProjectEvent = {
|
|
27
|
+
readonly [Kind in CollabCloudEventKind]: {
|
|
28
|
+
readonly kind: Kind;
|
|
29
|
+
readonly occurredAt: CollabIsoTimestamp;
|
|
30
|
+
readonly payload: CollabCloudEventPayloadMap[Kind];
|
|
31
|
+
readonly projectId: CollabProjectId;
|
|
32
|
+
readonly protocolVersion: typeof COLLAB_PROTOCOL_VERSION;
|
|
33
|
+
readonly sequence: number;
|
|
34
|
+
};
|
|
35
|
+
}[CollabCloudEventKind];
|
|
36
|
+
export interface CollabCloudSnapshotRequired {
|
|
37
|
+
readonly kind: 'snapshot.required';
|
|
38
|
+
readonly latestSequence: number;
|
|
39
|
+
}
|
|
40
|
+
export type CollabCloudProjectEventMessage = CollabCloudProjectEvent | CollabCloudSnapshotRequired;
|
|
41
|
+
export declare function decodeCollabCloudProjectEventMessage(value: unknown): CollabCloudProjectEventMessage;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.COLLAB_CLOUD_EVENT_KINDS = void 0;
|
|
4
|
+
exports.decodeCollabCloudProjectEventMessage = decodeCollabCloudProjectEventMessage;
|
|
5
|
+
const CollabConstants_1 = require("./CollabConstants");
|
|
6
|
+
const CollabError_1 = require("./CollabError");
|
|
7
|
+
const CollabValidation_1 = require("./CollabValidation");
|
|
8
|
+
exports.COLLAB_CLOUD_EVENT_KINDS = Object.freeze([
|
|
9
|
+
'membership.updated',
|
|
10
|
+
'request.updated',
|
|
11
|
+
'request.comment-added',
|
|
12
|
+
'ticket.updated',
|
|
13
|
+
'ticket.comment-added',
|
|
14
|
+
'main.updated',
|
|
15
|
+
]);
|
|
16
|
+
const EVENT_KIND_SET = new Set(exports.COLLAB_CLOUD_EVENT_KINDS);
|
|
17
|
+
function invalidPayload(field) {
|
|
18
|
+
return new CollabError_1.CollabError({
|
|
19
|
+
code: 'protocol-payload-invalid',
|
|
20
|
+
safeContext: { field },
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function record(value, field) {
|
|
24
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
25
|
+
throw invalidPayload(field);
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
function exactRecord(value, field, keys) {
|
|
30
|
+
const source = record(value, field);
|
|
31
|
+
const expected = new Set(keys);
|
|
32
|
+
if (!keys.every(key => Object.hasOwn(source, key))
|
|
33
|
+
|| Object.keys(source).some(key => !expected.has(key)))
|
|
34
|
+
throw invalidPayload(field);
|
|
35
|
+
return source;
|
|
36
|
+
}
|
|
37
|
+
function stringField(source, field, maximum, validate) {
|
|
38
|
+
const value = source[field];
|
|
39
|
+
if (typeof value !== 'string'
|
|
40
|
+
|| value.length === 0
|
|
41
|
+
|| value.length > maximum
|
|
42
|
+
|| !validate(value))
|
|
43
|
+
throw invalidPayload(field);
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
function nonNegativeInteger(source, field) {
|
|
47
|
+
const value = source[field];
|
|
48
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {
|
|
49
|
+
throw invalidPayload(field);
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
function positiveInteger(source, field) {
|
|
54
|
+
const value = nonNegativeInteger(source, field);
|
|
55
|
+
if (value < 1)
|
|
56
|
+
throw invalidPayload(field);
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
function timestamp(source, field) {
|
|
60
|
+
const value = source[field];
|
|
61
|
+
if (typeof value !== 'string'
|
|
62
|
+
|| value.length > 64
|
|
63
|
+
|| Number.isNaN(Date.parse(value))
|
|
64
|
+
|| new Date(value).toISOString() !== value)
|
|
65
|
+
throw invalidPayload(field);
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
function decodePayload(kind, value) {
|
|
69
|
+
switch (kind) {
|
|
70
|
+
case 'membership.updated': {
|
|
71
|
+
const source = exactRecord(value, 'payload', ['memberId']);
|
|
72
|
+
return { memberId: stringField(source, 'memberId', 64, CollabValidation_1.isCollabMemberId) };
|
|
73
|
+
}
|
|
74
|
+
case 'request.updated':
|
|
75
|
+
case 'request.comment-added': {
|
|
76
|
+
const source = exactRecord(value, 'payload', ['requestId']);
|
|
77
|
+
return { requestId: stringField(source, 'requestId', 128, CollabValidation_1.isCollabOpaqueId) };
|
|
78
|
+
}
|
|
79
|
+
case 'ticket.updated':
|
|
80
|
+
case 'ticket.comment-added': {
|
|
81
|
+
const source = exactRecord(value, 'payload', ['ticketId']);
|
|
82
|
+
return { ticketId: stringField(source, 'ticketId', 128, CollabValidation_1.isCollabOpaqueId) };
|
|
83
|
+
}
|
|
84
|
+
case 'main.updated': {
|
|
85
|
+
const source = exactRecord(value, 'payload', ['mainOid', 'requestId']);
|
|
86
|
+
return {
|
|
87
|
+
mainOid: stringField(source, 'mainOid', 64, CollabValidation_1.isCollabGitOid),
|
|
88
|
+
requestId: stringField(source, 'requestId', 128, CollabValidation_1.isCollabOpaqueId),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function decodeCollabCloudProjectEventMessage(value) {
|
|
94
|
+
let serialized;
|
|
95
|
+
try {
|
|
96
|
+
serialized = JSON.stringify(value);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
throw invalidPayload('event');
|
|
100
|
+
}
|
|
101
|
+
if (!(0, CollabValidation_1.hasUtf8ByteLengthAtMost)(serialized, CollabConstants_1.COLLAB_LIMITS.maxJsonPayloadUtf8Bytes)) {
|
|
102
|
+
throw invalidPayload('event');
|
|
103
|
+
}
|
|
104
|
+
const candidate = record(value, 'event');
|
|
105
|
+
if (candidate.kind === 'snapshot.required') {
|
|
106
|
+
const source = exactRecord(candidate, 'snapshot.required', ['kind', 'latestSequence']);
|
|
107
|
+
return {
|
|
108
|
+
kind: 'snapshot.required',
|
|
109
|
+
latestSequence: nonNegativeInteger(source, 'latestSequence'),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const source = exactRecord(candidate, 'event', [
|
|
113
|
+
'kind',
|
|
114
|
+
'occurredAt',
|
|
115
|
+
'payload',
|
|
116
|
+
'projectId',
|
|
117
|
+
'protocolVersion',
|
|
118
|
+
'sequence',
|
|
119
|
+
]);
|
|
120
|
+
if (source.protocolVersion !== CollabConstants_1.COLLAB_PROTOCOL_VERSION
|
|
121
|
+
|| typeof source.kind !== 'string'
|
|
122
|
+
|| !EVENT_KIND_SET.has(source.kind))
|
|
123
|
+
throw invalidPayload('event');
|
|
124
|
+
const kind = source.kind;
|
|
125
|
+
return {
|
|
126
|
+
kind,
|
|
127
|
+
occurredAt: timestamp(source, 'occurredAt'),
|
|
128
|
+
payload: decodePayload(kind, source.payload),
|
|
129
|
+
projectId: stringField(source, 'projectId', 64, CollabValidation_1.isCollabProjectId),
|
|
130
|
+
protocolVersion: CollabConstants_1.COLLAB_PROTOCOL_VERSION,
|
|
131
|
+
sequence: positiveInteger(source, 'sequence'),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { COLLAB_MAIN_REF } from './CollabConstants';
|
|
2
|
+
import type { CollabDecodeResult } from './CollabProtocol';
|
|
3
|
+
import type { CollabChangeRequest, CollabGitOid, CollabIsoTimestamp, CollabMemberId, CollabProjectId, CollabRole, CollabTicketSummary } from './types';
|
|
4
|
+
export interface CollabCloudProjectSummary {
|
|
5
|
+
readonly createdAt: CollabIsoTimestamp;
|
|
6
|
+
readonly expectedMainOid: CollabGitOid;
|
|
7
|
+
readonly id: CollabProjectId;
|
|
8
|
+
readonly mainRef: typeof COLLAB_MAIN_REF;
|
|
9
|
+
readonly name: string;
|
|
10
|
+
}
|
|
11
|
+
export interface CollabCloudProjectMember {
|
|
12
|
+
readonly activatedAt: CollabIsoTimestamp;
|
|
13
|
+
readonly createdAt: CollabIsoTimestamp;
|
|
14
|
+
readonly displayName: string;
|
|
15
|
+
readonly id: CollabMemberId;
|
|
16
|
+
readonly personalRef: string;
|
|
17
|
+
readonly role: CollabRole;
|
|
18
|
+
readonly status: 'active';
|
|
19
|
+
}
|
|
20
|
+
export interface CollabCloudProjectSnapshot {
|
|
21
|
+
readonly currentMember: CollabCloudProjectMember;
|
|
22
|
+
readonly eventSequence: number;
|
|
23
|
+
readonly members: readonly CollabCloudProjectMember[];
|
|
24
|
+
readonly openRequests: readonly CollabChangeRequest[];
|
|
25
|
+
readonly openTicketCount: number;
|
|
26
|
+
readonly project: CollabCloudProjectSummary;
|
|
27
|
+
readonly ticketHighlights: readonly CollabTicketSummary[];
|
|
28
|
+
}
|
|
29
|
+
export interface GetCollabCloudProjectSnapshotRequest {
|
|
30
|
+
readonly projectId: CollabProjectId;
|
|
31
|
+
}
|
|
32
|
+
export interface CollabCloudProjectSnapshotCodec {
|
|
33
|
+
readonly decodeRequest: (value: unknown) => CollabDecodeResult<GetCollabCloudProjectSnapshotRequest>;
|
|
34
|
+
readonly decodeResponse: (value: unknown) => CollabCloudProjectSnapshot;
|
|
35
|
+
}
|
|
36
|
+
export declare function decodeCollabCloudProjectSnapshot(value: unknown): CollabCloudProjectSnapshot;
|
|
37
|
+
declare function decodeSnapshotRequest(value: unknown): CollabDecodeResult<GetCollabCloudProjectSnapshotRequest>;
|
|
38
|
+
export declare const COLLAB_CLOUD_PROJECT_SNAPSHOT_CODEC: Readonly<{
|
|
39
|
+
readonly decodeRequest: typeof decodeSnapshotRequest;
|
|
40
|
+
readonly decodeResponse: typeof decodeCollabCloudProjectSnapshot;
|
|
41
|
+
}>;
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.COLLAB_CLOUD_PROJECT_SNAPSHOT_CODEC = void 0;
|
|
4
|
+
exports.decodeCollabCloudProjectSnapshot = decodeCollabCloudProjectSnapshot;
|
|
5
|
+
const CollabConstants_1 = require("./CollabConstants");
|
|
6
|
+
const CollabCloudBinding_1 = require("./CollabCloudBinding");
|
|
7
|
+
const CollabError_1 = require("./CollabError");
|
|
8
|
+
const types_1 = require("./types");
|
|
9
|
+
const CollabValidation_1 = require("./CollabValidation");
|
|
10
|
+
function invalidPayload(field) {
|
|
11
|
+
return new CollabError_1.CollabError({
|
|
12
|
+
code: 'protocol-payload-invalid',
|
|
13
|
+
safeContext: { field },
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function record(value, field) {
|
|
17
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
18
|
+
throw invalidPayload(field);
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
function exactRecord(value, field, required, optional = []) {
|
|
23
|
+
const source = record(value, field);
|
|
24
|
+
const allowed = new Set([...required, ...optional]);
|
|
25
|
+
if (!required.every(key => Object.hasOwn(source, key))
|
|
26
|
+
|| Object.keys(source).some(key => !allowed.has(key)))
|
|
27
|
+
throw invalidPayload(field);
|
|
28
|
+
return source;
|
|
29
|
+
}
|
|
30
|
+
function stringField(source, field, maximum, validate, allowEmpty = false) {
|
|
31
|
+
const value = source[field];
|
|
32
|
+
if (typeof value !== 'string'
|
|
33
|
+
|| (!allowEmpty && value.length === 0)
|
|
34
|
+
|| value.length > maximum
|
|
35
|
+
|| (validate && !validate(value)))
|
|
36
|
+
throw invalidPayload(field);
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
function textUtf8(source, field, maximum) {
|
|
40
|
+
const value = source[field];
|
|
41
|
+
if (typeof value !== 'string'
|
|
42
|
+
|| !(0, CollabValidation_1.hasUtf8ByteLengthAtMost)(value, maximum))
|
|
43
|
+
throw invalidPayload(field);
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
function timestamp(source, field) {
|
|
47
|
+
const value = stringField(source, field, 64);
|
|
48
|
+
if (Number.isNaN(Date.parse(value)) || new Date(value).toISOString() !== value) {
|
|
49
|
+
throw invalidPayload(field);
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
function nonNegativeInteger(source, field, maximum) {
|
|
54
|
+
const value = source[field];
|
|
55
|
+
if (typeof value !== 'number'
|
|
56
|
+
|| !Number.isSafeInteger(value)
|
|
57
|
+
|| value < 0
|
|
58
|
+
|| (maximum !== undefined && value > maximum))
|
|
59
|
+
throw invalidPayload(field);
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
function positiveInteger(source, field) {
|
|
63
|
+
const value = nonNegativeInteger(source, field);
|
|
64
|
+
if (value < 1)
|
|
65
|
+
throw invalidPayload(field);
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
function decodeProject(value) {
|
|
69
|
+
const source = exactRecord(value, 'project', [
|
|
70
|
+
'createdAt',
|
|
71
|
+
'expectedMainOid',
|
|
72
|
+
'id',
|
|
73
|
+
'mainRef',
|
|
74
|
+
'name',
|
|
75
|
+
]);
|
|
76
|
+
if (source.mainRef !== CollabConstants_1.COLLAB_MAIN_REF)
|
|
77
|
+
throw invalidPayload('mainRef');
|
|
78
|
+
return {
|
|
79
|
+
createdAt: timestamp(source, 'createdAt'),
|
|
80
|
+
expectedMainOid: stringField(source, 'expectedMainOid', 64, CollabValidation_1.isCollabGitOid),
|
|
81
|
+
id: stringField(source, 'id', 64, CollabValidation_1.isCollabProjectId),
|
|
82
|
+
mainRef: CollabConstants_1.COLLAB_MAIN_REF,
|
|
83
|
+
name: stringField(source, 'name', CollabConstants_1.COLLAB_LIMITS.maxProjectNameUtf16),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function decodeMember(value) {
|
|
87
|
+
const source = exactRecord(value, 'member', [
|
|
88
|
+
'activatedAt',
|
|
89
|
+
'createdAt',
|
|
90
|
+
'displayName',
|
|
91
|
+
'id',
|
|
92
|
+
'personalRef',
|
|
93
|
+
'role',
|
|
94
|
+
'status',
|
|
95
|
+
]);
|
|
96
|
+
const id = stringField(source, 'id', 64, CollabValidation_1.isCollabMemberId);
|
|
97
|
+
if (source.status !== 'active'
|
|
98
|
+
|| (source.role !== 'manager' && source.role !== 'member'))
|
|
99
|
+
throw invalidPayload('member');
|
|
100
|
+
const personalRef = stringField(source, 'personalRef', CollabConstants_1.COLLAB_LIMITS.maxRepositoryPathUtf16);
|
|
101
|
+
if (personalRef !== (0, types_1.collabMemberRef)(id))
|
|
102
|
+
throw invalidPayload('personalRef');
|
|
103
|
+
return {
|
|
104
|
+
activatedAt: timestamp(source, 'activatedAt'),
|
|
105
|
+
createdAt: timestamp(source, 'createdAt'),
|
|
106
|
+
displayName: stringField(source, 'displayName', CollabConstants_1.COLLAB_LIMITS.maxMemberDisplayNameUtf16),
|
|
107
|
+
id,
|
|
108
|
+
personalRef,
|
|
109
|
+
role: source.role,
|
|
110
|
+
status: 'active',
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function decodeRequestTicketRelation(value) {
|
|
114
|
+
const source = exactRecord(value, 'request.ticketRelations', [
|
|
115
|
+
'commitOid',
|
|
116
|
+
'id',
|
|
117
|
+
'kind',
|
|
118
|
+
'state',
|
|
119
|
+
'ticketId',
|
|
120
|
+
'ticketNumber',
|
|
121
|
+
'ticketRevision',
|
|
122
|
+
'ticketTitle',
|
|
123
|
+
]);
|
|
124
|
+
if ((source.kind !== 'references' && source.kind !== 'resolves')
|
|
125
|
+
|| (source.state !== 'pending' && source.state !== 'accepted'))
|
|
126
|
+
throw invalidPayload('request.ticketRelations');
|
|
127
|
+
return {
|
|
128
|
+
commitOid: stringField(source, 'commitOid', 64, CollabValidation_1.isCollabGitOid),
|
|
129
|
+
id: stringField(source, 'id', 128, CollabValidation_1.isCollabOpaqueId),
|
|
130
|
+
kind: source.kind,
|
|
131
|
+
state: source.state,
|
|
132
|
+
ticketId: stringField(source, 'ticketId', 128, CollabValidation_1.isCollabOpaqueId),
|
|
133
|
+
ticketNumber: positiveInteger(source, 'ticketNumber'),
|
|
134
|
+
ticketRevision: positiveInteger(source, 'ticketRevision'),
|
|
135
|
+
ticketTitle: stringField(source, 'ticketTitle', CollabConstants_1.COLLAB_LIMITS.maxTicketTitleUtf16),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function decodeOpenRequest(value) {
|
|
139
|
+
const source = exactRecord(value, 'openRequest', [
|
|
140
|
+
'commentCount',
|
|
141
|
+
'createdAt',
|
|
142
|
+
'description',
|
|
143
|
+
'firstBaseOid',
|
|
144
|
+
'id',
|
|
145
|
+
'latestHeadOid',
|
|
146
|
+
'memberId',
|
|
147
|
+
'revision',
|
|
148
|
+
'status',
|
|
149
|
+
'ticketRelations',
|
|
150
|
+
'updatedAt',
|
|
151
|
+
]);
|
|
152
|
+
if (source.status !== 'open'
|
|
153
|
+
|| !Array.isArray(source.ticketRelations)
|
|
154
|
+
|| source.ticketRelations.length > CollabConstants_1.COLLAB_LIMITS.maxRequestTicketRelations)
|
|
155
|
+
throw invalidPayload('openRequest');
|
|
156
|
+
return {
|
|
157
|
+
commentCount: nonNegativeInteger(source, 'commentCount', CollabConstants_1.COLLAB_LIMITS.maxRequestComments),
|
|
158
|
+
createdAt: timestamp(source, 'createdAt'),
|
|
159
|
+
description: textUtf8(source, 'description', CollabConstants_1.COLLAB_LIMITS.maxRequestDescriptionBytes),
|
|
160
|
+
firstBaseOid: stringField(source, 'firstBaseOid', 64, CollabValidation_1.isCollabGitOid),
|
|
161
|
+
id: stringField(source, 'id', 128, CollabValidation_1.isCollabOpaqueId),
|
|
162
|
+
latestHeadOid: stringField(source, 'latestHeadOid', 64, CollabValidation_1.isCollabGitOid),
|
|
163
|
+
memberId: stringField(source, 'memberId', 64, CollabValidation_1.isCollabMemberId),
|
|
164
|
+
revision: nonNegativeInteger(source, 'revision'),
|
|
165
|
+
status: 'open',
|
|
166
|
+
ticketRelations: source.ticketRelations.map(decodeRequestTicketRelation),
|
|
167
|
+
updatedAt: timestamp(source, 'updatedAt'),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function decodeOpenTicket(value) {
|
|
171
|
+
const source = exactRecord(value, 'ticketHighlight', [
|
|
172
|
+
'acceptedRelationCount',
|
|
173
|
+
'authorMemberId',
|
|
174
|
+
'commentCount',
|
|
175
|
+
'createdAt',
|
|
176
|
+
'id',
|
|
177
|
+
'number',
|
|
178
|
+
'revision',
|
|
179
|
+
'status',
|
|
180
|
+
'title',
|
|
181
|
+
'updatedAt',
|
|
182
|
+
]);
|
|
183
|
+
if (source.status !== 'open')
|
|
184
|
+
throw invalidPayload('ticketHighlight');
|
|
185
|
+
return {
|
|
186
|
+
acceptedRelationCount: nonNegativeInteger(source, 'acceptedRelationCount', CollabConstants_1.COLLAB_LIMITS.maxTicketAcceptedRelations),
|
|
187
|
+
authorMemberId: stringField(source, 'authorMemberId', 64, CollabValidation_1.isCollabMemberId),
|
|
188
|
+
commentCount: nonNegativeInteger(source, 'commentCount', CollabConstants_1.COLLAB_LIMITS.maxTicketComments),
|
|
189
|
+
createdAt: timestamp(source, 'createdAt'),
|
|
190
|
+
id: stringField(source, 'id', 128, CollabValidation_1.isCollabOpaqueId),
|
|
191
|
+
number: positiveInteger(source, 'number'),
|
|
192
|
+
revision: nonNegativeInteger(source, 'revision'),
|
|
193
|
+
status: 'open',
|
|
194
|
+
title: stringField(source, 'title', CollabConstants_1.COLLAB_LIMITS.maxTicketTitleUtf16),
|
|
195
|
+
updatedAt: timestamp(source, 'updatedAt'),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function assertSnapshotSize(value) {
|
|
199
|
+
let serialized;
|
|
200
|
+
try {
|
|
201
|
+
serialized = JSON.stringify(value);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
throw invalidPayload('snapshot');
|
|
205
|
+
}
|
|
206
|
+
if (!(0, CollabValidation_1.hasUtf8ByteLengthAtMost)(serialized, CollabCloudBinding_1.COLLAB_CLOUD_BINDING_LIMITS.maxCloudSnapshotUtf8Bytes))
|
|
207
|
+
throw invalidPayload('snapshot');
|
|
208
|
+
}
|
|
209
|
+
function decodeCollabCloudProjectSnapshot(value) {
|
|
210
|
+
assertSnapshotSize(value);
|
|
211
|
+
const source = exactRecord(value, 'snapshot', [
|
|
212
|
+
'currentMember',
|
|
213
|
+
'eventSequence',
|
|
214
|
+
'members',
|
|
215
|
+
'openRequests',
|
|
216
|
+
'openTicketCount',
|
|
217
|
+
'project',
|
|
218
|
+
'ticketHighlights',
|
|
219
|
+
]);
|
|
220
|
+
if (!Array.isArray(source.members)
|
|
221
|
+
|| source.members.length < 1
|
|
222
|
+
|| source.members.length > CollabCloudBinding_1.COLLAB_CLOUD_BINDING_LIMITS.maxCloudProjectMembers
|
|
223
|
+
|| !Array.isArray(source.openRequests)
|
|
224
|
+
|| source.openRequests.length > CollabCloudBinding_1.COLLAB_CLOUD_BINDING_LIMITS.maxCloudOpenRequests
|
|
225
|
+
|| !Array.isArray(source.ticketHighlights)
|
|
226
|
+
|| source.ticketHighlights.length > CollabCloudBinding_1.COLLAB_CLOUD_BINDING_LIMITS.maxCloudTicketHighlights)
|
|
227
|
+
throw invalidPayload('snapshotCollections');
|
|
228
|
+
const members = source.members.map(decodeMember);
|
|
229
|
+
if (members.some((item, index) => (index > 0 && members[index - 1].id.localeCompare(item.id, 'en-US') >= 0)))
|
|
230
|
+
throw invalidPayload('members');
|
|
231
|
+
const currentMember = decodeMember(source.currentMember);
|
|
232
|
+
const matchingMember = members.find(member => member.id === currentMember.id);
|
|
233
|
+
if (!matchingMember || JSON.stringify(matchingMember) !== JSON.stringify(currentMember)) {
|
|
234
|
+
throw invalidPayload('currentMember');
|
|
235
|
+
}
|
|
236
|
+
const openRequests = source.openRequests.map(decodeOpenRequest);
|
|
237
|
+
if (openRequests.some((item, index) => ((index > 0 && openRequests[index - 1].id.localeCompare(item.id, 'en-US') >= 0)
|
|
238
|
+
|| !members.some(member => member.id === item.memberId))))
|
|
239
|
+
throw invalidPayload('openRequests');
|
|
240
|
+
const ticketHighlights = source.ticketHighlights.map(decodeOpenTicket);
|
|
241
|
+
if (ticketHighlights.some((item, index) => {
|
|
242
|
+
if (index === 0)
|
|
243
|
+
return false;
|
|
244
|
+
const previous = ticketHighlights[index - 1];
|
|
245
|
+
return previous.updatedAt < item.updatedAt
|
|
246
|
+
|| (previous.updatedAt === item.updatedAt
|
|
247
|
+
&& previous.id.localeCompare(item.id, 'en-US') >= 0);
|
|
248
|
+
}))
|
|
249
|
+
throw invalidPayload('ticketHighlights');
|
|
250
|
+
const openTicketCount = nonNegativeInteger(source, 'openTicketCount');
|
|
251
|
+
if (openTicketCount < ticketHighlights.length)
|
|
252
|
+
throw invalidPayload('openTicketCount');
|
|
253
|
+
return {
|
|
254
|
+
currentMember,
|
|
255
|
+
eventSequence: nonNegativeInteger(source, 'eventSequence'),
|
|
256
|
+
members,
|
|
257
|
+
openRequests,
|
|
258
|
+
openTicketCount,
|
|
259
|
+
project: decodeProject(source.project),
|
|
260
|
+
ticketHighlights,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function decodeSnapshotRequest(value) {
|
|
264
|
+
try {
|
|
265
|
+
const source = exactRecord(value, 'request', ['projectId']);
|
|
266
|
+
return {
|
|
267
|
+
status: 'ok',
|
|
268
|
+
value: { projectId: stringField(source, 'projectId', 64, CollabValidation_1.isCollabProjectId) },
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
return {
|
|
273
|
+
status: 'invalid',
|
|
274
|
+
error: error instanceof CollabError_1.CollabError ? error : invalidPayload('request'),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
exports.COLLAB_CLOUD_PROJECT_SNAPSHOT_CODEC = Object.freeze({
|
|
279
|
+
decodeRequest: decodeSnapshotRequest,
|
|
280
|
+
decodeResponse: decodeCollabCloudProjectSnapshot,
|
|
281
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare const COLLAB_PROTOCOL_VERSION: 4;
|
|
2
|
+
export declare const COLLAB_MAIN_REF: "refs/heads/main";
|
|
3
|
+
export declare const COLLAB_MEMBER_REF_PREFIX: "refs/heads/members/";
|
|
4
|
+
export declare const COLLAB_LIMITS: Readonly<{
|
|
5
|
+
maxBlobBytes: number;
|
|
6
|
+
maxChangedPaths: 2000;
|
|
7
|
+
maxCommentBytes: number;
|
|
8
|
+
maxMemberDisplayNameUtf16: 200;
|
|
9
|
+
maxRequestDescriptionBytes: number;
|
|
10
|
+
maxProjectNameUtf16: 200;
|
|
11
|
+
maxTicketTitleUtf16: 200;
|
|
12
|
+
maxTicketBodyBytes: number;
|
|
13
|
+
maxTicketCommentBytes: number;
|
|
14
|
+
maxRequestTicketRelations: 32;
|
|
15
|
+
maxRequestComments: 500;
|
|
16
|
+
defaultTicketPageSize: 50;
|
|
17
|
+
maxTicketPageSize: 100;
|
|
18
|
+
maxTicketComments: 500;
|
|
19
|
+
maxTicketAcceptedRelations: 2000;
|
|
20
|
+
defaultCommentPageSize: 50;
|
|
21
|
+
maxCommentPageSize: 100;
|
|
22
|
+
commentPageMaxUtf8Bytes: number;
|
|
23
|
+
maxRelationsPerPage: 100;
|
|
24
|
+
relationPageMaxUtf8Bytes: number;
|
|
25
|
+
ticketPageMaxUtf8Bytes: number;
|
|
26
|
+
detailMaxUtf8Bytes: number;
|
|
27
|
+
maxJsonPayloadUtf8Bytes: number;
|
|
28
|
+
maxPageCursorUtf16: 512;
|
|
29
|
+
maxPathSegmentUtf16: 120;
|
|
30
|
+
maxRepositoryPathUtf16: 240;
|
|
31
|
+
}>;
|
|
32
|
+
export type CollabProtocolVersion = typeof COLLAB_PROTOCOL_VERSION;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.COLLAB_LIMITS = exports.COLLAB_MEMBER_REF_PREFIX = exports.COLLAB_MAIN_REF = exports.COLLAB_PROTOCOL_VERSION = void 0;
|
|
4
|
+
exports.COLLAB_PROTOCOL_VERSION = 4;
|
|
5
|
+
exports.COLLAB_MAIN_REF = 'refs/heads/main';
|
|
6
|
+
exports.COLLAB_MEMBER_REF_PREFIX = 'refs/heads/members/';
|
|
7
|
+
exports.COLLAB_LIMITS = Object.freeze({
|
|
8
|
+
maxBlobBytes: 50 * 1024 * 1024,
|
|
9
|
+
maxChangedPaths: 2_000,
|
|
10
|
+
maxCommentBytes: 16 * 1024,
|
|
11
|
+
maxMemberDisplayNameUtf16: 200,
|
|
12
|
+
maxRequestDescriptionBytes: 16 * 1024,
|
|
13
|
+
maxProjectNameUtf16: 200,
|
|
14
|
+
maxTicketTitleUtf16: 200,
|
|
15
|
+
maxTicketBodyBytes: 32 * 1024,
|
|
16
|
+
maxTicketCommentBytes: 16 * 1024,
|
|
17
|
+
maxRequestTicketRelations: 32,
|
|
18
|
+
maxRequestComments: 500,
|
|
19
|
+
defaultTicketPageSize: 50,
|
|
20
|
+
maxTicketPageSize: 100,
|
|
21
|
+
maxTicketComments: 500,
|
|
22
|
+
maxTicketAcceptedRelations: 2_000,
|
|
23
|
+
defaultCommentPageSize: 50,
|
|
24
|
+
maxCommentPageSize: 100,
|
|
25
|
+
commentPageMaxUtf8Bytes: 128 * 1024,
|
|
26
|
+
maxRelationsPerPage: 100,
|
|
27
|
+
relationPageMaxUtf8Bytes: 96 * 1024,
|
|
28
|
+
ticketPageMaxUtf8Bytes: 96 * 1024,
|
|
29
|
+
detailMaxUtf8Bytes: 448 * 1024,
|
|
30
|
+
maxJsonPayloadUtf8Bytes: 512 * 1024,
|
|
31
|
+
maxPageCursorUtf16: 512,
|
|
32
|
+
maxPathSegmentUtf16: 120,
|
|
33
|
+
maxRepositoryPathUtf16: 240,
|
|
34
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type CollabControlOperationMap, type CollabDecodeResult } from './CollabProtocol';
|
|
2
|
+
export type CollabControlOperation = keyof CollabControlOperationMap;
|
|
3
|
+
export interface CollabControlOperationCodec<Request, Response> {
|
|
4
|
+
readonly decodeRequest: (input: unknown) => CollabDecodeResult<Request>;
|
|
5
|
+
readonly decodeResponse: (input: unknown) => Response;
|
|
6
|
+
}
|
|
7
|
+
type CodecMap = {
|
|
8
|
+
readonly [Operation in CollabControlOperation]: CollabControlOperationCodec<CollabControlOperationMap[Operation]['request'], CollabControlOperationMap[Operation]['response']>;
|
|
9
|
+
};
|
|
10
|
+
export declare const COLLAB_CONTROL_OPERATION_CODECS: Readonly<{
|
|
11
|
+
readonly getRequest: CollabControlOperationCodec<import("./CollabProtocol").GetRequestRequest, import("./types").CollabRequestDetail>;
|
|
12
|
+
readonly listRequestComments: CollabControlOperationCodec<import("./CollabProtocol").ListRequestCommentsRequest, import("./types").CollabCommentPage>;
|
|
13
|
+
readonly ensureMyRequest: CollabControlOperationCodec<import("./CollabProtocol").EnsureMyRequestRequest, import("./CollabProtocol").EnsureMyRequestResponse>;
|
|
14
|
+
readonly createComment: CollabControlOperationCodec<import("./CollabProtocol").CreateCommentRequest, import("./CollabProtocol").CreateCommentResponse>;
|
|
15
|
+
readonly listTickets: CollabControlOperationCodec<import("./CollabProtocol").ListTicketsRequest, import("./types").CollabTicketPage>;
|
|
16
|
+
readonly getTicket: CollabControlOperationCodec<import("./CollabProtocol").GetTicketRequest, import("./types").CollabTicketDetail>;
|
|
17
|
+
readonly listTicketComments: CollabControlOperationCodec<import("./CollabProtocol").ListTicketCommentsRequest, import("./types").CollabTicketCommentPage>;
|
|
18
|
+
readonly listTicketAcceptedRelations: CollabControlOperationCodec<import("./CollabProtocol").ListTicketAcceptedRelationsRequest, import("./types").CollabTicketAcceptedRelationPage>;
|
|
19
|
+
readonly createTicket: CollabControlOperationCodec<import("./CollabProtocol").CreateTicketRequest, import("./CollabProtocol").CreateTicketResponse>;
|
|
20
|
+
readonly updateTicketContent: CollabControlOperationCodec<import("./CollabProtocol").UpdateTicketContentRequest, import("./CollabProtocol").TicketMutationResponse>;
|
|
21
|
+
readonly createTicketComment: CollabControlOperationCodec<import("./CollabProtocol").CreateTicketCommentRequest, import("./CollabProtocol").CreateTicketCommentResponse>;
|
|
22
|
+
readonly closeTicket: CollabControlOperationCodec<import("./CollabProtocol").ChangeTicketStatusRequest, import("./CollabProtocol").TicketMutationResponse>;
|
|
23
|
+
readonly reopenTicket: CollabControlOperationCodec<import("./CollabProtocol").ChangeTicketStatusRequest, import("./CollabProtocol").TicketMutationResponse>;
|
|
24
|
+
readonly updateMyRequestMetadata: CollabControlOperationCodec<import("./CollabProtocol").UpdateMyRequestMetadataRequest, import("./CollabProtocol").UpdateMyRequestMetadataResponse>;
|
|
25
|
+
readonly acceptRequest: CollabControlOperationCodec<import("./CollabProtocol").AcceptRequest, import("./CollabProtocol").AcceptResponse>;
|
|
26
|
+
}>;
|
|
27
|
+
export declare function collabControlOperationCodec<Operation extends CollabControlOperation>(operation: Operation): CodecMap[Operation];
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.COLLAB_CONTROL_OPERATION_CODECS = void 0;
|
|
4
|
+
exports.collabControlOperationCodec = collabControlOperationCodec;
|
|
5
|
+
const CollabError_1 = require("./CollabError");
|
|
6
|
+
const CollabRequestTicketRequestCodecs_1 = require("./CollabRequestTicketRequestCodecs");
|
|
7
|
+
const CollabRequestTicketResponseCodecs_1 = require("./CollabRequestTicketResponseCodecs");
|
|
8
|
+
function decodeRequest(operation, input) {
|
|
9
|
+
return (0, CollabRequestTicketRequestCodecs_1.decodeCollabRequestTicketOperationRequest)(operation, input);
|
|
10
|
+
}
|
|
11
|
+
function decodeResponse(operation, input) {
|
|
12
|
+
switch (operation) {
|
|
13
|
+
case 'getRequest': return (0, CollabRequestTicketResponseCodecs_1.decodeRequestDetailResponse)(input);
|
|
14
|
+
case 'listRequestComments': return (0, CollabRequestTicketResponseCodecs_1.decodeCommentPageResponse)(input);
|
|
15
|
+
case 'ensureMyRequest': return (0, CollabRequestTicketResponseCodecs_1.decodeEnsureMyRequestResponse)(input);
|
|
16
|
+
case 'createComment': return (0, CollabRequestTicketResponseCodecs_1.decodeCreateCommentResponse)(input);
|
|
17
|
+
case 'listTickets': return (0, CollabRequestTicketResponseCodecs_1.decodeTicketPageResponse)(input);
|
|
18
|
+
case 'getTicket': return (0, CollabRequestTicketResponseCodecs_1.decodeTicketDetailResponse)(input);
|
|
19
|
+
case 'listTicketComments': return (0, CollabRequestTicketResponseCodecs_1.decodeTicketCommentPageResponse)(input);
|
|
20
|
+
case 'listTicketAcceptedRelations':
|
|
21
|
+
return (0, CollabRequestTicketResponseCodecs_1.decodeTicketAcceptedRelationPageResponse)(input);
|
|
22
|
+
case 'createTicket': return (0, CollabRequestTicketResponseCodecs_1.decodeCreateTicketResponse)(input);
|
|
23
|
+
case 'updateTicketContent': return (0, CollabRequestTicketResponseCodecs_1.decodeTicketMutationResponse)(input);
|
|
24
|
+
case 'createTicketComment': return (0, CollabRequestTicketResponseCodecs_1.decodeTicketCommentResponse)(input);
|
|
25
|
+
case 'closeTicket': return (0, CollabRequestTicketResponseCodecs_1.decodeTicketMutationResponse)(input);
|
|
26
|
+
case 'reopenTicket': return (0, CollabRequestTicketResponseCodecs_1.decodeTicketMutationResponse)(input);
|
|
27
|
+
case 'updateMyRequestMetadata': return (0, CollabRequestTicketResponseCodecs_1.decodeUpdateRequestMetadataResponse)(input);
|
|
28
|
+
case 'acceptRequest': return (0, CollabRequestTicketResponseCodecs_1.decodeAcceptResponse)(input);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function codec(operation) {
|
|
32
|
+
return Object.freeze({
|
|
33
|
+
decodeRequest: (input) => decodeRequest(operation, input),
|
|
34
|
+
decodeResponse: (input) => decodeResponse(operation, input),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
exports.COLLAB_CONTROL_OPERATION_CODECS = Object.freeze({
|
|
38
|
+
getRequest: codec('getRequest'),
|
|
39
|
+
listRequestComments: codec('listRequestComments'),
|
|
40
|
+
ensureMyRequest: codec('ensureMyRequest'),
|
|
41
|
+
createComment: codec('createComment'),
|
|
42
|
+
listTickets: codec('listTickets'),
|
|
43
|
+
getTicket: codec('getTicket'),
|
|
44
|
+
listTicketComments: codec('listTicketComments'),
|
|
45
|
+
listTicketAcceptedRelations: codec('listTicketAcceptedRelations'),
|
|
46
|
+
createTicket: codec('createTicket'),
|
|
47
|
+
updateTicketContent: codec('updateTicketContent'),
|
|
48
|
+
createTicketComment: codec('createTicketComment'),
|
|
49
|
+
closeTicket: codec('closeTicket'),
|
|
50
|
+
reopenTicket: codec('reopenTicket'),
|
|
51
|
+
updateMyRequestMetadata: codec('updateMyRequestMetadata'),
|
|
52
|
+
acceptRequest: codec('acceptRequest'),
|
|
53
|
+
});
|
|
54
|
+
function collabControlOperationCodec(operation) {
|
|
55
|
+
if (!Object.hasOwn(exports.COLLAB_CONTROL_OPERATION_CODECS, operation)) {
|
|
56
|
+
throw new CollabError_1.CollabError({
|
|
57
|
+
code: 'operation-failed',
|
|
58
|
+
safeContext: { reason: 'control-operation-codec-missing' },
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const selected = exports.COLLAB_CONTROL_OPERATION_CODECS[operation];
|
|
62
|
+
return selected;
|
|
63
|
+
}
|