- Source lives in
src/; entrypoints arebootstrap.tsandsrc/setup.ts. - Domain packages under
src/package/(e.g.,config,news,series,episodes). - Infrastructure modules:
cache/,client/,common/,database/,experiment/,guard/,logger/,middleware/,secret/,service/,telemetry/. - Each module declares a scoped export via its
src/**/deno.json(e.g.,@scope/service). Use scoped imports; do not cross package boundaries with relative imports (enforced byanitrend/only-scoped-imports).
deno task dev— run the Danet app locally (honorsPORT). Example:deno task dev -- --swaggerto emit Swagger.deno task dev:watch— run with HMR.deno task test— run tests; writes coverage tocoverage/. Target a subset:deno task test -- --filter "series|episodes".deno task fmt/deno task fmt:check— format / verify formatting.deno task lint— lint with custom rules.deno task check— type-check entrypoint.deno task build— compile tobuild/edgefor container images.
- Formatting via Deno: 2-space indent, 80-char line width, single quotes.
- Prefer module-scoped imports like
import { TheXemService } from '@scope/service/thexem';. - File naming patterns:
*.module.ts,*.service.ts,*.controller.ts,*.schema.ts,*.types.ts; tests use*.test.tsor*.spec.ts.
- Test files match
**/*.test.ts,**/*.spec.ts, and**/testinghelpers. - Keep tests deterministic: use in-memory adapters and mock fetch utilities under
src/**/testing/. - Aim for meaningful coverage; CI mirrors
deno fmt --check,deno lint,deno task check,deno task test, anddeno task build, so the local equivalents must pass before committing.
- Follow Conventional Commits:
feat(scope): subject,fix(experiment): adjust typings. - Branch from
devusingfeature/123-brief-titleorfix/456-bug-title; PRs should targetdevand link issues. - Include tests, update docs when behavior changes, and before committing run the same quality gates enforced in
.github/workflows/ci.yml:bash .github/scripts/config-env.sh,deno fmt --check,deno lint,deno task check,deno task test, anddeno task build.
- Configure via
.env(copy from.env.example); never commit secrets. - For observability, set OTEL env vars if exporting traces/metrics/logs.
The generated swagger-spec.json is the source contract consumed by edge-graphql (GraphQL Mesh → anitrend-v2 Android). Every API change must keep the contract valid.
- Import
zfrom@scope/common/openapiin all contract and swagger files. - Never call
extendZodWithOpenApi(z)outside ofsrc/common/openapi/zod.ts. - Import
zfromzoddirectly only in runtime/domain schema files (*.schema.ts) that need no OpenAPI metadata.
src/package/<domain>/
<domain>.schema.ts // runtime validation (zod, preprocessors, coercion)
<domain>.contract.ts // public OpenAPI contract (z from @scope/common/openapi, explicit .openapi(), .nullable().optional())
<domain>.swagger.ts // re-exports from contract, query swagger wrappers
- Every public nested model must have an explicit
.openapi({ title: 'PascalCase', description: '...' })call. - Every public nested request object and enum must carry a globally unique, semantic PascalCase
.openapi({ title })(e.g.PushProfileDevice,PushRegistrationTopic). Without it the schema stays inline and GraphQL Mesh derives unstable, path-based names such asmutationInput_updateProfile_input_*. - Nested titles must be unique across the whole document (not just the domain) and read as
DomainName + Role(e.g.PushProfileIdentityProvider), never generic names likePlatformorTopics. - Use
.nullable().optional()instead of.nullish()for OpenAPI 3.0 compatibility. - Replace
z.custom<T>()with explicitz.enum([...])orz.string()in contracts. - Contract files own all public metadata (titles, descriptions, examples).
*.swagger.tsfiles are thin re-exports/wrappers and must not redefine or extend public schemas. - Enum values are changed only at the source schema (
*.schema.ts/*.contract.ts). Never rewrite enum values in the normalizer, extractor, guard, or generated artifacts (swagger-spec.jsonis generated, never hand-edited).
- Every
@Query()schema must have.openapi()metadata. Otherwise the generator producesundefinedcomponent names. - Export query swagger wrappers from
*.swagger.ts:// deno-lint-ignore no-explicit-any export const <Domain>QuerySwagger = (<Domain>QuerySchema as any).openapi({ title: '<Domain>Query', description: '...', });
- Controllers must import and use the swagger-decorated query schema in
@Query().
- Create/update
*.contract.tswith named.openapi()schemas for all response types. - Create/update
*.swagger.tswith re-exports from the contract + query swagger wrapper. - Add the new schema title to
EXPECTED_SCHEMA_NAMESinsrc/common/openapi/names.ts. - Add the new operation ID to
EXPECTED_OPERATION_IDSin the same file. - Use
@ReturnedSchema(SwaggerExport)and@Query(QuerySwaggerExport)in the controller.
SwaggerModule.createDocument() → normalizeOpenApiDocument() → extractInlineSchemas() → assertOpenApiContract() → write spec
The normalizer converts JSON Schema type arrays to OpenAPI 3.0 nullable.
The extractor promotes any inline schema carrying a title (enums, constrained scalars, array items, query parameters, and nested objects the danet generator did not promote) to a named components.schemas entry and replaces inline occurrences with $ref. First occurrence by title wins (deduplication). It is not a generic deep request-body walker; it relies on every public nested schema being explicitly titled in *.contract.ts.
The guard rejects: undefined schema names, inline 200 response objects without $ref, remaining type arrays, missing expected schemas/operation IDs, missing components.schemas or paths.
contract-checkjob runsswagger:generate+swagger:validatewith MongoDB/Redis services.- Swagger generation is fail-fast — no
continue-on-erroranywhere. - A failing contract check blocks merge.