Skip to content

Commit

Permalink
feat: typescript SDK resolve value by reference.
Browse files Browse the repository at this point in the history
Fixes dagger#8676.

Refactor typedef resolution to be resolve by reference.
The introspection starts from the entrypoint (which is the class named
the same as the module) and recursively resolve every references.

Refactor the TS compiler API to be abstract with a AST that handle
lookups, typedef resolutions and default value resolution.

Use `imports` to resolve default value that refers to an exported variable.

Add custom introspection error.

Improve error handling to point to the line that couldn't be introspected.

Add supports for native `enum` keyword and `type` native keyword.

Simplify overall code abstractions too minimal.
Avoid long abstractions functions.

Signed-off-by: Tom Chauveau <[email protected]>
  • Loading branch information
Tom Chauveau authored and TomChv committed Nov 14, 2024
1 parent 6fdf164 commit c59e8a8
Show file tree
Hide file tree
Showing 58 changed files with 2,419 additions and 1,628 deletions.
4 changes: 2 additions & 2 deletions core/integration/testdata/modules/typescript/minimal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ export class Minimal {
}

@func()
echoOptional(msg = "default"): string {
echoOptional(msg: string = "default"): string {
return this.echo(msg);
}

@func()
echoOptionalSlice(msg = ["foobar"]): string {
echoOptionalSlice(msg: string[] = ["foobar"]): string {
return this.echo(msg.join("+"));
}

Expand Down
11 changes: 11 additions & 0 deletions sdk/typescript/common/errors/IntrospectionError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { DaggerSDKError, DaggerSDKErrorOptions } from "./DaggerSDKError.js"
import { ERROR_CODES, ERROR_NAMES } from "./errors-codes.js"

export class IntrospectionError extends DaggerSDKError {
name = ERROR_NAMES.IntrospectionError
code = ERROR_CODES.IntrospectionError

constructor(message: string, options?: DaggerSDKErrorOptions) {
super(message, options)
}
}
5 changes: 5 additions & 0 deletions sdk/typescript/common/errors/errors-codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ export const ERROR_CODES = {
* (@link ExecError}
*/
ExecError: "D109",

/**
* {@link IntrospectionError}
*/
IntrospectionError: "D110",
} as const

type ErrorCodesType = typeof ERROR_CODES
Expand Down
2 changes: 1 addition & 1 deletion sdk/typescript/entrypoint/entrypoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export async function entrypoint() {
const moduleName = await dag.currentModule().name()
const parentName = await fnCall.parentName()

const scanResult = scan(files, moduleName)
const scanResult = await scan(files, moduleName)

// Pre allocate the result, we got one in both case.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
12 changes: 6 additions & 6 deletions sdk/typescript/entrypoint/invoke.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { FunctionNotFound } from "../common/errors/FunctionNotFound.js"
import { Executor } from "../introspector/executor/executor.js"
import { registry } from "../introspector/registry/registry.js"
import { Constructor } from "../introspector/scanner/abtractions/constructor.js"
import { DaggerEnum } from "../introspector/scanner/abtractions/enum.js"
import { Method } from "../introspector/scanner/abtractions/method.js"
import { DaggerModule } from "../introspector/scanner/abtractions/module.js"
import { DaggerObject } from "../introspector/scanner/abtractions/object.js"
import { DaggerConstructor as Constructor } from "../introspector/scanner/dagger_module/constructor.js"
import { DaggerEnumBase } from "../introspector/scanner/dagger_module/enumBase.js"
import { DaggerFunction as Method } from "../introspector/scanner/dagger_module/function.js"
import { DaggerModule } from "../introspector/scanner/dagger_module/module.js"
import { DaggerObjectBase } from "../introspector/scanner/dagger_module/objectBase.js"
import { InvokeCtx } from "./context.js"
import {
loadResult,
Expand Down Expand Up @@ -74,7 +74,7 @@ export async function invoke(
}

if (result) {
let returnType: DaggerObject | DaggerEnum
let returnType: DaggerObjectBase | DaggerEnumBase

// Handle alias serialization by getting the return type to load
// if the function called isn't a constructor.
Expand Down
39 changes: 28 additions & 11 deletions sdk/typescript/entrypoint/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import Module from "node:module"
import { dag, TypeDefKind } from "../api/client.gen.js"
import { Executor } from "../introspector/executor/executor.js"
import { Args } from "../introspector/executor/executor.js"
import { Constructor } from "../introspector/scanner/abtractions/constructor.js"
import { DaggerEnum } from "../introspector/scanner/abtractions/enum.js"
import { Method } from "../introspector/scanner/abtractions/method.js"
import { DaggerModule } from "../introspector/scanner/abtractions/module.js"
import { DaggerObject } from "../introspector/scanner/abtractions/object.js"
import { TypeDef } from "../introspector/scanner/typeDefs.js"
import { DaggerConstructor as Constructor } from "../introspector/scanner/dagger_module/constructor.js"
import { DaggerEnum } from "../introspector/scanner/dagger_module/enum.js"
import { DaggerEnumBase } from "../introspector/scanner/dagger_module/enumBase.js"
import { DaggerFunction as Method } from "../introspector/scanner/dagger_module/function.js"
import { DaggerModule } from "../introspector/scanner/dagger_module/module.js"
import { DaggerObject } from "../introspector/scanner/dagger_module/object.js"
import { DaggerObjectBase } from "../introspector/scanner/dagger_module/objectBase.js"
import { TypeDef } from "../introspector/scanner/typedef.js"
import { InvokeCtx } from "./context.js"

/**
Expand All @@ -32,7 +34,7 @@ export function loadInvokedObject(
module: DaggerModule,
parentName: string,
): DaggerObject {
return module.objects[parentName]
return module.objects[parentName] as DaggerObject
}

export function loadInvokedMethod(
Expand Down Expand Up @@ -60,12 +62,16 @@ export async function loadArgs(
const args: Args = {}

// Load arguments
for (const argName of method.getArgOrder()) {
for (const argName of method.getArgsOrder()) {
const argument = method.arguments[argName]
if (!argument) {
throw new Error(`could not find argument ${argName}`)
}

if (!argument.type) {
throw new Error(`could not find type for argument ${argName}`)
}

const loadedArg = await loadValue(
executor,
ctx.fnArgs[argName],
Expand Down Expand Up @@ -118,6 +124,10 @@ export async function loadParentState(
throw new Error(`could not find parent property ${key}`)
}

if (!property.type) {
throw new Error(`could not find type for parent property ${key}`)
}

parentState[property.name] = await loadValue(executor, value, property.type)
}

Expand Down Expand Up @@ -192,8 +202,11 @@ export function loadObjectReturnType(
module: DaggerModule,
object: DaggerObject,
method: Method,
): DaggerObject | DaggerEnum {
): DaggerObjectBase | DaggerEnumBase {
const retType = method.returnType
if (!retType) {
throw new Error(`could not find return type for ${method.name}`)
}

switch (retType.kind) {
case TypeDefKind.ListKind: {
Expand All @@ -218,7 +231,7 @@ export function loadObjectReturnType(
export async function loadResult(
result: any,
module: DaggerModule,
object: DaggerObject | DaggerEnum,
object: DaggerObjectBase | DaggerEnumBase,
): Promise<any> {
// Handle IDable objects
if (result && typeof result?.id === "function") {
Expand Down Expand Up @@ -246,7 +259,11 @@ export async function loadResult(
throw new Error(`could not find result property ${key}`)
}

let referencedObject: DaggerObject | undefined = undefined
if (!property.type) {
throw new Error(`could not find type for result property ${key}`)
}

let referencedObject: DaggerObjectBase | undefined = undefined

// Handle nested objects
if (property.type.kind === TypeDefKind.ObjectKind) {
Expand Down
18 changes: 9 additions & 9 deletions sdk/typescript/entrypoint/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ import {
TypeDef,
TypeDefKind,
} from "../api/client.gen.js"
import { Arguments } from "../introspector/scanner/abtractions/argument.js"
import { Constructor } from "../introspector/scanner/abtractions/constructor.js"
import { Method } from "../introspector/scanner/abtractions/method.js"
import { DaggerModule } from "../introspector/scanner/abtractions/module.js"
import { DaggerArguments as Arguments } from "../introspector/scanner/dagger_module/argument.js"
import { DaggerConstructor as Constructor } from "../introspector/scanner/dagger_module/constructor.js"
import { DaggerFunction as Method } from "../introspector/scanner/dagger_module/function.js"
import { DaggerModule } from "../introspector/scanner/dagger_module/module.js"
import {
EnumTypeDef,
ListTypeDef,
ObjectTypeDef,
ScalarTypeDef,
TypeDef as ScannerTypeDef,
} from "../introspector/scanner/typeDefs.js"
} from "../introspector/scanner/typedef.js"

/**
* Register the module files and returns its ID
Expand Down Expand Up @@ -50,7 +50,7 @@ export async function register(
if (field.isExposed) {
typeDef = typeDef.withField(
field.alias ?? field.name,
addTypeDef(field.type),
addTypeDef(field.type!),
{
description: field.description,
},
Expand Down Expand Up @@ -99,7 +99,7 @@ function addConstructor(constructor: Constructor, owner: TypeDef): Function_ {
*/
function addFunction(fct: Method): Function_ {
return dag
.function_(fct.alias ?? fct.name, addTypeDef(fct.returnType))
.function_(fct.alias ?? fct.name, addTypeDef(fct.returnType!))
.withDescription(fct.description)
.with(addArg(fct.arguments))
}
Expand All @@ -114,7 +114,7 @@ function addArg(args: Arguments): (fct: Function_) => Function_ {
description: arg.description,
}

let typeDef = addTypeDef(arg.type)
let typeDef = addTypeDef(arg.type!)
if (arg.isOptional) {
typeDef = typeDef.withOptional(true)
}
Expand All @@ -132,7 +132,7 @@ function addArg(args: Arguments): (fct: Function_) => Function_ {
// to workaround the fact that the API isn't aware of the default value and will
// expect it to be set as required input.
if (arg.defaultValue) {
if (isPrimitiveType(arg.type)) {
if (isPrimitiveType(arg.type!)) {
opts.defaultValue = arg.defaultValue as string & { __JSON: never }
} else {
typeDef = typeDef.withOptional(true)
Expand Down
Loading

0 comments on commit c59e8a8

Please sign in to comment.