Studio TypeScript classes and services
Canonical Octonode repository documentation.
Studio TypeScript classes and services
The Studio presents project source as four concepts that do not require users to understand the TypeScript AST:
| Studio concept | TypeScript authority | Runtime lifetime |
|---|---|---|
| Types | interface, type, and enum declarations | Compile time only; compatible shapes become input forms. |
| Fixed values | const declarations | Recreated with the source node; JSON-safe values can bind graph inputs. |
| Process memory | let, var, static fields, and ordinary instance fields | Memory only; it resets with the process or service lifetime. |
| Durable data | Data tables | Persistent storage; class fields never imply durability. |
Variables remain configuration owned by .octonode. The class builder maps constructor names
to environment keys (apiUrl becomes API_URL) and generated services read those values from
process.env; it never copies configured or secret values into source.
Class blueprint
The source index is the contract between the TypeScript compiler and Studio. A class symbol
includes its full declaration, decorators, type parameters, extends, implements, explicit
instances, and ordered members. Members retain constructors, parameter properties, fields,
methods, getters/setters, auto-accessors, index signatures, static blocks, visibility,
static, readonly, declare, definite assignment, abstract, override, async, generator,
optional, decorator, initializer, parameter, and return-type metadata.
Studio groups that data into Construction, State, and Capabilities. Methods backed by an
explicit top-level instance or exposed through defineService link directly to their workflow.
Plain and abstract classes stay visible without being made executable accidentally.
Valid nested class forms are first-class source-index entries. A class expression stored in a
field appears as Outer.Inner; a declaration or expression inside a method appears as
Outer.createInner.Inner. Studio keeps those classes inside the containing blueprint, including
their decorators, heritage, state, methods, services, and workflow links. An explicitly
constructed addressable class such as new Outer.Inner() can expose its methods as workflows.
Method-local classes run as part of the containing method because no stable instance exists at
module scope. TypeScript's invalid class Outer { class Inner {} } form remains a syntax error.
The class builder can create a plain class, a workflow class with an explicit instance, or a service with constructor dependencies, lifecycle, state fields, and exposed methods. Advanced method bodies remain editable as TypeScript so the visual form does not become a second programming language.
Decorators
TypeScript 5 standard decorators
can target classes and class members. Studio preserves their
source text, arguments, and ordering and displays them on the corresponding blueprint item.
Parameter decorators belong to the older
experimentalDecorators model;
standard decorators
cannot decorate parameters and are incompatible with emitDecoratorMetadata. Studio still
indexes and preserves legacy parameter decorators, labels them as legacy, and emits a
SOURCE_LEGACY_PARAMETER_DECORATOR warning so compiler configuration is explicit.
Decorators are runtime code. A decorator declared locally or imported from another project module is included in the selected source-function dependency closure and stays an internal helper instead of becoming a workflow. Decorated classes bypass expression-level native lowering so construction and decorator effects cannot be erased. Cyclic runtime imports are rejected with their dependency chain before materialization.
Services, injection, and inheritance
defineService is the explicit runtime boundary:
create(context)constructs the class; generated services inject attached project Variables through their environment keys, while advanced services may use the invocation context;exposeis the allow-list of callable class methods;lifecycleisinvocation,workflow-run, orworkermemory.
The scheduler routes every exposed method through one generated service host, passes one stable
run ID, and closes workflow-scoped processes at the end of the workflow. Direct createNodeWorker
callers can keep worker memory for the lifetime of their worker. invocation creates a fresh
instance for each call; workflow-run and worker reuse the routed host at their documented scope.
Inheritance, constructor arguments, parameter properties, and this state execute within the
same source-function process for explicit local instances. The JSON workflow boundary still
rejects class instances: a method must return JSON-safe data, not the instance itself.
worker lifecycle is a cache, not persistence. Durable state belongs in a data table because a
local process, container, or Cloudflare worker can restart at any time.
Two-way synchronization
TypeScript source -> compiler AST -> project source index -> Studio blueprint
Studio action -> expected revision -> declaration edit -> parse validation -> atomic write
-> refreshed source index -> generated workflow adaptersSource offsets and symbol IDs select one declaration; the file revision prevents an older UI from overwriting newer code. Create, replace, export, move, and delete validate the resulting file before committing it. Compile and scan remain code-to-config operations and never invent source edits.
Two-way synchronization is declaration-granular. Native expressions and Promise topology can
round-trip as graph structure. Structured control statements are indexed as nested regions with
stable identities and can be replaced through the revision-safe source action without introducing
DAG cycles. Mutation and other advanced statements remain inside one preserved
code.source-function node rather than being rewritten lossily. Nested classes move with and are
edited through their containing declaration, which prevents a local class from being accidentally
rewritten as an invalid top-level export.