Skip to content

Commit

Permalink
Use type-fest's tagged type for simple nominal typing
Browse files Browse the repository at this point in the history
  • Loading branch information
twschiller committed Jul 6, 2024
1 parent 63ee7cf commit dc5eac7
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 59 deletions.
2 changes: 2 additions & 0 deletions src/types/modComponentTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,8 @@ export type HydratedModComponent<Config extends UnknownObject = UnknownObject> =
/**
* Brand for nominal typing.
*/
// XXX: defining our own brand vs. using type-fest's tagged type because we need to be able to apply the brand
// in cooky-cutter factory definitions
_hydratedModComponentBrand: never;
};

Expand Down
16 changes: 4 additions & 12 deletions src/types/registryTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,12 @@

import { type UUID } from "@/types/stringTypes";
import { type ApiVersion } from "@/types/runtimeTypes";
import { type ValueOf } from "type-fest";
import { type Tagged, type ValueOf } from "type-fest";

/**
* A brick registry id conforming to `@scope/collection/name`
*/
export type RegistryId = string & {
// Nominal subtyping
_registryIdBrand: never;
};
export type RegistryId = Tagged<string, "RegistryId">;

/**
* Scope for inner definitions
Expand Down Expand Up @@ -53,9 +50,7 @@ export type DefinitionKind = ValueOf<typeof DefinitionKinds>;
/**
* Simple semantic version number, major.minor.patch
*/
export type SemVerString = string & {
_semVerBrand: never;
};
export type SemVerString = Tagged<string, "SemVer">;

/**
* Metadata about a Brick, StarterBrick, Integration, or Mod.
Expand Down Expand Up @@ -117,10 +112,7 @@ export type InnerDefinitions = Record<string, UnknownObject>;
* A reference to an entry in the mod's `definitions` map. _Not a valid RegistryId_.
* @see InnerDefinitions
*/
export type InnerDefinitionRef = string & {
// Nominal subtyping
_innerDefinitionRefBrand: never;
};
export type InnerDefinitionRef = Tagged<string, "InnerDefinitionRef">;

export interface RegistryItem<T extends RegistryId = RegistryId> {
id: T;
Expand Down
40 changes: 17 additions & 23 deletions src/types/runtimeTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import { type ComponentType } from "react";
import { type SafeHTML, type UUID } from "@/types/stringTypes";
import { type SanitizedIntegrationConfig } from "@/integrations/integrationTypes";
import { type Primitive } from "type-fest";
import { type Primitive, type Tagged } from "type-fest";
import { type Logger } from "@/types/loggerTypes";
import { type BrickPipeline } from "@/bricks/types";
import { type PanelPayload } from "./sidebarTypes";
Expand Down Expand Up @@ -54,9 +54,7 @@ export function isDocument(root: SelectorRoot): root is Document {
* A reference to an element on the page.
* @see getReferenceForElement
*/
export type ElementReference = UUID & {
_elementReferenceBrand: never;
};
export type ElementReference = Tagged<UUID, "ElementReference">;

/**
* A reference to a React component produced by a Renderer brick.
Expand All @@ -75,17 +73,15 @@ export type RendererOutput = SafeHTML | ComponentRef;
/**
* A valid identifier for a brick output key or a service key. (Does not include the preceding "@".)
*/
export type OutputKey = string & {
_outputKeyBrand: never;
};
export type OutputKey = Tagged<string, "OutputKey">;

/**
* A variable with a "@"-prefix that refers to an integration
*/
export type IntegrationDependencyVarRef = string & {
// Preserve legacy branding field name for backwards compatibility
_serviceVarRefBrand: never;
};
export type IntegrationDependencyVarRef = Tagged<
string,
"IntegrationDependencyVarRef"
>;

/**
* A text template engine.
Expand Down Expand Up @@ -226,12 +222,13 @@ export type OptionsArgs = Record<string, Primitive>;
* @see RenderedArgs
* @see BrickConfig.outputKey
*/
export type BrickArgsContext = UnknownObject & {
// Nominal typing
_blockArgsContextBrand: never;
"@input": UnknownObject;
"@options"?: OptionsArgs;
};
export type BrickArgsContext = Tagged<
UnknownObject & {
"@input": UnknownObject;
"@options"?: OptionsArgs;
},
"BrickArgsContext"
>;

/**
* Returns an object as a BrickArgsContext, or throw a TypeError if it's not a valid context.
Expand All @@ -257,19 +254,16 @@ export function validateBrickArgsContext(obj: UnknownObject): BrickArgsContext {
export type BrickArgs<
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- brick is responsible for providing shape
T extends Record<string, any> = Record<string, any>,
> = T & {
_blockArgBrand: never;
};
> = Tagged<T, "BrickArgs">;

/**
* The non-validated arguments to pass into the `run` method of a Brick.
* @see BrickArgs
*/
export type RenderedArgs = UnknownObject & {
_renderedArgBrand: never;
};
export type RenderedArgs = Tagged<UnknownObject, "RenderedArgs">;

export type IntegrationsContextValue = {
// NOTE: this is not a nominal type brand. The `__service` key is actually used in the runtime.
__service: SanitizedIntegrationConfig;
[prop: string]: string | SanitizedIntegrationConfig | null;
};
Expand Down
32 changes: 8 additions & 24 deletions src/types/stringTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { type Tagged } from "type-fest";

export const VALID_UUID_REGEX =
/^[\dA-Fa-f]{8}-[\dA-Fa-f]{4}-[1-5][\dA-Fa-f]{3}-[89ABab][\dA-Fa-f]{3}-[\dA-Fa-f]{12}$/;

Expand All @@ -23,49 +25,31 @@ export const VALID_UUID_REGEX =
* @see uuidv4
* @see isUUID
*/
export type UUID = string & {
// Nominal subtyping
_uuidBrand: never;
};
export type UUID = Tagged<string, "UUID">;

/**
* An ISO timestamp string
*/
export type Timestamp = string & {
// Nominal subtyping
_uuidTimestamp: never;
};
export type Timestamp = Tagged<string, "Timestamp">;

/**
* Base64 encoded JSON string
*/
export type EncodedJSON = string & {
// Nominal subtyping
_encodedJSONBrand: never;
};
export type EncodedJSON = Tagged<string, "EncodedJSON">;

/**
* A UTC timestamp followed by a sequence number valid in the current context.
* Useful to determine order of two calls to getTimedSequence.
*/
export type TimedSequence = string & {
// Nominal subtyping
_timedSequence: never;
};
export type TimedSequence = Tagged<string, "TimeSequence">;

/**
* A string known not to be tainted with user-generated input.
*/
export type SafeString = string & {
// Nominal subtyping
_safeStringBrand: never;
};
export type SafeString = Tagged<string, "SafeString">;

/**
* Rendered HTML that has been sanitized.
* @see sanitize
*/
export type SafeHTML = string & {
// Nominal subtyping
_safeHTMLBrand: never;
};
export type SafeHTML = Tagged<string, "SafeHTML">;

0 comments on commit dc5eac7

Please sign in to comment.