diff --git a/README.md b/README.md index 4da4563..74be9ce 100644 --- a/README.md +++ b/README.md @@ -9,16 +9,30 @@ Build tools for the Seam API using this blueprint. TODO +## Motivation + +TODO + ## Installation Add this as a dependency to your project using [npm] with ``` -$ npm install @seamapi/blueprint +$ npm install --save-dev @seamapi/blueprint ``` [npm]: https://www.npmjs.com/ +## Usage + +```ts +import { createBlueprint } from '@seamapi/blueprint' +import * as types from '@seamapi/types' + +const blueprint = createBlueprint(types) +console.log(JSON.stringify(blueprint) +``` + ## Development and Testing ### Quickstart diff --git a/examples/blueprint.ts b/examples/blueprint.ts new file mode 100644 index 0000000..d8fd7d7 --- /dev/null +++ b/examples/blueprint.ts @@ -0,0 +1,24 @@ +import type { Builder, Command, Describe, Handler } from 'landlubber' + +import { createBlueprint, type TypesModule } from '@seamapi/blueprint' + +interface Options { + moduleName: string +} + +export const command: Command = 'blueprint [moduleName]' + +export const describe: Describe = 'Create a blueprint from a module' + +export const builder: Builder = { + moduleName: { + type: 'string', + default: '@seamapi/types/connect', + describe: 'Module name or path to import', + }, +} + +export const handler: Handler = async ({ moduleName, logger }) => { + const types = (await import(moduleName)) as TypesModule + logger.info({ data: createBlueprint(types) }, 'blueprint') +} diff --git a/examples/index.ts b/examples/index.ts index 7a1cc11..141d555 100755 --- a/examples/index.ts +++ b/examples/index.ts @@ -2,8 +2,8 @@ import landlubber from 'landlubber' -import * as todo from './todo.js' +import * as blueprint from './blueprint.js' -const commands = [todo] +const commands = [blueprint] await landlubber(commands).parse() diff --git a/examples/todo.ts b/examples/todo.ts deleted file mode 100644 index b688584..0000000 --- a/examples/todo.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Builder, Command, Describe, Handler } from 'landlubber' - -import { todo } from '@seamapi/blueprint' - -interface Options { - x: string -} - -export const command: Command = 'todo x' - -export const describe: Describe = 'TODO' - -export const builder: Builder = { - x: { - type: 'string', - default: 'TODO', - describe: 'TODO', - }, -} - -export const handler: Handler = async ({ x, logger }) => { - logger.info({ data: todo(x) }, 'TODO') -} diff --git a/package-lock.json b/package-lock.json index 1aaf6e1..cbf9ea1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "license": "MIT", "devDependencies": { + "@seamapi/types": "1.184.0", "@types/node": "^20.8.10", "ava": "^6.0.1", "c8": "^10.1.2", @@ -24,7 +25,8 @@ "tsc-alias": "^1.8.2", "tsup": "^8.0.1", "tsx": "^4.6.2", - "typescript": "~5.3.3" + "typescript": "~5.3.3", + "zod": "^3.23.8" }, "engines": { "node": ">=18.12.0", @@ -1045,6 +1047,19 @@ "win32" ] }, + "node_modules/@seamapi/types": { + "version": "1.184.0", + "resolved": "https://registry.npmjs.org/@seamapi/types/-/types-1.184.0.tgz", + "integrity": "sha512-1XR7Hr0uuYuheiide8l5fHx68caX7nMzQ3eyqe2YEyST6gzMDHrmg/hmKtfxSSqEsvtmvlZ7Lr8X4ooo02xI5Q==", + "dev": true, + "engines": { + "node": ">=18.12.0", + "npm": ">= 9.0.0" + }, + "peerDependencies": { + "zod": "^3.21.4" + } + }, "node_modules/@sindresorhus/merge-streams": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", @@ -8178,6 +8193,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 4a4a7c8..155131d 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "npm": ">= 9.0.0" }, "devDependencies": { + "@seamapi/types": "1.184.0", "@types/node": "^20.8.10", "ava": "^6.0.1", "c8": "^10.1.2", @@ -83,6 +84,7 @@ "tsc-alias": "^1.8.2", "tsup": "^8.0.1", "tsx": "^4.6.2", - "typescript": "~5.3.3" + "typescript": "~5.3.3", + "zod": "^3.23.8" } } diff --git a/src/lib/blueprint.ts b/src/lib/blueprint.ts new file mode 100644 index 0000000..9c9b691 --- /dev/null +++ b/src/lib/blueprint.ts @@ -0,0 +1,15 @@ +import type { Openapi } from './openapi.js' + +export interface Blueprint { + name: string +} + +export interface TypesModule { + openapi: Openapi +} + +export const createBlueprint = ({ openapi }: TypesModule): Blueprint => { + return { + name: openapi.info.title, + } +} diff --git a/src/lib/index.ts b/src/lib/index.ts index 0cbac41..8745d01 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1 +1,5 @@ -export { todo } from './todo.js' +export { + type Blueprint, + createBlueprint, + type TypesModule, +} from './blueprint.js' diff --git a/src/lib/openapi.ts b/src/lib/openapi.ts new file mode 100644 index 0000000..140a788 --- /dev/null +++ b/src/lib/openapi.ts @@ -0,0 +1,5 @@ +export interface Openapi { + info: { + title: string + } +} diff --git a/src/lib/todo.test.ts b/src/lib/todo.test.ts deleted file mode 100644 index 4f4b033..0000000 --- a/src/lib/todo.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import test from 'ava' - -import { todo } from './todo.js' - -test('todo: returns argument', (t) => { - t.is(todo('todo'), 'todo', 'returns input') -}) diff --git a/src/lib/todo.ts b/src/lib/todo.ts deleted file mode 100644 index 5633fe7..0000000 --- a/src/lib/todo.ts +++ /dev/null @@ -1 +0,0 @@ -export const todo = (x: string): string => x diff --git a/test/blueprint.test.ts b/test/blueprint.test.ts new file mode 100644 index 0000000..95d9cb7 --- /dev/null +++ b/test/blueprint.test.ts @@ -0,0 +1,10 @@ +import test from 'ava' + +import { createBlueprint } from '@seamapi/blueprint' + +import * as types from './fixtures/types/index.js' + +test('createBlueprint', (t) => { + const blueprint = createBlueprint(types) + t.snapshot(blueprint, 'blueprint') +}) diff --git a/test/fixtures/types/index.ts b/test/fixtures/types/index.ts new file mode 100644 index 0000000..3a9695f --- /dev/null +++ b/test/fixtures/types/index.ts @@ -0,0 +1,8 @@ +import * as schemas from './schemas.js' + +export { schemas } + +export * from './model-types.js' +export { default as openapi } from './openapi.js' +export * from './route-schemas.js' +export * from './route-types.js' diff --git a/test/fixtures/types/model-types.ts b/test/fixtures/types/model-types.ts new file mode 100644 index 0000000..40b064e --- /dev/null +++ b/test/fixtures/types/model-types.ts @@ -0,0 +1,5 @@ +import type { z } from 'zod' + +import type { foo } from './schemas.js' + +export type Foo = z.infer diff --git a/test/fixtures/types/openapi.ts b/test/fixtures/types/openapi.ts new file mode 100644 index 0000000..00d1d76 --- /dev/null +++ b/test/fixtures/types/openapi.ts @@ -0,0 +1,79 @@ +export default { + openapi: '3.0.0', + info: { title: 'Foo', version: '1.0.0' }, + servers: [{ url: 'https://example.com' }], + tags: [{ description: 'foos', name: '/foos' }], + components: { + schemas: { + foo: { + type: 'object', + properties: { + foo_id: { + description: 'Foo id', + format: 'uuid', + type: 'string', + }, + name: { + description: 'Foo name', + type: 'string', + }, + }, + required: ['foo_id', 'name'], + }, + }, + }, + paths: { + '/foos/get': { + get: { + operationId: 'foosGetGet', + responses: { + 200: { + content: { + 'application/json': { + schema: { + properties: { + ok: { type: 'boolean' }, + foo: { $ref: '#/components/schemas/foo' }, + }, + required: ['foo', 'ok'], + type: 'object', + }, + }, + }, + description: 'OK', + }, + 400: { description: 'Bad Request' }, + 401: { description: 'Unauthorized' }, + }, + security: [], + summary: '/foos/get', + tags: ['/foos'], + }, + post: { + operationId: 'foosGetPost', + responses: { + 200: { + content: { + 'application/json': { + schema: { + properties: { + ok: { type: 'boolean' }, + foo: { $ref: '#/components/schemas/foo' }, + }, + required: ['foo', 'ok'], + type: 'object', + }, + }, + }, + description: 'OK', + }, + 400: { description: 'Bad Request' }, + 401: { description: 'Unauthorized' }, + }, + security: [], + summary: '/foos/get', + tags: ['/foos'], + }, + }, + }, +} diff --git a/test/fixtures/types/route-schemas.ts b/test/fixtures/types/route-schemas.ts new file mode 100644 index 0000000..0f5e31e --- /dev/null +++ b/test/fixtures/types/route-schemas.ts @@ -0,0 +1,13 @@ +import { z } from 'zod' + +import * as schemas from './schemas.js' + +export const routes = { + '/foos/get': { + auth: 'none', + methods: ['GET', 'POST'], + jsonResponse: z.object({ + foo: schemas.foo, + }), + }, +} as const diff --git a/test/fixtures/types/route-specs.ts b/test/fixtures/types/route-specs.ts new file mode 100644 index 0000000..0f5e31e --- /dev/null +++ b/test/fixtures/types/route-specs.ts @@ -0,0 +1,13 @@ +import { z } from 'zod' + +import * as schemas from './schemas.js' + +export const routes = { + '/foos/get': { + auth: 'none', + methods: ['GET', 'POST'], + jsonResponse: z.object({ + foo: schemas.foo, + }), + }, +} as const diff --git a/test/fixtures/types/route-types.ts b/test/fixtures/types/route-types.ts new file mode 100644 index 0000000..54d2ec9 --- /dev/null +++ b/test/fixtures/types/route-types.ts @@ -0,0 +1,25 @@ +export interface Routes { + '/foos/get': { + route: '/foos/get' + method: 'GET' | 'POST' + queryParams: Record + jsonBody: Record + commonParams: Record + formData: Record + jsonResponse: { + foo: { + foo_id: string + name: string + } + } + } +} + +export type RouteResponse = + Routes[Path]['jsonResponse'] + +export type RouteRequestBody = + Routes[Path]['jsonBody'] & Routes[Path]['commonParams'] + +export type RouteRequestParams = + Routes[Path]['queryParams'] & Routes[Path]['commonParams'] diff --git a/test/fixtures/types/schemas.ts b/test/fixtures/types/schemas.ts new file mode 100644 index 0000000..4125ae8 --- /dev/null +++ b/test/fixtures/types/schemas.ts @@ -0,0 +1,6 @@ +import { z } from 'zod' + +export const foo = z.object({ + foo_id: z.string().uuid(), + name: z.string(), +}) diff --git a/test/seamapi-blueprint.test.ts b/test/seamapi-blueprint.test.ts new file mode 100644 index 0000000..891ca3d --- /dev/null +++ b/test/seamapi-blueprint.test.ts @@ -0,0 +1,9 @@ +import { openapi } from '@seamapi/types/connect' +import test from 'ava' + +import { createBlueprint } from '@seamapi/blueprint' + +test('createBlueprint', (t) => { + const blueprint = createBlueprint({ openapi }) + t.snapshot(blueprint, 'blueprint') +}) diff --git a/test/snapshots/blueprint.test.ts.md b/test/snapshots/blueprint.test.ts.md new file mode 100644 index 0000000..2c9afcf --- /dev/null +++ b/test/snapshots/blueprint.test.ts.md @@ -0,0 +1,13 @@ +# Snapshot report for `test/blueprint.test.ts` + +The actual snapshot is saved in `blueprint.test.ts.snap`. + +Generated by [AVA](https://avajs.dev). + +## createBlueprint + +> blueprint + + { + name: 'Foo', + } diff --git a/test/snapshots/blueprint.test.ts.snap b/test/snapshots/blueprint.test.ts.snap new file mode 100644 index 0000000..e1fc83c Binary files /dev/null and b/test/snapshots/blueprint.test.ts.snap differ diff --git a/test/snapshots/seamapi-blueprint.test.ts.md b/test/snapshots/seamapi-blueprint.test.ts.md new file mode 100644 index 0000000..6e45115 --- /dev/null +++ b/test/snapshots/seamapi-blueprint.test.ts.md @@ -0,0 +1,13 @@ +# Snapshot report for `test/seamapi-blueprint.test.ts` + +The actual snapshot is saved in `seamapi-blueprint.test.ts.snap`. + +Generated by [AVA](https://avajs.dev). + +## createBlueprint + +> blueprint + + { + name: 'Seam Connect', + } diff --git a/test/snapshots/seamapi-blueprint.test.ts.snap b/test/snapshots/seamapi-blueprint.test.ts.snap new file mode 100644 index 0000000..f129021 Binary files /dev/null and b/test/snapshots/seamapi-blueprint.test.ts.snap differ diff --git a/test/todo.test.ts b/test/todo.test.ts deleted file mode 100644 index 6e7e0f2..0000000 --- a/test/todo.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import test from 'ava' - -import { todo } from '@seamapi/blueprint' - -test('todo: returns argument', (t) => { - t.is(todo('todo'), 'todo', 'returns input') -})