@doist/twist-sdk 2.2.0 → 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
+ }
@@ -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
+ }
@@ -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 {};
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@doist/twist-sdk",
3
- "version": "2.2.0",
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/**/*",