Giter Site home page Giter Site logo

cuetsy's Introduction

Cuetsy Logo

Installation · Usage

cuetsy

Converting CUE objects to their TypeScript equivalent (highly experimental!)

  • CUE makes defining and validating canonical data specification easy
  • TypeScript is dominant in the frontend, but cannot natively benefit from this
  • CUE types often have direct TypeScript equivalents, so cuetsy can bridge this gap

Example

CUETypeScript
DiceFaces: 1 | 2 | 3 | 4 | 5 | 6 @cuetsy(kind="type")

Animal: {
    Name: string
    Sound: string
} @cuetsy(kind="interface")

LeggedAnimal: Animal & {
    Legs: int
} @cuetsy(kind="interface")

Pets: "Cat" | "Dog" | "Horse" @cuetsy(kind="enum")
export type DiceFaces = 1 | 2 | 3 | 4 | 5 | 6;
export interface Animal {
  Name: string;
  Sound: string;
}
export interface LeggedAnimal extends Animal {
  Legs: number;
}
export enum Pets {
  Cat = "Cat",
  Dog = "Dog",
  Horse = "Horse",
}

Status

Cuetsy is experimental. The following are supported:

Installation

Cuetsy can be installed using Go 1.16+

$ go install github.com/grafana/cuetsy/cmd/cuetsy

Usage

cuetsy must be invoked on files as follows:

$ cuetsy [file.cue]

This will create a logically equivalent [file].ts

Alternatively, cuetsy can be used as a library for more customized code generation.

Union Types

CUE TypeScript @cuetsy(kind)
Disjunction Union Type type

Union types are expressed in CUE and TypeScript nearly the same way, namely a series of disjunctions (a | b | c):

CUETypeScript
MyUnion: 1 | 2 | 3 | 4 | 5 | 6 @cuetsy(kind="type")
export type MyUnion = 1 | 2 | 3 | 4 | 5 | 6;

Interfaces

CUE TypeScript @cuetsy(kind)
Struct Interface interface

TypeScript interfaces are expressed as regular structs in CUE.

Caveats:

CUETypeScript
MyInterface: {
    Num: number
    Text: string
    List: [...number]
    Truth: bool
} @cuetsy(kind="interface")
export interface MyInterface {
  List: number[];
  Num: number;
  Text: string;
  Truth: boolean;
}

Inheritance

Interfaces can optionally extend another interface. If a type marked for export as a kind="interface" is unified (whether by & or embedding) with another type marked for export as an interface, it will produce extend in output:

CUETypeScript
AInterface: {
    AField: string
} @cuetsy(kind="interface")

ByUnifying: AInterface & {
    BField: int
} @cuetsy(kind="interface")

ByEmbedding: {
    AInterface
    CField: bool
} @cuetsy(kind="interface")
export interface AInterface {
  AField: string;
}

export interface ByUnifying extends AInterface {
  BField: number;
}

export interface ByEmbedding extends AInterface {
  CField: boolean;
}

Enums

CUE TypeScript @cuetsy(kind)
Disjunction String enums, Numeric enums enum

TypeScript's enums are union types, and are a mostly-exact mapping of what can be expressed with CUE's disjunctions. Disjunctions may contain only string or numeric values.

For string enums, the member names (keys) of the TypeScript enum are automatically inferred as the titled camel-case variant of their string value, but may be explicitly specified using the memberNames attribute. For a numeric enum, memberNames must be specified.

CUE TypeScript
AutoCamel: "foo" | "bar" @cuetsy(kind="enum")
ManualCamel: "foo" | "bar" @cuetsy(kind="enum",memberNames="Foo|Bar")
Arbitrary: "foo" | "bar" @cuetsy(kind="enum",memberNames="Zip|Zap")
Numeric: 0 | 1 | 2 @cuetsy(kind="enum",memberNames="Zero|One|Two")
export enum AutoCamel {
  Bar = 'bar',
  Foo = 'foo',
}

export enum ManualCamel {
  Bar = 'bar',
  Foo = 'foo',
}

export enum Arbitrary {
  Zap = 'bar',
  Zip = 'foo',
}

export enum Numeric {
  One = 1,
  Two = 2,
  Zero = 0,
}

Defaults

CUE TypeScript
Defaults const

Cuetsy can optionally generate a const for each type that holds default values. For that, mark CUE Default Values to your type definitions:

CUETypeScript
MyUnion: 1 | 2 | *3 @cuetsy(kind="type")

MyEnum: "foo" | *"bar" @cuetsy(kind="enum")

MyInterface: {
    num: int | *6
    txt: string | *"CUE"
    enm: MyDisjEnum
} @cuetsy(kind="interface")
export type MyUnion = 1 | 2 | 3;

export const defaultMyUnion: MyUnion = 3;

export enum MyEnum {
  Bar = 'bar',
  Foo = 'foo',
}

export const defaultMyEnum: MyEnum = MyEnum.Bar;

export interface MyInterface {
  enm: MyDisjEnum;
  num: number;
  txt: string;
}

export const defaultMyInterface: Partial<MyInterface> = {
  enm: MyDisjEnum.Bar,
  num: 6,
  txt: 'CUE',
};

cuetsy's People

Contributors

julienduchesne avatar marimoiro avatar sdboyer avatar sh0rez avatar spinillos avatar ying-jeanne avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cuetsy's Issues

Adapt default generation approach to deal with optional structs, lists

Problem

Our current approach to generating Typescript defaults is to create a typed constant, and leave it to the caller to figure out how to conditionally apply that constant to the type in order to replace absent values with the CUE-specified default. This approach has two, not-unrelated problems:

  1. Leaving the mechanism for default application to the user leaves room for user error
  2. It is impossible (barring some TS type system magic i'm unable to imagine) to express defaults in a const for values within optional nested structures

The latter case could be considered a bug, as it violates cuetsy's goal of having the same semantics in Typescript as in CUE. Specifically, this is the problem:

Outer: {
  top: string | *"foo"
  inner?: {
    req: string
    value: string | *"bar"
  }
} @cuetsy(kind="interface")

We have two basic choices - either we generate a default const that contains inner, or we don't:

export interface Outer {
  top: string
  inner?: {
    value: string
  }
}

// Option 1 - exclude the inner structure
export const outerDefault: Outer = {
  top: "foo"
}

// Option 2 - include the inner structure
export const outerDefault: Outer = {
  top: "foo"
  inner: {
    value: "bar"
  }
}

Both of these options are critically flawed. Option 1 simply omits default information entirely, flagrantly violating any notion of semantic equivalence. Option 2 unconditionally includes the nested inner value, which effectively conflates the absence of inner with it being present for any object to which this default is applied.

This is especially problematic if inner contains another field that is required and lacks a default, like inner.req. outerDefault becomes completely impossible to use just for its outermost field default (top) due to a property of one of its optional nested structures.

Solution?

i don't know exactly what the best solution here looks like, but i'm reasonably sure that it will entail switching to functions for applying defaults, as it's the only way to achieve the necessary conditionality. At least, i think it's the only way - perhaps some type magic would allow us to get around it. However, given that we also have the first problem - leaving application a problem for the user - it seems preferable to just embrace functions, as it allows us to target a single pattern for all defaults: a function that returns T, with a parameter list that is basically a Pick<T, (the list of required fields that lack a default)>. Each level of structure would probably end up getting its own function, at which point...idk, something like uncurrying to roll everything up to the parent? I haven't thought it fully through.

cc @ryantxu

List defaults

This currently panics:

I2: {
  I2_TypedList: [...int] | *[1,2]
} @cuetsy(targetType="interface")

Instead, it should generate an interface with a number[] field and a default const with [1,2]

Reject incomplete defaults

With the model we're pursuing for generating defaults, it doesn't make sense to allow non-concrete default values. That means any of these cases should result in an error:

A: int | *string
B: "foo" | *("bar" | "baz")
C: "foo" | "bar" | *_|_

Not sure how realistic/important C is, but the other two definitely are.

s/Kind/Type

We target TypeScript, which calls it's types "Type" not "Kind"

Add config option to automatically generate definitions as interfaces (maybe)

Core CUE generators - like OpenAPI - generally rely on whether a field is a definition or not to decide whether to generate it as a top-level type. Cuetsy has, thus far, decided to go the attribute-driven approach.

There are pros and cons to each approach, but recently i've been noticing that when a given schema is targeted for multiple output languages (as it is in Grafana, TS and Go), having a divergence in how this works can make for some toil and potential for inconsistency.

In general i still want to keep cuetsy attribute-centric, but it does make sense to me that we might add a configurable mode where visited definition fields are treated as being @cuetsy(kind="interface"). (Or, if we're allowing configurability, maybe we allow the default to be either interface or alias 🤷 )

Not actually sure if this is a good idea, but getting it written down in any case.

Refactor tests, testing framework so that all are import-capable

For purely historical/organic growth reasons, cuetsy tests are currently awkwardly divided between a set that allow imports, and a set that don't.

In the interest of reducing friction around debugging tests and generally better organizing the body of tests, these two sets should be unified, and then triggered through the same test runner.

Import Mapping Fails for CUE standard library imports

When creating a lineage which imports a piece of the CUE standard library, such as time, cuetsy errors on codegen with:

generating TS for child elements of schema failed: a corresponding typescript import is not available for "time" for input "customkind"

Example CUE lineage file:

package foo

import (
    "time"
    "github.com/grafana/thema"
)

customKind: thema.#Lineage & {
    name: "customkind"
    schemas: [{
        version: [0,0]
        schema: {
            spec: {
                timestamp: string & time.Time @cuetsy(kind="string")
            }
        }
    }]
}

Currently, this can be remedied with a custom import mapper in the cuetsy.Config, or using cuetsy.IgnoreImportMapper, so it doesn't block codegen.

unreachable panic reached!

I managed to hit this panic while running the grafana-app-sdk codegen on a file that was missing @cuetsy attributes.

Here's a working reproduction - the source file is invalid, and should result in an error, but not panic.

Generate referenced enum defaults correctly

Currently, when there's an enum with a default value that's referenced, we generate the reference to it like this:

CUE TS
package cuetsy

AutoCamel: *"foo" | "bar" @cuetsy(kind="enum")
Ref: {
  ac: AutoCamel
} @cuetsy(kind="interface")
export enum AutoCamel {
  Foo = "foo",
  Bar = "bar",
}

export const defaultAutoCamel: AutoCamel = AutoCamel.Foo

export interface Ref {
  ac: AutoCamel
}

export const defaultRef: Ref = {
  ac: "foo"
}

This fails typechecking. The default should be:

export const defaultRef: Ref = {
  ac: AutoCamel.Foo
}

This (disabled) test contains a more exhaustive set of cases to check, but they all ultimately amount to the same problem.

Allow generation of enums from structs

Currently, the annotation @cuetsy(targetType="enum") may only be applied to lists, and we automatically apply an upper-camel-casing transform to the CUE label. So, this:

Foo: "bar" | "camelCase" @cuetsy(targetType="enum")

becomes this:

export enum Foo {
  Bar = 'bar',
  CamelCase = 'camelCase',
}

This is handy shorthand if the case mapping works, but more control may be desired. To provide that control, the easiest approach is just to extend support for @cuetsy(targetType="enum") onto structs, with the constraint that only concrete string values are allowed. So, this:

Foo: {
  Walla: "laadeedaa"
  run: "OMG"
} @cuetsy(targetType="enum")

becomes this:

export enum Foo {
  Walla = 'laadeedaa',
  run = 'OMG',
}

Just a straight mapping, no casing magic.

(Wait, should we allow int values, too? i'd have to look back at the TS docs about enums to decide if this is wise or not)

Generate defaults with `Partial<T>`

Currently, the defaults we generate may not pass Typescript's type checker. An incomplete cue.Value (one that has at least one required field without a default and therefore cannot be rendered to a concrete form like JSON) will fail, because the default const produced will lack any entry for that required field.

I think the simplest fix here is just to always generate our default consts with Partial<T>. We could do so conditionally based on a concreteness check, but until we're aware of a use case (@ryantxu?) that really requires a default not being Partial, this seems the best solution.

Translate empty struct types as `Record<string, unknown>` instead of `{}`

Currently, cuetsy translates an empty struct:

f: {...} @cuetsy(@kind="type")

To an empty object:

export type f = {};

It appears that {} is subtly different than the primitive type object in TypeScript's type system, as indicated by this playground). I think object is what we want here, but will probably have to research a bit further before committing to the approach.

Add the export option to cuetfy.exe.

Very unfortunate.
Now cuetify.exe cannot set config.export to true.

So when I convert DiceFaces: 1 | 2 | 3 | 4 | 5 | 6 @cuetsy(kind="type") with cuetify.exe,
I don't get export type DiceFaces = 1 | 2 | 3 | 4 | 5 | 6; but type DiceFaces = 1 | 2 | 3 | 4 | 5 | 6;.

I have implemented the export option in one way and created a PR(#82).
Would you accept this PR with some modifications?

Optional fields in nested structs are omitted

The following:

Inside: {
    optional?: [...{
        bar: string
        foo?: string
    }]
} @cuetsy(kind="interface")

Should produce:

export interface Inside {
  optional?: {
    foo?: string;
    bar: string;
  }[];
}

But instead produces:

export interface Inside {
  optional?: {
    bar: string;
  }[];
}

omitting the nested foo? field.

CUE Roadmap Alignment

To help the CUE folks decide what they ought work on next, we'll use this issue to highlight the things scuemata would most benefit from progress on.

Cleaner API for working with cue.Value

This is kinda broad-strokes, but we currently do absolutely unholy things in this project to achieve our desired generated Typescript results. N.B., it's possible that we're just approaching this problem space all wrong, and should be relying on more of a backbone of syntactic analysis (ast package) rather than almost entirely semantic (cue package).

  • Representing everything as a cue.Value gets really confusing when you're deep in the weeds of teasing out an .Expr()
  • Differentiating between a selector with a subject that's an import vs. a local-file struct is extremely, almost prohibitively difficult

Replacement for cue.Instance

Recent versions of CUE deprecated cue.Instance. In general, we prefer operating on cue.Value, as it allows us to grant flexible control to the cuetsy consumer about exactly what's getting converted. (This is essential for scuemata, where we want to convert only the latest schema, not the whole logical structure)

We need to be able to inspect imports in order to generate corresponding typescript imports, and being able to reach back up to them from a cue.Value - or whatever successor to it exists - is key to being able to make a nice, tight API for cuetsy.

Generate disjunctions in parentheses

Currently, the following input:

U1: "foo" | "bar" | "baz" @cuetsy(kind="type")
U2: 1 | 2 | 3 @cuetsy(kind="type")
U3: {
    listU: [...(dep.U1 | U2 | U1)]
} @cuetsy(kind="interface")

will produce the following output (modulo my comment):

export type U1 = 'foo' | 'bar' | 'baz';

export type U2 = 1 | 2 | 3;

export interface U3 {
  listU: dep.U1 | U2 | U1[]; // straight up inccorrect - needs parens
}

I think the simplest solution here is just to always generate parentheses on output. It's not the tidiest, but as long as the output is correct, i think that's sufficient.

Fully convert to a builder/IR for analyzing input

#48 began a journey towards a builder/IR-style approach to analyzing CUE inputs as part of outputting them. This issue is for outlining the path to achieving that.

From where we are now, it seems to me that intermediate goals/milestones would be:

  • Replace the generator with the standalone buildContext introduced in #48
  • Create individual builder/IR-types representing the different output targets, but having some shared interface
  • Refactor utility functions (e.g. tsprintField()) to avoid doing much analysis, moving as much of this as possible instead into the typed builders
  • Create a system of general rules for defining what describing and checking the permissibility of inputs

This last point would be especially valuable, as it would be a basis for explaining to the user what they are and are not allowed to input.

It's also possible that the introduction of a partial evaluator within the language facilities - basically, a new Eval() which can be told to preserve unchanged any nodes marked with a particular attribute - could obviate the need for a lot of the complexity in such an IR. If my thinking on this is correct, then at minimum, such a call could ensure simplification of the input cue.Value is a strictly smaller, better-defined superset of the graph of IR nodes that need to exist.

Render computed fields as their base type

Currently, the following input:

Something: {
	foo: bool & bar == "someval"
	bar: string
} @cuetsy(kind="interface")

will be translated as follows:

export interface Something {
  foo: false; // this is the problem
  bar: string;
}

It's doing this because in non-concrete schema form, it's true that bar != "someval", even though it's still possible that a value could arrive later and make it true.

These kinds of computed fields are a very powerful use case for CUE, and we want to be able to use them in schema without messing up language codegen. It'd be great if cuetsy could be smart enough to figure this out on its own. Or, if there's too much ambiguity, we could consider introducing a hinting attribute like @cuetsy(asBase) to indicate to the analyzer that it should just render the TS type corresponding to the incomplete kind of the field.

ts/ast: Eliminate ast.Raw

ast.Raw is a solution for gradual adoption of the ts/ast package, but really shouldn't be there at all (or at least not used by cuetsy itself)

Only make types `Partial` if it's necessary

Currently, the default const cuetsy generates for all interface-kinded translated symbols is unconditionally marked as Partial<>. This was, without question, me cutting what appeared to be a cuttable corner, because AFAICT there's no impact on behavior to doing it unconditionally.

Still, it'd be nice to only generate Partial<> defaults if it's actually necessary to do so. And it shouldn't (?) be terribly difficult, as i think the concept corresponds pretty closely to CUE's IsConcrete().

Enums within struct/object defaults use literal value as opposed to enum member

As a part of grafana/grafana#59363 ran into an issue in which using an enum member within a default field will emit the literal value for that enum as opposed to accessing the member.

For the following cue description:

cellOptions: TableCellOptions | *{ type: TableCellDisplayMode & "auto" }

this is emitted:

cellOptions: {
  type: 'auto',
}

However, it's expected that this would be emitted instead:

cellOptions: {
  type: TableCellDisplayMode.Auto,
}

Add toposort to ensure outputs are printed in valid order

Currently, cuetsy prints its outputs in alphabetical order. TS is sensitive to ordering in its constant declarations, however, meaning that this:

interface Bar {
  N: number;
}

const defaultBar: Bar = {
  N: defaultFoo,
}

type Foo = number;
const defaultFoo: Foo = 4;

Is invalid because defaultFoo is declared after it is referenced in defaultBar. Consequently, we have to add a (stable) toposort to cuetsy such that its const outputs are in an acceptable order.

It'll also mean erroring on circular references, if any.

Error when struct embeds exactly one reference

Embedding a single reference in a struct:

ref: {
  foo: string
} @cuetsy(kind="interface")

t: {
  ref
} @cuetsy(kind="interface")

Should result in...well, i'm not actually completely sure what this should result in, but this seems reasonable:

export interface ref {
  foo: string;
}

export interface t extends ref {}

However, the right outcome probably depends on the @cuetsy-specified kind.

Whatever the desirable output for each kind-case, none of them happen today. Instead, an error with the following super-helpful string is returned:

not a reference

This needs le fix.

`null` types are unsupported

The following CUE:

nullType: null @cuetsy(kind="type")
nullInUnion: string | null @cuetsy(kind="type")
nullDefault: "foo" | "bar" | *null @cuetsy(kind="type")
obj: {
    nullField: null
} @cuetsy(kind="interface")

Should this output, or something close:

export type nullType = null;

export type nullInUnion = string | null;

export type nullDefault = "foo" | "bar" | null;

export const defaultnullDefault: nullDefault = null;

export interface obj {
    nullField: null
}

But instead, we get:

export type nullType = %!s(<nil>);

export type nullInUnion = %!s(PANIC=String method: runtime error: invalid memory address or nil pointer dereference);

export type nullDefault = %!s(PANIC=String method: runtime error: invalid memory address or nil pointer dereference);

export const defaultnullDefault: nullDefault = %!s(<nil>);

export interface obj %!s(PANIC=String method: runtime error: invalid memory address or nil pointer dereference)

This is now codified in the testdata/imports/nulltype.txtar test.

Add Call expressions to ts ast

Cuetsy's scrappy little TS AST cannot currently represent Call expressions. We don't have a need for them yet, but we will when we get smarter about generating defaulters.

Add `memberNames` attr identifier to enhance `kind="enum"`

By default, kind="enum" uses a CamelCasing-ish strategy on values to decide on member names to correspond to those values. This is a nice default, but it doesn't help when the values of the disjunct are numerics instead of strings, or if the user just plain wants some other pattern.

For that, we need cuetsy to support another attribute identifier. What i did here should work fine - a memberNames attr identifier, which expects a |-delimited list of strings, equal in count to the number of elements in the disjunction. memberNames will be required if the disjunction contains any numeric values.

CUE TS
package cuetsy

// Enum-level comment
// Foo: member-level comment
// Bar: member-level comment
AutoCamel: "foo" | "bar" @cuetsy(kind="enum")
// Enum-level comment
// Foo: member-level comment
// Bar: member-level comment
ManualCamel: "foo" | "bar" @cuetsy(kind="enum",memberNames="Foo|Bar")
Arbitrary: "foo" | "bar" @cuetsy(kind="enum",memberNames="Zip|Zap")
Numeric: 0 | 1 | 2 @cuetsy(kind="enum",memberNames="Zero|One|Two")
ErrMismatchLen: "a" | "b" | "c" @cuetsy(kind="enum",memberNames="a|b")
ErrNamelessNumerics: 0 | 1 | 2 @cuetsy(kind="enum")
/**
 * Enum-level comment
 **/
enum AutoCamel {
  // member-level comment
  Foo = "foo",
  // member-level comment
  Bar = "bar",
}
/**
 * Enum-level comment
 **/
enum ManualCamel {
  // member-level comment
  Foo = "foo",
  // member-level comment
  Bar = "bar",
}
enum Arbitrary {
  Zip = "foo",
  Zap = "bar",
}
enum Numeric {
  Zero = 0,
  One = 1,
  Two = 2,
}

Flesh out the README

README Is terrible! Seriously just something i tapped out in five minutes. Needs instructions, suggestions, what cuetsy doesn't/won't do, etc.

Most crucial docs of all are probably "what are all the supported attribute values"

Add `withName` attribute to allow direct control over generated object names

Currently, the name of the generated typescript object is derived directly from the CUE object. It'd be nice to offer the user more direct control over the target object. Might be useful for CUE definitions and hidden fields.

i don't have a burning use case for this right now, though, so it can probably wait until one presents itself.

Generate "default objects"

Problem

Currently, we disregard CUE default values. This needs to change, though there's some flexibility here on exactly how we do it.

Proposed Solution

@ryantxu can say more about exactly how he thinks this ought to be shaped, but...

We should optionally generate:

  • A const with default values assigned for the type
  • A default-setter func:<typename>setDefaults(t <typename>)
  • An *isDefault() func that checks whether an instance of the type equal has the default values.

Simple example:

Foo: {
  Bar: string | *"ohai*
  Baz: int | *4
} @cuetsy(targetType="interface")
export interface Foo {
  Bar: string;
  Baz: int;
}

export const FooDefault {
  Bar: 'ohai';
  Baz: 4
}

export function FooIsDefault(obj: Foo): bool {
  ...
}

export function FooSetDefaults(obj: Foo): Foo {
  ...
}

i feel like there's probably a more elegant generic way of expressing the funcs, maybe even the default, perhaps leveraging some Partials. 🤷

This will be tricky. For one, i think we'll have to read through CUE references in a way we don't now. e.g.:

AType: "foo" | "bar" | *"baz" @cuetsy(targetType="type")
BType: {
  Composed: AType
} @cuetsy(targetType="interface")

For BType, the exported const would need to traverse the reference to AType to figure out the right default value for the exported const's Composed field.

Also, related, we can't assume that even required fields will have a default. I believe that means that the exported const isn't actually guaranteed to satisfy the exported interface. (That may just be more of an oddity than problematic, though).

In general, though, we don't need this to cover all possible CUE constructions perfectly, as an operating assumption in cuetsy, at least for now, is that folks are going to be checking the generated TS on the other side, and they can choose to rewrite their CUE if it doesn't come out right. (Yup, we iz prototyping!)

segfault in main

Da problem

❯ cuetsy monolith.cue
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x1204078]

goroutine 1 [running]:
cuelang.org/go/internal/core/adt.(*Vertex).getNodeContext(0x2, 0xdde26ece00000003)
	/Users/norman/go/pkg/mod/cuelang.org/[email protected]/internal/core/adt/eval.go:911 +0x18
cuelang.org/go/internal/core/adt.(*OpContext).Unify(0xc000222b60, 0x0, 0x5)
	/Users/norman/go/pkg/mod/cuelang.org/[email protected]/internal/core/adt/eval.go:172 +0x4e
cuelang.org/go/internal/core/adt.(*Vertex).Finalize(...)
	/Users/norman/go/pkg/mod/cuelang.org/[email protected]/internal/core/adt/composite.go:445
cuelang.org/go/cue.(*Instance).Value(0xc000465ce0)
	/Users/norman/go/pkg/mod/cuelang.org/[email protected]/cue/instance.go:214 +0x85
main.main()
	/Users/norman/go/pkg/mod/github.com/grafana/[email protected]/cmd/cuetsy/main.go:40 +0x2ba

monolith.cue

Generated using

cue get go github.com/ipfs/go-ipfs-config
go run ./cmd/aggregate-cues cue.mod/gen/**/*.cue > monolith.cue
❯ cue version
cue version v0.4.1-rc.2 darwin/amd64
❯ go version
go version go1.17.1 darwin/amd64

Support for generating generic types?

Currently, cuetsy can't generate generic Typescript types.

I'm pretty uncomfortable with the idea of generating generic types in cuetsy, as it's hard to see how we could do so in a way that maintains at least rough type equivalence. It's especially unclear how how generated default* would be reconciled with generics in the raw generated types.

In general, i prefer a "veneering" strategy, where it's expected that hand-written code is layered on top of the generic code that can add generic types. That way, the typescript type checker can enforce that whatever generics are layered on type, they're type constraints/refinements on the generated raw types.

But, i'm open to counterarguments, which folks are welcome to make in this issue, if they want :)

Change `targetType` to `kind`

The targetType annotation is unnecessarily wordy, as well as misleading. kind would be better:

Foo: string @cuetsy(kind="interface")

kind is maybe not entirely right either. But it at least makes it clear we're not talking about types, but a higher-order concept.

We can retain legacy support for targetType if we want. Not a big deal.

why not use HOF?

Couldn't HOF be used to perform this conversion instead of creating the new tool from scratch?

Generated TS imports have quoting syntax errors when CUE imports lack explicit package name

When we have a cue file with an import like this:

import schema "github.com/grafana/grafana/packages/grafana-schema/src/schema"

The generated TS has a corresponding import:

import * as schema from '@grafana/schema';

But that extra schema is redundant. This should be the import CUE:

import "github.com/grafana/grafana/packages/grafana-schema/src/schema"

However, that results in this output:

import * as schema" from '@grafana/schema';

I'm decently sure this is a problem in the ts ast for imports - we don't have test cases that cover imports 😬

Cuetsy.exe cannot convert E3 in cmd/cuetsy/test.cue.

I test below command:

 cuetsy.exe .\cmd\cuetsy\test.cue

and I get this error

typescript enums may only be generated from concrete strings, or ints with memberNames attribute:
    .\cmd\cuetsy\test.cue:5:1

Perhaps any specs have changed?

Support exporting const types

Given:

TimeZoneBrowser: "browser" @cuetsy(kind="const")

This should ideally produce:

export type TimeZoneBrowser = 'browser';

Reconnect CI

The setup with drone CI broke after moving this from my personal repo space to the grafana org. Needs to be reconnected, and...i now seem to lack perms, heh

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.