@doist/twist-sdk 2.1.4 → 2.3.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/README.md CHANGED
@@ -21,7 +21,8 @@ import { TwistApi } from '@doist/twist-sdk'
21
21
 
22
22
  const api = new TwistApi('YOUR_API_TOKEN')
23
23
 
24
- api.users.getSessionUser()
24
+ api.users
25
+ .getSessionUser()
25
26
  .then((user) => console.log(user))
26
27
  .catch((error) => console.log(error))
27
28
  ```
@@ -38,7 +39,7 @@ const authUrl = getAuthorizationUrl(
38
39
  'your-client-id',
39
40
  ['user:read', 'channels:read'],
40
41
  'state-parameter',
41
- 'https://yourapp.com/callback'
42
+ 'https://yourapp.com/callback',
42
43
  )
43
44
 
44
45
  // Step 2: Exchange authorization code for access token
@@ -46,7 +47,7 @@ const tokenResponse = await getAuthToken({
46
47
  clientId: 'your-client-id',
47
48
  clientSecret: 'your-client-secret',
48
49
  code: 'authorization-code',
49
- redirectUri: 'https://yourapp.com/callback'
50
+ redirectUri: 'https://yourapp.com/callback',
50
51
  })
51
52
 
52
53
  // Step 3: Use the access token
@@ -76,7 +77,7 @@ const user2 = await api.workspaceUsers.getUserById(123, 789)
76
77
  // Batch requests - executes in a single HTTP call
77
78
  const results = await api.batch(
78
79
  api.workspaceUsers.getUserById(123, 456, { batch: true }),
79
- api.workspaceUsers.getUserById(123, 789, { batch: true })
80
+ api.workspaceUsers.getUserById(123, 789, { batch: true }),
80
81
  )
81
82
 
82
83
  console.log(results[0].data.name) // First user
@@ -94,7 +95,7 @@ Each item in the batch response includes:
94
95
  ```typescript
95
96
  const results = await api.batch(
96
97
  api.channels.getChannel(123, { batch: true }),
97
- api.channels.getChannel(456, { batch: true })
98
+ api.channels.getChannel(456, { batch: true }),
98
99
  )
99
100
 
100
101
  results.forEach((result) => {
@@ -115,7 +116,7 @@ When all requests in a batch are GET requests, they are executed in parallel on
115
116
  const results = await api.batch(
116
117
  api.workspaceUsers.getUserById(123, 456, { batch: true }),
117
118
  api.channels.getChannel(789, { batch: true }),
118
- api.threads.getThread(101112, { batch: true })
119
+ api.threads.getThread(101112, { batch: true }),
119
120
  )
120
121
  ```
121
122
 
@@ -127,7 +128,7 @@ You can batch requests across different resource types:
127
128
  const results = await api.batch(
128
129
  api.workspaceUsers.getUserById(123, 456, { batch: true }),
129
130
  api.channels.getChannels({ workspaceId: 123 }, { batch: true }),
130
- api.conversations.getConversations({ workspaceId: 123 }, { batch: true })
131
+ api.conversations.getConversations({ workspaceId: 123 }, { batch: true }),
131
132
  )
132
133
 
133
134
  const [user, channels, conversations] = results
@@ -144,7 +145,7 @@ Individual requests in a batch can fail independently. Always check the status c
144
145
  ```typescript
145
146
  const results = await api.batch(
146
147
  api.channels.getChannel(123, { batch: true }),
147
- api.channels.getChannel(999999, { batch: true }) // Non-existent channel
148
+ api.channels.getChannel(999999, { batch: true }), // Non-existent channel
148
149
  )
149
150
 
150
151
  results.forEach((result, index) => {
@@ -178,7 +179,8 @@ import { TwistApi } from './twist-api'
178
179
  const token = 'YOUR_API_TOKEN'
179
180
  const api = new TwistApi(token)
180
181
 
181
- api.workspaces.getWorkspaces()
182
+ api.workspaces
183
+ .getWorkspaces()
182
184
  .then((workspaces) => {
183
185
  console.log(workspaces)
184
186
  })
@@ -46,12 +46,24 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
46
46
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
47
  }
48
48
  };
49
+ var __rest = (this && this.__rest) || function (s, e) {
50
+ var t = {};
51
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
52
+ t[p] = s[p];
53
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
54
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
55
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
56
+ t[p[i]] = s[p[i]];
57
+ }
58
+ return t;
59
+ };
49
60
  Object.defineProperty(exports, "__esModule", { value: true });
50
- exports.TWIST_SCOPES = void 0;
61
+ exports.TOKEN_ENDPOINT_AUTH_METHODS = exports.TWIST_SCOPES = void 0;
51
62
  exports.getAuthStateParameter = getAuthStateParameter;
52
63
  exports.getAuthorizationUrl = getAuthorizationUrl;
53
64
  exports.getAuthToken = getAuthToken;
54
65
  exports.revokeAuthToken = revokeAuthToken;
66
+ exports.registerClient = registerClient;
55
67
  var uuid_1 = require("uuid");
56
68
  var http_client_1 = require("./transport/http-client");
57
69
  var errors_1 = require("./types/errors");
@@ -139,9 +151,22 @@ exports.TWIST_SCOPES = [
139
151
  'notifications:read',
140
152
  'notifications:write',
141
153
  ];
154
+ /** Supported token endpoint authentication methods for dynamic client registration. */
155
+ exports.TOKEN_ENDPOINT_AUTH_METHODS = [
156
+ 'client_secret_post',
157
+ 'client_secret_basic',
158
+ 'none',
159
+ ];
142
160
  function getAuthStateParameter() {
143
161
  return (0, uuid_1.v4)();
144
162
  }
163
+ /**
164
+ * Generates the authorization URL for the OAuth2 flow.
165
+ *
166
+ * The `clientId` can be either a traditional client ID string (e.g. from
167
+ * {@link registerClient}) or an HTTPS URL pointing to a client metadata document,
168
+ * as defined in {@link https://drafts.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/ RFC draft-ietf-oauth-client-id-metadata-document}.
169
+ */
145
170
  function getAuthorizationUrl(clientId, scopes, state, redirectUri, baseUrl) {
146
171
  if (!(scopes === null || scopes === void 0 ? void 0 : scopes.length)) {
147
172
  throw new Error('At least one scope value is required.');
@@ -214,3 +239,52 @@ function revokeAuthToken(args, options) {
214
239
  });
215
240
  });
216
241
  }
242
+ /**
243
+ * Registers a new OAuth client via Dynamic Client Registration (RFC 7591).
244
+ *
245
+ * @example
246
+ * ```typescript
247
+ * const client = await registerClient({
248
+ * redirectUris: ['https://example.com/callback'],
249
+ * clientName: 'My App',
250
+ * scope: ['user:read', 'channels:read'],
251
+ * })
252
+ * // Use client.clientId and client.clientSecret for OAuth flows
253
+ * ```
254
+ *
255
+ * @returns The registered client details
256
+ * @throws {@link TwistRequestError} If the registration fails
257
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc7591 RFC 7591}
258
+ */
259
+ function registerClient(args, options) {
260
+ return __awaiter(this, void 0, void 0, function () {
261
+ var registerUrl, response, _a, clientIdIssuedAt, clientSecretExpiresAt, scope, rest;
262
+ var _b, _c;
263
+ return __generator(this, function (_d) {
264
+ switch (_d.label) {
265
+ case 0:
266
+ registerUrl = (options === null || options === void 0 ? void 0 : options.baseUrl)
267
+ ? "".concat(options.baseUrl, "/oauth/register")
268
+ : 'https://twist.com/oauth/register';
269
+ return [4 /*yield*/, (0, http_client_1.request)({
270
+ httpMethod: 'POST',
271
+ baseUri: registerUrl,
272
+ relativePath: '',
273
+ payload: __assign(__assign({}, args), { scope: (_b = args.scope) === null || _b === void 0 ? void 0 : _b.join(' ') }),
274
+ customFetch: options === null || options === void 0 ? void 0 : options.customFetch,
275
+ })];
276
+ case 1:
277
+ response = _d.sent();
278
+ if (!(0, http_client_1.isSuccess)(response) || !((_c = response.data) === null || _c === void 0 ? void 0 : _c.clientId)) {
279
+ throw new errors_1.TwistRequestError('Dynamic client registration failed.', response.status, response.data);
280
+ }
281
+ _a = response.data, clientIdIssuedAt = _a.clientIdIssuedAt, clientSecretExpiresAt = _a.clientSecretExpiresAt, scope = _a.scope, rest = __rest(_a, ["clientIdIssuedAt", "clientSecretExpiresAt", "scope"]);
282
+ return [2 /*return*/, __assign(__assign({}, rest), { scope: scope ? scope.split(' ') : undefined, clientIdIssuedAt: clientIdIssuedAt !== undefined ? new Date(clientIdIssuedAt * 1000) : undefined, clientSecretExpiresAt: clientSecretExpiresAt === undefined
283
+ ? undefined
284
+ : clientSecretExpiresAt === 0
285
+ ? null
286
+ : new Date(clientSecretExpiresAt * 1000) })];
287
+ }
288
+ });
289
+ });
290
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.addCommentRequest = addCommentRequest;
15
+ var endpoints_1 = require("../consts/endpoints");
16
+ var http_client_1 = require("../transport/http-client");
17
+ var entities_1 = require("../types/entities");
18
+ function addCommentRequest(context, params, options) {
19
+ var method = 'POST';
20
+ var url = "".concat(endpoints_1.ENDPOINT_COMMENTS, "/add");
21
+ var payload = (options === null || options === void 0 ? void 0 : options.threadAction)
22
+ ? __assign(__assign({}, params), { threadAction: options.threadAction }) : params;
23
+ var schema = entities_1.CommentSchema;
24
+ if (options === null || options === void 0 ? void 0 : options.batch) {
25
+ return { method: method, url: url, params: payload, schema: schema };
26
+ }
27
+ return (0, http_client_1.request)({
28
+ httpMethod: method,
29
+ baseUri: context.baseUri,
30
+ relativePath: url,
31
+ apiToken: context.apiToken,
32
+ payload: payload,
33
+ customFetch: context.customFetch,
34
+ }).then(function (response) { return schema.parse(response.data); });
35
+ }
@@ -20,6 +20,7 @@ var zod_1 = require("zod");
20
20
  var endpoints_1 = require("../consts/endpoints");
21
21
  var http_client_1 = require("../transport/http-client");
22
22
  var entities_1 = require("../types/entities");
23
+ var add_comment_helper_1 = require("./add-comment-helper");
23
24
  var base_client_1 = require("./base-client");
24
25
  /**
25
26
  * Client for interacting with Twist comment endpoints.
@@ -70,21 +71,7 @@ var CommentsClient = /** @class */ (function (_super) {
70
71
  }).then(function (response) { return wrappedSchema.parse(response.data); });
71
72
  };
72
73
  CommentsClient.prototype.createComment = function (args, options) {
73
- var method = 'POST';
74
- var url = "".concat(endpoints_1.ENDPOINT_COMMENTS, "/add");
75
- var params = args;
76
- var schema = entities_1.CommentSchema;
77
- if (options === null || options === void 0 ? void 0 : options.batch) {
78
- return { method: method, url: url, params: params, schema: schema };
79
- }
80
- return (0, http_client_1.request)({
81
- httpMethod: method,
82
- baseUri: this.getBaseUri(),
83
- relativePath: url,
84
- apiToken: this.apiToken,
85
- payload: params,
86
- customFetch: this.customFetch,
87
- }).then(function (response) { return schema.parse(response.data); });
74
+ return (0, add_comment_helper_1.addCommentRequest)({ baseUri: this.getBaseUri(), apiToken: this.apiToken, customFetch: this.customFetch }, args, options);
88
75
  };
89
76
  CommentsClient.prototype.updateComment = function (args, options) {
90
77
  var method = 'POST';
@@ -14,12 +14,35 @@ var __extends = (this && this.__extends) || (function () {
14
14
  d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
15
15
  };
16
16
  })();
17
+ var __assign = (this && this.__assign) || function () {
18
+ __assign = Object.assign || function(t) {
19
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
20
+ s = arguments[i];
21
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
22
+ t[p] = s[p];
23
+ }
24
+ return t;
25
+ };
26
+ return __assign.apply(this, arguments);
27
+ };
28
+ var __rest = (this && this.__rest) || function (s, e) {
29
+ var t = {};
30
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
31
+ t[p] = s[p];
32
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
33
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
34
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
35
+ t[p[i]] = s[p[i]];
36
+ }
37
+ return t;
38
+ };
17
39
  Object.defineProperty(exports, "__esModule", { value: true });
18
40
  exports.ThreadsClient = void 0;
19
41
  var zod_1 = require("zod");
20
42
  var endpoints_1 = require("../consts/endpoints");
21
43
  var http_client_1 = require("../transport/http-client");
22
44
  var entities_1 = require("../types/entities");
45
+ var add_comment_helper_1 = require("./add-comment-helper");
23
46
  var base_client_1 = require("./base-client");
24
47
  /**
25
48
  * Client for interacting with Twist thread endpoints.
@@ -361,6 +384,16 @@ var ThreadsClient = /** @class */ (function (_super) {
361
384
  customFetch: this.customFetch,
362
385
  }).then(function (response) { return schema.parse(response.data); });
363
386
  };
387
+ ThreadsClient.prototype.closeThread = function (args, options) {
388
+ return this.addCommentWithAction(args, 'close', options);
389
+ };
390
+ ThreadsClient.prototype.reopenThread = function (args, options) {
391
+ return this.addCommentWithAction(args, 'reopen', options);
392
+ };
393
+ ThreadsClient.prototype.addCommentWithAction = function (args, threadAction, options) {
394
+ var id = args.id, rest = __rest(args, ["id"]);
395
+ return (0, add_comment_helper_1.addCommentRequest)({ baseUri: this.getBaseUri(), apiToken: this.apiToken, customFetch: this.customFetch }, __assign({ threadId: id }, rest), __assign(__assign({}, options), { threadAction: threadAction }));
396
+ };
364
397
  return ThreadsClient;
365
398
  }(base_client_1.BaseClient));
366
399
  exports.ThreadsClient = ThreadsClient;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AWAY_MODE_TYPES = exports.GetOrCreateConversationArgsSchema = exports.GetConversationsArgsSchema = exports.GetCommentsArgsSchema = exports.GetThreadsArgsSchema = exports.GetChannelsArgsSchema = exports.CreateMessageArgsSchema = exports.CreateConversationArgsSchema = exports.UpdateCommentArgsSchema = exports.CreateCommentArgsSchema = exports.UpdateThreadArgsSchema = exports.CreateThreadArgsSchema = exports.UpdateChannelArgsSchema = exports.CreateChannelArgsSchema = void 0;
3
+ exports.THREAD_ACTIONS = exports.AWAY_MODE_TYPES = exports.GetOrCreateConversationArgsSchema = exports.GetConversationsArgsSchema = exports.GetCommentsArgsSchema = exports.GetThreadsArgsSchema = exports.GetChannelsArgsSchema = exports.CreateMessageArgsSchema = exports.CreateConversationArgsSchema = exports.UpdateCommentArgsSchema = exports.CreateCommentArgsSchema = exports.UpdateThreadArgsSchema = exports.CreateThreadArgsSchema = exports.UpdateChannelArgsSchema = exports.CreateChannelArgsSchema = void 0;
4
4
  var zod_1 = require("zod");
5
5
  exports.CreateChannelArgsSchema = zod_1.z.object({
6
6
  workspaceId: zod_1.z.number(),
@@ -91,3 +91,5 @@ exports.GetOrCreateConversationArgsSchema = zod_1.z.object({
91
91
  userIds: zod_1.z.array(zod_1.z.number()),
92
92
  });
93
93
  exports.AWAY_MODE_TYPES = ['parental', 'vacation', 'sickleave', 'other'];
94
+ // Threads
95
+ exports.THREAD_ACTIONS = ['close', 'reopen'];
@@ -45,6 +45,17 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
45
45
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
46
46
  }
47
47
  };
48
+ var __rest = (this && this.__rest) || function (s, e) {
49
+ var t = {};
50
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
51
+ t[p] = s[p];
52
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
53
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
54
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
55
+ t[p[i]] = s[p[i]];
56
+ }
57
+ return t;
58
+ };
48
59
  import { v4 as uuid } from 'uuid';
49
60
  import { isSuccess, request } from './transport/http-client.js';
50
61
  import { TwistRequestError } from './types/errors.js';
@@ -132,9 +143,22 @@ export var TWIST_SCOPES = [
132
143
  'notifications:read',
133
144
  'notifications:write',
134
145
  ];
146
+ /** Supported token endpoint authentication methods for dynamic client registration. */
147
+ export var TOKEN_ENDPOINT_AUTH_METHODS = [
148
+ 'client_secret_post',
149
+ 'client_secret_basic',
150
+ 'none',
151
+ ];
135
152
  export function getAuthStateParameter() {
136
153
  return uuid();
137
154
  }
155
+ /**
156
+ * Generates the authorization URL for the OAuth2 flow.
157
+ *
158
+ * The `clientId` can be either a traditional client ID string (e.g. from
159
+ * {@link registerClient}) or an HTTPS URL pointing to a client metadata document,
160
+ * as defined in {@link https://drafts.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/ RFC draft-ietf-oauth-client-id-metadata-document}.
161
+ */
138
162
  export function getAuthorizationUrl(clientId, scopes, state, redirectUri, baseUrl) {
139
163
  if (!(scopes === null || scopes === void 0 ? void 0 : scopes.length)) {
140
164
  throw new Error('At least one scope value is required.');
@@ -207,3 +231,52 @@ export function revokeAuthToken(args, options) {
207
231
  });
208
232
  });
209
233
  }
234
+ /**
235
+ * Registers a new OAuth client via Dynamic Client Registration (RFC 7591).
236
+ *
237
+ * @example
238
+ * ```typescript
239
+ * const client = await registerClient({
240
+ * redirectUris: ['https://example.com/callback'],
241
+ * clientName: 'My App',
242
+ * scope: ['user:read', 'channels:read'],
243
+ * })
244
+ * // Use client.clientId and client.clientSecret for OAuth flows
245
+ * ```
246
+ *
247
+ * @returns The registered client details
248
+ * @throws {@link TwistRequestError} If the registration fails
249
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc7591 RFC 7591}
250
+ */
251
+ export function registerClient(args, options) {
252
+ return __awaiter(this, void 0, void 0, function () {
253
+ var registerUrl, response, _a, clientIdIssuedAt, clientSecretExpiresAt, scope, rest;
254
+ var _b, _c;
255
+ return __generator(this, function (_d) {
256
+ switch (_d.label) {
257
+ case 0:
258
+ registerUrl = (options === null || options === void 0 ? void 0 : options.baseUrl)
259
+ ? "".concat(options.baseUrl, "/oauth/register")
260
+ : 'https://twist.com/oauth/register';
261
+ return [4 /*yield*/, request({
262
+ httpMethod: 'POST',
263
+ baseUri: registerUrl,
264
+ relativePath: '',
265
+ payload: __assign(__assign({}, args), { scope: (_b = args.scope) === null || _b === void 0 ? void 0 : _b.join(' ') }),
266
+ customFetch: options === null || options === void 0 ? void 0 : options.customFetch,
267
+ })];
268
+ case 1:
269
+ response = _d.sent();
270
+ if (!isSuccess(response) || !((_c = response.data) === null || _c === void 0 ? void 0 : _c.clientId)) {
271
+ throw new TwistRequestError('Dynamic client registration failed.', response.status, response.data);
272
+ }
273
+ _a = response.data, clientIdIssuedAt = _a.clientIdIssuedAt, clientSecretExpiresAt = _a.clientSecretExpiresAt, scope = _a.scope, rest = __rest(_a, ["clientIdIssuedAt", "clientSecretExpiresAt", "scope"]);
274
+ return [2 /*return*/, __assign(__assign({}, rest), { scope: scope ? scope.split(' ') : undefined, clientIdIssuedAt: clientIdIssuedAt !== undefined ? new Date(clientIdIssuedAt * 1000) : undefined, clientSecretExpiresAt: clientSecretExpiresAt === undefined
275
+ ? undefined
276
+ : clientSecretExpiresAt === 0
277
+ ? null
278
+ : new Date(clientSecretExpiresAt * 1000) })];
279
+ }
280
+ });
281
+ });
282
+ }
@@ -0,0 +1,32 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ import { ENDPOINT_COMMENTS } from '../consts/endpoints.js';
13
+ import { request } from '../transport/http-client.js';
14
+ import { CommentSchema } from '../types/entities.js';
15
+ export function addCommentRequest(context, params, options) {
16
+ var method = 'POST';
17
+ var url = "".concat(ENDPOINT_COMMENTS, "/add");
18
+ var payload = (options === null || options === void 0 ? void 0 : options.threadAction)
19
+ ? __assign(__assign({}, params), { threadAction: options.threadAction }) : params;
20
+ var schema = CommentSchema;
21
+ if (options === null || options === void 0 ? void 0 : options.batch) {
22
+ return { method: method, url: url, params: payload, schema: schema };
23
+ }
24
+ return request({
25
+ httpMethod: method,
26
+ baseUri: context.baseUri,
27
+ relativePath: url,
28
+ apiToken: context.apiToken,
29
+ payload: payload,
30
+ customFetch: context.customFetch,
31
+ }).then(function (response) { return schema.parse(response.data); });
32
+ }
@@ -17,6 +17,7 @@ import { z } from 'zod';
17
17
  import { ENDPOINT_COMMENTS } from '../consts/endpoints.js';
18
18
  import { request } from '../transport/http-client.js';
19
19
  import { CommentSchema } from '../types/entities.js';
20
+ import { addCommentRequest } from './add-comment-helper.js';
20
21
  import { BaseClient } from './base-client.js';
21
22
  /**
22
23
  * Client for interacting with Twist comment endpoints.
@@ -67,21 +68,7 @@ var CommentsClient = /** @class */ (function (_super) {
67
68
  }).then(function (response) { return wrappedSchema.parse(response.data); });
68
69
  };
69
70
  CommentsClient.prototype.createComment = function (args, options) {
70
- var method = 'POST';
71
- var url = "".concat(ENDPOINT_COMMENTS, "/add");
72
- var params = args;
73
- var schema = CommentSchema;
74
- if (options === null || options === void 0 ? void 0 : options.batch) {
75
- return { method: method, url: url, params: params, schema: schema };
76
- }
77
- return request({
78
- httpMethod: method,
79
- baseUri: this.getBaseUri(),
80
- relativePath: url,
81
- apiToken: this.apiToken,
82
- payload: params,
83
- customFetch: this.customFetch,
84
- }).then(function (response) { return schema.parse(response.data); });
71
+ return addCommentRequest({ baseUri: this.getBaseUri(), apiToken: this.apiToken, customFetch: this.customFetch }, args, options);
85
72
  };
86
73
  CommentsClient.prototype.updateComment = function (args, options) {
87
74
  var method = 'POST';
@@ -13,10 +13,33 @@ var __extends = (this && this.__extends) || (function () {
13
13
  d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
14
14
  };
15
15
  })();
16
+ var __assign = (this && this.__assign) || function () {
17
+ __assign = Object.assign || function(t) {
18
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
19
+ s = arguments[i];
20
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
21
+ t[p] = s[p];
22
+ }
23
+ return t;
24
+ };
25
+ return __assign.apply(this, arguments);
26
+ };
27
+ var __rest = (this && this.__rest) || function (s, e) {
28
+ var t = {};
29
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
30
+ t[p] = s[p];
31
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
32
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
33
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
34
+ t[p[i]] = s[p[i]];
35
+ }
36
+ return t;
37
+ };
16
38
  import { z } from 'zod';
17
39
  import { ENDPOINT_THREADS } from '../consts/endpoints.js';
18
40
  import { request } from '../transport/http-client.js';
19
- import { ThreadSchema, UnreadThreadSchema } from '../types/entities.js';
41
+ import { ThreadSchema, UnreadThreadSchema, } from '../types/entities.js';
42
+ import { addCommentRequest } from './add-comment-helper.js';
20
43
  import { BaseClient } from './base-client.js';
21
44
  /**
22
45
  * Client for interacting with Twist thread endpoints.
@@ -358,6 +381,16 @@ var ThreadsClient = /** @class */ (function (_super) {
358
381
  customFetch: this.customFetch,
359
382
  }).then(function (response) { return schema.parse(response.data); });
360
383
  };
384
+ ThreadsClient.prototype.closeThread = function (args, options) {
385
+ return this.addCommentWithAction(args, 'close', options);
386
+ };
387
+ ThreadsClient.prototype.reopenThread = function (args, options) {
388
+ return this.addCommentWithAction(args, 'reopen', options);
389
+ };
390
+ ThreadsClient.prototype.addCommentWithAction = function (args, threadAction, options) {
391
+ var id = args.id, rest = __rest(args, ["id"]);
392
+ return addCommentRequest({ baseUri: this.getBaseUri(), apiToken: this.apiToken, customFetch: this.customFetch }, __assign({ threadId: id }, rest), __assign(__assign({}, options), { threadAction: threadAction }));
393
+ };
361
394
  return ThreadsClient;
362
395
  }(BaseClient));
363
396
  export { ThreadsClient };
@@ -88,3 +88,5 @@ export var GetOrCreateConversationArgsSchema = z.object({
88
88
  userIds: z.array(z.number()),
89
89
  });
90
90
  export var AWAY_MODE_TYPES = ['parental', 'vacation', 'sickleave', 'other'];
91
+ // Threads
92
+ export var THREAD_ACTIONS = ['close', 'reopen'];
@@ -83,7 +83,78 @@ export type RevokeAuthTokenRequestArgs = {
83
83
  clientSecret: string;
84
84
  accessToken: string;
85
85
  };
86
+ /** Supported token endpoint authentication methods for dynamic client registration. */
87
+ export declare const TOKEN_ENDPOINT_AUTH_METHODS: readonly ["client_secret_post", "client_secret_basic", "none"];
88
+ /**
89
+ * Authentication method used at the token endpoint.
90
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc7591#section-2 RFC 7591 Section 2}
91
+ */
92
+ export type TokenEndpointAuthMethod = (typeof TOKEN_ENDPOINT_AUTH_METHODS)[number];
93
+ /**
94
+ * Parameters for registering a new OAuth client via Dynamic Client Registration.
95
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc7591 RFC 7591}
96
+ */
97
+ export type ClientRegistrationRequest = {
98
+ redirectUris: string[];
99
+ clientName?: string;
100
+ clientUri?: string;
101
+ logoUri?: string;
102
+ scope?: readonly TwistScope[];
103
+ grantTypes?: string[];
104
+ responseTypes?: string[];
105
+ tokenEndpointAuthMethod?: TokenEndpointAuthMethod;
106
+ };
107
+ type RawClientRegistrationResponse = {
108
+ clientId: string;
109
+ clientSecret?: string;
110
+ clientName: string;
111
+ redirectUris: string[];
112
+ scope?: string;
113
+ grantTypes: string[];
114
+ responseTypes: string[];
115
+ tokenEndpointAuthMethod: string;
116
+ clientIdIssuedAt?: number;
117
+ clientSecretExpiresAt?: number;
118
+ clientUri?: string;
119
+ logoUri?: string;
120
+ };
121
+ /**
122
+ * Response from a successful dynamic client registration.
123
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc7591#section-3.2.1 RFC 7591 Section 3.2.1}
124
+ */
125
+ export type ClientRegistrationResponse = Omit<RawClientRegistrationResponse, 'clientIdIssuedAt' | 'clientSecretExpiresAt' | 'scope'> & {
126
+ scope?: TwistScope[];
127
+ clientIdIssuedAt?: Date;
128
+ /** `null` indicates the client secret never expires. Absent when no secret is issued. */
129
+ clientSecretExpiresAt?: Date | null;
130
+ };
86
131
  export declare function getAuthStateParameter(): string;
132
+ /**
133
+ * Generates the authorization URL for the OAuth2 flow.
134
+ *
135
+ * The `clientId` can be either a traditional client ID string (e.g. from
136
+ * {@link registerClient}) or an HTTPS URL pointing to a client metadata document,
137
+ * as defined in {@link https://drafts.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/ RFC draft-ietf-oauth-client-id-metadata-document}.
138
+ */
87
139
  export declare function getAuthorizationUrl(clientId: string, scopes: TwistScope[], state: string, redirectUri?: string, baseUrl?: string): string;
88
140
  export declare function getAuthToken(args: AuthTokenRequestArgs, options?: AuthOptions): Promise<AuthTokenResponse>;
89
141
  export declare function revokeAuthToken(args: RevokeAuthTokenRequestArgs, options?: AuthOptions): Promise<boolean>;
142
+ /**
143
+ * Registers a new OAuth client via Dynamic Client Registration (RFC 7591).
144
+ *
145
+ * @example
146
+ * ```typescript
147
+ * const client = await registerClient({
148
+ * redirectUris: ['https://example.com/callback'],
149
+ * clientName: 'My App',
150
+ * scope: ['user:read', 'channels:read'],
151
+ * })
152
+ * // Use client.clientId and client.clientSecret for OAuth flows
153
+ * ```
154
+ *
155
+ * @returns The registered client details
156
+ * @throws {@link TwistRequestError} If the registration fails
157
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc7591 RFC 7591}
158
+ */
159
+ export declare function registerClient(args: ClientRegistrationRequest, options?: AuthOptions): Promise<ClientRegistrationResponse>;
160
+ export {};
@@ -0,0 +1,14 @@
1
+ import type { BatchRequestDescriptor } from '../types/batch.js';
2
+ import { type Comment } from '../types/entities.js';
3
+ import type { CustomFetch } from '../types/http.js';
4
+ import type { ThreadAction } from '../types/requests.js';
5
+ type ClientContext = {
6
+ baseUri: string;
7
+ apiToken: string;
8
+ customFetch?: CustomFetch;
9
+ };
10
+ export declare function addCommentRequest(context: ClientContext, params: Record<string, unknown>, options?: {
11
+ batch?: boolean;
12
+ threadAction?: ThreadAction;
13
+ }): Promise<Comment> | BatchRequestDescriptor<Comment>;
14
+ export {};
@@ -1,6 +1,6 @@
1
1
  import type { BatchRequestDescriptor } from '../types/batch.js';
2
- import { type Thread, type UnreadThread } from '../types/entities.js';
3
- import { type CreateThreadArgs, type GetThreadsArgs, type MarkThreadReadArgs, type MarkThreadUnreadArgs, type MarkThreadUnreadForOthersArgs, type MoveThreadToChannelArgs, type MuteThreadArgs, type UpdateThreadArgs } from '../types/requests.js';
2
+ import { type Comment, type Thread, type UnreadThread } from '../types/entities.js';
3
+ import { type CloseThreadArgs, type CreateThreadArgs, type GetThreadsArgs, type MarkThreadReadArgs, type MarkThreadUnreadArgs, type MarkThreadUnreadForOthersArgs, type MoveThreadToChannelArgs, type MuteThreadArgs, type ReopenThreadArgs, type UpdateThreadArgs } from '../types/requests.js';
4
4
  import { BaseClient } from './base-client.js';
5
5
  /**
6
6
  * Client for interacting with Twist thread endpoints.
@@ -319,4 +319,59 @@ export declare class ThreadsClient extends BaseClient {
319
319
  unmuteThread(id: number, options?: {
320
320
  batch?: false;
321
321
  }): Promise<Thread>;
322
+ /**
323
+ * Closes a thread by adding a comment with a close action.
324
+ *
325
+ * @param args - The arguments for closing a thread.
326
+ * @param args.id - The thread ID.
327
+ * @param args.content - The comment content.
328
+ * @param args.tempId - Optional temporary identifier.
329
+ * @param args.attachments - Optional array of attachment objects.
330
+ * @param args.actions - Optional array of action objects.
331
+ * @param args.recipients - Optional array of user IDs to notify.
332
+ * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests.
333
+ * @returns The created comment object.
334
+ *
335
+ * @example
336
+ * ```typescript
337
+ * const comment = await api.threads.closeThread({
338
+ * id: 789,
339
+ * content: 'Closing this thread — resolved.'
340
+ * })
341
+ * ```
342
+ */
343
+ closeThread(args: CloseThreadArgs, options: {
344
+ batch: true;
345
+ }): BatchRequestDescriptor<Comment>;
346
+ closeThread(args: CloseThreadArgs, options?: {
347
+ batch?: false;
348
+ }): Promise<Comment>;
349
+ /**
350
+ * Reopens a thread by adding a comment with a reopen action.
351
+ *
352
+ * @param args - The arguments for reopening a thread.
353
+ * @param args.id - The thread ID.
354
+ * @param args.content - The comment content.
355
+ * @param args.tempId - Optional temporary identifier.
356
+ * @param args.attachments - Optional array of attachment objects.
357
+ * @param args.actions - Optional array of action objects.
358
+ * @param args.recipients - Optional array of user IDs to notify.
359
+ * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests.
360
+ * @returns The created comment object.
361
+ *
362
+ * @example
363
+ * ```typescript
364
+ * const comment = await api.threads.reopenThread({
365
+ * id: 789,
366
+ * content: 'Reopening — need further discussion.'
367
+ * })
368
+ * ```
369
+ */
370
+ reopenThread(args: ReopenThreadArgs, options: {
371
+ batch: true;
372
+ }): BatchRequestDescriptor<Comment>;
373
+ reopenThread(args: ReopenThreadArgs, options?: {
374
+ batch?: false;
375
+ }): Promise<Comment>;
376
+ private addCommentWithAction;
322
377
  }
@@ -202,6 +202,24 @@ export type RemoveChannelUsersArgs = {
202
202
  id: number;
203
203
  userIds: number[];
204
204
  };
205
+ export declare const THREAD_ACTIONS: readonly ["close", "reopen"];
206
+ export type ThreadAction = (typeof THREAD_ACTIONS)[number];
207
+ export type CloseThreadArgs = {
208
+ id: number;
209
+ content: string;
210
+ tempId?: number | null;
211
+ attachments?: unknown | null;
212
+ actions?: unknown | null;
213
+ recipients?: number[] | null;
214
+ };
215
+ export type ReopenThreadArgs = {
216
+ id: number;
217
+ content: string;
218
+ tempId?: number | null;
219
+ attachments?: unknown | null;
220
+ actions?: unknown | null;
221
+ recipients?: number[] | null;
222
+ };
205
223
  export type MoveThreadToChannelArgs = {
206
224
  id: number;
207
225
  toChannel: number;
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@doist/twist-sdk",
3
- "version": "2.1.4",
3
+ "version": "2.3.0",
4
4
  "description": "A TypeScript wrapper for the Twist REST API.",
5
5
  "author": "Doist developers",
6
6
  "homepage": "https://doist.github.io/twist-sdk-typescript/",
7
7
  "repository": "https://github.com/Doist/twist-sdk-typescript",
8
8
  "publishConfig": {
9
- "registry": "https://registry.npmjs.org"
9
+ "access": "public",
10
+ "provenance": true
10
11
  },
11
12
  "license": "MIT",
12
13
  "type": "module",
@@ -33,13 +34,8 @@
33
34
  "build:post": "echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json",
34
35
  "build": "npm-run-all clean build:cjs build:esm build:fix-esm build:fix-dts build:post",
35
36
  "type-check": "npx tsc --noEmit",
36
- "biome:sort-imports": "biome check --formatter-enabled=false --linter-enabled=false --organize-imports-enabled=true --write .",
37
- "lint:check": "biome lint",
38
- "lint:write": "biome lint --write",
39
- "format:check": "biome format",
40
- "format:write": "biome format --write",
41
- "check": "biome check",
42
- "check:fix": "biome check --fix --unsafe",
37
+ "check": "oxlint src && oxfmt --check",
38
+ "check:fix": "oxlint src --fix && oxfmt",
43
39
  "attw": "npx @arethetypeswrong/cli --pack --ignore-rules fallback-condition false-esm",
44
40
  "audit": "npm audit --audit-level=moderate",
45
41
  "integrity-checks": "npm-run-all clean check test build attw",
@@ -54,13 +50,18 @@
54
50
  "zod": "4.1.12"
55
51
  },
56
52
  "devDependencies": {
57
- "@biomejs/biome": "2.3.4",
53
+ "@semantic-release/changelog": "6.0.3",
54
+ "@semantic-release/git": "10.0.1",
55
+ "conventional-changelog-conventionalcommits": "9.3.0",
58
56
  "husky": "9.1.7",
59
57
  "lint-staged": "16.2.6",
60
58
  "msw": "^2.11.3",
61
59
  "npm-run-all2": "8.0.4",
62
60
  "obsidian": "^1.10.2-1",
61
+ "oxfmt": "0.43.0",
62
+ "oxlint": "1.58.0",
63
63
  "rimraf": "6.1.0",
64
+ "semantic-release": "25.0.3",
64
65
  "ts-node": "10.9.2",
65
66
  "type-fest": "^5.0.0",
66
67
  "typescript": "5.9.3",
@@ -75,9 +76,8 @@
75
76
  }
76
77
  },
77
78
  "lint-staged": {
78
- "*": [
79
- "biome check --write --no-errors-on-unmatched --files-ignore-unknown=true"
80
- ]
79
+ "*.{ts,tsx}": "oxlint --fix",
80
+ "*.{ts,tsx,json,md,yml,yaml}": "oxfmt --no-error-on-unmatched-pattern"
81
81
  },
82
82
  "files": [
83
83
  "dist/cjs/**/*",