@@ -27,16 +27,13 @@ Proposed. Revised after the review on #72047.
2727
28281 . ** ` dag.task(handler) ` returns a factory, and the task id is optional.** With no id the task takes
2929 the handler's function name (` dag.task(extract) ` → task ` "extract" ` ); ` dag.task(taskId, handler) `
30- sets it explicitly, which an anonymous handler must do. Calling the factory both places the task in
31- the Dag and supplies its arguments, in the order the handler declares them
32- (` load(transform(extract(), "us")) ` ). A handler that declares a single object of named arguments can
33- also be called with that object — the shape Python TaskFlow uses for
34- ` load(transformed=transform(...)) ` .
35- 2 . ** The call graph is the task graph.** ` tsc ` checks every wired key against the handler's own
36- parameter type, and a ` TaskRef ` exists only once its producing call has returned, so a cycle
37- through arguments is unrepresentable rather than rejected by a validator. A reference passed by
38- position is checked against the argument's own type, which is what tells the two call shapes apart
39- when a handler declares a single argument.
30+ sets it explicitly, which an anonymous handler must do. A handler takes one object of named
31+ arguments, and calling the factory both places the task in the Dag and names each of its inputs
32+ (` load({ total: transform({ rows: extract(), region: "us" }) }) ` ) — the shape Python TaskFlow uses
33+ for ` load(transformed=transform(...)) ` .
34+ 2 . ** The call graph is the task graph.** ` tsc ` checks every named input against the handler's own
35+ argument type, and a ` TaskRef ` exists only once its producing call has returned, so a cycle
36+ through arguments is unrepresentable rather than rejected by a validator.
40373 . ** Every task is called exactly once.** An uncalled task fails when the Dag is read, so none can be
4138 silently left out of the graph.
42394 . ** ` before ` and ` after ` draw order-only edges** — the TypeScript pair for ` >> ` and ` << ` , both
@@ -107,13 +104,12 @@ const extract = dag.task(async function extract(): Promise<number> {
107104// task id "extract"
108105```
109106
110- The id comes from the handler's * source* name, resolved when the bundle is packed and written into
111- the registration — not from ` handler.name ` at runtime, which minification renames (see
112- Implementation Notes). A handler with no source name — a bare anonymous arrow passed inline,
113- ` dag.task(async () => 42) ` — has nothing to resolve and is a compile error until given an explicit
114- id. This default is for native Dags, where both ends of every name are TypeScript; a mixed-language
115- handler names the Python-owned task explicitly and does not default from the handler's function
116- name ([ ADR-0001] ( 0001-mixed-lang-dag-interface.md ) , decision 3).
107+ The id is the handler's ` name ` , which ` airflow-ts-pack ` keeps through minification (see
108+ Implementation Notes). A handler with no name, such as a bare anonymous arrow passed inline,
109+ ` dag.task(async () => 42) ` , has nothing to take an id from and fails when the Dag is declared until
110+ given an explicit id. This default is for native Dags, where both ends of every name are
111+ TypeScript; a mixed-language handler names the Python-owned task explicitly and does not default
112+ from the handler's function name ([ ADR-0001] ( 0001-mixed-lang-dag-interface.md ) , decision 3).
117113
118114The ` TaskSpec ` also carries the task id, so it can be set alongside the other task options:
119115
@@ -149,20 +145,19 @@ convention.
149145 by design. Native declaration is what fills them, generated from the serialized-Dag JSON schema the
150146 way ` src/generated/supervisor.ts ` is. This ADR does not choose those fields; it fixes where an
151147 author writes them.
152- - ` TaskOptions ` carries the spec and the handler's positional argument names, which the packer fills in
153- from the parameter list so the Dag names each argument as its handler does. With wiring moved to the
154- factory call, ` inputs ` is no longer an option.
148+ - ` TaskOptions ` carries the task's spec and nothing else: the names on the wire are the keys of the
149+ call itself. With wiring moved to the factory call, ` inputs ` is no longer an option.
155150- ` TaskHandlerArgs ` is removed from the public API, ` DagRegistry ` becomes ` Bundle ` , and
156151 ` serveDags(registry) ` becomes ` bundle.serve() ` , which breaks
157152 0.1.0-beta1 authors; see [ ADR-0001] ( 0001-mixed-lang-dag-interface.md ) for the shipped call sites
158153 that change.
159154
160155## Alternatives
161156
162- - ** Named-only wiring ** , rejected in the review on # 73435 : naming every input reads well at twenty
163- tasks but forces an object around a single argument, and positional calls are what TypeScript
164- authors write. Both are offered , and the handler's own parameter list decides which one a task can
165- use .
157+ - ** Positional handlers ** , ` async (rows: number, region: string) => ... ` , offered first and then
158+ dropped: a positional parameter list has no names on the wire unless the SDK reads them out of the
159+ handler's source , and a single object of named arguments is what a TypeScript library takes
160+ anyway .
166161- ** Injected ` ctx ` /` client ` arguments** , mimicking the Python signature. Rejected, per the above and
167162 because feeling native to TypeScript matters more than matching Python's parameter list.
168163
@@ -181,17 +176,14 @@ convention.
181176- ** The spec argument already has its slot.** ` dag.task(taskId, handler, options) ` reads ` { spec = {} } `
182177 and runs ` validateEmptySpec ` on it (` ts-sdk/src/sdk/dag.ts ` ), so task fields land on a path that
183178 exists rather than a new one.
184- - ** A positional argument binds by order, and its name is a label.** The serialized Dag names each
185- argument, so the packer reads the names from the handler's parameter list; ` arg0 ` , ` arg1 ` and so on
186- stand in for a name it cannot see, without changing which value reaches which argument.
187179- ** A ` TaskRef ` is inert** — a handle for wiring, not a promise. Nothing in a Dag file executes a task
188180 body.
189- - ** A defaulted task id is resolved at pack time, not read at runtime .** esbuild renames function
190- identifiers , so ` handler.name ` in a packed bundle is the minified name, not the author's. The pack
191- step ( ` ts-sdk/src/cli/pack.ts ` ) therefore reads an omitted id from the handler's declared name in
192- source and writes it into the registration, rather than depending on ` handler.name ` or enabling
193- esbuild's ` keepNames ` across the whole bundle . A handler with no source name leaves nothing to
194- read, which is why an anonymous handler must state its id.
181+ - ** A defaulted task id is read off the handler itself .** The pack step ( ` ts-sdk/src/cli/pack.ts ` )
182+ minifies and passes esbuild's ` keepNames ` , so ` handler.name ` is the author's in a packed bundle as
183+ much as in one run from source. Rewriting the call at pack time was tried first and dropped: it
184+ needed a TypeScript parser in the packer to tell a real ` .task( ` from one inside a string or a
185+ comment, and it could not see a handler declared in another module . A handler with no name leaves
186+ nothing to read, which is why an anonymous handler must state its id.
195187- ** ` withArgNames ` and the name folding behind it** ([ ADR-0001] ( 0001-mixed-lang-dag-interface.md ) )
196188 exist for the mixed-language case and are never needed here: both ends of every name are
197189 TypeScript, so ` tsc ` checks the wiring end to end and there is no foreign name to reconcile.
0 commit comments