You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
infer TypeScript types from pattern guards (#3133)
closes: #2392
supports: Agoric/agoric-sdk#6160
## Summary
Add comprehensive TypeScript type inference from pattern guards (`M.*`
matchers) to exo methods and interfaces. When `makeExo`,
`defineExoClass`, and `defineExoClassKit` are called with typed
`InterfaceGuard` values, method signatures are now inferred from the
guard at compile time.
## Changes
### Core Features
- **`TypeFromPattern<P>`** — infer static types from any pattern matcher
(string, bigint, remotable, arrays, records, etc.)
- **`TypeFromMethodGuard<G>`** — infer function signatures from
`M.call()` or `M.callWhen()` guards
- **`TypeFromInterfaceGuard<G>`** — infer method records from interface
guard definitions
- **`M.remotable<typeof Guard>()`** — facet-isolated return types with
guard polymorphism
- **Per-facet `ThisType`** in `defineExoClassKit` — `this.facets` and
`this.state` properly typed
### Breaking Changes
- `defineExoClassKit` now requires facet method types to match their
guard signatures (compile-time check)
- `makeExo` and `defineExoClass` enforce method signatures against guard
at compile time
- Single-facet exos have `this.self` (not `this.facets`); multi-facet
kits have `this.facets` (not `this.self`)
### Type System Architecture
- New `types-index.d.ts` / `types-index.js` convention for re-exporting
values with enhanced type signatures
- `.ts` files (e.g. `type-from-pattern.ts`) contain type definitions
only; no runtime code
- `JSDoc` `@import` for type-only imports in `.js` files
### Documentation
- **`AGENTS.md`** — TypeScript conventions for monorepo contributors
- Enhanced JSDoc in `src/types.d.ts` explaining `ClassContext` vs
`KitContext`
- Comprehensive type test suite in `types.test-d.ts` covering all
inference cases
### Tests
- Full runtime test coverage for kit facet isolation and `this` context
- Type-level tests (tsd) for guard-driven method inference
- Tests for `M.callWhen`, `M.await`, `M.promise`, and async method
returns
This file provides conventions and constraints for AI agents working in this repository.
4
+
5
+
## Repository structure
6
+
7
+
- Monorepo managed with Yarn workspaces
8
+
- Packages live in `packages/`
9
+
- Tests use `ava` (runtime) and `tsd` (types)
10
+
- Linting: `eslint` with project-specific rules; run `yarn lint` per-package
11
+
12
+
## TypeScript usage
13
+
14
+
Our TypeScript conventions accommodate `.js` development (this repo) and `.ts` consumers (e.g. agoric-sdk). See [agoric-sdk/docs/typescript.md](https://github.com/Agoric/agoric-sdk/blob/master/docs/typescript.md) for full background.
15
+
16
+
### No `.ts` in runtime bundles
17
+
18
+
Never use `.ts` files in modules that are transitively imported into an Endo bundle. The Endo bundler does not understand `.ts` syntax. We avoid build steps for runtime imports.
19
+
20
+
### `.ts` files are for type definitions only
21
+
22
+
Use `.ts` files to define exported types. These are never imported at runtime. They are made available to consumers through a `types-index` module.
23
+
24
+
When a `.ts` file contains runtime code (e.g. `type-from-pattern.ts` with `declare` statements), it still produces only `.d.ts` output — the `declare` keyword ensures no JS is emitted. Actual runtime code belongs in `.js` files.
25
+
26
+
### The `types-index` convention
27
+
28
+
Each package that exports types uses a pair of files:
29
+
30
+
-**`types-index.js`** — Runtime re-exports. Contains `export { ... } from './src/foo.js'` for values that need enhanced type signatures (e.g. `M`, `matches`, `mustMatch`).
31
+
-**`types-index.d.ts`** — **Pure re-export index.** Contains only `export type * from` and `export { ... } from` lines. **No type definitions belong here.**
32
+
33
+
Why: `.d.ts` files are not checked by `tsc` (we use `skipLibCheck: true`). Type definitions in `.d.ts` files silently pass even if they contain errors. Definitions in `.ts` files are checked.
34
+
35
+
The entrypoint (`index.js`) re-exports from `types-index.js`:
36
+
```js
37
+
// eslint-disable-next-line import/export
38
+
export*from'./types-index.js';
39
+
```
40
+
41
+
### Where type definitions go
42
+
43
+
| What | Where | Why |
44
+
|------|-------|-----|
45
+
| Interface types, data types |`src/types.ts`| Canonical type definitions |
46
+
| Inferred/computed types |`src/type-from-pattern.ts` (or similar `.ts`) | Complex type logic, checked by tsc |
47
+
| Value + namespace merges | Same `.ts` file as the namespace | TS requires both in one module for merging |
| Re-exports only |`types-index.d.ts`| Pure index, no definitions |
50
+
51
+
### `emitDeclarationOnly`
52
+
53
+
The repo-wide `tsconfig-build-options.json` sets `emitDeclarationOnly: true`. `tsc` only generates `.d.ts` files, not `.js`. This means `.ts` files with runtime code (not just types) would need `build-ts-to-js` or equivalent — which this repo does not currently have. Keep `.ts` files type-only.
54
+
55
+
### Imports in `.js` files
56
+
57
+
Use `/** @import */` JSDoc comments to import types without runtime module loading:
58
+
```js
59
+
/** @import { Pattern, MatcherNamespace } from './types.js' */
60
+
```
61
+
62
+
## Exo `this` context
63
+
64
+
Exo methods receive a `this` context (via `ThisType<>`) that differs between single-facet and multi-facet exos:
|`defineExoClass`| ✅ the exo instance | ❌ | ✅ from `init()`|
70
+
|`defineExoClassKit`| ❌ | ✅ all facets in cohort | ✅ from `init()`|
71
+
72
+
**Why no `self` on kits?** A kit has multiple facets (e.g. `public`, `admin`), each a separate remotable object. There is no single "self". Use `this.facets.facetName` to access any facet in the cohort.
73
+
74
+
When writing `ThisType<>` annotations in `types-index.d.ts`:
75
+
- Single-facet: `ThisType<{ self: Guarded<M>; state: S }>`
76
+
- Multi-facet: `ThisType<{ facets: GuardedKit<F>; state: S }>`
77
+
78
+
Never mix `self` and `facets` in the same context type.
79
+
80
+
## Testing
81
+
82
+
- Runtime tests: `yarn test` (uses `ava`)
83
+
- Type tests: `yarn lint:types` (uses `tsd` — test files are `test/types.test-d.ts`)
84
+
- Lint: `yarn lint` (runs both `lint:types` and `lint:eslint`)
85
+
86
+
Always run `yarn lint` in each package you've modified before committing.
87
+
88
+
## Commit conventions
89
+
90
+
- Use conventional commits: `feat(pkg):`, `fix(pkg):`, `refactor(pkg):`, `chore:`, `test(pkg):`
91
+
- Breaking changes: `feat(pkg)!:` or `fix(pkg)!:`
92
+
- File conversions (`.js` to `.ts`) get their own `refactor:` commit
[^1]: We pick the impl type because it has the param names and the guard doesn't.
11
+
[^2]: Use `GuardedMethods<typeof exo>` to opt into the guard's type contract (e.g. `.optional()` params). Parameter names are not preserved (TS limitation).
0 commit comments