-
Notifications
You must be signed in to change notification settings - Fork 80
rosetta docs with TypeScript and Zod #2977
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
|
||
| The examples demonstrate both successful and unsuccessful conversions. The tests in [`../../test/rosetta/examples.test.js`](../../test/rosetta/examples.test.js) exercise these snippets. They prove the conversions that work today and highlight the known mismatches when a translation is not possible. | ||
|
|
||
| > **Note:** Zod is an optional development dependency. Install it within this repository (`yarn workspace @endo/patterns add --dev zod`) before running the Rosetta tests locally. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this somehow more optional than other devDependencies in package.json? For other devDependencies, I just depend on normal yarn && yarn build to install so I can use them for local development. What makes this one different.
(I'm not opposed to this one being more optional. I just don't understand what the mechanism is.)
| | Pattern | Suggested TypeScript | Notes | | ||
| | --- | --- | --- | | ||
| | `M.string()` | `type T = string;` | Strings translate directly. | | ||
| | `M.number()` | `type T = number;` | TypeScript cannot exclude `NaN` or `Infinity` at the type level; rely on runtime validation. | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
M.number() does no range checking. Did you mean
| | `M.number()` | `type T = number;` | TypeScript cannot exclude `NaN` or `Infinity` at the type level; rely on runtime validation. | | |
| | `M.number()` | `type T = number;` | | | |
| | `M.nat()` | `type T = number;` | TypeScript cannot exclude `NaN` or `Infinity` at the type level; rely on runtime validation. | |
| | `M.splitRecord({ id: M.nat() }, { note: M.string() })` | ```ts | ||
| interface RecordShape { | ||
| id: bigint; | ||
| note?: string; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know how markdown tables relate to markdown triple backtick. But this is not rendering correctly.
Ignoring the backtick issue I don't know how to solve, you could also extend this in place, or perhaps include as a distinct line, for some suitable T
| | `M.splitRecord({ id: M.nat() }, { note: M.string() })` | ```ts | |
| interface RecordShape { | |
| id: bigint; | |
| note?: string; | |
| } | |
| | `M.splitRecord({ id: M.nat() }, { note: M.string() }, T)` | ```ts | |
| interface RecordShape { | |
| id: bigint; | |
| note?: string; | |
| ...T; | |
| } |
| @@ -0,0 +1,27 @@ | |||
| # Relating Endo Patterns to TypeScript Types | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do these relate to the types you already infer from patterns and guards?
| id: bigint; | ||
| note?: string; | ||
| } | ||
| ``` | TypeScript `bigint` does not encode non-negativity. Convey the constraint in docs or branded types. | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue. M.bigint() also does not encode non-negativity
| note?: string; | ||
| } | ||
| ``` | TypeScript `bigint` does not encode non-negativity. Convey the constraint in docs or branded types. | | ||
| | `M.arrayOf(M.string())` | `type T = string[];` | Combine with tuple types if `arrayLengthLimit` is finite. | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does "Combine with tuple types" mean here?
| | `M.setOf(M.string())` | `type T = ReadonlyArray<string>;` | TypeScript lacks a native `CopySet`. Express as `ReadonlyArray` with documentation noting deduplication. | | ||
| | `M.bagOf(M.string())` | `type T = ReadonlyArray<[string, bigint]>;` | A bag is best expressed as entries with counts. | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For setOf, bagOf, and the missing mapOf, typescript can type the enclosing envelope record, including the literal tag value ('set', 'bag', or 'map'). In fact, I thought you already created the typescript types for these.
| | `M.arrayOf(M.string(), harden({ arrayLengthLimit: 2 }))` | `type T = readonly [string?, string?];` | Endo can enforce maximum length at runtime. A tuple with optional slots expresses the same upper bound statically. | | ||
| | `M.setOf(M.string())` | `type T = ReadonlyArray<string>;` | TypeScript lacks a native `CopySet`. Express as `ReadonlyArray` with documentation noting deduplication. | | ||
| | `M.bagOf(M.string())` | `type T = ReadonlyArray<[string, bigint]>;` | A bag is best expressed as entries with counts. | | ||
| | `M.promise()` | `type T = Promise<unknown>;` | Add generics for resolved types if known; runtime checks must ensure the promise is handled according to Endo’s pass-style rules. | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand why statements about runtime checks are relevant to patterns/guard and to zod. But what does this mean as applied to TypeScript?
| | `M.interface({ getValue: M.callWhen([M.number()], M.number()) })` | ```ts | ||
| interface ValueService { | ||
| getValue(input: number): number; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| | `M.interface({ getValue: M.callWhen([M.number()], M.number()) })` | ```ts | |
| interface ValueService { | |
| getValue(input: number): number; | |
| | `M.interface({ getValue: M.callWhen([M.number()], M.number()) })` | ```ts | |
| interface ValueService { | |
| async getValue(input: number): number; |
A callWhen corresponds to an async method declaration.
| interface ValueService { | ||
| getValue(input: number): number; | ||
| } | ||
| ``` | TypeScript describes the shape but cannot enforce async/await behaviour. The Endo guard still provides runtime enforcement. | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, what does it mean to even talk about enforcement when talking about TypeScript (as opposed to patterns/guards and to zod)?
M.await(M.number()) still has the typing issue that the external type should be Promise<number> while the internal type should be number
kriskowal
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I defer to @erights for the review as a subject matter expert, but if the resulting docs are also useful to humans (they look useful to humans), consider incorporating them in typedoc. That may entail moving them to top-level docs, adding YAML front-matter to give the document a title consistent with the convention in neighboring docs, and adding it to its place in order in typedoc.json. The result would be that it would be prominent on https://docs.endojs.org. Consequently, any disagreement about how to render Markdown should be settled toward Typedoc’s rendering over Github’s, if both cannot be satisfied.
|
|
||
| `M` also has {@link GuardMakers} methods to make {@link InterfaceGuard}s that use Patterns to characterize dynamic behavior such as method argument/response signatures and promise awaiting. The {@link @endo/exo!} package uses `InterfaceGuard`s as the first level of defense for Exo objects against malformed input. | ||
|
|
||
| If you need to translate between Endo Patterns, Zod schemas, and TypeScript declarations, start with the [Rosetta guide](./docs/rosetta/index.md). It collects executable examples together with notes about fidelity gaps and recommended work-arounds. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest making Zod a link. Also would be handy to call-out which Rosetta this is referring to.
| } | ||
| ``` | TypeScript describes the shape but cannot enforce async/await behaviour. The Endo guard still provides runtime enforcement. | | ||
| When a runtime constraint has no native static representation, consider branded types: e.g., `type Nat = bigint & { readonly brand: unique symbol; }`. Guard constructors can cast the value after validation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you explain this so a non-TS expert like me can understand this?
|
I'm going to put this on the backburner in favor of Agoric/agoric-sdk#12044 |
no issue
Description
In Agoric-SDK we often have to turn a TypeScript type into an Endo Pattern, or vice-versa. In YMax work I'm doing the same with Zod schemas.
I'd like the LLMs to do this for me so here is some documentation they can use. It also has unit tests to validate the examples.
Security Considerations
none
Scaling Considerations
none
Documentation Considerations
per se
Testing Considerations
included
Compatibility Considerations
Upgrade Considerations
n/a