@doist/twist-sdk 0.1.0-alpha.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 +222 -0
  3. package/dist/authentication.d.ts +79 -0
  4. package/dist/authentication.d.ts.map +1 -0
  5. package/dist/authentication.js +115 -0
  6. package/dist/authentication.test.d.ts.map +1 -0
  7. package/dist/batch-builder.d.ts +27 -0
  8. package/dist/batch-builder.d.ts.map +1 -0
  9. package/dist/batch-builder.js +191 -0
  10. package/dist/batch-builder.test.d.ts.map +1 -0
  11. package/dist/clients/channels-client.d.ts +215 -0
  12. package/dist/clients/channels-client.d.ts.map +1 -0
  13. package/dist/clients/channels-client.js +140 -0
  14. package/dist/clients/channels-client.test.d.ts.map +1 -0
  15. package/dist/clients/comments-client.d.ts +125 -0
  16. package/dist/clients/comments-client.d.ts.map +1 -0
  17. package/dist/clients/comments-client.js +86 -0
  18. package/dist/clients/comments-client.test.d.ts.map +1 -0
  19. package/dist/clients/conversation-messages-client.d.ts +141 -0
  20. package/dist/clients/conversation-messages-client.d.ts.map +1 -0
  21. package/dist/clients/conversation-messages-client.js +92 -0
  22. package/dist/clients/conversation-messages-client.test.d.ts.map +1 -0
  23. package/dist/clients/conversations-client.d.ts +263 -0
  24. package/dist/clients/conversations-client.d.ts.map +1 -0
  25. package/dist/clients/conversations-client.js +174 -0
  26. package/dist/clients/conversations-client.test.d.ts.map +1 -0
  27. package/dist/clients/groups-client.d.ts +175 -0
  28. package/dist/clients/groups-client.d.ts.map +1 -0
  29. package/dist/clients/groups-client.js +104 -0
  30. package/dist/clients/groups-client.test.d.ts.map +1 -0
  31. package/dist/clients/inbox-client.d.ts +146 -0
  32. package/dist/clients/inbox-client.d.ts.map +1 -0
  33. package/dist/clients/inbox-client.js +92 -0
  34. package/dist/clients/reactions-client.d.ts +98 -0
  35. package/dist/clients/reactions-client.d.ts.map +1 -0
  36. package/dist/clients/reactions-client.js +86 -0
  37. package/dist/clients/reactions-client.test.d.ts.map +1 -0
  38. package/dist/clients/search-client.d.ts +128 -0
  39. package/dist/clients/search-client.d.ts.map +1 -0
  40. package/dist/clients/search-client.js +89 -0
  41. package/dist/clients/threads-client.d.ts +321 -0
  42. package/dist/clients/threads-client.d.ts.map +1 -0
  43. package/dist/clients/threads-client.js +212 -0
  44. package/dist/clients/threads-client.test.d.ts.map +1 -0
  45. package/dist/clients/users-client.d.ts +254 -0
  46. package/dist/clients/users-client.d.ts.map +1 -0
  47. package/dist/clients/users-client.js +155 -0
  48. package/dist/clients/users-client.test.d.ts.map +1 -0
  49. package/dist/clients/workspace-users-client.d.ts +220 -0
  50. package/dist/clients/workspace-users-client.d.ts.map +1 -0
  51. package/dist/clients/workspace-users-client.js +132 -0
  52. package/dist/clients/workspace-users-client.test.d.ts.map +1 -0
  53. package/dist/clients/workspaces-client.d.ts +143 -0
  54. package/dist/clients/workspaces-client.d.ts.map +1 -0
  55. package/dist/clients/workspaces-client.js +89 -0
  56. package/dist/clients/workspaces-client.test.d.ts.map +1 -0
  57. package/dist/consts/endpoints.d.ts +16 -0
  58. package/dist/consts/endpoints.d.ts.map +1 -0
  59. package/dist/consts/endpoints.js +23 -0
  60. package/dist/index.d.ts +10 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +32 -0
  63. package/dist/rest-client.d.ts +5 -0
  64. package/dist/rest-client.d.ts.map +1 -0
  65. package/dist/rest-client.js +201 -0
  66. package/dist/rest-client.test.d.ts.map +1 -0
  67. package/dist/testUtils/msw-handlers.d.ts +29 -0
  68. package/dist/testUtils/msw-handlers.d.ts.map +1 -0
  69. package/dist/testUtils/msw-handlers.js +52 -0
  70. package/dist/testUtils/msw-setup.d.ts +2 -0
  71. package/dist/testUtils/msw-setup.d.ts.map +1 -0
  72. package/dist/testUtils/msw-setup.js +21 -0
  73. package/dist/testUtils/test-defaults.d.ts +11 -0
  74. package/dist/testUtils/test-defaults.d.ts.map +1 -0
  75. package/dist/testUtils/test-defaults.js +96 -0
  76. package/dist/twist-api.d.ts +64 -0
  77. package/dist/twist-api.d.ts.map +1 -0
  78. package/dist/twist-api.js +76 -0
  79. package/dist/twist-api.test.d.ts.map +1 -0
  80. package/dist/types/batch.d.ts +36 -0
  81. package/dist/types/batch.d.ts.map +1 -0
  82. package/dist/types/batch.js +2 -0
  83. package/dist/types/entities.d.ts +367 -0
  84. package/dist/types/entities.d.ts.map +1 -0
  85. package/dist/types/entities.js +358 -0
  86. package/dist/types/enums.d.ts +22 -0
  87. package/dist/types/enums.d.ts.map +1 -0
  88. package/dist/types/enums.js +7 -0
  89. package/dist/types/errors.d.ts +7 -0
  90. package/dist/types/errors.d.ts.map +1 -0
  91. package/dist/types/errors.js +30 -0
  92. package/dist/types/http.d.ts +12 -0
  93. package/dist/types/http.d.ts.map +1 -0
  94. package/dist/types/http.js +2 -0
  95. package/dist/types/index.d.ts +7 -0
  96. package/dist/types/index.d.ts.map +1 -0
  97. package/dist/types/index.js +22 -0
  98. package/dist/types/requests.d.ts +97 -0
  99. package/dist/types/requests.d.ts.map +1 -0
  100. package/dist/types/requests.js +92 -0
  101. package/dist/utils/case-conversion.d.ts +9 -0
  102. package/dist/utils/case-conversion.d.ts.map +1 -0
  103. package/dist/utils/case-conversion.js +56 -0
  104. package/dist/utils/case-conversion.test.d.ts.map +1 -0
  105. package/dist/utils/index.d.ts +3 -0
  106. package/dist/utils/index.d.ts.map +1 -0
  107. package/dist/utils/index.js +18 -0
  108. package/dist/utils/timestamp-conversion.d.ts +14 -0
  109. package/dist/utils/timestamp-conversion.d.ts.map +1 -0
  110. package/dist/utils/timestamp-conversion.js +47 -0
  111. package/dist/utils/url-helpers.d.ts +154 -0
  112. package/dist/utils/url-helpers.d.ts.map +1 -0
  113. package/dist/utils/url-helpers.js +223 -0
  114. package/dist/utils/url-helpers.test.d.ts.map +1 -0
  115. package/package.json +70 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Doist
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,222 @@
1
+ # Twist SDK TypeScript
2
+
3
+ <p align="center">
4
+ <img src="./website/static/img/twist-logo.png" alt="Twist Logo" width="100" />
5
+ </p>
6
+
7
+ This is the official TypeScript SDK for the Twist REST API.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @doist/twist-sdk
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ An example of initializing the API client and fetching a user's information:
18
+
19
+ ```typescript
20
+ import { TwistApi } from '@doist/twist-sdk'
21
+
22
+ const api = new TwistApi('YOUR_API_TOKEN')
23
+
24
+ api.users.getSessionUser()
25
+ .then((user) => console.log(user))
26
+ .catch((error) => console.log(error))
27
+ ```
28
+
29
+ ### OAuth 2.0 Authentication
30
+
31
+ For applications that need to access user data, use OAuth 2.0:
32
+
33
+ ```typescript
34
+ import { getAuthorizationUrl, getAuthToken, TwistApi } from '@doist/twist-sdk'
35
+
36
+ // Step 1: Generate authorization URL
37
+ const authUrl = getAuthorizationUrl(
38
+ 'your-client-id',
39
+ ['user:read', 'channels:read'],
40
+ 'state-parameter',
41
+ 'https://yourapp.com/callback'
42
+ )
43
+
44
+ // Step 2: Exchange authorization code for access token
45
+ const tokenResponse = await getAuthToken({
46
+ clientId: 'your-client-id',
47
+ clientSecret: 'your-client-secret',
48
+ code: 'authorization-code',
49
+ redirectUri: 'https://yourapp.com/callback'
50
+ })
51
+
52
+ // Step 3: Use the access token
53
+ const api = new TwistApi(tokenResponse.accessToken)
54
+ const user = await api.users.getSessionUser()
55
+ ```
56
+
57
+ ### Batch Requests
58
+
59
+ The SDK supports making multiple API calls in a single HTTP request using the `/batch` endpoint. This can significantly improve performance when you need to fetch or update multiple resources.
60
+
61
+ **Note:** Batch requests are completely optional. If you only need to make a single API call, simply call the method normally without the `{ batch: true }` option.
62
+
63
+ #### How It Works
64
+
65
+ To use batch requests:
66
+
67
+ 1. Pass `{ batch: true }` as the last parameter to any API method
68
+ 2. This returns a `BatchRequestDescriptor` instead of executing the request immediately
69
+ 3. Pass multiple descriptors to `api.batch()` to execute them together
70
+
71
+ ```typescript
72
+ // Single requests (normal usage)
73
+ const user1 = await api.workspaceUsers.getUserById(123, 456)
74
+ const user2 = await api.workspaceUsers.getUserById(123, 789)
75
+
76
+ // Batch requests - executes in a single HTTP call
77
+ const results = await api.batch(
78
+ api.workspaceUsers.getUserById(123, 456, { batch: true }),
79
+ api.workspaceUsers.getUserById(123, 789, { batch: true })
80
+ )
81
+
82
+ console.log(results[0].data.name) // First user
83
+ console.log(results[1].data.name) // Second user
84
+ ```
85
+
86
+ #### Response Structure
87
+
88
+ Each item in the batch response includes:
89
+
90
+ - `code` - HTTP status code for that specific request (e.g., 200, 404)
91
+ - `headers` - Response headers as a key-value object
92
+ - `data` - The parsed and validated response data
93
+
94
+ ```typescript
95
+ const results = await api.batch(
96
+ api.channels.getChannel(123, { batch: true }),
97
+ api.channels.getChannel(456, { batch: true })
98
+ )
99
+
100
+ results.forEach((result) => {
101
+ if (result.code === 200) {
102
+ console.log('Success:', result.data.name)
103
+ } else {
104
+ console.error('Error:', result.code)
105
+ }
106
+ })
107
+ ```
108
+
109
+ #### Performance Optimization
110
+
111
+ When all requests in a batch are GET requests, they are executed in parallel on the server for optimal performance. Mixed GET and POST requests are executed sequentially.
112
+
113
+ ```typescript
114
+ // These GET requests execute in parallel
115
+ const results = await api.batch(
116
+ api.workspaceUsers.getUserById(123, 456, { batch: true }),
117
+ api.channels.getChannel(789, { batch: true }),
118
+ api.threads.getThread(101112, { batch: true })
119
+ )
120
+ ```
121
+
122
+ #### Mixing Different API Calls
123
+
124
+ You can batch requests across different resource types:
125
+
126
+ ```typescript
127
+ const results = await api.batch(
128
+ api.workspaceUsers.getUserById(123, 456, { batch: true }),
129
+ api.channels.getChannels({ workspaceId: 123 }, { batch: true }),
130
+ api.conversations.getConversations({ workspaceId: 123 }, { batch: true })
131
+ )
132
+
133
+ const [user, channels, conversations] = results
134
+ // TypeScript maintains proper types for each result
135
+ console.log(user.data.name)
136
+ console.log(channels.data.length)
137
+ console.log(conversations.data.length)
138
+ ```
139
+
140
+ #### Error Handling
141
+
142
+ Individual requests in a batch can fail independently. Always check the status code of each result:
143
+
144
+ ```typescript
145
+ const results = await api.batch(
146
+ api.channels.getChannel(123, { batch: true }),
147
+ api.channels.getChannel(999999, { batch: true }) // Non-existent channel
148
+ )
149
+
150
+ results.forEach((result, index) => {
151
+ if (result.code >= 200 && result.code < 300) {
152
+ console.log(`Request ${index} succeeded:`, result.data)
153
+ } else {
154
+ console.error(`Request ${index} failed with status ${result.code}`)
155
+ }
156
+ })
157
+ ```
158
+
159
+ ## Documentation
160
+
161
+ For detailed documentation, visit the [Twist SDK Documentation](https://doist.github.io/twist-sdk-typescript/).
162
+
163
+ For information about the Twist REST API, see the [Twist API Documentation](https://developer.twist.com/v3/).
164
+
165
+ ## Development and Testing
166
+
167
+ This project uses [ts-node](https://github.com/TypeStrong/ts-node) for local development and testing.
168
+
169
+ - `npm install`
170
+ - Add a file named `scratch.ts` in the `src` folder
171
+ - Configure your IDE to run the scratch file with `ts-node` (instructions for [VSCode](https://medium.com/@dupski/debug-typescript-in-vs-code-without-compiling-using-ts-node-9d1f4f9a94a), [WebStorm](https://www.jetbrains.com/help/webstorm/running-and-debugging-typescript.html#ws_ts_run_debug_server_side_ts_node)), or run it directly: `npx ts-node ./src/scratch.ts`
172
+
173
+ Example scratch.ts file:
174
+
175
+ ```typescript
176
+ import { TwistApi } from './twist-api'
177
+
178
+ const token = 'YOUR_API_TOKEN'
179
+ const api = new TwistApi(token)
180
+
181
+ api.workspaces.getWorkspaces()
182
+ .then((workspaces) => {
183
+ console.log(workspaces)
184
+ })
185
+ .catch((error) => console.error(error))
186
+ ```
187
+
188
+ ## Scripts
189
+
190
+ - `npm test` - Run tests
191
+ - `npm run test:watch` - Run tests in watch mode
192
+ - `npm run test:coverage` - Run tests with coverage
193
+ - `npm run build` - Build the package
194
+ - `npm run type-check` - Type check without building
195
+ - `npm run lint:check` - Check code style
196
+ - `npm run format:check` - Check formatting
197
+
198
+ ## Releases
199
+
200
+ This package follows semantic versioning. Currently in alpha release (0.1.0-alpha.x).
201
+
202
+ A new version is published to the NPM Registry whenever a new release on GitHub is created. The workflow automatically detects prerelease versions (alpha, beta, rc) and publishes them with the appropriate tag.
203
+
204
+ To prepare a new release:
205
+
206
+ ```bash
207
+ # For alpha releases
208
+ npm version prerelease --preid=alpha --no-git-tag-version
209
+
210
+ # For stable releases
211
+ npm version <major|minor|patch> --no-git-tag-version
212
+ ```
213
+
214
+ Once the version in `package.json` is updated, push the changes and create a GitHub release. The workflow will automatically publish to npm with the correct tag.
215
+
216
+ ## Feedback
217
+
218
+ Any feedback, such as bugs, questions, comments, etc. can be reported as _Issues_ in this repository, and will be handled by the Doist team.
219
+
220
+ ## Contributions
221
+
222
+ We welcome contributions in the form of _Pull requests_ in this repository.
@@ -0,0 +1,79 @@
1
+ /**
2
+ * OAuth scopes for the Twist API.
3
+ *
4
+ * @remarks
5
+ * Request only the scopes your application needs:
6
+ *
7
+ * **User Scopes:**
8
+ * - `user:read` - Access user's personal settings
9
+ * - `user:write` - Access and update user's personal settings
10
+ *
11
+ * **Workspace Scopes:**
12
+ * - `workspaces:read` - Access teams the user is part of
13
+ * - `workspaces:write` - Access and update teams the user is part of
14
+ *
15
+ * **Channel Scopes:**
16
+ * - `channels:read` - Access channels
17
+ * - `channels:write` - Access and update channels
18
+ * - `channels:remove` - Access, update, and delete channels
19
+ *
20
+ * **Thread Scopes:**
21
+ * - `threads:read` - Access threads
22
+ * - `threads:write` - Access and update threads
23
+ * - `threads:remove` - Access, update, and delete threads
24
+ *
25
+ * **Comment Scopes:**
26
+ * - `comments:read` - Access comments
27
+ * - `comments:write` - Access and update comments
28
+ * - `comments:remove` - Access, update, and delete comments
29
+ *
30
+ * **Group Scopes:**
31
+ * - `groups:read` - Access groups
32
+ * - `groups:write` - Access and update groups
33
+ * - `groups:remove` - Access, update, and delete groups
34
+ *
35
+ * **Message Scopes:**
36
+ * - `messages:read` - Access messages
37
+ * - `messages:write` - Access and update messages
38
+ * - `messages:remove` - Access, update, and delete messages
39
+ *
40
+ * **Reaction Scopes:**
41
+ * - `reactions:read` - Access reactions
42
+ * - `reactions:write` - Access and update reactions
43
+ * - `reactions:remove` - Access, update, and delete reactions
44
+ *
45
+ * **Search Scopes:**
46
+ * - `search:read` - Search
47
+ *
48
+ * **Attachment Scopes:**
49
+ * - `attachments:read` - Access attachments
50
+ * - `attachments:write` - Access and update attachments
51
+ *
52
+ * **Notification Scopes:**
53
+ * - `notifications:read` - Read user's notifications settings
54
+ * - `notifications:write` - Read and update user's notifications settings
55
+ */
56
+ export type TwistScope = 'user:read' | 'user:write' | 'workspaces:read' | 'workspaces:write' | 'channels:read' | 'channels:write' | 'channels:remove' | 'threads:read' | 'threads:write' | 'threads:remove' | 'comments:read' | 'comments:write' | 'comments:remove' | 'groups:read' | 'groups:write' | 'groups:remove' | 'messages:read' | 'messages:write' | 'messages:remove' | 'reactions:read' | 'reactions:write' | 'reactions:remove' | 'search:read' | 'attachments:read' | 'attachments:write' | 'notifications:read' | 'notifications:write';
57
+ export type AuthTokenRequestArgs = {
58
+ clientId: string;
59
+ clientSecret: string;
60
+ code: string;
61
+ redirectUri?: string;
62
+ };
63
+ export type AuthTokenResponse = {
64
+ accessToken: string;
65
+ tokenType: string;
66
+ refreshToken?: string;
67
+ expiresIn?: number;
68
+ scope?: string;
69
+ };
70
+ export type RevokeAuthTokenRequestArgs = {
71
+ clientId: string;
72
+ clientSecret: string;
73
+ accessToken: string;
74
+ };
75
+ export declare function getAuthStateParameter(): string;
76
+ export declare function getAuthorizationUrl(clientId: string, scopes: TwistScope[], state: string, redirectUri?: string, baseUrl?: string): string;
77
+ export declare function getAuthToken(args: AuthTokenRequestArgs, baseUrl?: string): Promise<AuthTokenResponse>;
78
+ export declare function revokeAuthToken(args: RevokeAuthTokenRequestArgs, baseUrl?: string): Promise<boolean>;
79
+ //# sourceMappingURL=authentication.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authentication.d.ts","sourceRoot":"","sources":["../src/authentication.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,MAAM,MAAM,UAAU,GAChB,WAAW,GACX,YAAY,GACZ,iBAAiB,GACjB,kBAAkB,GAClB,eAAe,GACf,gBAAgB,GAChB,iBAAiB,GACjB,cAAc,GACd,eAAe,GACf,gBAAgB,GAChB,eAAe,GACf,gBAAgB,GAChB,iBAAiB,GACjB,aAAa,GACb,cAAc,GACd,eAAe,GACf,eAAe,GACf,gBAAgB,GAChB,iBAAiB,GACjB,gBAAgB,GAChB,iBAAiB,GACjB,kBAAkB,GAClB,aAAa,GACb,kBAAkB,GAClB,mBAAmB,GACnB,oBAAoB,GACpB,qBAAqB,CAAA;AAE3B,MAAM,MAAM,oBAAoB,GAAG;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC5B,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,0BAA0B,GAAG;IACrC,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED,wBAAgB,mBAAmB,CAC/B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,UAAU,EAAE,EACpB,KAAK,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,MAAM,GACjB,MAAM,CAmBR;AAED,wBAAsB,YAAY,CAC9B,IAAI,EAAE,oBAAoB,EAC1B,OAAO,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,iBAAiB,CAAC,CAsB5B;AAED,wBAAsB,eAAe,CACjC,IAAI,EAAE,0BAA0B,EAChC,OAAO,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC,CAUlB"}
@@ -0,0 +1,115 @@
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
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
14
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
15
+ return new (P || (P = Promise))(function (resolve, reject) {
16
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
17
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
18
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
19
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
20
+ });
21
+ };
22
+ var __generator = (this && this.__generator) || function (thisArg, body) {
23
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
24
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
25
+ function verb(n) { return function (v) { return step([n, v]); }; }
26
+ function step(op) {
27
+ if (f) throw new TypeError("Generator is already executing.");
28
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
29
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
30
+ if (y = 0, t) op = [op[0] & 2, t.value];
31
+ switch (op[0]) {
32
+ case 0: case 1: t = op; break;
33
+ case 4: _.label++; return { value: op[1], done: false };
34
+ case 5: _.label++; y = op[1]; op = [0]; continue;
35
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
36
+ default:
37
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
38
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
39
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
40
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
41
+ if (t[2]) _.ops.pop();
42
+ _.trys.pop(); continue;
43
+ }
44
+ op = body.call(thisArg, _);
45
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
46
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
+ }
48
+ };
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.getAuthStateParameter = getAuthStateParameter;
51
+ exports.getAuthorizationUrl = getAuthorizationUrl;
52
+ exports.getAuthToken = getAuthToken;
53
+ exports.revokeAuthToken = revokeAuthToken;
54
+ var uuid_1 = require("uuid");
55
+ var rest_client_1 = require("./rest-client");
56
+ var errors_1 = require("./types/errors");
57
+ function getAuthStateParameter() {
58
+ return (0, uuid_1.v4)();
59
+ }
60
+ function getAuthorizationUrl(clientId, scopes, state, redirectUri, baseUrl) {
61
+ if (!(scopes === null || scopes === void 0 ? void 0 : scopes.length)) {
62
+ throw new Error('At least one scope value is required.');
63
+ }
64
+ var authBaseUrl = baseUrl ? "".concat(baseUrl, "/oauth") : 'https://twist.com/oauth';
65
+ var scope = scopes.join(' ');
66
+ var params = new URLSearchParams({
67
+ client_id: clientId,
68
+ response_type: 'code',
69
+ scope: scope,
70
+ state: state,
71
+ });
72
+ if (redirectUri) {
73
+ params.append('redirect_uri', redirectUri);
74
+ }
75
+ return "".concat(authBaseUrl, "/authorize?").concat(params.toString());
76
+ }
77
+ function getAuthToken(args, baseUrl) {
78
+ return __awaiter(this, void 0, void 0, function () {
79
+ var tokenUrl, payload, response;
80
+ var _a;
81
+ return __generator(this, function (_b) {
82
+ switch (_b.label) {
83
+ case 0:
84
+ tokenUrl = baseUrl ? "".concat(baseUrl, "/oauth/token") : 'https://twist.com/oauth/token';
85
+ payload = __assign({ clientId: args.clientId, clientSecret: args.clientSecret, code: args.code, grantType: 'authorization_code' }, (args.redirectUri && { redirectUri: args.redirectUri }));
86
+ return [4 /*yield*/, (0, rest_client_1.request)('POST', tokenUrl, '', undefined, payload)];
87
+ case 1:
88
+ response = _b.sent();
89
+ if (!(0, rest_client_1.isSuccess)(response) || !((_a = response.data) === null || _a === void 0 ? void 0 : _a.accessToken)) {
90
+ throw new errors_1.TwistRequestError('Authentication token exchange failed.', response.status, response.data);
91
+ }
92
+ return [2 /*return*/, response.data];
93
+ }
94
+ });
95
+ });
96
+ }
97
+ function revokeAuthToken(args, baseUrl) {
98
+ return __awaiter(this, void 0, void 0, function () {
99
+ var revokeUrl, response;
100
+ return __generator(this, function (_a) {
101
+ switch (_a.label) {
102
+ case 0:
103
+ revokeUrl = baseUrl ? "".concat(baseUrl, "/oauth/revoke") : 'https://twist.com/oauth/revoke';
104
+ return [4 /*yield*/, (0, rest_client_1.request)('POST', revokeUrl, '', undefined, {
105
+ clientId: args.clientId,
106
+ clientSecret: args.clientSecret,
107
+ token: args.accessToken,
108
+ })];
109
+ case 1:
110
+ response = _a.sent();
111
+ return [2 /*return*/, (0, rest_client_1.isSuccess)(response)];
112
+ }
113
+ });
114
+ });
115
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authentication.test.d.ts","sourceRoot":"","sources":["../src/authentication.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,27 @@
1
+ import type { BatchRequestDescriptor, BatchResponseArray } from './types/batch';
2
+ /**
3
+ * Executes multiple API requests in a single HTTP call.
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * const results = await api.batch(
8
+ * api.workspaceUsers.getUserById(123, 456, { batch: true }),
9
+ * api.workspaceUsers.getUserById(123, 789, { batch: true })
10
+ * )
11
+ * ```
12
+ */
13
+ export declare class BatchBuilder {
14
+ private apiToken;
15
+ private baseUrl?;
16
+ constructor(apiToken: string, baseUrl?: string | undefined);
17
+ private getBaseUri;
18
+ /**
19
+ * Executes an array of batch request descriptors in a single API call.
20
+ *
21
+ * @param requests - Array of batch request descriptors
22
+ * @returns Array of BatchResponse objects with processed data
23
+ * @throws {TwistRequestError} If the batch request fails
24
+ */
25
+ execute<T extends readonly BatchRequestDescriptor<unknown>[]>(requests: T): Promise<BatchResponseArray<T>>;
26
+ }
27
+ //# sourceMappingURL=batch-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch-builder.d.ts","sourceRoot":"","sources":["../src/batch-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAoB,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAKjG;;;;;;;;;;GAUG;AACH,qBAAa,YAAY;IAEjB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,OAAO,CAAC;gBADR,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,YAAA;IAG5B,OAAO,CAAC,UAAU;IAIlB;;;;;;OAMG;IACG,OAAO,CAAC,CAAC,SAAS,SAAS,sBAAsB,CAAC,OAAO,CAAC,EAAE,EAC9D,QAAQ,EAAE,CAAC,GACZ,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC;CAyHpC"}
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
13
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.BatchBuilder = void 0;
40
+ var errors_1 = require("./types/errors");
41
+ var case_conversion_1 = require("./utils/case-conversion");
42
+ var timestamp_conversion_1 = require("./utils/timestamp-conversion");
43
+ /**
44
+ * Executes multiple API requests in a single HTTP call.
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * const results = await api.batch(
49
+ * api.workspaceUsers.getUserById(123, 456, { batch: true }),
50
+ * api.workspaceUsers.getUserById(123, 789, { batch: true })
51
+ * )
52
+ * ```
53
+ */
54
+ var BatchBuilder = /** @class */ (function () {
55
+ function BatchBuilder(apiToken, baseUrl) {
56
+ this.apiToken = apiToken;
57
+ this.baseUrl = baseUrl;
58
+ }
59
+ BatchBuilder.prototype.getBaseUri = function () {
60
+ return this.baseUrl ? "".concat(this.baseUrl, "/api/v3") : 'https://api.twist.com/api/v3/';
61
+ };
62
+ /**
63
+ * Executes an array of batch request descriptors in a single API call.
64
+ *
65
+ * @param requests - Array of batch request descriptors
66
+ * @returns Array of BatchResponse objects with processed data
67
+ * @throws {TwistRequestError} If the batch request fails
68
+ */
69
+ BatchBuilder.prototype.execute = function (requests) {
70
+ return __awaiter(this, void 0, void 0, function () {
71
+ var batchRequests, allGets, formData, response, errorText, batchApiResponses;
72
+ var _this = this;
73
+ return __generator(this, function (_a) {
74
+ switch (_a.label) {
75
+ case 0:
76
+ if (requests.length === 0) {
77
+ return [2 /*return*/, []];
78
+ }
79
+ batchRequests = requests.map(function (descriptor) {
80
+ // Convert params to snake_case
81
+ var snakeCaseParams = descriptor.params
82
+ ? (0, case_conversion_1.snakeCaseKeys)(descriptor.params)
83
+ : undefined;
84
+ // Build the full URL with query params for GET requests
85
+ var url = "".concat(_this.getBaseUri()).concat(descriptor.url);
86
+ if (descriptor.method === 'GET' && snakeCaseParams) {
87
+ var searchParams_1 = new URLSearchParams();
88
+ Object.entries(snakeCaseParams).forEach(function (_a) {
89
+ var key = _a[0], value = _a[1];
90
+ if (value != null) {
91
+ if (Array.isArray(value)) {
92
+ searchParams_1.append(key, value.join(','));
93
+ }
94
+ else {
95
+ searchParams_1.append(key, String(value));
96
+ }
97
+ }
98
+ });
99
+ var queryString = searchParams_1.toString();
100
+ if (queryString) {
101
+ url += "?".concat(queryString);
102
+ }
103
+ }
104
+ return {
105
+ method: descriptor.method,
106
+ url: url,
107
+ };
108
+ });
109
+ allGets = batchRequests.every(function (req) { return req.method === 'GET'; });
110
+ formData = new URLSearchParams();
111
+ formData.append('requests', JSON.stringify(batchRequests));
112
+ if (allGets) {
113
+ formData.append('parallel', 'true');
114
+ }
115
+ return [4 /*yield*/, fetch("".concat(this.getBaseUri(), "batch"), {
116
+ method: 'POST',
117
+ headers: {
118
+ 'Content-Type': 'application/x-www-form-urlencoded',
119
+ Authorization: "Bearer ".concat(this.apiToken),
120
+ },
121
+ body: formData.toString(),
122
+ })];
123
+ case 1:
124
+ response = _a.sent();
125
+ if (!!response.ok) return [3 /*break*/, 3];
126
+ return [4 /*yield*/, response.text()];
127
+ case 2:
128
+ errorText = _a.sent();
129
+ throw new errors_1.TwistRequestError("Batch request failed with status ".concat(response.status), response.status, errorText);
130
+ case 3: return [4 /*yield*/, response.json()
131
+ // Process each response
132
+ ];
133
+ case 4:
134
+ batchApiResponses = _a.sent();
135
+ // Process each response
136
+ return [2 /*return*/, batchApiResponses.map(function (apiResponse, index) {
137
+ var descriptor = requests[index];
138
+ // Parse the body JSON
139
+ var parsedBody;
140
+ try {
141
+ parsedBody = apiResponse.body ? JSON.parse(apiResponse.body) : undefined;
142
+ }
143
+ catch (_error) {
144
+ parsedBody = apiResponse.body;
145
+ }
146
+ // Apply transformations: camelCase -> timestamps
147
+ var camelCased = (0, case_conversion_1.camelCaseKeys)(parsedBody);
148
+ var transformed = (0, timestamp_conversion_1.transformTimestamps)(camelCased);
149
+ // Validate with schema if provided
150
+ var finalData = transformed;
151
+ if (descriptor.schema && apiResponse.code >= 200 && apiResponse.code < 300) {
152
+ try {
153
+ finalData = descriptor.schema.parse(transformed);
154
+ }
155
+ catch (error) {
156
+ // If validation fails, include the error in the response
157
+ console.error('Batch response validation failed:', error);
158
+ }
159
+ }
160
+ // Parse headers string into object
161
+ var headers = {};
162
+ if (apiResponse.headers && typeof apiResponse.headers === 'string') {
163
+ try {
164
+ var headerLines = apiResponse.headers.split('\n');
165
+ headerLines.forEach(function (line) {
166
+ var separatorIndex = line.indexOf(':');
167
+ if (separatorIndex > 0) {
168
+ var key = line.substring(0, separatorIndex).trim();
169
+ var value = line.substring(separatorIndex + 1).trim();
170
+ headers[key] = value;
171
+ }
172
+ });
173
+ }
174
+ catch (error) {
175
+ // If header parsing fails, just leave headers empty
176
+ console.error('Failed to parse batch response headers:', error);
177
+ }
178
+ }
179
+ return {
180
+ code: apiResponse.code,
181
+ headers: headers,
182
+ data: finalData,
183
+ };
184
+ })];
185
+ }
186
+ });
187
+ });
188
+ };
189
+ return BatchBuilder;
190
+ }());
191
+ exports.BatchBuilder = BatchBuilder;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch-builder.test.d.ts","sourceRoot":"","sources":["../src/batch-builder.test.ts"],"names":[],"mappings":""}