@doist/comms-sdk 0.0.1 → 0.2.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 (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +143 -45
  3. package/dist/cjs/authentication.js +211 -0
  4. package/dist/cjs/clients/add-comment-helper.js +70 -0
  5. package/dist/cjs/clients/base-client.js +25 -0
  6. package/dist/cjs/clients/channels-client.js +200 -0
  7. package/dist/cjs/clients/comments-client.js +159 -0
  8. package/dist/cjs/clients/conversation-messages-client.js +158 -0
  9. package/dist/cjs/clients/conversations-client.js +243 -0
  10. package/dist/cjs/clients/groups-client.js +164 -0
  11. package/dist/cjs/clients/inbox-client.js +171 -0
  12. package/dist/cjs/clients/reactions-client.js +97 -0
  13. package/dist/cjs/clients/search-client.js +138 -0
  14. package/dist/cjs/clients/threads-client.js +330 -0
  15. package/dist/cjs/clients/users-client.js +326 -0
  16. package/dist/cjs/clients/workspace-users-client.js +240 -0
  17. package/dist/cjs/clients/workspaces-client.js +166 -0
  18. package/dist/cjs/comms-api.js +66 -0
  19. package/dist/cjs/consts/endpoints.js +32 -0
  20. package/dist/cjs/index.js +48 -0
  21. package/dist/cjs/package.json +1 -0
  22. package/dist/cjs/testUtils/msw-handlers.js +51 -0
  23. package/dist/cjs/testUtils/msw-setup.js +21 -0
  24. package/dist/cjs/testUtils/obsidian-fetch-adapter.js +53 -0
  25. package/dist/cjs/testUtils/test-defaults.js +104 -0
  26. package/dist/cjs/transport/fetch-with-retry.js +136 -0
  27. package/dist/cjs/transport/http-client.js +56 -0
  28. package/dist/cjs/transport/http-dispatcher.js +143 -0
  29. package/dist/cjs/types/api-version.js +8 -0
  30. package/dist/cjs/types/entities.js +411 -0
  31. package/dist/cjs/types/enums.js +37 -0
  32. package/dist/cjs/types/errors.js +12 -0
  33. package/dist/cjs/types/http.js +4 -0
  34. package/dist/cjs/types/index.js +22 -0
  35. package/dist/cjs/types/requests.js +116 -0
  36. package/dist/cjs/utils/case-conversion.js +54 -0
  37. package/dist/cjs/utils/index.js +19 -0
  38. package/dist/cjs/utils/timestamp-conversion.js +49 -0
  39. package/dist/cjs/utils/url-helpers.js +131 -0
  40. package/dist/cjs/utils/uuidv7.js +174 -0
  41. package/dist/esm/authentication.js +203 -0
  42. package/dist/esm/clients/add-comment-helper.js +67 -0
  43. package/dist/esm/clients/base-client.js +21 -0
  44. package/dist/esm/clients/channels-client.js +196 -0
  45. package/dist/esm/clients/comments-client.js +155 -0
  46. package/dist/esm/clients/conversation-messages-client.js +154 -0
  47. package/dist/esm/clients/conversations-client.js +239 -0
  48. package/dist/esm/clients/groups-client.js +160 -0
  49. package/dist/esm/clients/inbox-client.js +167 -0
  50. package/dist/esm/clients/reactions-client.js +93 -0
  51. package/dist/esm/clients/search-client.js +134 -0
  52. package/dist/esm/clients/threads-client.js +326 -0
  53. package/dist/esm/clients/users-client.js +322 -0
  54. package/dist/esm/clients/workspace-users-client.js +236 -0
  55. package/dist/esm/clients/workspaces-client.js +162 -0
  56. package/dist/esm/comms-api.js +62 -0
  57. package/dist/esm/consts/endpoints.js +28 -0
  58. package/dist/esm/index.js +17 -0
  59. package/dist/esm/testUtils/msw-handlers.js +45 -0
  60. package/dist/esm/testUtils/msw-setup.js +18 -0
  61. package/dist/esm/testUtils/obsidian-fetch-adapter.js +50 -0
  62. package/dist/esm/testUtils/test-defaults.js +101 -0
  63. package/dist/esm/transport/fetch-with-retry.js +133 -0
  64. package/dist/esm/transport/http-client.js +51 -0
  65. package/dist/esm/transport/http-dispatcher.js +104 -0
  66. package/dist/esm/types/api-version.js +5 -0
  67. package/dist/esm/types/entities.js +408 -0
  68. package/dist/esm/types/enums.js +34 -0
  69. package/dist/esm/types/errors.js +8 -0
  70. package/dist/esm/types/http.js +1 -0
  71. package/dist/esm/types/index.js +6 -0
  72. package/dist/esm/types/requests.js +113 -0
  73. package/dist/esm/utils/case-conversion.js +47 -0
  74. package/dist/esm/utils/index.js +3 -0
  75. package/dist/esm/utils/timestamp-conversion.js +45 -0
  76. package/dist/esm/utils/url-helpers.js +112 -0
  77. package/dist/esm/utils/uuidv7.js +163 -0
  78. package/dist/types/authentication.d.ts +160 -0
  79. package/dist/types/clients/add-comment-helper.d.ts +29 -0
  80. package/dist/types/clients/base-client.d.ts +28 -0
  81. package/dist/types/clients/channels-client.d.ts +208 -0
  82. package/dist/types/clients/comments-client.d.ts +224 -0
  83. package/dist/types/clients/conversation-messages-client.d.ts +198 -0
  84. package/dist/types/clients/conversations-client.d.ts +346 -0
  85. package/dist/types/clients/groups-client.d.ts +148 -0
  86. package/dist/types/clients/inbox-client.d.ts +96 -0
  87. package/dist/types/clients/reactions-client.d.ts +57 -0
  88. package/dist/types/clients/search-client.d.ts +70 -0
  89. package/dist/types/clients/threads-client.d.ts +536 -0
  90. package/dist/types/clients/users-client.d.ts +250 -0
  91. package/dist/types/clients/workspace-users-client.d.ts +147 -0
  92. package/dist/types/clients/workspaces-client.d.ts +152 -0
  93. package/dist/types/comms-api.d.ts +62 -0
  94. package/dist/types/consts/endpoints.d.ts +24 -0
  95. package/dist/types/index.d.ts +18 -0
  96. package/dist/types/testUtils/msw-handlers.d.ts +28 -0
  97. package/dist/types/testUtils/msw-setup.d.ts +1 -0
  98. package/dist/types/testUtils/obsidian-fetch-adapter.d.ts +29 -0
  99. package/dist/types/testUtils/test-defaults.d.ts +17 -0
  100. package/dist/types/transport/fetch-with-retry.d.ts +4 -0
  101. package/dist/types/transport/http-client.d.ts +13 -0
  102. package/dist/types/transport/http-dispatcher.d.ts +10 -0
  103. package/dist/types/types/api-version.d.ts +6 -0
  104. package/dist/types/types/entities.d.ts +1288 -0
  105. package/dist/types/types/enums.d.ts +55 -0
  106. package/dist/types/types/errors.d.ts +6 -0
  107. package/dist/types/types/http.d.ts +54 -0
  108. package/dist/types/types/index.d.ts +6 -0
  109. package/dist/types/types/requests.d.ts +366 -0
  110. package/dist/types/utils/case-conversion.d.ts +8 -0
  111. package/dist/types/utils/index.d.ts +3 -0
  112. package/dist/types/utils/timestamp-conversion.d.ts +13 -0
  113. package/dist/types/utils/url-helpers.d.ts +88 -0
  114. package/dist/types/utils/uuidv7.d.ts +40 -0
  115. package/package.json +91 -8
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchWithRetry = fetchWithRetry;
4
+ const errors_1 = require("../types/errors");
5
+ const case_conversion_1 = require("../utils/case-conversion");
6
+ const timestamp_conversion_1 = require("../utils/timestamp-conversion");
7
+ const http_dispatcher_1 = require("./http-dispatcher");
8
+ async function fetchWithRetry(url, options, maxRetries = 3, customFetch) {
9
+ let lastError;
10
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
11
+ let clearTimeoutFn;
12
+ try {
13
+ let requestSignal = options.signal || undefined;
14
+ if (options.timeout && options.timeout > 0) {
15
+ const timeoutResult = createTimeoutSignal(options.timeout, requestSignal);
16
+ requestSignal = timeoutResult.signal;
17
+ clearTimeoutFn = timeoutResult.clear;
18
+ }
19
+ const response = customFetch
20
+ ? await customFetch(url, options)
21
+ : await fetchWithDefaultTransport(url, options, requestSignal);
22
+ const responseText = await response.text();
23
+ let responseData;
24
+ try {
25
+ responseData = responseText ? JSON.parse(responseText) : undefined;
26
+ }
27
+ catch {
28
+ responseData = responseText;
29
+ }
30
+ if (!response.ok) {
31
+ throw new errors_1.CommsRequestError(`Request failed with status ${response.status}`, response.status, responseData);
32
+ }
33
+ const camelCased = (0, case_conversion_1.camelCaseKeys)(responseData);
34
+ const transformed = (0, timestamp_conversion_1.transformTimestamps)(camelCased);
35
+ if (clearTimeoutFn) {
36
+ clearTimeoutFn();
37
+ }
38
+ return {
39
+ data: transformed,
40
+ status: response.status,
41
+ headers: response.headers,
42
+ };
43
+ }
44
+ catch (error) {
45
+ if (clearTimeoutFn) {
46
+ clearTimeoutFn();
47
+ }
48
+ lastError = error instanceof Error ? error : new Error('Unknown error');
49
+ if (attempt < maxRetries && isNetworkError(lastError)) {
50
+ const delay = getRetryDelay(attempt + 1);
51
+ if (delay > 0) {
52
+ await sleep(delay);
53
+ }
54
+ continue;
55
+ }
56
+ break;
57
+ }
58
+ }
59
+ if (lastError instanceof errors_1.CommsRequestError) {
60
+ throw lastError;
61
+ }
62
+ throw new errors_1.CommsRequestError(lastError?.message ?? 'Request failed');
63
+ }
64
+ async function fetchWithDefaultTransport(url, options, signal) {
65
+ const dispatcher = await (0, http_dispatcher_1.getDefaultDispatcher)();
66
+ const response = dispatcher
67
+ ? await fetch(url, {
68
+ ...options,
69
+ signal,
70
+ // @ts-expect-error - dispatcher is valid for Node.js fetch but not in TS types
71
+ dispatcher,
72
+ })
73
+ : await fetch(url, {
74
+ ...options,
75
+ signal,
76
+ });
77
+ return convertResponseToCustomFetch(response);
78
+ }
79
+ async function sleep(ms) {
80
+ return new Promise((resolve) => setTimeout(resolve, ms));
81
+ }
82
+ const timeoutErrorName = 'TimeoutError';
83
+ function createTimeoutError(timeoutMs) {
84
+ const error = new Error(`Request timeout after ${timeoutMs}ms`);
85
+ error.name = timeoutErrorName;
86
+ return error;
87
+ }
88
+ function isNetworkError(error) {
89
+ return (error.name === 'TypeError' ||
90
+ error.name === timeoutErrorName ||
91
+ error.message.toLowerCase().includes('network'));
92
+ }
93
+ function getRetryDelay(retryCount) {
94
+ return retryCount === 1 ? 0 : 500;
95
+ }
96
+ function createTimeoutSignal(timeoutMs, existingSignal) {
97
+ const controller = new AbortController();
98
+ const timeoutId = setTimeout(() => {
99
+ controller.abort(createTimeoutError(timeoutMs));
100
+ }, timeoutMs);
101
+ let abortHandler;
102
+ function clear() {
103
+ clearTimeout(timeoutId);
104
+ if (existingSignal && abortHandler) {
105
+ existingSignal.removeEventListener('abort', abortHandler);
106
+ }
107
+ }
108
+ if (existingSignal) {
109
+ if (existingSignal.aborted) {
110
+ clearTimeout(timeoutId);
111
+ controller.abort(existingSignal.reason);
112
+ }
113
+ else {
114
+ abortHandler = () => {
115
+ clearTimeout(timeoutId);
116
+ controller.abort(existingSignal.reason);
117
+ };
118
+ existingSignal.addEventListener('abort', abortHandler, { once: true });
119
+ }
120
+ }
121
+ return { signal: controller.signal, clear };
122
+ }
123
+ function convertResponseToCustomFetch(response) {
124
+ const headers = {};
125
+ response.headers.forEach((value, key) => {
126
+ headers[key] = value;
127
+ });
128
+ return {
129
+ ok: response.ok,
130
+ status: response.status,
131
+ statusText: response.statusText,
132
+ headers,
133
+ text: () => response.clone().text(),
134
+ json: () => response.json(),
135
+ };
136
+ }
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.paramsSerializer = paramsSerializer;
4
+ exports.request = request;
5
+ exports.isSuccess = isSuccess;
6
+ const case_conversion_1 = require("../utils/case-conversion");
7
+ const fetch_with_retry_1 = require("./fetch-with-retry");
8
+ function paramsSerializer(params) {
9
+ const qs = new URLSearchParams();
10
+ Object.keys(params).forEach((key) => {
11
+ const value = params[key];
12
+ if (value != null) {
13
+ if (Array.isArray(value)) {
14
+ qs.append(key, value.join(','));
15
+ }
16
+ else {
17
+ qs.append(key, String(value));
18
+ }
19
+ }
20
+ });
21
+ return qs.toString();
22
+ }
23
+ const defaultHeaders = {
24
+ 'Content-Type': 'application/json',
25
+ };
26
+ function getAuthHeader(apiToken) {
27
+ return `Bearer ${apiToken}`;
28
+ }
29
+ function getRequestConfiguration(baseURL, apiToken, requestId) {
30
+ const authHeader = apiToken ? { Authorization: getAuthHeader(apiToken) } : undefined;
31
+ const requestIdHeader = requestId ? { 'X-Request-Id': requestId } : undefined;
32
+ const headers = { ...defaultHeaders, ...authHeader, ...requestIdHeader };
33
+ return { baseURL, headers, timeout: 30000 };
34
+ }
35
+ async function request(args) {
36
+ const { httpMethod, baseUri, relativePath, apiToken, payload, requestId, customFetch } = args;
37
+ const config = getRequestConfiguration(baseUri, apiToken, requestId);
38
+ const url = new URL(relativePath, config.baseURL).toString();
39
+ const options = {
40
+ method: httpMethod,
41
+ headers: config.headers,
42
+ timeout: config.timeout,
43
+ };
44
+ if (httpMethod === 'GET' && payload) {
45
+ const searchParams = paramsSerializer((0, case_conversion_1.snakeCaseKeys)(payload));
46
+ const urlWithParams = searchParams ? `${url}?${searchParams}` : url;
47
+ return (0, fetch_with_retry_1.fetchWithRetry)(urlWithParams, options, 3, customFetch);
48
+ }
49
+ if (payload && httpMethod !== 'GET') {
50
+ options.body = JSON.stringify((0, case_conversion_1.snakeCaseKeys)(payload));
51
+ }
52
+ return (0, fetch_with_retry_1.fetchWithRetry)(url, options, 3, customFetch);
53
+ }
54
+ function isSuccess(response) {
55
+ return response.status >= 200 && response.status < 300;
56
+ }
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getDefaultDispatcher = getDefaultDispatcher;
37
+ exports.closeDefaultDispatcher = closeDefaultDispatcher;
38
+ exports.resetDefaultDispatcherForTests = resetDefaultDispatcherForTests;
39
+ exports.suppressExperimentalWarningsSync = suppressExperimentalWarningsSync;
40
+ let defaultDispatcher;
41
+ let defaultDispatcherPromise;
42
+ async function getDefaultDispatcher() {
43
+ if (defaultDispatcher) {
44
+ return defaultDispatcher;
45
+ }
46
+ if (!defaultDispatcherPromise) {
47
+ defaultDispatcherPromise = createDefaultDispatcher()
48
+ .then((dispatcher) => {
49
+ defaultDispatcher = dispatcher;
50
+ return dispatcher;
51
+ })
52
+ .catch((error) => {
53
+ defaultDispatcher = undefined;
54
+ defaultDispatcherPromise = undefined;
55
+ throw error;
56
+ });
57
+ }
58
+ return defaultDispatcherPromise;
59
+ }
60
+ /**
61
+ * Drains the default dispatcher's connection pool. CLIs and scripts should
62
+ * `await` this before exit so Node's event loop empties immediately instead
63
+ * of waiting ~4s on keep-alive. No-op in the browser branch.
64
+ */
65
+ async function closeDefaultDispatcher() {
66
+ // Clear the singleton *before* awaiting init, so any concurrent
67
+ // `getDefaultDispatcher()` after this point creates a fresh dispatcher
68
+ // instead of receiving a reference to the one we're about to close.
69
+ const initPromise = defaultDispatcherPromise;
70
+ defaultDispatcher = undefined;
71
+ defaultDispatcherPromise = undefined;
72
+ if (!initPromise)
73
+ return;
74
+ try {
75
+ const dispatcher = await initPromise;
76
+ if (dispatcher) {
77
+ await dispatcher.close();
78
+ }
79
+ }
80
+ catch {
81
+ // init failed; nothing to close
82
+ }
83
+ }
84
+ function resetDefaultDispatcherForTests() {
85
+ defaultDispatcher = undefined;
86
+ defaultDispatcherPromise = undefined;
87
+ }
88
+ function isNodeEnvironment() {
89
+ return typeof process !== 'undefined' && typeof process.versions?.node === 'string';
90
+ }
91
+ async function createDefaultDispatcher() {
92
+ if (!isNodeEnvironment()) {
93
+ return undefined;
94
+ }
95
+ // Dynamic import so non-Node consumers (browser, edge runtimes) don't pull
96
+ // undici into their bundle. `isNodeEnvironment()` above already gated this
97
+ // branch, so undici is safe to load when we get here.
98
+ const { EnvHttpProxyAgent, interceptors } = await Promise.resolve().then(() => __importStar(require('undici')));
99
+ // `allowH2: true` opts into HTTP/2 via ALPN; undici falls back to h1.1
100
+ // when the server doesn't negotiate h2. Without this flag undici
101
+ // defaults to h1.1 even when the server supports h2.
102
+ //
103
+ // `interceptors.decompress()` decodes gzip/deflate/br/zstd bodies. On
104
+ // Node 24+, attaching any custom dispatcher to global `fetch` strips
105
+ // `content-encoding` without actually decompressing the body.
106
+ // See https://github.com/Doist/todoist-cli/issues/318.
107
+ //
108
+ // Both emit ExperimentalWarning on first use; suppressed during init.
109
+ return suppressExperimentalWarningsSync(() => {
110
+ const decompress = interceptors.decompress();
111
+ return new EnvHttpProxyAgent({ allowH2: true }).compose(decompress);
112
+ });
113
+ }
114
+ // `suppressExperimentalWarningsSync` is exported for direct unit testing —
115
+ // the integration path through `getDefaultDispatcher()` can't reliably
116
+ // exercise it because both the dispatcher singleton and undici's internal
117
+ // `warningEmitted` flag are once-per-process.
118
+ //
119
+ // `fn` must be synchronous so the override covers a single critical section
120
+ // (microseconds) — no unrelated `ExperimentalWarning` from elsewhere can
121
+ // interleave and be lost. We suppress every `ExperimentalWarning` rather than
122
+ // pattern-matching the message text: the message wording is an undici
123
+ // implementation detail (not a stable API), and the suppression window is
124
+ // narrow enough that a coarse type filter is safe.
125
+ function suppressExperimentalWarningsSync(fn) {
126
+ const originalEmit = process.emitWarning;
127
+ process.emitWarning = ((warning, typeOrOptions, ...rest) => {
128
+ const type = typeof typeOrOptions === 'string'
129
+ ? typeOrOptions
130
+ : typeof typeOrOptions === 'object' && typeOrOptions !== null
131
+ ? typeOrOptions.type
132
+ : undefined;
133
+ if (type === 'ExperimentalWarning')
134
+ return;
135
+ originalEmit.call(process, warning, typeOrOptions, ...rest);
136
+ });
137
+ try {
138
+ return fn();
139
+ }
140
+ finally {
141
+ process.emitWarning = originalEmit;
142
+ }
143
+ }
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_API_VERSION = exports.API_VERSIONS = void 0;
4
+ /**
5
+ * Supported Comms API versions
6
+ */
7
+ exports.API_VERSIONS = ['v1'];
8
+ exports.DEFAULT_API_VERSION = 'v1';