templ.js

Status

Accepted - March 2026

Context

templjs now needs consistent semantic behavior across authoring and runtime features:

The current design mixes feature-specific resolvers and context heuristics. That makes behavior inconsistent across features and difficult to evolve as new schema or host-language scenarios are introduced.

We need a model where multiple independently valid ASTs/providers can contribute context without direct dependencies between providers.

Decision

Adopt a Context Graph Platform with the following constraints:

  1. N-provider model: Support N providers where N >= 1.
  2. Independent validity: Each provider AST/model is independently valid and replaceable.
  3. No direct provider coupling: Providers communicate only by publishing facts/edges through the context graph API.
  4. Profile-based specialization: Text-location and editor semantics are implemented as profiles over a shared core model.
  5. TypeScript-first implementation: Implement v1 in TypeScript to maximize delivery speed and integration with current packages.
  6. Rust-ready contract from day 1: Public interfaces must remain stable and implementation-agnostic so the execution engine can move to Rust later.
  7. No dependency leakage in public API: Public contracts must not expose third-party package symbols or types.
  8. Transport-open architecture: Keep the contract transport-agnostic so future inter-process usage (for example protobuf, GraphQL, or IPC) can be added without breaking in-process consumers.
  9. Profile-first semantics: profile is first-class in fact and query contracts.
  10. Query contract first, query language later: v1 standardizes a versioned query contract; a standalone query language is explicitly deferred.

Core Model

The context graph is a typed fact graph:

Providers publish facts and relationships. Consumers query graph state and subscribe to graph deltas.

Profile model

Query model

This is an in-process architecture and intentionally excludes distributed-bus concerns (transport, service discovery, cross-process marshalling).

The model is intentionally transport-agnostic at the contract layer so optional inter-process adapters can be introduced later without changing core semantic contracts.

Public API Rules

Public APIs in @templjs/context-graph must follow these rules:

  1. Export only package-owned types/interfaces.
  2. Export only JSON-serializable or primitive-based payload shapes.
  3. Do not export third-party classes/generics/enums in signatures.
  4. Keep ids opaque (NodeId, ProviderId, SnapshotId).
  5. Version all externally observable payload shapes.
  6. Provide deterministic query ordering.
  7. Do not encode transport-specific assumptions in core contracts.
  8. Include profile scoping in fact and query contracts.
  9. Standardize a versioned query request/response shape.

Rust-Ready-from-Day-1 Checklist

  1. Stable wire contract: Versioned payload schema for facts, queries, and deltas.
  2. Opaque identifiers: No object-identity assumptions.
  3. Boundary purity: No callbacks captured inside stored graph state.
  4. Serialization-safe values: Restrict to JSON-compatible data at public boundary.
  5. Deterministic results: Stable sorting for repeated identical queries.
  6. Error contract: Structured error codes and payloads (avoid class-instance error dependence).
  7. Async boundary isolation: Provider lifecycle and graph queries are interface-driven and runtime-agnostic.
  8. No dependency type leakage: Public .d.ts remains free of external dependency symbols.

Consequences

Positive

Negative

Neutral

Implementation Plan (Phased)

  1. Scaffold @templjs/context-graph package with strict API boundaries.
  2. Implement minimal graph kernel + provider lifecycle + query interface.
  3. Add first-class profile support in fact and query contracts.
  4. Implement versioned query contract (request / response) in public API.
  5. Add API boundary enforcement and contract tests.
  6. Add Volar adapter and migrate hover/definition/completion reads.
  7. Add diagnostics and schema-provider integrations.
  8. Add extraction-provider integration points to support reverse-rendering workflows.
  9. Evaluate performance; only then consider Rust engine replacement behind same API.
  10. If needed later, add optional inter-process adapters (protobuf/GraphQL/IPC) behind unchanged contracts.

Deferred

References