This repository has been archived by the owner on Oct 31, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* moving etl sync iterator to connector-common * adding posting entity to sync for lever * fixing logging * fixing lever sdk * adding token refresh to lever * updating lever-sdk * adding lever entities to sync * fixing lints
- Loading branch information
1 parent
fb980f6
commit 24ed4b5
Showing
9 changed files
with
191 additions
and
113 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
|
||
// MARK: - New way of doing things | ||
|
||
import {z, rxjs, Rx} from "@openint/util" | ||
|
||
export interface EtlSource< | ||
TEntityMap extends Record<string, unknown> = Record<string, unknown>, | ||
> { | ||
listEntities<TK extends keyof TEntityMap>( | ||
type: TK, | ||
options: { | ||
cursor?: string | null | ||
page_size?: number | ||
}, | ||
): Promise<{ | ||
entities: Array<{ | ||
id: string | ||
/** `null` means deleted */ | ||
data: TEntityMap[TK] | null | ||
}> | ||
next_cursor: string | null | ||
has_next_page: boolean | ||
}> | ||
} | ||
|
||
interface CursorParser<T> { | ||
fromString: (cursor: string | undefined | null) => T | ||
toString: (value: T) => string | null | ||
} | ||
|
||
export const NextPageCursor: CursorParser<{next_page: number}> = { | ||
fromString(cursor) { | ||
const num = z.coerce.number().positive().safeParse(cursor) | ||
return {next_page: num.success ? num.data : 1} | ||
}, | ||
toString(value) { | ||
return JSON.stringify(value) | ||
}, | ||
} | ||
export function observableFromEtlSource( | ||
source: EtlSource, | ||
streams: Record<string, boolean | {disabled?: boolean | undefined} | null>, | ||
state: Record<string, {cursor?: string | null}> = {}, | ||
) { | ||
async function* iterateEntities() { | ||
for (const streamName of Object.keys(streams)) { | ||
const streamValue = streams[streamName] | ||
if ( | ||
!streamValue || | ||
(streamValue as {disabled: boolean}).disabled === true | ||
// Should further check weather streamName is valid for a given connector | ||
) { | ||
continue | ||
} | ||
|
||
const {cursor} = state[streamName] ?? {} | ||
const {entities, next_cursor, has_next_page} = await source.listEntities( | ||
streamName, | ||
{cursor}, | ||
) | ||
|
||
yield entities.map((j) => ({ | ||
type: 'data' as const, | ||
// We should make the messages easier to construct | ||
data: {entityName: streamName, id: j.id, entity: j.data}, | ||
})) | ||
|
||
state[streamName] = {cursor: next_cursor} | ||
if (!has_next_page) { | ||
continue | ||
} | ||
} | ||
} | ||
// DO somethign with the new state... | ||
|
||
return rxjs | ||
.from(iterateEntities()) | ||
.pipe(Rx.mergeMap((ops) => rxjs.from([...ops, {type: 'commit' as const}]))) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,40 +1,96 @@ | ||
import {initSDK} from '@opensdks/runtime' | ||
import { | ||
leverSdkDef, | ||
type initLeverSDK, | ||
type LeverSDK, | ||
type leverTypes, | ||
} from '@opensdks/sdk-lever' | ||
import type {ConnectorServer} from '@openint/cdk' | ||
import type {leverSchemas} from './def' | ||
|
||
export type LeverSDKType = ReturnType<typeof initLeverSDK> | ||
import {initLeverSDK, type leverTypes} from '@opensdks/sdk-lever' | ||
import {nangoProxyLink, type ConnectorServer} from '@openint/cdk' | ||
import {type leverSchemas} from './def' | ||
import {EtlSource, NextPageCursor, observableFromEtlSource} from '../connector-common' | ||
|
||
export type LeverSDK = ReturnType<typeof initLeverSDK> | ||
|
||
export type LeverTypes = leverTypes | ||
|
||
export type LeverObjectType = LeverTypes['components']['schemas'] | ||
|
||
export const leverServer = { | ||
newInstance: ({config, settings}) => { | ||
const lever = initSDK(leverSdkDef, { | ||
newInstance: ({config, settings, fetchLinks}) => { | ||
const lever = initLeverSDK({ | ||
headers: { | ||
authorization: `Bearer ${settings.oauth.credentials.access_token}`, | ||
}, | ||
envName: config.envName, | ||
links: (defaultLinks) => [ | ||
(req, next) => { | ||
if (lever.clientOptions.baseUrl) { | ||
req.headers.set( | ||
nangoProxyLink.kBaseUrlOverride, | ||
lever.clientOptions.baseUrl, | ||
) | ||
} | ||
return next(req) | ||
}, | ||
...fetchLinks, | ||
...defaultLinks | ||
], | ||
}) | ||
return lever | ||
}, | ||
sourceSync: ({instance: lever, streams, state}) => | ||
observableFromEtlSource( | ||
leverSource({sdk: lever}), | ||
streams, | ||
(state ?? {}) as {}, | ||
), | ||
} satisfies ConnectorServer< | ||
typeof leverSchemas, | ||
ReturnType<typeof initLeverSDK> | ||
> | ||
|
||
export default leverServer | ||
|
||
// TODO: Implement incremental sync | ||
// TODO2: Implement low-code connector spec | ||
function leverSource({sdk}: {sdk: LeverSDK}): EtlSource<{ | ||
posting: LeverObjectType['posting'] | ||
// contact: LeverObjectType['contact'] | ||
opportunity: LeverObjectType['opportunity'] | ||
offer: LeverObjectType['offer'] | ||
// Add other entity types as needed | ||
}> { | ||
return { | ||
// @ts-expect-error discuss with tony | ||
async listEntities(type, {cursor}) { | ||
const {next_page: page} = NextPageCursor.fromString(cursor) | ||
|
||
if (type === 'offer') { | ||
const opportunitiesRes = await sdk.GET('/opportunities', { | ||
params: {query: {limit: 50, offset: cursor ?? undefined}} | ||
}) | ||
|
||
async proxy(instance, req) { | ||
return instance | ||
.request(req.method as 'GET', req.url.replace(/.+\/api\/proxy/, ''), { | ||
headers: req.headers, | ||
...(!['GET', 'OPTIONS', 'HEAD'].includes(req.method) && { | ||
body: await req.blob(), // See if this works... We need to figure out how to do streaming here... | ||
}), | ||
const allOffers = [] | ||
for (const opportunity of opportunitiesRes.data.data) { | ||
const offersRes = await sdk.GET(`/opportunities/{id}/offers`, { | ||
params: {path: {id: opportunity.id}} | ||
}) | ||
|
||
allOffers.push(...offersRes.data.data.map((e) => ({id: `${e.id}`, data: e}))) | ||
} | ||
|
||
return { | ||
entities: allOffers, | ||
next_cursor: NextPageCursor.toString({next_page: page + 1}), | ||
has_next_page: opportunitiesRes.data.hasNext ?? false, | ||
} | ||
} | ||
// for opportunity or posting | ||
const pluralizeType = (type: string) => type === 'opportunity' ? 'opportunities' : `${type}s`; | ||
|
||
const res = await sdk.GET(`/${pluralizeType(type) as 'postings' | 'opportunities'}`, { | ||
params: {query: {limit: 50, offset: cursor ?? undefined}} | ||
}) | ||
.then((r) => r.response.clone()) | ||
}, | ||
} satisfies ConnectorServer<typeof leverSchemas, LeverSDK> | ||
|
||
export default leverServer | ||
return { | ||
entities: res.data.data.map((e) => ({id: `${e.id}`, data: e})), | ||
next_cursor: NextPageCursor.toString({next_page: page + 1}), | ||
has_next_page: res.data.hasNext ?? false, | ||
} | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.