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
3 changes: 2 additions & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"@cossistant/react",
"@cossistant/types",
"@cossistant/next",
"@cossistant/browser"
"@cossistant/browser",
"@cossistant/protocol"
]
],
"linked": [],
Expand Down
23 changes: 23 additions & 0 deletions .changeset/wild-mice-shake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
"@cossistant/protocol": minor
---

Add `@cossistant/protocol`, the canonical HTTP and WebSocket wire contract for
the Cossistant API.

The package owns all 65 public operations (64 REST routes plus the `/ws`
handshake) as `@hono/zod-openapi` route descriptors, along with the shared
security schemes, protocol headers, error responses, and operation-ID/tag
normalization. It is side-effect free: importing it initializes no database,
Redis, S3, billing, auth, or queue clients, and reads no environment variables.

- `@cossistant/protocol/routes` — descriptors and the recursive mount table,
registerable with `app.openapi(route, yourHandler)`
- `@cossistant/protocol/openapi` — `buildCossistantOpenApiDocument()`, pure
and deterministic, with server URLs as arguments
- `@cossistant/protocol/testkit` — compatibility checking so a second
implementation can prove in its own CI that it still matches the contract
- `@cossistant/protocol/openapi.json` — the generated specification

`apps/api` now imports its descriptors from this package and keeps its
handlers. The served document is byte-identical to before the extraction.
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ jobs:
- name: Check types
run: bun run check-types

- name: Check protocol OpenAPI document is up to date
run: bun run check:openapi

- name: Test protocol package
run: bun run test --filter @cossistant/protocol

- name: Test REST OpenAPI contract
# Run this file on its own: bun's mock.module registrations are global to
# the test process, so partial factories in other files leak across a
# combined `bun test src` run.
run: cd apps/api && bun test src/rest/openapi-contract.test.ts

- name: Build browser embed package
run: bun run build --filter @cossistant/browser

Expand Down
7 changes: 4 additions & 3 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@
"@cossistant/jobs": "workspace:*",
"@cossistant/location": "workspace:*",
"@cossistant/memory": "workspace:*",
"@cossistant/protocol": "workspace:*",
"@cossistant/redis": "workspace:*",
"@cossistant/transactional": "workspace:*",
"@cossistant/types": "workspace:*",
"@hono-rate-limiter/redis": "^0.1.4",
"@hono/node-server": "^1.19.9",
"@hono/swagger-ui": "^0.5.3",
"@hono/trpc-server": "^0.4.2",
"@hono/zod-openapi": "^1.2.0",
"@hono/zod-validator": "^0.7.6",
"@hono/zod-openapi": "^1.5.1",
"@hono/zod-validator": "^0.9.0",
"@openrouter/ai-sdk-provider": "^2.5.1",
"@polar-sh/sdk": "^0.44.0",
"@tinybirdco/sdk": "^0.0.33",
Expand All @@ -39,7 +40,7 @@
"ai": "^6.0.154",
"drizzle-orm": "^0.45.2",
"email-reply-parser": "^2.3.5",
"hono": "^4.11.5",
"hono": "^4.12.32",
"hono-rate-limiter": "^0.4.2",
"ioredis": "^5.9.2",
"jose": "^6.1.3",
Expand Down
10 changes: 10 additions & 0 deletions apps/api/src/rest/openapi-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,17 @@ mock.module("@api/realtime/emitter", () => ({
},
}));

// Mocked because importing the real module constructs an S3Client at load time.
// Every export must be listed: mock.module replaces the module wholesale, so an
// omitted symbol becomes "Export named 'x' not found" for any importer.
mock.module("@api/services/upload", () => ({
s3Client: {},
deleteConversationFiles: mock(async () => 0),
deleteContactFiles: mock(async () => 0),
deleteOrganizationFiles: mock(async () => 0),
deleteVisitorFiles: mock(async () => 0),
deleteWebsiteFiles: mock(async () => 0),
getWebsiteUsageBytes: mock(async () => 0),
generateUploadUrl: mock(async () => ({
uploadUrl: "https://example.com/upload",
key: "uploads/example.txt",
Expand Down
Loading
Loading