Skip to content

Commit 1f20036

Browse files
razor-xseambot
andauthored
feat: SeamHttpMultiWorkspace (#21)
Co-authored-by: Seam Bot <[email protected]>
1 parent a43e10e commit 1f20036

11 files changed

+364
-52
lines changed

src/lib/seam/connect/auth.ts

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import {
2+
isSeamHttpMultiWorkspaceOptionsWithConsoleSessionToken,
3+
isSeamHttpMultiWorkspaceOptionsWithPersonalAccessToken,
24
isSeamHttpOptionsWithApiKey,
35
isSeamHttpOptionsWithClientSessionToken,
46
isSeamHttpOptionsWithConsoleSessionToken,
57
isSeamHttpOptionsWithPersonalAccessToken,
68
SeamHttpInvalidOptionsError,
9+
type SeamHttpMultiWorkspaceOptionsWithConsoleSessionToken,
10+
type SeamHttpMultiWorkspaceOptionsWithPersonalAccessToken,
711
type SeamHttpOptionsWithApiKey,
812
type SeamHttpOptionsWithClientSessionToken,
913
type SeamHttpOptionsWithConsoleSessionToken,
@@ -26,11 +30,17 @@ export const getAuthHeaders = (options: Options): Headers => {
2630
return getAuthHeadersForClientSessionToken(options)
2731
}
2832

29-
if (isSeamHttpOptionsWithConsoleSessionToken(options)) {
33+
if (
34+
isSeamHttpMultiWorkspaceOptionsWithConsoleSessionToken(options) ||
35+
isSeamHttpOptionsWithConsoleSessionToken(options)
36+
) {
3037
return getAuthHeadersForConsoleSessionToken(options)
3138
}
3239

33-
if (isSeamHttpOptionsWithPersonalAccessToken(options)) {
40+
if (
41+
isSeamHttpMultiWorkspaceOptionsWithPersonalAccessToken(options) ||
42+
isSeamHttpOptionsWithPersonalAccessToken(options)
43+
) {
3444
return getAuthHeadersForPersonalAccessToken(options)
3545
}
3646

@@ -40,8 +50,8 @@ export const getAuthHeaders = (options: Options): Headers => {
4050
'an apiKey,',
4151
'clientSessionToken,',
4252
'publishableKey,',
43-
'consoleSessionToken with a workspaceId',
44-
'or personalAccessToken with a workspaceId',
53+
'consoleSessionToken',
54+
'or personalAccessToken',
4555
].join(' '),
4656
)
4757
}
@@ -117,8 +127,12 @@ const getAuthHeadersForClientSessionToken = ({
117127

118128
const getAuthHeadersForConsoleSessionToken = ({
119129
consoleSessionToken,
120-
workspaceId,
121-
}: SeamHttpOptionsWithConsoleSessionToken): Headers => {
130+
...options
131+
}:
132+
| SeamHttpMultiWorkspaceOptionsWithConsoleSessionToken
133+
| SeamHttpOptionsWithConsoleSessionToken): Headers => {
134+
const workspaceId = 'workspaceId' in options ? options.workspaceId : undefined
135+
122136
if (isAccessToken(consoleSessionToken)) {
123137
throw new SeamHttpInvalidTokenError(
124138
'An Access Token cannot be used as a consoleSessionToken',
@@ -145,14 +159,18 @@ const getAuthHeadersForConsoleSessionToken = ({
145159

146160
return {
147161
authorization: `Bearer ${consoleSessionToken}`,
148-
'seam-workspace-id': workspaceId,
162+
...(workspaceId != null ? { 'seam-workspace-id': workspaceId } : {}),
149163
}
150164
}
151165

152166
const getAuthHeadersForPersonalAccessToken = ({
153167
personalAccessToken,
154-
workspaceId,
155-
}: SeamHttpOptionsWithPersonalAccessToken): Headers => {
168+
...options
169+
}:
170+
| SeamHttpMultiWorkspaceOptionsWithPersonalAccessToken
171+
| SeamHttpOptionsWithPersonalAccessToken): Headers => {
172+
const workspaceId = 'workspaceId' in options ? options.workspaceId : undefined
173+
156174
if (isJwt(personalAccessToken)) {
157175
throw new SeamHttpInvalidTokenError(
158176
'A JWT cannot be used as a personalAccessToken',
@@ -179,7 +197,7 @@ const getAuthHeadersForPersonalAccessToken = ({
179197

180198
return {
181199
authorization: `Bearer ${personalAccessToken}`,
182-
'seam-workspace-id': workspaceId,
200+
...(workspaceId != null ? { 'seam-workspace-id': workspaceId } : {}),
183201
}
184202
}
185203

src/lib/seam/connect/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
export { SeamHttpInvalidTokenError } from './auth.js'
12
export * from './error-interceptor.js'
23
export * from './options.js'
34
export * from './routes/index.js'
45
export * from './seam-http.js'
56
export * from './seam-http-error.js'
7+
export * from './seam-http-multi-workspace.js'
68
export * from 'lib/params-serializer.js'

src/lib/seam/connect/options.ts

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import type { Client, ClientOptions } from './client.js'
22

3+
export type SeamHttpMultiWorkspaceOptions =
4+
| SeamHttpMultiWorkspaceOptionsWithClient
5+
| SeamHttpMultiWorkspaceOptionsWithConsoleSessionToken
6+
| SeamHttpMultiWorkspaceOptionsWithPersonalAccessToken
7+
38
export type SeamHttpOptions =
49
| SeamHttpOptionsFromEnv
510
| SeamHttpOptionsWithClient
@@ -17,6 +22,15 @@ export interface SeamHttpFromPublishableKeyOptions
1722

1823
export interface SeamHttpOptionsFromEnv extends SeamHttpCommonOptions {}
1924

25+
export interface SeamHttpMultiWorkspaceOptionsWithClient {
26+
client: Client
27+
}
28+
29+
export const isSeamHttpMultiWorkspaceOptionsWithClient = (
30+
options: SeamHttpOptions,
31+
): options is SeamHttpMultiWorkspaceOptionsWithClient =>
32+
isSeamHttpOptionsWithClient(options)
33+
2034
export interface SeamHttpOptionsWithClient {
2135
client: Client
2236
}
@@ -102,24 +116,17 @@ export const isSeamHttpOptionsWithClientSessionToken = (
102116
return true
103117
}
104118

105-
export interface SeamHttpOptionsWithConsoleSessionToken
119+
export interface SeamHttpMultiWorkspaceOptionsWithConsoleSessionToken
106120
extends SeamHttpCommonOptions {
107121
consoleSessionToken: string
108-
workspaceId: string
109122
}
110123

111-
export const isSeamHttpOptionsWithConsoleSessionToken = (
124+
export const isSeamHttpMultiWorkspaceOptionsWithConsoleSessionToken = (
112125
options: SeamHttpOptions,
113-
): options is SeamHttpOptionsWithConsoleSessionToken => {
126+
): options is SeamHttpMultiWorkspaceOptionsWithConsoleSessionToken => {
114127
if (!('consoleSessionToken' in options)) return false
115128
if (options.consoleSessionToken == null) return false
116129

117-
if (!('workspaceId' in options) || options.workspaceId == null) {
118-
throw new SeamHttpInvalidOptionsError(
119-
'Must pass a workspaceId when using a consoleSessionToken',
120-
)
121-
}
122-
123130
if ('apiKey' in options && options.apiKey != null) {
124131
throw new SeamHttpInvalidOptionsError(
125132
'The apiKey option cannot be used with the consoleSessionToken option',
@@ -141,24 +148,39 @@ export const isSeamHttpOptionsWithConsoleSessionToken = (
141148
return true
142149
}
143150

144-
export interface SeamHttpOptionsWithPersonalAccessToken
151+
export interface SeamHttpOptionsWithConsoleSessionToken
145152
extends SeamHttpCommonOptions {
146-
personalAccessToken: string
153+
consoleSessionToken: string
147154
workspaceId: string
148155
}
149156

150-
export const isSeamHttpOptionsWithPersonalAccessToken = (
157+
export const isSeamHttpOptionsWithConsoleSessionToken = (
151158
options: SeamHttpOptions,
152-
): options is SeamHttpOptionsWithPersonalAccessToken => {
153-
if (!('personalAccessToken' in options)) return false
154-
if (options.personalAccessToken == null) return false
159+
): options is SeamHttpOptionsWithConsoleSessionToken => {
160+
if (!isSeamHttpMultiWorkspaceOptionsWithConsoleSessionToken(options)) {
161+
return false
162+
}
155163

156164
if (!('workspaceId' in options) || options.workspaceId == null) {
157165
throw new SeamHttpInvalidOptionsError(
158-
'Must pass a workspaceId when using a personalAccessToken',
166+
'Must pass a workspaceId when using a consoleSessionToken',
159167
)
160168
}
161169

170+
return true
171+
}
172+
173+
export interface SeamHttpMultiWorkspaceOptionsWithPersonalAccessToken
174+
extends SeamHttpCommonOptions {
175+
personalAccessToken: string
176+
}
177+
178+
export const isSeamHttpMultiWorkspaceOptionsWithPersonalAccessToken = (
179+
options: SeamHttpOptions,
180+
): options is SeamHttpMultiWorkspaceOptionsWithPersonalAccessToken => {
181+
if (!('personalAccessToken' in options)) return false
182+
if (options.personalAccessToken == null) return false
183+
162184
if ('apiKey' in options && options.apiKey != null) {
163185
throw new SeamHttpInvalidOptionsError(
164186
'The apiKey option cannot be used with the personalAccessToken option',
@@ -180,10 +202,34 @@ export const isSeamHttpOptionsWithPersonalAccessToken = (
180202
return true
181203
}
182204

205+
export interface SeamHttpOptionsWithPersonalAccessToken
206+
extends SeamHttpCommonOptions {
207+
personalAccessToken: string
208+
workspaceId: string
209+
}
210+
211+
export const isSeamHttpOptionsWithPersonalAccessToken = (
212+
options: SeamHttpOptions,
213+
): options is SeamHttpOptionsWithPersonalAccessToken => {
214+
if (!isSeamHttpMultiWorkspaceOptionsWithPersonalAccessToken(options)) {
215+
return false
216+
}
217+
218+
if (!('workspaceId' in options) || options.workspaceId == null) {
219+
throw new SeamHttpInvalidOptionsError(
220+
'Must pass a workspaceId when using a personalAccessToken',
221+
)
222+
}
223+
224+
return true
225+
}
226+
183227
export class SeamHttpInvalidOptionsError extends Error {
184228
constructor(message: string) {
185229
super(`SeamHttp received invalid options: ${message}`)
186230
this.name = this.constructor.name
187231
Error.captureStackTrace(this, this.constructor)
188232
}
189233
}
234+
235+
export class SeamHttpMultiWorkspaceInvalidOptionsError extends SeamHttpInvalidOptionsError {}

src/lib/seam/connect/parse-options.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import version from 'lib/version.js'
33
import { getAuthHeaders } from './auth.js'
44
import type { ClientOptions } from './client.js'
55
import {
6+
isSeamHttpMultiWorkspaceOptionsWithClient,
67
isSeamHttpOptionsWithClient,
78
isSeamHttpOptionsWithClientSessionToken,
9+
type SeamHttpMultiWorkspaceOptions,
810
type SeamHttpOptions,
911
} from './options.js'
1012

@@ -15,14 +17,17 @@ const sdkHeaders = {
1517
'seam-sdk-version': version,
1618
}
1719

18-
export type Options = SeamHttpOptions & { publishableKey?: string }
20+
export type Options =
21+
| SeamHttpMultiWorkspaceOptions
22+
| (SeamHttpOptions & { publishableKey?: string })
1923

2024
export const parseOptions = (
2125
apiKeyOrOptions: string | Options,
2226
): ClientOptions => {
2327
const options = getNormalizedOptions(apiKeyOrOptions)
2428

2529
if (isSeamHttpOptionsWithClient(options)) return options
30+
if (isSeamHttpMultiWorkspaceOptionsWithClient(options)) return options
2631

2732
return {
2833
axiosOptions: {
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { type Client, createClient } from './client.js'
2+
import {
3+
isSeamHttpMultiWorkspaceOptionsWithClient,
4+
isSeamHttpMultiWorkspaceOptionsWithConsoleSessionToken,
5+
isSeamHttpMultiWorkspaceOptionsWithPersonalAccessToken,
6+
SeamHttpMultiWorkspaceInvalidOptionsError,
7+
type SeamHttpMultiWorkspaceOptions,
8+
type SeamHttpMultiWorkspaceOptionsWithClient,
9+
type SeamHttpMultiWorkspaceOptionsWithConsoleSessionToken,
10+
type SeamHttpMultiWorkspaceOptionsWithPersonalAccessToken,
11+
} from './options.js'
12+
import { parseOptions } from './parse-options.js'
13+
import { SeamHttpWorkspaces } from './routes/index.js'
14+
15+
export class SeamHttpMultiWorkspace {
16+
client: Client
17+
18+
constructor(options: SeamHttpMultiWorkspaceOptions) {
19+
const clientOptions = parseOptions(options)
20+
this.client = createClient(clientOptions)
21+
}
22+
23+
static fromClient(
24+
client: SeamHttpMultiWorkspaceOptionsWithClient['client'],
25+
options: Omit<SeamHttpMultiWorkspaceOptionsWithClient, 'client'> = {},
26+
): SeamHttpMultiWorkspace {
27+
const constructorOptions = { ...options, client }
28+
if (!isSeamHttpMultiWorkspaceOptionsWithClient(constructorOptions)) {
29+
throw new SeamHttpMultiWorkspaceInvalidOptionsError('Missing client')
30+
}
31+
return new SeamHttpMultiWorkspace(constructorOptions)
32+
}
33+
34+
static fromConsoleSessionToken(
35+
consoleSessionToken: SeamHttpMultiWorkspaceOptionsWithConsoleSessionToken['consoleSessionToken'],
36+
options: Omit<
37+
SeamHttpMultiWorkspaceOptionsWithConsoleSessionToken,
38+
'consoleSessionToken'
39+
> = {},
40+
): SeamHttpMultiWorkspace {
41+
const constructorOptions = { ...options, consoleSessionToken }
42+
if (
43+
!isSeamHttpMultiWorkspaceOptionsWithConsoleSessionToken(
44+
constructorOptions,
45+
)
46+
) {
47+
throw new SeamHttpMultiWorkspaceInvalidOptionsError(
48+
'Missing consoleSessionToken',
49+
)
50+
}
51+
return new SeamHttpMultiWorkspace(constructorOptions)
52+
}
53+
54+
static fromPersonalAccessToken(
55+
personalAccessToken: SeamHttpMultiWorkspaceOptionsWithPersonalAccessToken['personalAccessToken'],
56+
options: Omit<
57+
SeamHttpMultiWorkspaceOptionsWithPersonalAccessToken,
58+
'personalAccessToken'
59+
> = {},
60+
): SeamHttpMultiWorkspace {
61+
const constructorOptions = { ...options, personalAccessToken }
62+
if (
63+
!isSeamHttpMultiWorkspaceOptionsWithPersonalAccessToken(
64+
constructorOptions,
65+
)
66+
) {
67+
throw new SeamHttpMultiWorkspaceInvalidOptionsError(
68+
'Missing personalAccessToken',
69+
)
70+
}
71+
return new SeamHttpMultiWorkspace(constructorOptions)
72+
}
73+
74+
get workspaces(): SeamHttpWorkspaces {
75+
return SeamHttpWorkspaces.fromClient(this.client)
76+
}
77+
}

test/seam/connect/api-key.test.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import test from 'ava'
22
import { getTestServer } from 'fixtures/seam/connect/api.js'
33

4-
import { SeamHttp } from '@seamapi/http/connect'
5-
6-
import { SeamHttpInvalidTokenError } from 'lib/seam/connect/auth.js'
4+
import { SeamHttp, SeamHttpInvalidTokenError } from '@seamapi/http/connect'
75

86
test('SeamHttp: fromApiKey returns instance authorized with apiKey', async (t) => {
97
const { seed, endpoint } = await getTestServer(t)

test/seam/connect/client-session-token.test.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import test from 'ava'
22
import { getTestServer } from 'fixtures/seam/connect/api.js'
33

4-
import { SeamHttp } from '@seamapi/http/connect'
5-
6-
import { SeamHttpInvalidTokenError } from 'lib/seam/connect/auth.js'
4+
import { SeamHttp, SeamHttpInvalidTokenError } from '@seamapi/http/connect'
75

86
test('SeamHttp: fromClientSessionToken returns instance authorized with clientSessionToken', async (t) => {
97
const { seed, endpoint } = await getTestServer(t)

0 commit comments

Comments
 (0)