refactor(api): V2 response envelope, snake_case schema, named views#2729
Open
Innei wants to merge 31 commits into
Open
refactor(api): V2 response envelope, snake_case schema, named views#2729Innei wants to merge 31 commits into
Innei wants to merge 31 commits into
Conversation
Add src/common/response (envelope/meta/error types, MetaObjectBuilder, ResponseInterceptorV2, AppExceptionFilter, @RawResponse) and src/common/views (parseView). Add createPagerSchema factory to pager.dto.ts. Additive only; not yet wired into the global pipeline. 41 unit tests; typecheck and lint clean.
Rename Drizzle column TS property names and ripple through apps/core repositories. Better Auth tables (readers, accounts, sessions, apiKeys, passkeys, verifications) keep camelCase props for drizzle-adapter compatibility. SQL column names unchanged; no migration needed.
@ResponseV2() now composes SetMetadata (legacy interceptors skip), UseInterceptors(ResponseInterceptorV2), and UseFilters(AppExceptionFilter). Migrated controllers opt in with this one decorator; un-migrated controllers keep the legacy pipeline untouched.
Apply @ResponseV2() to all controllers in ai (and all sub-controllers:
ai-agent, ai-insights, ai-summary, ai-task, ai-translation including
translation-entry, ai-writer), enrichment, analyze, activity, and link
modules.
- Add ai.exceptions.ts, enrichment.exceptions.ts, link.exceptions.ts
with AppException subclasses replacing BizException/HttpException usage
- Add @RawResponse on SSE/streaming endpoints in ai-agent, ai-insights,
ai-summary, and ai-translation controllers
- Wrap all controller return values in { data } / { data, meta } envelopes
- Add RESPONSE_V2_METADATA constant to system.constant.ts (required by
v2-controller.decorator.ts)
- Update contract tests and e2e specs to assert { data } envelope shape
and AppException error envelope { error: { code, message } }
Migrate feed, pageproxy, render, sitemap, snippet-route, snippet,
serverless, health, server-time, update, debug, dependency, cron-task,
backup, poll, markdown, option (base + email), and app.controller.ts
to the V2 response layer.
- Add @ResponseV2() to each controller class
- Replace @HTTPDecorators.Bypass with @RawResponse on genuinely
non-JSON methods (RSS/XML, HTML render, binary streams, SSE,
user-defined function outputs)
- Remove Bypass from JSON-returning endpoints (health /ping,
dependency /graph, cron-task list, backup list, poll state,
option /form-schema, app /uptime)
- Migrate poll controller from class-level Bypass to @ResponseV2()
with all endpoints returning plain JSON envelopes
- Fix snippet.controller list methods to return {data, meta:{pagination}}
instead of legacy spread shape; update two tests accordingly
- Fix missing RESPONSE_V2_METADATA export in system.constant.ts
- Add missing isDev import in app.controller.ts
Update 7 test files to match the V2 response envelope ({ data, meta? })
and new AppException subclasses replacing BizException/CannotFindException.
Fix snake_case DB column references in all batch-3 repository files (auth, file-reference, meta-preset, owner, project, say, subscribe, webhook) that were authored against a pre-rename base. Typecheck 0 errors, lint clean, tests 0 failed.
Delete json-transform.interceptor, legacy response.interceptor, translation-entry.interceptor, translate-fields.decorator, and case.util. Promote ResponseInterceptorV2 as the single global APP_INTERCEPTOR. Remove UseInterceptors from @ResponseV2() since the interceptor now runs globally. Fix activity.controller.ts to build presence response with plain objects instead of snakecaseKeys.
Rewrite CannotFindException (NOT_FOUND/404), BanInDemoExcpetion (DEMO_FORBIDDEN/403), NoContentCanBeModifiedException (NO_CONTENT_MODIFIABLE/400), and BusinessException (enum key code) to extend AppException with stable string codes. Promote AppExceptionFilter as the single global APP_FILTER, replacing AllExceptionsFilter. Fold in AllExceptionsFilter's side effects: Bark push on 429, EventBusEvents.SystemException broadcast on 5xx, uncaughtException/unhandledRejection process hooks. Services are @optional so the filter still instantiates without DI in unit tests.
Introduce snakeCaseKeys utility and apply it explicitly at every controller boundary so HTTP responses emit snake_case keys without relying on a global interceptor. Covers aggregate, category, comment, draft, enrichment, link, note, page, post, recently, snippet, subscribe, and topic controllers.
SafeDep Report SummaryNo dependency changes detected. Nothing to scan. This report is generated by SafeDep Github App |
- getDataFromResponse unwraps the { data, meta? } envelope; folds
meta.pagination into PaginateResult
- expose raw response meta via a non-enumerable $meta accessor
- parse the { error: { code, message, details? } } error body;
RequestError gains string `code` and `details`
- Pager becomes { page, size, total, totalPages }; add ResponseMeta,
InteractionMeta, ArticleTranslation, EntryTranslation, RelatedRef
- drop the removed ?select= param from post/note/page; retire SelectFields
- unwrap { data: X } list/random returns to bare X
- update test mocks/assertions to the V2 envelope
…onse layer The Drizzle schema column TS property names are renamed from snake_case back to camelCase (DB column name strings unchanged). Business code — repositories, services, domain types — is camelCase throughout; the ResponseInterceptorV2 converts the response to snake_case at the wire boundary via transformResponseCase, so the external contract is unchanged. - db-schema package: all 7 schema files use camelCase column properties - repositories/services: snake_case column access renamed to camelCase - SayRow domain type aligned to camelCase - ResponseInterceptorV2: guard bypass-path metadata to an array - test fixtures: Drizzle insert/select keys renamed to camelCase
…terceptor ResponseInterceptorV2 now converts every response to snake_case at the wire boundary, so the ad-hoc snakeCaseKeys() calls in 13 controllers are redundant. Remove them and delete the now-unused case.util helper.
The schema-layer snake_case decision (design spec §2) was reversed: code is camelCase end to end and ResponseInterceptorV2 converts to snake_case at the wire boundary. Update §2, the goals list, the §7 RawResponse note, the migration reference, and the project CLAUDE.md API rules; document @BypassCaseTransform.
V2 note-detail responses return `data` as a flat NoteModel with `next`/
`prev` siblings, matching post detail — not the V1 `{ data, next, prev }`
wrapper. Retype NoteWrappedPayload accordingly; bump to 5.0.1.
Unify aggregate/category/topic/note controllers on the V2 meta.translation contract: emit id-keyed translation maps instead of mutating translated titles into data. Taxonomy names (category/topic) resolve via the entry-translation subsystem rather than the article pipeline. - aggregate: buildTitleTranslationMap replaces inline title mutation - category: category.name via getEntityTranslations - topic: /topics/all gains @lang(), topic fields via translation entries - note: note-detail meta carries the embedded topic translation - add TranslationService.getTopicTranslationFields shared helper - type translation maps as Map<string, EntryTranslation>
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
Breaking 4-phase refactor of the mx-core HTTP response layer per
docs/superpowers/specs/2026-05-15-v2-api-response-design.md.Every successful JSON response is now
{ data, meta? }; every error is{ error: { code, message, details? } }.Phases
src/common/response/*(envelope/meta/error types,MetaObjectBuilder,ResponseInterceptorV2,AppExceptionFilter,@RawResponse),src/common/views/view.types.ts(parseView),createPagerSchemafactory.packages/db-schemaand rippled through ~32 repositories. The 6 Better Auth tables (readers,accounts,sessions,apiKeys,passkeys,verifications) keep camelCase props fordrizzleAdaptercompatibility.{ data, meta? }envelope, named*.views.tsZod views,MetaObjectBuilder, andAppExceptionsubclasses.JSONTransformInterceptor, legacyResponseInterceptor,translation-entry.interceptor,@TranslateFields; removed theBypassalias; generic exceptions migrated toAppException;ResponseInterceptorV2+AppExceptionFilterwired as globalAPP_INTERCEPTOR/APP_FILTER.Notable deviations
drizzleAdapter) — a design-spec gap resolved here.src/utils/case.util.tsretains an explicitsnakeCaseKeyshelper, called directly in ~13 controllers for responses whose source shape is still camelCase (notably enrichment screenshot/quota fields). The spec's hard requirements hold (uniform envelope, no global case-conversion interceptor); a follow-up could push those shapes into the views/repositories.Verification
208 files changed, +6125 / -3381. Typecheck clean, lint clean, full Vitest suite 1088 passed / 3 skipped / 0 failed.