Skip to content

Commit

Permalink
Efv2 to main (microsoft#5996)
Browse files Browse the repository at this point in the history
# Efv2 Integration  

This PR introduces the work on **Emitter Framework v2** from the feature
branch into main. The existing emitter framework functionality remains
unchanged for now, the new framework is added as a new package
@typespec/emitter-framework

### **Core Changes**  

- **Compiler Alloy Support**  
- The compiler now detects if `$onEmit` returns an **Alloy component**.
If so, it loads Alloy and calls `render`.
  - This enables emitters to use Alloy components.  

- **New Package: `@typespec/emitter-framework`**  
- Wraps raw Alloy components to accept **TypeSpec types as props** and
automatically generate language elements.
- Currently includes a **subexport for TypeScript (`/typescript`)**,
with plans to extend support to other languages.
- Example: `ts.Interface` can take a `Model` or `Interface` and directly
render a TypeScript interface with model properties or operation
signatures.

- **New Package: `@typespec/http-client`**  
- Provides **TypeKits** for querying the **type graph** in the context
of an **HTTP client**.
- The goal is to eventually **power TCGC** and host decorators that make
sense in this context.
- Remains **decoupled from Azure**, but we may later introduce an
extension package (`@azure-tools/http-client-azure`) for Azure-specific
logic and decorators.

###  **Follow Up Work**  
- Deprecate EFv1
- A follow up API review and clean-up will be needed, specifically
around TypeKits
  • Loading branch information
joheredi authored Feb 13, 2025
1 parent 4aff72e commit ba241e8
Show file tree
Hide file tree
Showing 119 changed files with 9,814 additions and 67 deletions.
11 changes: 11 additions & 0 deletions .chronus/changes/joheredi-efv2-main-2025-1-13-1-49-16.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
changeKind: feature
packages:
- "@typespec/html-program-viewer"
- "@typespec/http-client-csharp"
- "@typespec/http-client-java"
- "@typespec/http-server-javascript"
- "@typespec/http"
---

Emitter Framework V2
8 changes: 8 additions & 0 deletions .chronus/changes/joheredi-efv2-main-2025-1-13-21-57-25.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
changeKind: feature
packages:
- "@typespec/emitter-framework"
- "@typespec/http-client"
---

Adding Emitter Framework and Http Client packages
7 changes: 7 additions & 0 deletions .chronus/changes/joheredi-efv2-main-2025-1-13-5-45-17.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@typespec/compiler"
---

Add Typekits to support EFV2
13 changes: 13 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"plugins": [
"./packages/prettier-plugin-typespec/dist/index.js",
"./node_modules/prettier-plugin-organize-imports/index.js",
"@alloy-js/prettier-plugin-alloy",
"prettier-plugin-astro",
"prettier-plugin-sh"
],
Expand All @@ -19,6 +20,18 @@
"parser": "typespec"
}
},
{
"files": ["packages/http-client/**/*.tsx"],
"options": {
"parser": "alloy-ts"
}
},
{
"files": ["packages/emitter-framework/**/*.tsx"],
"options": {
"parser": "alloy-ts"
}
},
{
"files": "*.astro",
"options": {
Expand Down
1 change: 1 addition & 0 deletions cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ words:
- imple
- Infima
- initcs
- Initializable
- inlines
- inmemory
- instanceid
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"upload-manifest": "pnpm -r --filter=@typespec/http-specs run upload-manifest"
},
"devDependencies": {
"@alloy-js/prettier-plugin-alloy": "^0.1.0",
"@chronus/chronus": "^0.14.1",
"@chronus/github": "^0.4.7",
"@eslint/js": "^9.18.0",
Expand Down
19 changes: 18 additions & 1 deletion packages/compiler/src/experimental/typekit/define-kit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export type StripGuards<T> = {
: StripGuards<T[K]>;
};

export const TypekitNamespaceSymbol = Symbol.for("TypekitNamespace");

/**
* Defines an extension to the Typekit interface.
*
Expand All @@ -43,6 +45,21 @@ export function defineKit<T extends Record<string, any>>(
source: StripGuards<T> & ThisType<Typekit>,
): void {
for (const [name, fnOrNs] of Object.entries(source)) {
TypekitPrototype[name] = fnOrNs;
let kits = fnOrNs;

if (TypekitPrototype[name] !== undefined) {
kits = { ...TypekitPrototype[name], ...fnOrNs };
}

// Tag top-level namespace objects with the symbol
if (typeof kits === "object" && kits !== null) {
Object.defineProperty(kits, TypekitNamespaceSymbol, {
value: true,
enumerable: false, // Keep the symbol non-enumerable
configurable: false,
});
}

TypekitPrototype[name] = kits;
}
}
13 changes: 10 additions & 3 deletions packages/compiler/src/experimental/typekit/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { compilerAssert, Program } from "../../core/index.js";
import { Realm } from "../realm.js";
import { Typekit, TypekitPrototype } from "./define-kit.js";
import { Typekit, TypekitNamespaceSymbol, TypekitPrototype } from "./define-kit.js";

export * from "./define-kit.js";
export * from "./kits/index.js";
Expand Down Expand Up @@ -32,14 +32,16 @@ export function createTypekit(realm: Realm): Typekit {

const value = Reflect.get(target, prop, receiver);

// Wrap functions to set `this` correctly
if (typeof value === "function") {
return function (this: any, ...args: any[]) {
return value.apply(proxy, args);
};
}

if (typeof value === "object" && value !== null) {
return new Proxy(value, handler);
// Only wrap objects marked as Typekit namespaces
if (typeof value === "object" && value !== null && isTypekitNamespace(value)) {
return new Proxy(value, handler); // Wrap namespace objects
}

return value;
Expand All @@ -50,6 +52,11 @@ export function createTypekit(realm: Realm): Typekit {
return proxy;
}

// Helper function to check if an object is a Typekit namespace
function isTypekitNamespace(obj: any): boolean {
return obj && !!obj[TypekitNamespaceSymbol];
}

// #region Default Typekit

const CURRENT_PROGRAM = Symbol.for("TypeSpec.Typekit.CURRENT_PROGRAM");
Expand Down
56 changes: 56 additions & 0 deletions packages/compiler/src/experimental/typekit/kits/array.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { isArrayModelType } from "../../../core/type-utils.js";
import { Model, Type } from "../../../core/types.js";
import { defineKit } from "../define-kit.js";

/**
* @experimental
*/
export interface ArrayKit {
/**
* Check if a type is an array.
*/
is(type: Type): boolean;
/**
* Get the element type of an array.
*/
getElementType(type: Model): Type;
/**
* Create an array type.
*/
create(elementType: Type): Model;
}

interface TypekitExtension {
/** @experimental */
array: ArrayKit;
}

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

defineKit<TypekitExtension>({
array: {
is(type) {
return (
type.kind === "Model" && isArrayModelType(this.program, type) && type.properties.size === 0
);
},
getElementType(type) {
if (!this.array.is(type)) {
throw new Error("Type is not an array.");
}
return type.indexer!.value;
},
create(elementType) {
return this.model.create({
name: "Array",
properties: {},
indexer: {
key: this.builtin.integer,
value: elementType,
},
});
},
},
});
Loading

0 comments on commit ba241e8

Please sign in to comment.