Skip to content

feat(ogar-render-askama): T2 — TsInterface askama template + emitter#80

Closed
AdaWorldAPI wants to merge 1 commit into
mainfrom
claude/ogar-render-askama-t2-ts-interface
Closed

feat(ogar-render-askama): T2 — TsInterface askama template + emitter#80
AdaWorldAPI wants to merge 1 commit into
mainfrom
claude/ogar-render-askama-t2-ts-interface

Conversation

@AdaWorldAPI

Copy link
Copy Markdown
Owner

What

T2 — TsInterface from the Northstar plan §3. Second real emitter in the artifact-kind kit after T1 (RustStruct, in PR #78). Same &Class input, same dispatch path, different template + binding-struct + type mapping. Validates the kit's claim that adding a new target = one template + one emitter.

What it emits

Sample output rendering project_work_item():

//
// WorkPackage — canonical class generated from `ogar_vocab::project_work_item()`.
// DO NOT EDIT BY HAND. Re-render via `ogar-render-askama`.
//

/** Canonical OGAR codebook id for [`WorkPackage`]. */
export const CLASS_ID = 0x0102 as const;

/** Canonical concept name as in the OGAR codebook. */
export const CANONICAL_CONCEPT = "project_work_item" as const;

export interface WorkPackage {
    /** `subject` (curator type: `string`) */
    subject: string;
    // ... attributes ...
    // Family edges (canonical relations):
    /** belongs_to `project` → `Project` */
    project: number | null;
    /** belongs_to `status` → `Status` */
    status: number | null;
    /** has_many `journals` → ... */
    journals: ReadonlyArray<number>;
    // ...
}

Same shape as the Rust output, different syntax. as const so consumers get the literal type, not a widened number.

Mapping

Rails type TS type
string / text string
integer / big_integer / bigint number
boolean boolean
date / datetime / timestamp string (ISO-8601; consumers parse to Date)
json / jsonb unknown
Association kind TS shape
belongs_to / has_one number | null
has_many / has_and_belongs_to_many ReadonlyArray<number>

Keyword escape (TS-side companion of Rust's r#type)

escape_ts_property() quotes JS reserved + TS contextual keywords + non-bare-identifier names. The same hazard codex caught on #78 (project_actor() declares an attribute named type) gets resolved by quoting:

"type": string;     // not  →  type: string;

Also handles Odoo-style dotted identifiers ("account.move.line": string;).

Conservative quote list: strict JS reserved (new, delete, function, …) + ES strict-mode reserved (let, static, interface, public, …) + TS contextual (type, async, await, as, from, of, is, infer, keyof, namespace, satisfies, readonly). Zero downside; removes a class of "compiles here, breaks at consumer" footguns.

Tests (12/12, +5 for T2)

  • ts_interface_emits_class_id_and_canonical_concept — proof of shape on project_work_item (CLASS_ID = 0x0102, etc.).
  • ts_interface_maps_rails_types_to_ts — integer/string/text mapping verified against project_role.
  • ts_interface_emits_family_edges_as_arrays_and_nullable_ids — both edge shapes pinned (belongs_to vs has_many).
  • ts_interface_handles_keyword_property_names_safelyproject_actor.type must emit "type":, never bare type:.
  • ts_interface_quotes_dotted_property_names — Odoo account.move.line must be quoted.

Workspace check + workspace test green.

Per Northstar §1.6

The template is mass-mail simple — bag of variables the template names, supplied by TsInterfaceCtx. No template hardcodes a class name. Adding a future TS-specific variant (e.g. zod schemas, generated from the same source) = one new ArtifactKind variant + one new template; TsInterfaceCtx's field set is the reference shape.

Remaining in this kit

3 stubs left for T3 (SurrealqlTable), T4 (OpenapiSchema), T5 (NodeGuidRoutingArm). The dispatcher mod doc updated to reflect 2/5 real emitters.

Second real emitter in the artifact-kind kit (T2 per Northstar plan §3).
Mirrors the shape of T1 (RustStruct, PR #78): same canonical input
(`&ogar_vocab::Class`), same dispatch path through `for_kind`, different
template + binding-struct + type mapping.

Surface (additive):
- `templates/dispatch/ts_interface.askama` — emits an `export interface X
  { ... }` + `export const CLASS_ID = 0xDDCC as const;` + canonical concept
  const.
- `artifact_kinds::ts_interface::TsInterfaceEmitter` — typed binding via
  `#[derive(askama::Template)]`. Maps Rails-side type names to TS types
  (string/number/boolean/unknown), family edges to `number | null`
  (belongs_to/has_one) or `ReadonlyArray<number>` (has_many/habtm).
- `escape_ts_property` — quotes JS reserved + TS contextual keywords +
  non-bare-identifier names (Odoo `account.move.line` etc.). TS-side
  companion of Rust's `r#type` escape from codex P1 on #78.
- Dispatcher wires the real emitter in place of the stub.

Tests (+5):
- ts_interface_emits_class_id_and_canonical_concept — proof of shape on
  project_work_item (CLASS_ID = 0x0102 as const, etc.).
- ts_interface_maps_rails_types_to_ts — integer→number, string→string,
  text→string, against project_role.
- ts_interface_emits_family_edges_as_arrays_and_nullable_ids —
  belongs_to/has_one as `number | null`, has_many as
  `ReadonlyArray<number>`, asserted on billable_work_entry +
  project_actor.
- ts_interface_handles_keyword_property_names_safely — project_actor's
  `type` attribute emits `"type": string;` (quoted JS-contextual), never
  unquoted `type: string;`.
- ts_interface_quotes_dotted_property_names — fabricated `account.move.line`
  attribute must be quoted in the emitter output.

Stub coverage updated: 3 kinds remain stubbed (SurrealqlTable, OpenapiSchema,
NodeGuidRoutingArm) for T3-T5.

12/12 unit tests + 1 ignored doctest; workspace check + workspace test
green. Per Northstar §1.6: template is mass-mail simple (bag of variables
named by the template, supplied by `TsInterfaceCtx`). No template hardcodes
a class name.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3f549f45f3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

/** Canonical concept name as in the OGAR codebook. */
export const CANONICAL_CONCEPT = "{{ canonical_concept }}" as const;

export interface {{ name }} {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Sanitize interface names before emitting

When rendering a supported Class whose name is not a TypeScript identifier, this splices the raw source name into the declaration and produces invalid TS such as export interface account.move { (the Class::name docs explicitly preserve dotted ORM names in crates/ogar-vocab/src/lib.rs:89-91, and aliases like account.analytic.line are part of the codebook flow). Since this emitter accepts arbitrary ogar_vocab::Class values, derive or sanitize a valid TS interface identifier instead of using the source name directly.

Useful? React with 👍 / 👎.

@AdaWorldAPI

Copy link
Copy Markdown
Owner Author

Closing — misread the architecture. Askama is the render output (server-side HTML, per WoA-rs's templates/*.html pattern), not a codegen-to-TypeScript path. Generating .ts source files is the exact wrong abstraction here: the consumer reads askama-rendered output directly, no TS layer.

The Northstar plan §3 will be updated to drop TS-side targets (TsInterface, OpenapiSchema as a separate kind) and replace them with askama HTML render templates in the WoA list_view / detail / form shape. Follow-up PR coming.

Anti-pattern added to plan §4: "emit TS source from askama" — wrong layer; askama is the output.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants