@doist/comms-sdk 3.0.1 → 3.1.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/dist/esm/clients/attachments-client.js +28 -3
- package/dist/esm/consts/endpoints.js +1 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/transport/fetch-with-retry.js +67 -12
- package/dist/types/clients/attachments-client.d.ts +16 -1
- package/dist/types/consts/endpoints.d.ts +1 -0
- package/dist/types/index.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ENDPOINT_ATTACHMENTS, ENDPOINT_FILES } from '../consts/endpoints.js';
|
|
3
|
+
import { request } from '../transport/http-client.js';
|
|
2
4
|
import { AttachmentSchema } from '../types/entities.js';
|
|
3
5
|
import { uploadMultipartFile } from '../utils/multipart-upload.js';
|
|
4
|
-
import { isValidUuidV7Base58, resolveCreateId, UuidV7Error } from '../utils/uuidv7.js';
|
|
6
|
+
import { isValidUuidV7Base58, resolveCreateId, resolveReferenceId, UuidV7Error, } from '../utils/uuidv7.js';
|
|
5
7
|
import { BaseClient } from './base-client.js';
|
|
6
8
|
/**
|
|
7
9
|
* Resolve the `attachment_id`: validate a caller-supplied value (throwing an
|
|
@@ -15,14 +17,37 @@ function resolveAttachmentId(attachmentId) {
|
|
|
15
17
|
}
|
|
16
18
|
return resolveCreateId(attachmentId);
|
|
17
19
|
}
|
|
20
|
+
export const IMAGE_READ_MIME_TYPES = ['image/jpeg', 'image/png', 'image/webp'];
|
|
21
|
+
export const ImageReadResultSchema = z.object({
|
|
22
|
+
mimeType: z.enum(IMAGE_READ_MIME_TYPES),
|
|
23
|
+
dataBase64: z.string().min(1).max(5592408).base64(),
|
|
24
|
+
byteLength: z
|
|
25
|
+
.number()
|
|
26
|
+
.int()
|
|
27
|
+
.positive()
|
|
28
|
+
.max(4 * 1024 * 1024),
|
|
29
|
+
});
|
|
18
30
|
/**
|
|
19
|
-
* Client for uploading
|
|
31
|
+
* Client for uploading attachments and reading thread images.
|
|
20
32
|
*
|
|
21
33
|
* Attachments are uploaded independently, then referenced by passing the returned
|
|
22
34
|
* {@link Attachment} into the `attachments` array of `comments.createComment`,
|
|
23
35
|
* `conversationMessages.createMessage`, and similar calls.
|
|
24
36
|
*/
|
|
25
37
|
export class AttachmentsClient extends BaseClient {
|
|
38
|
+
/** Read one thread image under the caller's content scope. */
|
|
39
|
+
async readImage(uploadId, threadId) {
|
|
40
|
+
const id = resolveReferenceId(uploadId, 'uploadId');
|
|
41
|
+
const checkedThreadId = resolveReferenceId(threadId, 'threadId');
|
|
42
|
+
const response = await request({
|
|
43
|
+
httpMethod: 'GET',
|
|
44
|
+
baseUri: this.getBaseUri(),
|
|
45
|
+
relativePath: `${ENDPOINT_FILES}/image/${checkedThreadId}/${id}`,
|
|
46
|
+
apiToken: this.apiToken,
|
|
47
|
+
customFetch: this.customFetch,
|
|
48
|
+
});
|
|
49
|
+
return ImageReadResultSchema.parse(response.data);
|
|
50
|
+
}
|
|
26
51
|
/**
|
|
27
52
|
* Uploads a file and returns the created {@link Attachment}.
|
|
28
53
|
*
|
|
@@ -27,4 +27,5 @@ export const ENDPOINT_REACTIONS = 'reactions';
|
|
|
27
27
|
export const ENDPOINT_SEARCH = 'search';
|
|
28
28
|
export const ENDPOINT_CONVERSATION_MESSAGES = 'conversation_messages';
|
|
29
29
|
export const ENDPOINT_ATTACHMENTS = 'attachments';
|
|
30
|
+
export const ENDPOINT_FILES = 'files';
|
|
30
31
|
export const ENDPOINT_HOOKS = 'hooks';
|
package/dist/esm/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from './authentication.js';
|
|
2
2
|
export * from './migration.js';
|
|
3
|
-
export { AttachmentsClient } from './clients/attachments-client.js';
|
|
3
|
+
export { AttachmentsClient, IMAGE_READ_MIME_TYPES, ImageReadResultSchema, } from './clients/attachments-client.js';
|
|
4
4
|
export { ChannelsClient } from './clients/channels-client.js';
|
|
5
5
|
export { CommentsClient } from './clients/comments-client.js';
|
|
6
6
|
export { ConversationMessagesClient } from './clients/conversation-messages-client.js';
|
|
@@ -79,22 +79,30 @@ function describeTransportFailure(error, attempts) {
|
|
|
79
79
|
}
|
|
80
80
|
function describeCauseChain(error) {
|
|
81
81
|
const descriptions = [];
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
while (current instanceof Error &&
|
|
85
|
-
!seen.has(current) &&
|
|
86
|
-
descriptions.length < CAUSE_CHAIN_LIMIT) {
|
|
87
|
-
seen.add(current);
|
|
88
|
-
const description = describeCause(current);
|
|
82
|
+
for (const cause of causeChain(error)) {
|
|
83
|
+
const description = describeCause(cause);
|
|
89
84
|
if (description) {
|
|
90
85
|
descriptions.push(description);
|
|
91
86
|
}
|
|
92
|
-
|
|
93
|
-
|
|
87
|
+
}
|
|
88
|
+
return descriptions.length > 0 ? descriptions.join(' <- ') : undefined;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Walks an error's `cause` chain, stopping at {@link CAUSE_CHAIN_LIMIT} and on
|
|
92
|
+
* a cycle. A failed connect can carry one error per address it tried; the first
|
|
93
|
+
* is representative and the rest repeat the same failure.
|
|
94
|
+
*/
|
|
95
|
+
function* causeChain(error) {
|
|
96
|
+
const seen = new Set();
|
|
97
|
+
let current = error?.cause;
|
|
98
|
+
let depth = 0;
|
|
99
|
+
while (current instanceof Error && !seen.has(current) && depth < CAUSE_CHAIN_LIMIT) {
|
|
100
|
+
seen.add(current);
|
|
101
|
+
depth++;
|
|
102
|
+
yield current;
|
|
94
103
|
const aggregated = current instanceof AggregateError ? current.errors[0] : undefined;
|
|
95
104
|
current = aggregated ?? current.cause;
|
|
96
105
|
}
|
|
97
|
-
return descriptions.length > 0 ? descriptions.join(' <- ') : undefined;
|
|
98
106
|
}
|
|
99
107
|
function describeCause(error) {
|
|
100
108
|
const code = error.code;
|
|
@@ -134,13 +142,60 @@ function createTimeoutError(timeoutMs) {
|
|
|
134
142
|
error.name = timeoutErrorName;
|
|
135
143
|
return error;
|
|
136
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Transport-level failure codes worth another attempt: the request never got an
|
|
147
|
+
* answer, so nothing about the response argues against retrying. `UND_ERR_SOCKET`
|
|
148
|
+
* covers the HTTP/2 `GOAWAY` an edge proxy sends when it retires a pooled
|
|
149
|
+
* connection, which is otherwise indistinguishable from any other `fetch failed`.
|
|
150
|
+
*/
|
|
151
|
+
const NETWORK_ERROR_CODES = new Set([
|
|
152
|
+
'ECONNREFUSED',
|
|
153
|
+
'ECONNRESET',
|
|
154
|
+
'EAI_AGAIN',
|
|
155
|
+
'ENOTFOUND',
|
|
156
|
+
'EPIPE',
|
|
157
|
+
'ETIMEDOUT',
|
|
158
|
+
'UND_ERR_SOCKET',
|
|
159
|
+
]);
|
|
137
160
|
function isNetworkError(error) {
|
|
138
161
|
return (error.name === 'TypeError' ||
|
|
139
162
|
error.name === timeoutErrorName ||
|
|
140
|
-
error.message.toLowerCase().includes('network')
|
|
163
|
+
error.message.toLowerCase().includes('network') ||
|
|
164
|
+
hasNetworkErrorCode(error));
|
|
165
|
+
}
|
|
166
|
+
function hasNetworkErrorCode(error) {
|
|
167
|
+
// The code can be on the error itself — a `customFetch` is free to reject
|
|
168
|
+
// with one directly — or on anything `fetch` wrapped to produce it.
|
|
169
|
+
if (hasRetryableCode(error)) {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
for (const cause of causeChain(error)) {
|
|
173
|
+
if (hasRetryableCode(cause)) {
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return false;
|
|
141
178
|
}
|
|
179
|
+
function hasRetryableCode(error) {
|
|
180
|
+
const code = error.code;
|
|
181
|
+
return typeof code === 'string' && NETWORK_ERROR_CODES.has(code);
|
|
182
|
+
}
|
|
183
|
+
/** First retry delay, doubling by {@link RETRY_DELAY_FACTOR} per attempt. */
|
|
184
|
+
const RETRY_BASE_DELAY_MS = 100;
|
|
185
|
+
const RETRY_DELAY_FACTOR = 4;
|
|
186
|
+
const RETRY_MAX_DELAY_MS = 2000;
|
|
187
|
+
/**
|
|
188
|
+
* How long to wait before retry `retryCount` (1-based).
|
|
189
|
+
*
|
|
190
|
+
* A retired connection takes a moment to be torn down and replaced, so retrying
|
|
191
|
+
* immediately just re-dispatches onto the socket that is going away and burns
|
|
192
|
+
* the whole retry budget inside a second. The delay grows so the later attempts
|
|
193
|
+
* land on a fresh connection, and carries jitter because one retirement hits
|
|
194
|
+
* every pooled connection at once: without it, every caller reconnects in step.
|
|
195
|
+
*/
|
|
142
196
|
function getRetryDelay(retryCount) {
|
|
143
|
-
|
|
197
|
+
const delay = Math.min(RETRY_BASE_DELAY_MS * RETRY_DELAY_FACTOR ** (retryCount - 1), RETRY_MAX_DELAY_MS);
|
|
198
|
+
return delay / 2 + Math.random() * (delay / 2);
|
|
144
199
|
}
|
|
145
200
|
function createTimeoutSignal(timeoutMs, existingSignal) {
|
|
146
201
|
const controller = new AbortController();
|
|
@@ -1,14 +1,29 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
import { type Attachment } from '../types/entities.js';
|
|
2
3
|
import type { UploadAttachmentArgs } from '../types/requests.js';
|
|
3
4
|
import { BaseClient } from './base-client.js';
|
|
5
|
+
export declare const IMAGE_READ_MIME_TYPES: readonly ["image/jpeg", "image/png", "image/webp"];
|
|
6
|
+
export type ImageReadMimeType = (typeof IMAGE_READ_MIME_TYPES)[number];
|
|
7
|
+
export declare const ImageReadResultSchema: z.ZodObject<{
|
|
8
|
+
mimeType: z.ZodEnum<{
|
|
9
|
+
"image/png": "image/png";
|
|
10
|
+
"image/jpeg": "image/jpeg";
|
|
11
|
+
"image/webp": "image/webp";
|
|
12
|
+
}>;
|
|
13
|
+
dataBase64: z.ZodString;
|
|
14
|
+
byteLength: z.ZodNumber;
|
|
15
|
+
}, z.core.$strip>;
|
|
16
|
+
export type ImageReadResult = z.infer<typeof ImageReadResultSchema>;
|
|
4
17
|
/**
|
|
5
|
-
* Client for uploading
|
|
18
|
+
* Client for uploading attachments and reading thread images.
|
|
6
19
|
*
|
|
7
20
|
* Attachments are uploaded independently, then referenced by passing the returned
|
|
8
21
|
* {@link Attachment} into the `attachments` array of `comments.createComment`,
|
|
9
22
|
* `conversationMessages.createMessage`, and similar calls.
|
|
10
23
|
*/
|
|
11
24
|
export declare class AttachmentsClient extends BaseClient {
|
|
25
|
+
/** Read one thread image under the caller's content scope. */
|
|
26
|
+
readImage(uploadId: string, threadId: string): Promise<ImageReadResult>;
|
|
12
27
|
/**
|
|
13
28
|
* Uploads a file and returns the created {@link Attachment}.
|
|
14
29
|
*
|
|
@@ -23,4 +23,5 @@ export declare const ENDPOINT_REACTIONS = "reactions";
|
|
|
23
23
|
export declare const ENDPOINT_SEARCH = "search";
|
|
24
24
|
export declare const ENDPOINT_CONVERSATION_MESSAGES = "conversation_messages";
|
|
25
25
|
export declare const ENDPOINT_ATTACHMENTS = "attachments";
|
|
26
|
+
export declare const ENDPOINT_FILES = "files";
|
|
26
27
|
export declare const ENDPOINT_HOOKS = "hooks";
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from './authentication.js';
|
|
2
2
|
export * from './migration.js';
|
|
3
|
-
export { AttachmentsClient } from './clients/attachments-client.js';
|
|
3
|
+
export { AttachmentsClient, IMAGE_READ_MIME_TYPES, ImageReadResultSchema, type ImageReadMimeType, type ImageReadResult, } from './clients/attachments-client.js';
|
|
4
4
|
export { ChannelsClient } from './clients/channels-client.js';
|
|
5
5
|
export { CommentsClient } from './clients/comments-client.js';
|
|
6
6
|
export { ConversationMessagesClient } from './clients/conversation-messages-client.js';
|