Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/common-hoops-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lightmill/log-api': major
---

Removes typescript-openapi type export. The prefered way to rely on our contract's type is now to use the zod schemas directly. openapi.yaml is still being generated so typescript-openapi types can be generated from it if needed.
5 changes: 5 additions & 0 deletions .changeset/cyan-teeth-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lightmill/log-api': major
---

Switch openapi.json export to openapi.yaml. This aligns with openapi most widespread use. Author should update their code to use openapi.yamd instead of .json, and switch to corresponding parser.
5 changes: 5 additions & 0 deletions .changeset/free-pianos-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lightmill/log-api': minor
---

Export zod schemas that may be used to validate request and reponses.
5 changes: 5 additions & 0 deletions .changeset/honest-geckos-add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lightmill/log-server': major
---

Change some validation error codes to match new log-api. Please refer to @lightmill/log-api openapi.yaml for the new validation error codes.
5 changes: 5 additions & 0 deletions .changeset/shaky-candles-fall.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lightmill/log-client': minor
---

Slightly improve some client's errors.
5 changes: 5 additions & 0 deletions .changeset/thirty-needles-camp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lightmill/log-api': major
---

Update validation error codes to be more explicit. Refer to openapi.yaml or the exported schemas to update your code if needed.
4 changes: 3 additions & 1 deletion .github/workflows/node.js.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ jobs:
- uses: actions/upload-artifact@v4
with:
name: 'build'
path: 'packages/*/dist/'
path: |
packages/*/dist/
packages/*/src/generated/

test:
needs: ['build']
Expand Down
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,20 @@
"publint": "node ./scripts/publint.js"
},
"devDependencies": {
"@changesets/cli": "^2.27.1",
"@eslint/js": "^9.20.0",
"@changesets/cli": "^2.29.5",
"@eslint/js": "^9.30.1",
"@types/eslint-config-prettier": "^6.11.3",
"eslint": "^9.20.1",
"eslint": "^9.30.1",
"eslint-config-prettier": "10.1.2",
"eslint-plugin-jsdoc": "^50.6.3",
"eslint-plugin-react": "^7.37.4",
"globals": "^16.0.0",
"eslint-plugin-jsdoc": "^50.8.0",
"eslint-plugin-react": "^7.37.5",
"globals": "^16.3.0",
"jiti": "^2.4.2",
"prettier": "3.5.3",
"prettier-plugin-organize-imports": "^4.1.0",
"publint": "^0.3.12",
"typescript": "catalog:",
"typescript-eslint": "^8.27.0",
"typescript-eslint": "^8.35.1",
"vitest": "catalog:"
},
"packageManager": "pnpm@10.13.1",
Expand All @@ -39,6 +39,6 @@
},
"dependencies": {
"@tsconfig/node-ts": "^23.6.1",
"@tsconfig/node22": "^22.0.1"
"@tsconfig/node22": "^22.0.2"
}
}
27 changes: 14 additions & 13 deletions packages/log-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,27 @@
"default": "./dist/index.js"
}
},
"./openapi.json": "./dist/openapi.json"
"./openapi.yaml": "./dist/openapi.yaml"
},
"files": [
"dist"
"dist",
"src"
],
"scripts": {
"generate-api-types": "openapi-typescript temp/openapi.json --empty-objects-unknown --output temp/openapi.ts",
"build-api": "tsp compile ./type-spec",
"build-js": "cp -rf src/** temp/ && tsc -b tsconfig.build.json",
"build": "rm -rf temp && pnpm run build-api && pnpm run generate-api-types && pnpm run build-js && rm -rf temp",
"watch:build": "onchange -i 'src/index.ts' 'type-spec/**/*' -- pnpm build",
"build-api": "mkdir -p dist && node --experimental-strip-types scripts/build-api.ts > dist/openapi.yaml",
"build-js": "tsc -b tsconfig.build.json",
"build": "pnpm run build-api && pnpm run build-js",
"watch:build": "onchange -i 'src/*' -i 'scripts/*.ts' -- pnpm build",
"prepublish": "pnpm run build"
},
"devDependencies": {
"@typespec/compiler": "1.0.0",
"@typespec/http": "1.0.1",
"@typespec/openapi": "1.0.0",
"@typespec/openapi3": "1.0.0",
"@typespec/rest": "^0.70.0",
"@std/yaml": "jsr:^1.0.8",
"onchange": "^7.1.0",
"openapi-typescript": "^7.6.1"
"openapi-typescript": "^7.8.0"
},
"dependencies": {
"@asteasolutions/zod-to-openapi": "8.0.0-beta.4",
"type-fest": "^4.41.0",
"zod": "^3.25.73"
}
}
31 changes: 31 additions & 0 deletions packages/log-api/scripts/build-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { OpenApiGeneratorV31 } from '@asteasolutions/zod-to-openapi';
import { stringify } from '@std/yaml';
import type { Entries } from 'type-fest';
import manifest from '../package.json' with { type: 'json' };
import { routes } from '../src/routes.ts';
import { sessionAuth } from '../src/security.ts';
import { ServerErrorResponse } from '../src/server-errors.ts';
import { sessionRoutes } from '../src/session-schemas.ts';
import { registry } from '../src/zod-openapi.ts';

for (const [path, pathMethods] of Object.entries(routes) as Entries<
typeof sessionRoutes
>) {
for (const [method, config] of Object.entries(pathMethods) as Entries<
typeof pathMethods
>) {
registry.registerPath({ path, method: method, ...config });
}
}

registry.register('ServerErrorResponse', ServerErrorResponse);

const openApiDocument = new OpenApiGeneratorV31(
registry.definitions,
).generateDocument({
openapi: '3.1.0',
info: { title: 'Log API', version: manifest.version },
security: [{ [sessionAuth.name]: [] }],
});

console.log(stringify(openApiDocument));
124 changes: 124 additions & 0 deletions packages/log-api/src/experiment-schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import {
getDataDocumentSchema,
getErrorDocumentSchema,
getErrorSchema,
getResourceIdentifierSchema,
mediaType,
} from './jsonapi.ts';
import { ForbiddenErrorResponse, StringOrArrayOfStrings } from './utils.ts';
import { z, type RouteConfig } from './zod-openapi.ts';

// Resource schema
// -----------------------------------------------------------------------------
export const ExperimentResourceIdentifier = getResourceIdentifierSchema(
'experiments',
).openapi('ExperimentResourceIdentifier');
const ExperimentAttributes = z
.strictObject({ name: z.string().min(1).describe('Name of the experiment') })
.openapi('ExperimentAttributes');
// Zod recommend using the spread operator on the shape of a zod object
// instead of using the `extend` method, but we then lose zod-to-openapi's
// ability to generate inherited schemas.
export const ExperimentResource = z
.strictObject({
...ExperimentResourceIdentifier.shape,
attributes: ExperimentAttributes,
})
.openapi('ExperimentResource');
const ExperimentResourceCreate = ExperimentResource.omit({ id: true }).openapi(
'ExperimentResourceCreate',
);

// Query parameters schemas
// -----------------------------------------------------------------------------
const ExperimentQueryFilter = z.strictObject({
'filter[name]': StringOrArrayOfStrings.optional(),
});

// Requests schemas
// -----------------------------------------------------------------------------
const ExperimentPostRequest = getDataDocumentSchema({
data: ExperimentResourceCreate,
}).openapi('ExperimentPostRequest');

// OK Response schemas
// -----------------------------------------------------------------------------
const ExperimentGetResponse = getDataDocumentSchema({
data: ExperimentResource,
}).openapi('ExperimentGetResponse');
const ExperimentGetCollectionResponse = getDataDocumentSchema({
data: z.array(ExperimentResource),
}).openapi('ExperimentGetCollectionResponse');
const ExperimentPostResponse = getDataDocumentSchema({
data: ExperimentResourceIdentifier,
}).openapi('ExperimentPostResponse');

// Error Response schemas
// -----------------------------------------------------------------------------
const ExperimentNotFoundErrorResponse = getErrorDocumentSchema(
getErrorSchema({ code: 'EXPERIMENT_NOT_FOUND', statusText: 'Not Found' }),
).openapi('ExperimentNotFoundErrorResponse');
const ExperimentExistsErrorResponse = getErrorDocumentSchema(
getErrorSchema({ code: 'EXPERIMENT_EXISTS', statusText: 'Conflict' }),
).openapi('ExperimentExistsErrorResponse');

// Route configuration
// -----------------------------------------------------------------------------
export const experimentRoutes = {
'/': {
get: {
description: 'List all experiments',
request: { query: ExperimentQueryFilter },
responses: {
200: {
description: 'List of experiments',
content: { [mediaType]: { schema: ExperimentGetCollectionResponse } },
},
},
},
post: {
description: 'Create a new experiment.',
request: {
body: {
required: true,
content: {
[mediaType]: { schema: ExperimentPostRequest.required() },
},
},
},
responses: {
201: {
description: 'Experiment created successfully',
content: { [mediaType]: { schema: ExperimentPostResponse } },
headers: z.strictObject({ location: z.string() }),
},
403: {
description: 'Forbidden',
content: { [mediaType]: { schema: ForbiddenErrorResponse } },
},
409: {
description: 'Experiment already exists',
content: { [mediaType]: { schema: ExperimentExistsErrorResponse } },
},
},
},
},
'/{id}': {
get: {
description: 'Get a specific experiment by ID',
request: {
params: z.object({ id: z.string().describe('ID of the experiment') }),
},
responses: {
200: {
description: 'Experiment retrieved successfully',
content: { [mediaType]: { schema: ExperimentGetResponse } },
},
404: {
description: 'Experiment not found',
content: { [mediaType]: { schema: ExperimentNotFoundErrorResponse } },
},
},
},
},
} satisfies RouteConfig;
7 changes: 3 additions & 4 deletions packages/log-api/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export type * from './openapi.js';
import openAPI from './openapi.json' with { type: 'json' };

export { openAPI };
export { openApiDocument as openAPI } from './openapi-document.ts';
export { routes } from './routes.ts';
export * from './server-errors.ts';
Loading