Skip to content

Commit

Permalink
[Experimental] Add Typekit for @typespec/http (#5340)
Browse files Browse the repository at this point in the history
This PR includes:

* Consolidating the format used to define Typekits
* Adding @typespec/compiler/experimental/typekit sub export
* Adding @typespec/http/experimental/typekit sub export and
implementation

Note: experimental/typekit subpaths were added for improved UX. The
typekit import has a side effect which is augmenting existing typekit so
having it within its own export isolates the side effect and makes it
easier consumers to migrate once it becomes stable into
`@typespec/compiler/typekit` and `@typespec/http/typekit` respectively
  • Loading branch information
joheredi authored Jan 24, 2025
1 parent 555612d commit 2389dc7
Show file tree
Hide file tree
Showing 20 changed files with 1,595 additions and 526 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
changeKind: feature
packages:
- "@typespec/compiler"
- "@typespec/http"
---

Add Experimental Typekit helpers for @typespec/http
4 changes: 4 additions & 0 deletions packages/compiler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
"./experimental": {
"types": "./dist/src/experimental/index.d.ts",
"default": "./dist/src/experimental/index.js"
},
"./experimental/typekit": {
"types": "./dist/src/experimental/typekit/index.d.ts",
"default": "./dist/src/experimental/typekit/index.js"
}
},
"browser": {
Expand Down
116 changes: 63 additions & 53 deletions packages/compiler/src/experimental/typekit/kits/literal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,71 +3,81 @@ import type { BooleanLiteral, NumericLiteral, StringLiteral, Type } from "../../
import { defineKit } from "../define-kit.js";

/** @experimental */
interface LiteralKit {
literal: {
/**
* Create a literal type from a JavaScript value.
*
* @param value The JavaScript value to turn into a TypeSpec literal type.
*/
create(value: string | number | boolean): StringLiteral | NumericLiteral | BooleanLiteral;
export interface LiteralKit {
/**
* Create a literal type from a JavaScript value.
*
* @param value The JavaScript value to turn into a TypeSpec literal type.
*/
create(value: string | number | boolean): StringLiteral | NumericLiteral | BooleanLiteral;

/**
* Create a string literal type from a JavaScript string value.
*
* @param value The string value.
*/
createString(value: string): StringLiteral;

/**
* Create a string literal type from a JavaScript string value.
*
* @param value The string value.
*/
createString(value: string): StringLiteral;
/**
* Create a numeric literal type from a JavaScript number value.
*
* @param value The numeric value.
*/
createNumeric(value: number): NumericLiteral;

/**
* Create a numeric literal type from a JavaScript number value.
*
* @param value The numeric value.
*/
createNumeric(value: number): NumericLiteral;
/**
* Create a boolean literal type from a JavaScript boolean value.
*
* @param value The boolean value.
*/
createBoolean(value: boolean): BooleanLiteral;

/**
* Create a boolean literal type from a JavaScript boolean value.
*
* @param value The boolean value.
*/
createBoolean(value: boolean): BooleanLiteral;
/**
* Check if `type` is a literal type.
*
* @param type The type to check.
*/
is(type: Type): type is StringLiteral | NumericLiteral | BooleanLiteral;

/**
* Check if `type` is a literal type.
*
* @param type The type to check.
*/
is(type: Type): type is StringLiteral | NumericLiteral | BooleanLiteral;
/**
* Check if `type` is a string literal type.
*
* @param type The type to check.
*/
isString(type: Type): type is StringLiteral;

/**
* Check if `type` is a string literal type.
*
* @param type The type to check.
*/
isString(type: Type): type is StringLiteral;
/**
* Check if `type` is a numeric literal type.
*
* @param type The type to check.
*/
isNumeric(type: Type): type is NumericLiteral;

/**
* Check if `type` is a numeric literal type.
*
* @param type The type to check.
*/
isNumeric(type: Type): type is NumericLiteral;
/**
* Check if `type` is a boolean literal type.
*
* @param type The type to check.
*/
isBoolean(type: Type): type is BooleanLiteral;
}

/**
* Check if `type` is a boolean literal type.
*
* @param type The type to check.
*/
isBoolean(type: Type): type is BooleanLiteral;
};
interface TypekitExtension {
/**
* Utilities for working with literal types.
*
* Literal types are types that represent a single value, such as a string,
* number, or boolean.
*
* @experimental
*/
literal: LiteralKit;
}

declare module "../define-kit.js" {
interface Typekit extends LiteralKit {}
interface Typekit extends TypekitExtension {}
}

defineKit<LiteralKit>({
defineKit<TypekitExtension>({
literal: {
create(value) {
if (typeof value === "string") {
Expand Down
16 changes: 12 additions & 4 deletions packages/compiler/src/experimental/typekit/kits/model-property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@ import { getVisibilityForClass } from "../../../core/visibility/core.js";
import { EncodeData, getEncode, getFormat } from "../../../lib/decorators.js";
import { defineKit } from "../define-kit.js";

/** @experimental */
/**
* @experimental
* Utilities for working with model properties.
*
* For many reflection operations, the metadata being asked for may be found
* on the model property or the type of the model property. In such cases,
* these operations will return the metadata from the model property if it
* exists, or the type of the model property if it exists.
*/
export interface ModelPropertyKit {
/**
* Check if the given `type` is a model property.
Expand Down Expand Up @@ -34,7 +42,7 @@ export interface ModelPropertyKit {
getVisibilityForClass(property: ModelProperty, visibilityClass: Enum): Set<EnumMember>;
}

interface TypeKit {
interface TypekitExtension {
/**
* Utilities for working with model properties.
*
Expand All @@ -47,10 +55,10 @@ interface TypeKit {
}

declare module "../define-kit.js" {
interface Typekit extends TypeKit {}
interface Typekit extends TypekitExtension {}
}

defineKit<TypeKit>({
defineKit<TypekitExtension>({
modelProperty: {
is(type) {
return type.kind === "ModelProperty";
Expand Down
64 changes: 48 additions & 16 deletions packages/compiler/src/experimental/typekit/kits/model.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getEffectiveModelType } from "../../../core/checker.js";
import type { Model, ModelProperty, SourceModel, Type } from "../../../core/types.js";
import { createRekeyableMap } from "../../../utils/misc.js";
import { defineKit } from "../define-kit.js";
Expand Down Expand Up @@ -32,29 +33,57 @@ interface ModelDescriptor {
sourceModels?: SourceModel[];
}

/**
* Utilities for working with models.
* @experimental
*/
export interface ModelKit {
model: {
/**
* Create a model type.
*
* @param desc The descriptor of the model.
*/
create(desc: ModelDescriptor): Model;
/**
* Create a model type.
*
* @param desc The descriptor of the model.
*/
create(desc: ModelDescriptor): Model;

/**
* Check if the given `type` is a model..
*
* @param type The type to check.
*/
is(type: Type): type is Model;
};
/**
* Check if the given `type` is a model..
*
* @param type The type to check.
*/
is(type: Type): type is Model;

/**
* If the input is anonymous (or the provided filter removes properties)
* and there exists a named model with the same set of properties
* (ignoring filtered properties), then return that named model.
* Otherwise, return the input unchanged.
*
* This can be used by emitters to find a better name for a set of
* properties after filtering. For example, given `{ @metadata prop:
* string} & SomeName`, and an emitter that wishes to discard properties
* marked with `@metadata`, the emitter can use this to recover that the
* best name for the remaining properties is `SomeName`.
*
* @param model The input model
* @param filter An optional filter to apply to the input model's
* properties.
*/
getEffectiveModel(model: Model, filter?: (property: ModelProperty) => boolean): Model;
}

interface TypekitExtension {
/**
* Utilities for working with models.
* @experimental
*/
model: ModelKit;
}

declare module "../define-kit.js" {
interface Typekit extends ModelKit {}
interface Typekit extends TypekitExtension {}
}

export const ModelKit = defineKit<ModelKit>({
export const ModelKit = defineKit<TypekitExtension>({
model: {
create(desc) {
const properties = createRekeyableMap(Array.from(Object.entries(desc.properties)));
Expand All @@ -76,5 +105,8 @@ export const ModelKit = defineKit<ModelKit>({
is(type) {
return type.kind === "Model";
},
getEffectiveModel(model, filter?: (property: ModelProperty) => boolean) {
return getEffectiveModelType(this.program, model, filter);
},
},
});
Loading

0 comments on commit 2389dc7

Please sign in to comment.