Skip to content

Commit

Permalink
feat: Generate first proper openapi spec via @usevenice/trpc-openai p…
Browse files Browse the repository at this point in the history
…owered by zod-openapi
  • Loading branch information
tonyxiao committed Nov 19, 2023
1 parent daa9613 commit d4d0fa0
Show file tree
Hide file tree
Showing 17 changed files with 2,916 additions and 108 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
**/__generated__/
*.generated.*
*.gen.*
*.oas.*

# jest
*.snap
Expand Down
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
**/*.generated.* linguist-generated=true
**/*.gen.* linguist-generated=true
**/*.openapi.* linguist-generated=true
**/*.oas.* linguist-generated=true
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
**/__generated__/
*.generated.*
*.gen.*
*.oas.*

# jest
*.snap
Expand Down
19 changes: 13 additions & 6 deletions apps/web/lib-server/appRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,22 @@ const customRouter = trpc.router({
.meta({openapi: {method: 'GET', path: '/'}})
.input(z.void())
.output(z.unknown())
.query(() => openApiDocument),
.query((): unknown => generateOpenApi()),
})

export const appRouter = trpc.mergeRouters(flatRouter, customRouter)

export const openApiDocument = generateOpenApiDocument(appRouter, {
title: 'Venice OpenAPI',
version: '0.0.0',
baseUrl: getServerUrl(null) + '/api/v0',
})
function generateOpenApi() {
return generateOpenApiDocument(appRouter, {
// openApiVersion: '3.1.0',
title: 'Venice OpenAPI',
version: '0.0.0',
baseUrl: getServerUrl(null) + '/api/v0',
})
}

export type AppRouter = typeof appRouter

if (require.main === module) {
console.log(JSON.stringify(generateOpenApi(), null, 2))
}
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"@usevenice/engine-backend": "workspace:*",
"@usevenice/engine-frontend": "workspace:*",
"@usevenice/integration-postgres": "workspace:*",
"@usevenice/trpc-openapi": "1.3.6",
"@usevenice/trpc-openapi": "1.3.8",
"@usevenice/ui": "workspace:*",
"@usevenice/util": "workspace:*",
"commandbar": "1.7.3",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"lint-staged": "13.0.3",
"ngrok": "5.0.0-beta.2",
"npm-run-all": "4.1.5",
"openapi-typescript": "6.7.1",
"prettier": "2.8.8",
"prettier-plugin-packagejson": "2.2.18",
"prettier-plugin-sql": "0.12.1",
Expand Down
30 changes: 21 additions & 9 deletions packages/cdk/id.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// TODO: Maybe this belongs in engine backend?
import {invert, R, z} from '@usevenice/util'
import {invert, memoize, R, z} from '@usevenice/util'

export type ExternalId = z.infer<typeof zExternalId>
export const zExternalId = z.union([z.string(), z.number()])
Expand Down Expand Up @@ -35,6 +35,26 @@ export type Id<TName extends string = string> = {
: `${k}_${TName}${string}` // 3rd segment is not guaranteed to exist
}

/**
* This needs to be memoized because duplicate calls to .openapi with
* the same ref is an error
*/
function _zId<TPrefix extends IdPrefix>(prefix: TPrefix) {
return z
.string()
.refine(
// Add support for doubly-prefixed ids...
(s): s is Id[TPrefix] => s.startsWith(`${prefix}_`),
`Is not a valid ${IDS_INVERTED[prefix]} id, expecting ${prefix}_`,
)
.openapi({ref: `id.${prefix}`, description: `Must start with '${prefix}_'`})
}

export const zId = memoize(_zId, {
// Should make it easier like -1 to never clear cache...
maxSize: Number.POSITIVE_INFINITY, // Forever and always ;)
})

/** Unfortunately userId is mostly *not* prefixed */
export const zUserId = zId('user')
export type UserId = z.infer<typeof zUserId>
Expand All @@ -46,14 +66,6 @@ export type EndUserId = z.infer<typeof zEndUserId>
export const zExtEndUserId = z.string().min(1).brand<'ext_end_user'>()
export type ExtEndUserId = z.infer<typeof zExtEndUserId>

export function zId<TPrefix extends IdPrefix>(prefix: TPrefix) {
return z.string().refine(
// Add support for doubly-prefixed ids...
(s): s is Id[TPrefix] => s.startsWith(`${prefix}_`),
`Is not a valid ${IDS_INVERTED[prefix]} id, expecting ${prefix}_`,
)
}

export function makeId<TPrefix extends IdPrefix, TPName extends string>(
...args: TPrefix extends INDEPENDENT_ID_PREFIX
? [TPrefix, ExternalId]
Expand Down
120 changes: 64 additions & 56 deletions packages/cdk/meta.types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {z, zJsonObject} from '@usevenice/util'
import {z} from '@usevenice/util'

import {zEndUserId, zId} from './id.types'

Expand Down Expand Up @@ -78,60 +78,68 @@ const zBase = z.object({
updatedAt: z.date(), // should be string but slonik returns date
})
export const zRaw = {
integration: zBase.extend({
id: zId('int'),
/** This is a generated column, which is not the most flexible. Maybe we need some kind of mapStandardIntegration method? */
envName: z.string().nullish(),
providerName: z.string(),
config: zJsonObject.nullish(),
endUserAccess: z
.boolean()
.nullish()
.describe(
"Allow end user to create resources using this integration's configuration",
),
orgId: zId('org'),
displayName: z.string().nullish(),
disabled: z.boolean().optional(),
}),
resource: zBase.extend({
id: zId('reso'),
providerName: z.string(),
displayName: z.string().nullish(),
endUserId: zEndUserId.nullish(),
integrationId: zId('int'),
institutionId: zId('ins').nullish(),
settings: zJsonObject.nullish(),
standard: zStandard.resource.omit({id: true}).nullish(),
disabled: z.boolean().optional(),
}),
pipeline: zBase.extend({
id: zId('pipe'),
// TODO: Remove nullish now that pipelines are more fixed
sourceId: zId('reso').optional(),
sourceState: zJsonObject.optional(),
destinationId: zId('reso').optional(),
destinationState: zJsonObject.optional(),
linkOptions: z
.array(z.unknown())
// z.union([
// z.string(),
// z.tuple([z.string()]),
// z.tuple([z.string(), z.unknown()]),
// ]),
.nullish(),
// TODO: Add two separate tables sync_jobs to keep track of this instead of these two
// though questionnable whether it should be in a separate database completely
// just like Airbyte. Or perhaps using airbyte itself as the jobs database
lastSyncStartedAt: z.date().nullish(),
lastSyncCompletedAt: z.date().nullish(),
disabled: z.boolean().optional(),
}),
institution: zBase.extend({
id: zId('ins'),
providerName: z.string(),
standard: zStandard.institution.omit({id: true}).nullish(),
external: zJsonObject.nullish(),
}),
integration: zBase
.extend({
id: zId('int'),
/** This is a generated column, which is not the most flexible. Maybe we need some kind of mapStandardIntegration method? */
envName: z.string().nullish(),
providerName: z.string(),
config: z.record(z.unknown()).nullish(),
endUserAccess: z
.boolean()
.nullish()
.describe(
"Allow end user to create resources using this integration's configuration",
),
orgId: zId('org'),
displayName: z.string().nullish(),
disabled: z.boolean().optional(),
})
.openapi({ref: 'integration'}),
resource: zBase
.extend({
id: zId('reso'),
providerName: z.string().describe('Unique name of the connector'),
displayName: z.string().nullish(),
endUserId: zEndUserId.nullish(),
integrationId: zId('int'),
institutionId: zId('ins').nullish(),
settings: z.record(z.unknown()).nullish(),
standard: zStandard.resource.omit({id: true}).nullish(),
disabled: z.boolean().optional(),
})
.openapi({ref: 'resource'}),
pipeline: zBase
.extend({
id: zId('pipe'),
// TODO: Remove nullish now that pipelines are more fixed
sourceId: zId('reso').optional(),
sourceState: z.record(z.unknown()).optional(),
destinationId: zId('reso').optional(),
destinationState: z.record(z.unknown()).optional(),
linkOptions: z
.array(z.unknown())
// z.union([
// z.string(),
// z.tuple([z.string()]),
// z.tuple([z.string(), z.unknown()]),
// ]),
.nullish(),
// TODO: Add two separate tables sync_jobs to keep track of this instead of these two
// though questionnable whether it should be in a separate database completely
// just like Airbyte. Or perhaps using airbyte itself as the jobs database
lastSyncStartedAt: z.date().nullish(),
lastSyncCompletedAt: z.date().nullish(),
disabled: z.boolean().optional(),
})
.openapi({ref: 'pipeline'}),
institution: zBase
.extend({
id: zId('ins'),
providerName: z.string(),
standard: zStandard.institution.omit({id: true}).nullish(),
external: z.record(z.unknown()).nullish(),
})
.openapi({ref: 'institution'}),
// TODO: Add connection_attempts
}
2 changes: 1 addition & 1 deletion packages/cdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"@nangohq/frontend": "0.33.8",
"@types/jsonwebtoken": "9.0.2",
"@types/react": "*",
"@usevenice/trpc-openapi": "1.3.6"
"@usevenice/trpc-openapi": "1.3.8"
},
"peerDependencies": {
"react": "*"
Expand Down
5 changes: 3 additions & 2 deletions packages/engine-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
"@trpc/server": "10.40.0",
"@usevenice/cdk": "workspace:*",
"@usevenice/util": "workspace:*",
"inngest": "1.3.1"
"inngest": "1.3.1",
"zod-openapi": "2.11.0"
},
"devDependencies": {
"@usevenice/trpc-openapi": "1.3.6"
"@usevenice/trpc-openapi": "1.3.8"
}
}
15 changes: 15 additions & 0 deletions packages/sdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "@usevenice/sdk",
"version": "0.0.0",
"private": false,
"sideEffects": false,
"module": "./index.ts",
"scripts": {
"generate:schema": "node --loader tsx ../../apps/web/lib-server/appRouter.ts > ./venice.oas.json",
"generate:types": "openapi-typescript ./venice.oas.json --output ./venice.oas.d.ts"
},
"dependencies": {},
"devDependencies": {
"openapi-typescript": "6.7.1"
}
}
Loading

0 comments on commit d4d0fa0

Please sign in to comment.