Skip to content

Codegen: typed client, resource typings, and WarpDrive schema generation from the resource registry #1

Description

@evoactivity

Motivation

Real-world evidence from an app using this package alongside tuyau's generated client registry: tuyau's types are wrong or vacuous for every JSON:API endpoint, through no fault of its own — it lacks the information.

  • Request bodies are actively wrong. Tuyau infers from vine validators, but the JSON:API pattern validates post-deserialization attributes (request.validateUsing(v, { data: input.attributes })). Tuyau therefore emits the flat internal shape with internal attribute names (e.g. { name, type }) while the endpoint requires the envelope with wire names ({ data: { type: 'libraries', attributes: { name, libraryType } } }).
  • Responses are vacuous. Controller return types are the wide Document union; per-resource attribute shapes only exist at runtime in the registry, so every endpoint types identically and clients narrow by hand.
  • Error shapes are wrong. Tuyau declares vine's { errors: SimpleError[] } for 422s, but renderJsonApiError emits JSON:API error documents.

This package is the source of truth for everything tuyau is missing: the resource registry (model ↔ resource ↔ type name), Lucid column metadata including serializeAs wire names, relationship definitions with cardinality, declared filters, route registration per resource, the deserializer (request contract), the document builder (response contract), and the error renderer (error contract). It can generate what tuyau structurally cannot.

Proposed artifacts (phased)

Phase 1: resource typings + document types

  • Per-resource interfaces generated from the registry: attributes (wire names, TS types reflected type-level from the model via ModelAttributes-style indirection so generated code cannot drift), relationships with cardinality and related type names.
  • Per-endpoint document types: SingleDocument<T> / CollectionDocument<T>, request documents for store/update (envelope-aware, wire names, to-one → linkage not FK), and the real error document type.
  • Include-aware narrowing: valid include paths derived from exposed relationships as template-literal types ('plugins' | 'plugins.library' | ...), with included unions computed from the requested paths.
  • Enabler: tighten JsonApiResource.pick to pick<K extends keyof Serialized>(names: K[]): Pick<Serialized, K> so ReturnType<resource['attributes']> is exact for customized resources; the default derivation is already deterministic from Lucid metadata. Conditional attributes (e.g. a create-only plaintext token) surface as optional fields; document annotating attributes() return types for these.

Phase 2: typed fetch client

  • Tuyau-style generated client keyed by registered resources/actions: api.libraries.index({ include, filter, sort, page }) with filters typed from declared FilterHandlers, include paths compile-checked, responses narrowed by the include argument, and errorResponse as JSON:API error documents.
  • Generation as an assembler hook (like tuyau's generateRegistry) or an ace command; needs the booted registry, not static analysis.

Phase 3: WarpDrive schema emission

  • Registry → WarpDrive ResourceSchema modules: { identity, fields: [{ kind: 'attribute' | 'relationship', name, type, options }] }, cardinality mapped to relationship kinds, wire names respected.
  • Paired Type-branded TS interfaces for SchemaRecord, so an Ember app registers generated schemas instead of hand-maintaining models that drift from the server.

Design notes

  • Heterogeneous/mixed collections (multiple resource types in one document) need union document types.
  • Non-JSON:API endpoints (auth token exchanges etc.) are explicitly out of scope; they are the one place tuyau-style validator inference is actually correct.
  • Route-group awareness (e.g. /api/v1 vs /api/v2 mounting) should follow the same route-name namespacing the LinkBuilder already uses.
  • Emission target: a configurable output directory the host app can re-export (the consuming monorepo currently exports .adonisjs/client artifacts to sibling apps; this would replace that role for JSON:API surfaces).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions