TypeScript Backend support

Viewed 43

Hi @Revant,

I love the great progress of Framework M, including the WASM runtime, just tried the playground, worked nicely.

However, I am more and more thinking about moving to a TypeScript only approach also on the backend. I am not saying that Python backend support should be removed, but that backend development should also be possible in TypeScript, and if someone wants it. It should also be possible to use the framework without any Python dependencies, if all the required modules have TypeScript implementations.

What do you think?

One thing that I already looked at is schema definitions. For doctypes defined in Python now Pydantic is used.

Pydantic schemas can be converted to JSON Schemas.

For TypeScript types my suggestion would be to use Standard JSON Schemas https://standardschema.dev/json-schema

This standard is implemented by every TypeScript validation library that matters. (Zod, ArkType ... etc.) Cross-language validation would happen using the JSON Schema standard, which is supported by pydantic as well (both input and output).

Of course the backend interfaces would have to be implemented in TypeScript as well, but that should not be an issue in the AI world.

As a target runtime, I would use Workerd from Cloudflare: https://github.com/cloudflare/workerd - it would provide instant CloudFlare Workers support (essentially free hosting for small businesses), and has limited but usually enough Node support, so most libraries work there.

Of course other runtimes (Node, Deno, Bun ... etc.) would work as well.

What do you think? Is this something you would be open to add to Framework M?

Kind regards,
Gergely

2 Answers

Hi Gergely,

Thanks for trying out the WASM runtime in the playground!

Here is my take on how we can achieve the flexibility you are looking for without falling into the trap of dual-track maintenance.

1. TypeScript-Only Backend

Why restrict this architectural shift strictly to TypeScript? Because we have strictly typed Python throughout Framework M, our primitives are language-agnostic data structures. In theory, any target runtime should be possible once we decouple the engine from the delivery layer.

2. Zero Python Dependency Option

This is the most challenging piece of the puzzle. It is practically impossible to manually write, maintain, and sync the full framework logic across multiple different runtimes in parallel. I will only take this route if we write the core logic once and automate the downstream translations.

Instead of writing runtime adapters, my plan is to keep the core engine entirely in Python. We will expose the internal APIs that define our primitives:

  • DocType / Meta
  • Controller / Services
  • Repositories
  • Permissions, etc.

Once these primitives are cleanly exposed, we can write CLI extensions that read, interpret, and compile them into a given runtime (like NestJS for TypeScript). Instead of forcing a Python-like GenericRepository onto a Node environment, the compiler will generate native, idiomatic boilerplate specific to that runtime.

3. Schema & Validation Strategy

Instead of baking a specific validation tool into the core framework, we will choose and emit these tools dynamically based on the requirements of the target code generation. If we are compiling to a NestJS/TS target, the generator will output the appropriate Zod or Standard Schema constructs automatically.

4. Implementation & Runtime (The Envisioned Workflow)

I disagree slightly on the feasibility of continuous AI-assisted porting. Because Python will always receive priority features, performance adapters, and updates, relying on AI to continuously translate the core framework introduces too much non-deterministic maintenance overhead in CI/CD pipelines.

Instead, my envisioned workflow utilizes structured abstract syntax tree (AST) generation to handle updates cleanly:

[ Python Core / Meta ] 
       │
       ▼ (CLI Generation / ts_morph)
[ Pure TypeScript / NestJS Output ] 
       │
       ▼ (Merge into Dev Target)
[ Developer Overrides & Custom Logic ]

Proposed Codegen Lifecycle:

  1. Source of Truth: Everything is defined, typed, and authored in Code-First Python (Permissions, JSONSchema, Contexts, Endpoints, Workers, Jobs).
  2. Compilation: We use tools like ts_morph (leveraging our existing Python wrappers) to parse the Python metadata and programmatically generate clean TypeScript/NestJS files with ~80% accuracy.
  3. Distribution: The CLI dumps out the generated code into the target repository. The developer can deploy this code directly.
  4. Service Continuity: Infrastructure connections remain unchanged. Because services like SpiceDB (permissions), NATS (event bus), Redis, or the primary database use the exact same schema regardless of runtime, hydrating and migrating them happens seamlessly via the primary workflow anyway.
  5. Conflict Resolution: The remaining ~20% of developer-tweaked logic is funneled into designated placeholder files (e.g., using a .base.ts split-file pattern) to prevent future schema regenerations from triggering nasty Git merge conflicts.

This approach gives developers a completely zero-Python production environment (perfect for running light on Cloudflare workerd), while keeping the framework single-sourced and maintainable for us.

Strategic Considerations & Trade-Offs

While this multi-runtime compiler approach is a fascinating architectural puzzle, it isn't an immediate framework priority. If anyone from the community wants to pick this up, we will happily and officially support it by formally declaring and locking down our Primitives API.

As we look at this long-term, there are a few foundational questions we have to answer:

  • What are the actual trade-offs of the runtime environment? No complex, multi-service application runs entirely on the edge as-is. Instead of chasing full-framework feature parity, shouldn't the goal be allowing devs to isolate and export only the specific parts fit for the edge? For example, exporting a lightweight worker, job, or a highly validated entry controller to a specialized runtime, while keeping the heavy application logic intact.
  • Why complicate feature parity? Ports and Adapters already give us a programmatic blueprint to interact with and decompose the monolith when needed.
  • Is there a deterministic bridge? If there is a "magic bullet" engine out there that can fully, deterministically translate our Python source-of-truth into an auto-generated, QA-tested runtime, I’d love to explore it. Otherwise, manual or AI-driven synchronization is too fragile.

Core Focus: ERPNext Feature Parity (Without the Technical Debt)

Right now, my engineering focus is strictly locked onto achieving high-quality ERPNext feature parity, while fundamentally solving the architectural problems that plague the traditional ecosystem:

  • True Macroservices Mode: Building genuinely modular apps (Frontend = Shell + Micro-frontends; Backend = Main App + Libs). If a module like CRM needs to duplicate a customer record to decouple from Core, it should happen seamlessly—allowing the app to run as a single-process monolith by default, or split cleanly into macroservices when scale demands it.
  • High-Write Tree Architectures: Moving beyond just NestedSet. I have already added AdjacencyList (leveraging Recursive CTEs) to handle high-write tree structures cleanly and efficiently.
  • Next-Gen Document Infrastructure: Implementing bulletproof GitOps-based print format templates, unified reporting engines, and robust read-model-based data architectures.
  • Flawless Form Entry: Elevating data-entry ergonomics with high-fidelity forms, strict link-field validation, auto-alignment, and elegant workflow engine integrations (Invoices, Approvals, etc.).
  • Global Accessibility: Expanding core localization with native RTL language re-arrangement layouts and exploring frictionless, headless localization contribution flows.
  • AI-Driven Studio Architecture: Revamping the Studio out of the manual "user-clicks-to-create-doctype" paradigm and moving toward an AI-assisted context engine. You feed it an initial form; it maps the full end-to-end scaffolding, summarizes the conversational context, and assists you deterministically across later application versions, while keeping manual controls as a zero-cost fallback.
  • Headless Localization / Studio Sync: Designing a decoupled Studio interface that can act as a lightweight translation portal for non-technical contributors. By utilizing GitLab/OAuth2 integrations, translators can sync JSON files and open Merge Requests directly without needing the full framework running locally.
  • High-Availability (HA) Scheduling: Architecting a resilient, distributed cron and background task scheduler that scales smoothly across multi-node deployment topologies without double-execution risks.
  • Permission-Driven UX & Entitlements: Dynamically tailoring the frontend navigation layouts, entry landing pages, and functional system entitlements based strictly on the user's granular security context and roles.
  • Tenant Onboarding & Management: Building smooth, automated multi-tenant provisioning, lifecycle management, and resource allocation primitives directly into the core orchestration tooling.
  • Enterprise-Grade Backup & Recovery: Implementing robust document backup and point-in-time recovery (PITR) workflows by leveraging Postgres backup best practices alongside NATS-driven event streams for data durability.

I'd love to know your thoughts on this compiler/generation approach versus a native runtime port, and how you see edge-delivery playing into modular architectures!

Revant.

Edit 1: I'd just use pyodide on workerd and run the app directly as it is showcased in playground. As long as app logic is using standard adapters and services they are override-able and whichever adapter breaks can be overridden via developer's app for the case. No need for transpiling then.

Hi @Revant,

thanks for the quick reply.

I can accept the decision to keep the Python source as the source of truth. In this case, I think we can do better than ts-morph for Transpiling the Python code to JavaScript:

https://www.transcrypt.org/

It is a Python to JS transpiler that produces performant JS code with sourcemaps, so even the transpiled JS code can be debugged as if it was Python.

The transpiled JS runs in the browser and on Node (need to check on CF). This means that if Transcrypt was integrated into Framework M, and some business logic (e.g. custom validation) would be a good idea to be executed on the client as well, if those parts are kept in separate python modules (e.g. in a folder named "shared" to mark it shared between the frontend and the backend), then Transcrypt could generate JS libraries of it to be used on the frontend automatically.

The project officially only has a Python 3.9 compatible release, but here is the patch for 3.14: https://github.com/TranscryptOrg/Transcrypt/issues/904

This means that Python to JS transpilation could work with near 100% automation, if FrameworkM would make the architectural decision to only allow Python constructs that are supported by Transcrypt (which is essentially a list of what one needs to support any Python AOT compilation system down the line):
https://www.transcrypt.org/docs/html/differences_cpython.html

I would love to see your thoughts on this.

Thank you,
Gergely

  • I'm okay with semgrep, ruff rules. e.g. use f-strings or format method instead of % (ruff lint default). Or any other that are great to keep code modern python.
  • If Python code is restricted just for restricted environment then I'll re-think and put that into transpiler layer to transpile/warn/error instead of restricting python in framework. Runtime extension packages can handle difficulty in their code.

If required we can come up with semantics for m transpile command or let the developer decide the subcommand structure.

Good idea?

Related