diff --git a/docs/typescript.md b/docs/typescript.md index 8e612623e15..70e53e4d00b 100644 --- a/docs/typescript.md +++ b/docs/typescript.md @@ -4,41 +4,54 @@ Our use of TypeScript has to accommodate both .js development in agoric-sdk (whi ## Best practices -- `.d.ts` for types modules +### Exported types + +- `.ts` for modules defining exported types - package entrypoint(s) exports explicit types +- use `/** @import ` comments to import types without getting the runtime module + +### Ambient types + +- `.d.ts` for modules defining ambient types +- import types using [triple-slash reference](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-types-) - for packages upon which other packages expect ambient types: - `exported.js` supplies ambients - don't use runtime imports to get types ([issue](https://github.com/Agoric/agoric-sdk/issues/6512)) -## .d.ts modules +## .ts modules We cannot use `.ts` files in any modules that are transitively imported into an Endo bundle. The reason is that the Endo bundler doesn't understand `.ts` syntax and we don't want it to until we have sufficient auditability of the transformation. Moreover we've tried to avoid a build step in order to import a module. (The one exception so far is `@agoric/cosmic-proto` because we codegen the types. Those modules are written in `.ts` syntax and build to `.js` by a build step that creates `dist`, which is the package export.) -### Benefits - -- A `.d.ts` module allows defining the type in `.ts` syntax, without any risk that it will be included in runtime code. The `.js` is what actually gets imported. -- Only `.d.ts` files can be used in [triple-slash reference types](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-types-) +The trick is to use `.ts` for defining types and then make them available in the packages using a `types-index` module that has both `.js` and `.d.ts` files. -The are some consequences to this approach. - -### File pair - -You have to create a `.js` and `.d.ts` pair for each module. Usually it's of the form, +**Entrypoint (index.js)** +```js +// eslint-disable-next-line import/export +export * from './src/types-index.js'; // no named exports +``` +**types-index.js** ```js // Empty JS file to correspond with its .d.ts twin export {}; ``` -### Lack of type checking +**types-index.d.ts** +```ts +// Export all the types this package provides +export * from './types.js'; +export * from './other-types.js'; +``` + +The actual type implementation is then written in `types.ts` and `other-types.ts` files (per the example above). +These files are never runtime imported as they are only linked through a `.d.ts` file. -We have `"skipLibCheck": true"` in the root tsconfig.json because some libraries we depend on have their own type errors. (A massive one is the output of Telescope, used in `@agoric/cosmic-proto`.) -This means that the types you write in `.d.ts` file won't be checked by `tsc`. To gain some confidence, you can temporarily flip that setting in a package's own `tsconfig.json` and pay attention to only the relevant errors. +## d.ts modules -### Alternatives +We take on the complexity above of indirection because `.d.ts` files aren't checked. We have `"skipLibCheck": true"` in the root tsconfig.json because some libraries we depend on have their own type errors. (A massive one is the output of Telescope, used in `@agoric/cosmic-proto`.) -We've experimented with having `.ts` files. It works, and gets around the skipLibCheck problem, but it complicates the build and exports. It also necessitates a build step even in package that don't currently need it. +This means that the types you write in `.d.ts` file won't be checked by `tsc`. To gain some confidence, you can temporarily flip that setting in a package's own `tsconfig.json` and pay attention to only the relevant errors. ## entrypoint diff --git a/packages/async-flow/index.js b/packages/async-flow/index.js index 9ea1e5a6f19..a795feb1424 100644 --- a/packages/async-flow/index.js +++ b/packages/async-flow/index.js @@ -1,3 +1,3 @@ export * from './src/async-flow.js'; -export * from './src/types.js'; +export * from './src/types-index.js'; export { makeSharedStateRecord } from './src/endowments.js'; diff --git a/packages/async-flow/src/types-index.d.ts b/packages/async-flow/src/types-index.d.ts new file mode 100644 index 00000000000..0e98a59385a --- /dev/null +++ b/packages/async-flow/src/types-index.d.ts @@ -0,0 +1,2 @@ +// Export all the types this package provides +export * from './types.js'; diff --git a/packages/async-flow/src/types.js b/packages/async-flow/src/types-index.js similarity index 100% rename from packages/async-flow/src/types.js rename to packages/async-flow/src/types-index.js diff --git a/packages/async-flow/src/types.d.ts b/packages/async-flow/src/types.ts similarity index 98% rename from packages/async-flow/src/types.d.ts rename to packages/async-flow/src/types.ts index 5541611961c..c80dec35d99 100644 --- a/packages/async-flow/src/types.d.ts +++ b/packages/async-flow/src/types.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-use-before-define */ import type { Passable } from '@endo/pass-style'; import type { Vow, VowTools } from '@agoric/vow'; import type { LogStore } from './log-store.js'; @@ -52,7 +53,7 @@ type Simplify = { [KeyType in keyof T]: T[KeyType] } & {}; /** * Convert an entire Guest interface into what the host will implement. */ -type HostInterface = { +export type HostInterface = { [K in keyof T]: T[K] extends CallableFunction ? HostOf : T[K] extends Record diff --git a/packages/boot/test/bootstrapTests/ibcClientMock.js b/packages/boot/test/bootstrapTests/ibcClientMock.js index d25e0d2017a..2966743698f 100644 --- a/packages/boot/test/bootstrapTests/ibcClientMock.js +++ b/packages/boot/test/bootstrapTests/ibcClientMock.js @@ -5,7 +5,8 @@ import { heapVowE as E } from '@agoric/vow/vat.js'; /** * @import {Connection, PortAllocator} from '@agoric/network'; - * @import {FarRef, ERef} from '@agoric/vow'; + * @import {FarRef} from '@agoric/internal'; + * @import {ERef} from '@agoric/vow'; */ /** diff --git a/packages/internal/src/index.js b/packages/internal/src/index.js index 536d8ffa125..457c35502e3 100644 --- a/packages/internal/src/index.js +++ b/packages/internal/src/index.js @@ -11,7 +11,7 @@ export * from './typeCheck.js'; export * from './typeGuards.js'; // eslint-disable-next-line import/export -- just types -export * from './types.js'; +export * from './types-index.js'; export { objectMap } from '@endo/common/object-map.js'; export { objectMetaMap } from '@endo/common/object-meta-map.js'; diff --git a/packages/internal/src/types-index.d.ts b/packages/internal/src/types-index.d.ts new file mode 100644 index 00000000000..d4702960d54 --- /dev/null +++ b/packages/internal/src/types-index.d.ts @@ -0,0 +1 @@ +export * from './types.js'; diff --git a/packages/internal/src/types.js b/packages/internal/src/types-index.js similarity index 100% rename from packages/internal/src/types.js rename to packages/internal/src/types-index.js diff --git a/packages/internal/src/types.d.ts b/packages/internal/src/types.ts similarity index 96% rename from packages/internal/src/types.d.ts rename to packages/internal/src/types.ts index a91602bf1f4..54d13d2d509 100644 --- a/packages/internal/src/types.d.ts +++ b/packages/internal/src/types.ts @@ -1,6 +1,7 @@ /* eslint-disable max-classes-per-file */ import type { ERef, RemotableBrand } from '@endo/eventual-send'; import type { Primitive } from '@endo/pass-style'; +import type { Pattern } from '@endo/patterns'; import type { Callable } from './utils.js'; /** @@ -11,7 +12,7 @@ export type TotalMap = Omit, 'get'> & { /** Returns the element associated with the specified key in the TotalMap. */ get: (key: K) => V; }; -export type TotalMapFrom = +export type TotalMapFrom> = M extends Map ? TotalMap : never; export declare class Callback any> { diff --git a/packages/notifier/src/index.js b/packages/notifier/src/index.js index 4033aad55bc..5bfef74afa9 100644 --- a/packages/notifier/src/index.js +++ b/packages/notifier/src/index.js @@ -28,5 +28,5 @@ export { export * from './storesub.js'; export * from './stored-notifier.js'; -// eslint-disable-next-line import/export -- doesn't know types -export * from './types.js'; +// eslint-disable-next-line import/export +export * from './types-index.js'; // no named exports in JS diff --git a/packages/notifier/src/types-index.d.ts b/packages/notifier/src/types-index.d.ts new file mode 100644 index 00000000000..0e98a59385a --- /dev/null +++ b/packages/notifier/src/types-index.d.ts @@ -0,0 +1,2 @@ +// Export all the types this package provides +export * from './types.js'; diff --git a/packages/notifier/src/types-index.js b/packages/notifier/src/types-index.js new file mode 100644 index 00000000000..d382eb5feaf --- /dev/null +++ b/packages/notifier/src/types-index.js @@ -0,0 +1,2 @@ +// Empty JS file to correspond with its .d.ts twin +export {}; diff --git a/packages/notifier/src/types.js b/packages/notifier/src/types.js deleted file mode 100644 index 7533439b677..00000000000 --- a/packages/notifier/src/types.js +++ /dev/null @@ -1,300 +0,0 @@ -// @jessie-check - -/** - * @import {StoredFacet, Unserializer} from '@agoric/internal/src/lib-chainStorage.js'; - */ - -// Ensure this is a module. -export {}; - -/** - * @template T - * @template [TReturn=any] - * @template [TNext=undefined] - * @typedef {AsyncIterator & { - * fork(): ForkableAsyncIterator - * }} ForkableAsyncIterator An AsyncIterator that can be forked at a given position - * into multiple independent ForkableAsyncIterators starting from that position. - */ - -/** - * @template T - * @template [TReturn=any] - * @template [TNext=undefined] - * @typedef {{ [Symbol.asyncIterator](): AsyncIterableIterator }} AsyncIterableOnly - */ - -/** - * @template T - * @template [TReturn=any] - * @template [TNext=undefined] - * @typedef {{ - * [Symbol.asyncIterator](): ForkableAsyncIterableIterator, - * fork(): ForkableAsyncIterableIterator } & - * ForkableAsyncIterator - * } ForkableAsyncIterableIterator - */ - -/** - * @template T - * @template [TReturn=any] - * @template [TNext=undefined] - * @typedef {{ - * [Symbol.asyncIterator]: () => ForkableAsyncIterator - * }} ForkableAsyncIterable - * An AsyncIterable that produces ForkableAsyncIterators. - */ - -/** - * @template T - * @typedef {object} IterationObserver - * A valid sequence of calls to the methods of an `IterationObserver` - * represents an iteration. A valid sequence consists of any number of calls - * to `updateState` with the successive non-final values, followed by a - * final call to either `finish` with a successful `completion` value - * or `fail` with the alleged `reason` for failure. After at most one - * terminating calls, no further calls to these methods are valid and must be - * rejected. - * @property {(nonFinalValue: T) => void} updateState - * @property {(completion: T) => void} finish - * @property {(reason: unknown) => void} fail - */ - -// ///////////////////////////////////////////////////////////////////////////// - -/** - * @template T - * @typedef {object} PublicationRecord - * Will be shared between machines, so it must be safe to expose. But software - * outside the current package should consider it opaque, not depending on its - * internal structure. - * @property {IteratorResult} head - * @property {bigint} publishCount starts at 1 for the first result - * and advances by 1 for each subsequent result - * @property {Promise>} tail - */ - -/** - * @template T - * @typedef {object} EachTopic - * @property {(publishCount?: bigint) => Promise>} subscribeAfter - * Returns a promise for a "current" PublicationRecord (referencing its - * immediate successor via a `tail` promise) that is later than the - * provided publishCount. - * Used to make forward-lossless ("each") iterators. - */ - -/** - * @template T - * @typedef {ForkableAsyncIterable & EachTopic} IterableEachTopic - * An EachTopic with default asyncIterable behaviour. - * - * NOTE: the publication records and iterators returned by this object are - * ephemeral and will be severed during upgrade. A caller should use - * `subscribeEach` to wrap this topic in a local iterable which automatically - * attempts to reconnect upon being severed. - */ - -/** - * @template T - * @typedef {AsyncIterableOnly & LatestTopic} IterableLatestTopic - * A LatestTopic with default asyncIterable behaviour. - * - * NOTE: the iterators returned by this object are ephemeral and will be severed - * during upgrade. A caller should use `subscribeLatest` to wrap this topic in - * a local iterable which automatically attempts to reconnect upon being - * severed. - */ - -/** - * @template T - * @typedef {object} LatestTopic - * @property {(updateCount?: bigint | number) => Promise>} getUpdateSince - * Returns a promise for an update record as of an update count. - * If the `updateCount` argument is omitted or differs from the current update count, - * the promise promptly resolves to the current record. - * Otherwise, after the next state change, the promise resolves to the resulting record. - * This is an attenuated form of `subscribeAfter` whose return value stands alone and - * does not allow consumers to pin a chain of historical PublicationRecords. - * Used to make lossy ("latest") iterators. - * NOTE: Use of `number` as an `updateCount` is deprecated. - */ - -/** - * @template T - * @typedef {LatestTopic} BaseNotifier This type is deprecated but is still - * used externally. - */ - -/** - * @template T - * @typedef {LatestTopic & EachTopic} Subscriber - * A stream of results that allows consumers to configure - * forward-lossless "each" iteration with `subscribeEach` and - * lossy "latest" iteration with `subscribeLatest`. - */ - -/** - * @template T - * @typedef {object} Publisher - * A valid sequence of calls to the methods of an `IterationObserver` - * represents an iteration. A valid sequence consists of any number of calls - * to `publish` with the successive non-final values, followed by a - * final call to either `finish` with a successful `completion` value - * or `fail` with the alleged `reason` for failure. After at most one - * terminating calls, no further calls to these methods are valid and must be - * rejected. - * @property {(nonFinalValue: T) => void} publish - * @property {(completion: T) => void} finish - * @property {(reason: any) => void} fail - */ - -/** - * @template T - * @typedef {Partial>} PublishObserver - */ - -/** - * @template T - * @typedef {object} PublishKit - * @property {Publisher} publisher - * @property {Subscriber} subscriber - */ - -/** - * @template T - * @typedef {object} StoredPublishKit - * @property {Publisher} publisher - * @property {StoredSubscriber} subscriber - */ - -// ///////////////////////////////////////////////////////////////////////////// - -/** - * @typedef {'mandatory' | 'opportunistic' | 'ignored'} DurablePublishKitValueDurability - * - * Durability configures constraints on non-terminal values that can be - * published to a durable publish kit (terminal values sent to finish or fail - * must always be durable). - * - 'mandatory' means that each value must be durable, so it can be restored - * on upgrade. - * - 'opportunistic' means that a durable value is persisted for post-upgrade - * restoration, but a non-durable value is still accepted (and will result in - * valueless restoration). - * - 'ignored' means that a value is not persisted for restoration even if it - * is durable. - * - * 'mandatory' is the only currently-supported value, and others must not be - * enabled without test coverage. - */ - -/** - * @typedef {object} DurablePublishKitState - * - * @property {DurablePublishKitValueDurability} valueDurability - * - * @property {bigint} publishCount - * - * @property {'live' | 'finished' | 'failed'} status - * - * @property {boolean} hasValue - * hasValue indicates the presence of value. It starts off false, - * and can be reset to false when a durable publish kit is restored and - * the previous value was not durable, or non-terminal and valueDurablity is 'ignored'. - * - * @property {any} value - * value holds either a non-terminal value from `publish` or a terminal value - * from `finish` or `fail`, depending upon the value in status. - */ - -// ///////////////////////////////////////////////////////////////////////////// - -/** - * @template T - * @typedef {object} UpdateRecord - * @property {T} value is whatever state the service wants to publish - * @property {bigint} [updateCount] is a value that identifies the update. For - * the last update, it is `undefined`. - */ - -/** - * @template T - * @typedef {BaseNotifier} NotifierInternals Will be shared between machines, - * so it must be safe to expose. But other software should avoid depending on - * its internal structure. - */ - -/** - * @template T - * @typedef {import('@endo/marshal').RemotableObject & NotifierInternals & - * ForkableAsyncIterable & - * SharableNotifier - * } Notifier an object that can be used to get the current state or updates - */ - -/** - * @template T - * @typedef {object} SharableNotifier - * @property {() => Promise>} getSharableNotifierInternals - * Used to replicate the multicast values at other sites. To manually create a - * local representative of a Notification, do - * ```js - * localIterable = - * makeNotifier(E(remoteIterable).getSharableNotifierInternals()); - * ``` - * The resulting `localIterable` also supports such remote use, and - * will return access to the same representation. - */ - -/** - * @template T - * @typedef {object} NotifierRecord the produced notifier/updater pair - * @property {import('@endo/marshal').RemotableObject & IterationObserver} updater the (closely-held) notifier producer - * @property {Notifier} notifier the (widely-held) notifier consumer - */ - -// ///////////////////////////////////////////////////////////////////////////// - -/** - * @template T - * @typedef {IterableEachTopic & EachTopic & - * SharableSubscription} Subscription - * A form of AsyncIterable supporting distributed and multicast usage. - */ - -/** - * @template T - * @typedef {object} SharableSubscription - * @property {() => Promise>} getSharableSubscriptionInternals - * Used to replicate the multicast values at other sites. To manually create a - * local representative of a Subscription, do - * ```js - * localIterable = - * makeSubscription(E(remoteIterable).getSharableSubscriptionInternals()); - * ``` - * The resulting `localIterable` also supports such remote use, and - * will return access to the same representation. - */ - -/** - * @template T - * @typedef {object} SubscriptionRecord - * @property {IterationObserver} publication - * @property {Subscription} subscription - */ - -// ///////////////////////////////////////////////////////////////////////////// - -/** - * @deprecated use StoredSubscriber - * @template T - * @typedef {Subscription & { - * getStoreKey: () => Promise }>, - * getUnserializer: () => Unserializer, - * }} StoredSubscription - */ - -/** - * @template T - * @typedef {Subscriber & StoredFacet} StoredSubscriber - */ diff --git a/packages/notifier/src/types.ts b/packages/notifier/src/types.ts new file mode 100644 index 00000000000..972ce7cf0c0 --- /dev/null +++ b/packages/notifier/src/types.ts @@ -0,0 +1,254 @@ +/* eslint-disable no-use-before-define */ +import type { + StoredFacet, + Unserializer, + VStorageKey, +} from '@agoric/internal/src/lib-chainStorage.js'; + +/** + * An AsyncIterator that can be forked at a given position + * into multiple independent ForkableAsyncIterators starting from that position. + */ +export type ForkableAsyncIterator< + T, + TReturn = any, + TNext = undefined, +> = AsyncIterator & { + fork(): ForkableAsyncIterator; +}; +export type AsyncIterableOnly = { + [Symbol.asyncIterator](): AsyncIterableIterator; +}; +export type ForkableAsyncIterableIterator< + T, + TReturn = any, + TNext = undefined, +> = { + [Symbol.asyncIterator](): ForkableAsyncIterableIterator; + fork(): ForkableAsyncIterableIterator; +} & ForkableAsyncIterator; +/** + * An AsyncIterable that produces ForkableAsyncIterators. + */ +export type ForkableAsyncIterable = { + [Symbol.asyncIterator]: () => ForkableAsyncIterator; +}; +/** + * A valid sequence of calls to the methods of an `IterationObserver` + * represents an iteration. A valid sequence consists of any number of calls + * to `updateState` with the successive non-final values, followed by a + * final call to either `finish` with a successful `completion` value + * or `fail` with the alleged `reason` for failure. After at most one + * terminating calls, no further calls to these methods are valid and must be + * rejected. + */ +export type IterationObserver = { + updateState: (nonFinalValue: T) => void; + finish: (completion: T) => void; + fail: (reason: unknown) => void; +}; +/** + * Will be shared between machines, so it must be safe to expose. But software + * outside the current package should consider it opaque, not depending on its + * internal structure. + */ +export type PublicationRecord = { + head: IteratorResult; + /** + * starts at 1 for the first result + * and advances by 1 for each subsequent result + */ + publishCount: bigint; + tail: Promise>; +}; +export type EachTopic = { + /** + * Returns a promise for a "current" PublicationRecord (referencing its + * immediate successor via a `tail` promise) that is later than the + * provided publishCount. + * Used to make forward-lossless ("each") iterators. + */ + subscribeAfter: (publishCount?: bigint) => Promise>; +}; +/** + * An EachTopic with default asyncIterable behaviour. + * + * NOTE: the publication records and iterators returned by this object are + * ephemeral and will be severed during upgrade. A caller should use + * `subscribeEach` to wrap this topic in a local iterable which automatically + * attempts to reconnect upon being severed. + */ +export type IterableEachTopic = ForkableAsyncIterable & EachTopic; +/** + * A LatestTopic with default asyncIterable behaviour. + * + * NOTE: the iterators returned by this object are ephemeral and will be severed + * during upgrade. A caller should use `subscribeLatest` to wrap this topic in + * a local iterable which automatically attempts to reconnect upon being + * severed. + */ +export type IterableLatestTopic = AsyncIterableOnly & LatestTopic; +export type LatestTopic = { + /** + * Returns a promise for an update record as of an update count. + * If the `updateCount` argument is omitted or differs from the current update count, + * the promise promptly resolves to the current record. + * Otherwise, after the next state change, the promise resolves to the resulting record. + * This is an attenuated form of `subscribeAfter` whose return value stands alone and + * does not allow consumers to pin a chain of historical PublicationRecords. + * Used to make lossy ("latest") iterators. + * NOTE: Use of `number` as an `updateCount` is deprecated. + */ + getUpdateSince: (updateCount?: bigint | number) => Promise>; +}; +/** + * This type is deprecated but is still + * used externally. + */ +export type BaseNotifier = LatestTopic; +/** + * A stream of results that allows consumers to configure + * forward-lossless "each" iteration with `subscribeEach` and + * lossy "latest" iteration with `subscribeLatest`. + */ +export type Subscriber = LatestTopic & EachTopic; +/** + * A valid sequence of calls to the methods of an `IterationObserver` + * represents an iteration. A valid sequence consists of any number of calls + * to `publish` with the successive non-final values, followed by a + * final call to either `finish` with a successful `completion` value + * or `fail` with the alleged `reason` for failure. After at most one + * terminating calls, no further calls to these methods are valid and must be + * rejected. + */ +export type Publisher = { + publish: (nonFinalValue: T) => void; + finish: (completion: T) => void; + fail: (reason: any) => void; +}; +export type PublishObserver = Partial>; +export type PublishKit = { + publisher: Publisher; + subscriber: Subscriber; +}; +export type StoredPublishKit = { + publisher: Publisher; + subscriber: StoredSubscriber; +}; +/** + * Durability configures constraints on non-terminal values that can be + * published to a durable publish kit (terminal values sent to finish or fail + * must always be durable). + * - 'mandatory' means that each value must be durable, so it can be restored + * on upgrade. + * - 'opportunistic' means that a durable value is persisted for post-upgrade + * restoration, but a non-durable value is still accepted (and will result in + * valueless restoration). + * - 'ignored' means that a value is not persisted for restoration even if it + * is durable. + * + * 'mandatory' is the only currently-supported value, and others must not be + * enabled without test coverage. + */ +export type DurablePublishKitValueDurability = + | 'mandatory' + | 'opportunistic' + | 'ignored'; +export type DurablePublishKitState = { + valueDurability: DurablePublishKitValueDurability; + publishCount: bigint; + status: 'live' | 'finished' | 'failed'; + /** + * hasValue indicates the presence of value. It starts off false, + * and can be reset to false when a durable publish kit is restored and + * the previous value was not durable, or non-terminal and valueDurablity is 'ignored'. + */ + hasValue: boolean; + /** + * value holds either a non-terminal value from `publish` or a terminal value + * from `finish` or `fail`, depending upon the value in status. + */ + value: any; +}; +export type UpdateRecord = { + /** + * is whatever state the service wants to publish + */ + value: T; + /** + * is a value that identifies the update. For + * the last update, it is `undefined`. + */ + updateCount?: bigint | undefined; +}; +/** + * Will be shared between machines, + * so it must be safe to expose. But other software should avoid depending on + * its internal structure. + */ +export type NotifierInternals = BaseNotifier; +/** + * an object that can be used to get the current state or updates + */ +export type Notifier = import('@endo/marshal').RemotableObject & + NotifierInternals & + ForkableAsyncIterable & + SharableNotifier; +export type SharableNotifier = { + /** + * Used to replicate the multicast values at other sites. To manually create a + * local representative of a Notification, do + * ```js + * localIterable = + * makeNotifier(E(remoteIterable).getSharableNotifierInternals()); + * ``` + * The resulting `localIterable` also supports such remote use, and + * will return access to the same representation. + */ + getSharableNotifierInternals: () => Promise>; +}; +/** + * the produced notifier/updater pair + */ +export type NotifierRecord = { + /** + * the (closely-held) notifier producer + */ + updater: import('@endo/marshal').RemotableObject & IterationObserver; + /** + * the (widely-held) notifier consumer + */ + notifier: Notifier; +}; +/** + * A form of AsyncIterable supporting distributed and multicast usage. + */ +export type Subscription = IterableEachTopic & + EachTopic & + SharableSubscription; +export type SharableSubscription = { + /** + * Used to replicate the multicast values at other sites. To manually create a + * local representative of a Subscription, do + * ```js + * localIterable = + * makeSubscription(E(remoteIterable).getSharableSubscriptionInternals()); + * ``` + * The resulting `localIterable` also supports such remote use, and + * will return access to the same representation. + */ + getSharableSubscriptionInternals: () => Promise>; +}; +export type SubscriptionRecord = { + publication: IterationObserver; + subscription: Subscription; +}; +export type StoredSubscription = Subscription & { + getStoreKey: () => Promise< + VStorageKey & { + subscription: Subscription; + } + >; + getUnserializer: () => Unserializer; +}; +export type StoredSubscriber = Subscriber & StoredFacet; diff --git a/packages/orchestration/index.js b/packages/orchestration/index.js index e6981b73d64..e124fcacb12 100644 --- a/packages/orchestration/index.js +++ b/packages/orchestration/index.js @@ -3,7 +3,7 @@ /// // eslint-disable-next-line import/export -export * from './types.js'; // no named exports +export * from './src/types-index.js'; // no named exports export * from './src/exos/cosmos-interchain-service.js'; export * from './src/exos/chain-hub-admin.js'; export * from './src/typeGuards.js'; diff --git a/packages/orchestration/src/types-index.d.ts b/packages/orchestration/src/types-index.d.ts new file mode 100644 index 00000000000..d4702960d54 --- /dev/null +++ b/packages/orchestration/src/types-index.d.ts @@ -0,0 +1 @@ +export * from './types.js'; diff --git a/packages/orchestration/types.js b/packages/orchestration/src/types-index.js similarity index 100% rename from packages/orchestration/types.js rename to packages/orchestration/src/types-index.js diff --git a/packages/orchestration/test/network-fakes.ts b/packages/orchestration/test/network-fakes.ts index 9a948ace95d..40fafb5538e 100644 --- a/packages/orchestration/test/network-fakes.ts +++ b/packages/orchestration/test/network-fakes.ts @@ -71,7 +71,7 @@ export const ibcBridgeMocks: { : T extends 'acknowledgementPacket' ? ( obj: IBCMethod<'sendPacket'>, - opts: { sequence: number; acknowledgement: string }, + opts: { sequence: bigint; acknowledgement: string }, ) => IBCEvent<'acknowledgementPacket'> : never; } = { @@ -109,7 +109,7 @@ export const ibcBridgeMocks: { acknowledgementPacket: ( obj: IBCMethod<'sendPacket'>, - opts: { sequence: number; acknowledgement: string }, + opts: { sequence: bigint; acknowledgement: string }, ): IBCEvent<'acknowledgementPacket'> => { const { sequence, acknowledgement } = opts; return { @@ -124,8 +124,7 @@ export const ibcBridgeMocks: { sequence, source_channel: obj.packet.source_channel, source_port: obj.packet.source_port, - timeout_height: 0, - timeout_timestamp: 1712183910866313000, + timeout_timestamp: 1712183910866313000n, }, relayer: 'agoric1gtkg0g6x8lqc734ht3qe2sdkrfugpdp2h7fuu0', type: 'IBC_EVENT', @@ -173,9 +172,9 @@ export const makeFakeIBCBridge = ( * accounts. * XXX teach this about IBCConnections and store sequence on a * per-channel basis. - * @type {number} + * @type {bigint} */ - let ibcSequenceNonce = 0; + let ibcSequenceNonce = 0n; /** * The number of channels created. Currently used as a proxy to increment * fake account addresses and channels. @@ -276,7 +275,7 @@ export const makeFakeIBCBridge = ( : errorAcknowledgments.error5, }); bridgeEvents = bridgeEvents.concat(ackEvent); - ibcSequenceNonce += 1; + ibcSequenceNonce += 1n; bridgeHandler?.fromBridge(ackEvent); return ackEvent.packet; } diff --git a/packages/orchestration/types.d.ts b/packages/orchestration/types.d.ts deleted file mode 100644 index 4c253cec241..00000000000 --- a/packages/orchestration/types.d.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './src/types.js'; diff --git a/packages/swingset-liveslots/src/index.js b/packages/swingset-liveslots/src/index.js index ac3a9ddb0e6..50ca0ac4108 100644 --- a/packages/swingset-liveslots/src/index.js +++ b/packages/swingset-liveslots/src/index.js @@ -11,5 +11,4 @@ export { insistVatSyscallResult, } from './message.js'; -export * from './types.js'; -export * from './vatDataTypes.js'; +export * from './types-index.js'; diff --git a/packages/swingset-liveslots/src/types-index.d.ts b/packages/swingset-liveslots/src/types-index.d.ts new file mode 100644 index 00000000000..f3d7de61e66 --- /dev/null +++ b/packages/swingset-liveslots/src/types-index.d.ts @@ -0,0 +1,4 @@ +// Export all the types this package provides +// eslint-disable-next-line import/export +export * from './types.js'; +export * from './vatDataTypes.js'; diff --git a/packages/swingset-liveslots/src/types-index.js b/packages/swingset-liveslots/src/types-index.js new file mode 100644 index 00000000000..d382eb5feaf --- /dev/null +++ b/packages/swingset-liveslots/src/types-index.js @@ -0,0 +1,2 @@ +// Empty JS file to correspond with its .d.ts twin +export {}; diff --git a/packages/swingset-liveslots/src/vatDataTypes.js b/packages/swingset-liveslots/src/vatDataTypes.js deleted file mode 100644 index 1fad0fe84f6..00000000000 --- a/packages/swingset-liveslots/src/vatDataTypes.js +++ /dev/null @@ -1,2 +0,0 @@ -// Empty JS file to correspond with vatDataTypes.d.ts -export {}; diff --git a/packages/swingset-liveslots/src/vatDataTypes.d.ts b/packages/swingset-liveslots/src/vatDataTypes.ts similarity index 99% rename from packages/swingset-liveslots/src/vatDataTypes.d.ts rename to packages/swingset-liveslots/src/vatDataTypes.ts index 91cd954c784..2b0638b9ba5 100644 --- a/packages/swingset-liveslots/src/vatDataTypes.d.ts +++ b/packages/swingset-liveslots/src/vatDataTypes.ts @@ -30,10 +30,6 @@ export type Baggage = MapStore; type WatchedPromisesManager = ReturnType; -type Tail = T extends [head: any, ...rest: infer Rest] - ? Rest - : []; - // used to omit the 'context' parameter type OmitFirstArg = F extends (x: any, ...args: infer P) => infer R ? (...args: P) => R diff --git a/packages/time/index.js b/packages/time/index.js index bf6f74c6ceb..e3197c1a645 100644 --- a/packages/time/index.js +++ b/packages/time/index.js @@ -1,4 +1,4 @@ export * from './src/timeMath.js'; export * from './src/typeGuards.js'; // eslint-disable-next-line import/export -- just types -export * from './src/types.js'; +export * from './src/types-index.js'; diff --git a/packages/time/src/types-index.d.ts b/packages/time/src/types-index.d.ts new file mode 100644 index 00000000000..d4702960d54 --- /dev/null +++ b/packages/time/src/types-index.d.ts @@ -0,0 +1 @@ +export * from './types.js'; diff --git a/packages/time/src/types.js b/packages/time/src/types-index.js similarity index 100% rename from packages/time/src/types.js rename to packages/time/src/types-index.js diff --git a/packages/time/src/types.d.ts b/packages/time/src/types.ts similarity index 99% rename from packages/time/src/types.d.ts rename to packages/time/src/types.ts index 42e8544f36f..dd3ca10ae68 100644 --- a/packages/time/src/types.d.ts +++ b/packages/time/src/types.ts @@ -1,9 +1,8 @@ +/* eslint-disable no-use-before-define */ import type { ERef, RemotableBrand } from '@endo/eventual-send'; import type { RankComparison, RemotableObject } from '@endo/marshal'; -/// - // These aren't in the global runtime environment. They are just types that are // meant to be globally accessible as a side-effect of importing this module. /** diff --git a/packages/vats/index.js b/packages/vats/index.js index d5b8fbf49b9..6499c5ee3a9 100644 --- a/packages/vats/index.js +++ b/packages/vats/index.js @@ -2,7 +2,7 @@ import './src/core/types-ambient.js'; // eslint-disable-next-line import/export -- no named exports -export * from './src/types.js'; +export * from './src/types-index.js'; export * from './src/nameHub.js'; export * from './src/bridge.js'; export { makePromiseSpace } from './src/core/promise-space.js'; diff --git a/packages/vats/src/ibc.js b/packages/vats/src/ibc.js index 52ec6a8d566..f224184aa98 100644 --- a/packages/vats/src/ibc.js +++ b/packages/vats/src/ibc.js @@ -222,7 +222,7 @@ export const prepareIBCProtocol = (zone, powers) => { /** @type {MapStore} */ const srcPortToOutbounds = detached.mapStore('srcPortToOutbounds'); - /** @type {MapStore>>} */ + /** @type {MapStore>>} */ const channelKeyToSeqAck = detached.mapStore('channelKeyToSeqAck'); /** @type {MapStore>} */ @@ -669,7 +669,7 @@ export const prepareIBCProtocol = (zone, powers) => { /** * @param {IBCChannelID} channelID * @param {IBCPortID} portID - * @param {number} sequence + * @param {bigint} sequence */ findAckKit(channelID, portID, sequence) { const { channelKeyToSeqAck } = this.state; diff --git a/packages/vats/src/types-index.d.ts b/packages/vats/src/types-index.d.ts new file mode 100644 index 00000000000..5b21c087be6 --- /dev/null +++ b/packages/vats/src/types-index.d.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/export -- no named exports +export * from './types.js'; diff --git a/packages/vats/src/types-index.js b/packages/vats/src/types-index.js new file mode 100644 index 00000000000..cda223f55aa --- /dev/null +++ b/packages/vats/src/types-index.js @@ -0,0 +1,2 @@ +// Empty JS file to correspond with types.d.ts +export {}; diff --git a/packages/vats/src/types.d.ts b/packages/vats/src/types.ts similarity index 98% rename from packages/vats/src/types.d.ts rename to packages/vats/src/types.ts index e4a7f330ace..f3c8205fe15 100644 --- a/packages/vats/src/types.d.ts +++ b/packages/vats/src/types.ts @@ -1,7 +1,9 @@ +/* eslint-disable no-use-before-define */ import type { FungibleTokenPacketData } from '@agoric/cosmic-proto/ibc/applications/transfer/v2/packet.js'; import type { BridgeIdValue, Remote } from '@agoric/internal'; import type { Bytes } from '@agoric/network'; import type { Guarded } from '@endo/exo'; +import type { PacketSDKType } from '@agoric/cosmic-proto/ibc/core/channel/v1/channel.js'; import type { LocalChainAccount } from './localchain.js'; import type { TargetApp } from './bridge-target.js'; diff --git a/packages/vow/src/index.js b/packages/vow/src/index.js index 5b14dd2afd2..2c5348e2a12 100644 --- a/packages/vow/src/index.js +++ b/packages/vow/src/index.js @@ -6,12 +6,5 @@ export * from '../vat.js'; export { default as makeE } from './E.js'; export { VowShape, toPassableCap } from './vow-utils.js'; -/** - * @typedef {import('./tools.js').VowTools} VowTools - */ - // eslint-disable-next-line import/export -export * from './types.js'; - -// XXX re-exporting the Remote type for back-compat -export * from '@agoric/internal/src/types.js'; +export * from './types-index.js'; diff --git a/packages/vow/src/tools.js b/packages/vow/src/tools.js index 2ce1d9517e4..1764483e56b 100644 --- a/packages/vow/src/tools.js +++ b/packages/vow/src/tools.js @@ -8,36 +8,7 @@ import { makeWhen } from './when.js'; /** * @import {Zone} from '@agoric/base-zone'; * @import {Passable} from '@endo/pass-style'; - * @import {EUnwrap, IsRetryableReason, AsPromiseFunction, Vow, VowKit, EVow, PromiseVow, Watcher} from './types.js'; - */ - -/** - * @typedef VowTools - * @property {(maybeVows: unknown[]) => Vow} all Vow-tolerant implementation of Promise.all that takes an iterable of vows - * and other {@link Passable}s and returns a single {@link Vow}. It resolves - * with an array of values when all of the input's promises or vows are - * fulfilled and rejects when any of the input's promises or vows are rejected - * with the first rejection reason. - * @property {(maybeVows: unknown[]) => Vow<({status: 'fulfilled', value: any} | - * {status: 'rejected', reason: any})[]>} allSettled Vow-tolerant - * implementation of Promise.allSettled that takes an iterable of vows and other - * {@link Passable}s and returns a single {@link Vow}. It resolves when all of - * the input's promises or vows are settled with an array of settled outcome - * objects. - * @property {(maybeVows: unknown[]) => Vow} allVows - * @property {AsPromiseFunction} asPromise Convert a vow or promise to a promise, ensuring proper handling of ephemeral promises. - * @property {(fn: (...args: any[]) => Vow> | - * Awaited | PromiseVow) => Vow>} asVow Helper function that - * coerces the result of a function to a Vow. Helpful for scenarios like a - * synchronously thrown error. - * @property {() => VowKit} makeVowKit - * @property { Promise>(fnZone: Zone, name: string, fn: F) => F extends (...args: infer Args) => Promise ? (...args: Args) => Vow : never} retriable - * @property {(specimenP: EVow, watcher?: Watcher | undefined, ...watcherArgs: C) => Vow | Exclude extends never ? TResult1 : Exclude | Exclude>} watch - * @property {, TResult2 = never>(specimenP: T, onFulfilled?: ((value: EUnwrap) => TResult1 | PromiseLike) | undefined, onRejected?: ((reason: any) => TResult2 | PromiseLike) | undefined) => Promise} when Shorten `specimenP` until we achieve a final result. - * - * Does not survive upgrade (even if specimenP is a durable Vow). - * - * Use only if the Vow will resolve _promptly_ {@see {@link @agoric/swingset-vat/docs/async.md}}. + * @import {IsRetryableReason, AsPromiseFunction, Vow, VowTools} from './types.js'; */ /** diff --git a/packages/vow/src/types-index.d.ts b/packages/vow/src/types-index.d.ts new file mode 100644 index 00000000000..7c8123e0792 --- /dev/null +++ b/packages/vow/src/types-index.d.ts @@ -0,0 +1,5 @@ +// Export all the types this package provides +export * from './types.js'; + +// XXX re-exporting the Remote type for back-compat +export { Remote } from '@agoric/internal/src/types.js'; diff --git a/packages/vow/src/types-index.js b/packages/vow/src/types-index.js new file mode 100644 index 00000000000..d382eb5feaf --- /dev/null +++ b/packages/vow/src/types-index.js @@ -0,0 +1,2 @@ +// Empty JS file to correspond with its .d.ts twin +export {}; diff --git a/packages/vow/src/types.js b/packages/vow/src/types.js deleted file mode 100644 index 1370be6bd1c..00000000000 --- a/packages/vow/src/types.js +++ /dev/null @@ -1,109 +0,0 @@ -// @ts-check -export {}; - -/** - * @import {CopyTagged} from '@endo/pass-style' - * @import {RemotableObject} from '@endo/pass-style'; - * @import {Remote} from '@agoric/internal'; - * @import {prepareVowTools} from './tools.js' - */ - -/** - * @callback IsRetryableReason - * Return truthy if a rejection reason should result in a retry. - * @param {any} reason - * @param {any} priorRetryValue the previous value returned by this function - * when deciding whether to retry the same logical operation - * @returns {any} If falsy, the reason is not retryable. If truthy, the - * priorRetryValue for the next call. - */ - -/** - * @template T - * @typedef {Promise>} PromiseVow Return type of a function that may - * return a promise or a vow. - */ - -/** - * @template T - * @typedef {T | PromiseLike} ERef - */ - -/** - * Eventually a value T or Vow for it. - * @template T - * @typedef {ERef>} EVow - */ - -/** - * Follow the chain of vow shortening to the end, returning the final value. - * This is used within E, so we must narrow the type to its remote form. - * @template T - * @typedef {( - * T extends Vow ? EUnwrap : - * T extends PromiseLike ? EUnwrap : - * T - * )} EUnwrap - */ - -/** - * @template [T=any] - * @typedef {object} VowV0 The first version of the vow implementation - * object. CAVEAT: These methods must never be changed or added to, to provide - * forward/backward compatibility. Create a new object and bump its version - * number instead. - * - * @property {() => Promise} shorten Attempt to unwrap all vows in this - * promise chain, returning a promise for the final value. A rejection may - * indicate a temporary routing failure requiring a retry, otherwise that the - * decider of the terminal promise rejected it. - */ - -/** - * @template [T=any] - * @typedef {object} VowPayload - * @property {RemotableObject & Remote>} vowV0 - */ - -/** - * Vows are objects that represent promises that can be stored durably. - * @template [T=any] - * @typedef {CopyTagged<'Vow', VowPayload>} Vow - */ - -/** - * @template [T=any] - * @typedef {{ - * vow: Vow, - * resolver: VowResolver, - * }} VowKit - */ - -/** - * @template [T=any] - * @typedef {{ resolve(value?: T | PromiseVow): void, reject(reason?: any): void }} VowResolver - */ - -/** - * @template [T=any] - * @template [TResult1=T] - * @template [TResult2=never] - * @template {any[]} [C=any[]] watcher args - * @typedef {object} Watcher - * @property {(value: T, ...args: C) => Vow | PromiseVow | TResult1} [onFulfilled] - * @property {(reason: any, ...args: C) => Vow | PromiseVow | TResult2} [onRejected] - */ - -/** - * Converts a vow or promise to a promise, ensuring proper handling of ephemeral promises. - * - * @template [T=any] - * @template [TResult1=T] - * @template [TResult2=never] - * @template {any[]} [C=any[]] - * @callback AsPromiseFunction - * @param {ERef>} specimenP - * @param {Watcher} [watcher] - * @param {C} [watcherArgs] - * @returns {Promise} - */ diff --git a/packages/vow/src/types.ts b/packages/vow/src/types.ts new file mode 100644 index 00000000000..dee2d14531f --- /dev/null +++ b/packages/vow/src/types.ts @@ -0,0 +1,177 @@ +/* eslint-disable no-use-before-define */ +import type { Remote } from '@agoric/internal'; +import type { Zone } from '@agoric/zone'; +import type { CopyTagged, RemotableObject } from '@endo/pass-style'; + +/** + * Return truthy if a rejection reason should result in a retry. + */ +export type IsRetryableReason = (reason: any, priorRetryValue: any) => any; + +/** + * Return type of a function that may + * return a promise or a vow. + */ +export type PromiseVow = Promise>; + +export type ERef = T | PromiseLike; +/** + * Eventually a value T or Vow for it. + */ +export type EVow = ERef>; + +/** + * Follow the chain of vow shortening to the end, returning the final value. + * This is used within E, so we must narrow the type to its remote form. + */ +export type EUnwrap = + T extends Vow + ? EUnwrap + : T extends PromiseLike + ? EUnwrap + : T; + +/** + * The first version of the vow implementation + * object. CAVEAT: These methods must never be changed or added to, to provide + * forward/backward compatibility. Create a new object and bump its version + * number instead. + */ +export type VowV0 = { + /** + * Attempt to unwrap all vows in this + * promise chain, returning a promise for the final value. A rejection may + * indicate a temporary routing failure requiring a retry, otherwise that the + * decider of the terminal promise rejected it. + */ + shorten: () => Promise; +}; + +export type VowPayload = { + vowV0: RemotableObject & Remote>; +}; + +/** + * Vows are objects that represent promises that can be stored durably. + */ +export type Vow = CopyTagged<'Vow', VowPayload>; + +export type VowKit = { + vow: Vow; + resolver: VowResolver; +}; + +export type VowResolver = { + resolve(value?: T | PromiseVow): void; + reject(reason?: any): void; +}; + +export type Watcher< + T = any, + TResult1 = T, + TResult2 = never, + C extends any[] = any[], +> = { + onFulfilled?: + | (( + value: T, + ...args: C + ) => Vow | PromiseVow | TResult1) + | undefined; + onRejected?: + | (( + reason: any, + ...args: C + ) => Vow | PromiseVow | TResult2) + | undefined; +}; + +/** + * Converts a vow or promise to a promise, ensuring proper handling of ephemeral promises. + */ +export type AsPromiseFunction< + T = any, + TResult1 = T, + TResult2 = never, + C extends any[] = any[], +> = ( + specimenP: ERef>, + watcher?: Watcher | undefined, + watcherArgs?: C | undefined, +) => Promise; + +export type VowTools = { + /** + * Vow-tolerant implementation of Promise.all that takes an iterable of vows + * and other {@link Passable}s and returns a single {@link Vow}. It resolves + * with an array of values when all of the input's promises or vows are + * fulfilled and rejects when any of the input's promises or vows are rejected + * with the first rejection reason. + */ + all: (maybeVows: unknown[]) => Vow; + /** + * Vow-tolerant + * implementation of Promise.allSettled that takes an iterable of vows and other + * {@link Passable}s and returns a single {@link Vow}. It resolves when all of + * the input's promises or vows are settled with an array of settled outcome + * objects. + */ + allSettled: (maybeVows: unknown[]) => Vow< + ( + | { + status: 'fulfilled'; + value: any; + } + | { + status: 'rejected'; + reason: any; + } + )[] + >; + allVows: (maybeVows: unknown[]) => Vow; + /** + * Convert a vow or promise to a promise, ensuring proper handling of ephemeral promises. + */ + asPromise: AsPromiseFunction; + /** + * Helper function that + * coerces the result of a function to a Vow. Helpful for scenarios like a + * synchronously thrown error. + */ + asVow: ( + fn: (...args: any[]) => Vow> | Awaited | PromiseVow, + ) => Vow>; + makeVowKit: () => VowKit; + retriable: Promise>( + fnZone: Zone, + name: string, + fn: F, + ) => F extends (...args: infer Args) => Promise + ? (...args: Args) => Vow + : never; + watch: ( + specimenP: EVow, + watcher?: Watcher | undefined, + ...watcherArgs: C + ) => Vow< + Exclude | Exclude extends never + ? TResult1 + : Exclude | Exclude + >; + /** + * Shorten `specimenP` until we achieve a final result. + * + * Does not survive upgrade (even if specimenP is a durable Vow). + * + * Use only if the Vow will resolve _promptly_ {@see {@link @agoric/swingset-vat/docs/async.md}}. + */ + when: , TResult2 = never>( + specimenP: T, + onFulfilled?: + | ((value: EUnwrap) => TResult1 | PromiseLike) + | undefined, + onRejected?: + | ((reason: any) => TResult2 | PromiseLike) + | undefined, + ) => Promise; +}; diff --git a/packages/vow/test/types.test-d.ts b/packages/vow/test/types.test-d.ts index 6f2968a7631..0993fe14e0b 100644 --- a/packages/vow/test/types.test-d.ts +++ b/packages/vow/test/types.test-d.ts @@ -1,7 +1,7 @@ import { expectType } from 'tsd'; + import type { Zone } from '@agoric/base-zone'; -import type { Vow } from '../src/types.js'; -import type { VowTools } from '../src/tools.js'; +import type { Vow, VowTools } from '../src/types.js'; const vt: VowTools = null as any; diff --git a/tsconfig-build-options.json b/tsconfig-build-options.json index 9be1f99a8a6..7a1f311017e 100644 --- a/tsconfig-build-options.json +++ b/tsconfig-build-options.json @@ -6,7 +6,9 @@ "declarationMap": true }, "exclude": [ + // Implies there's a types-index.d.ts already which shouldn't be overwritten + "**/types-index.js", "**/scripts", - "**/test" + "**/test", ] }