Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions packages/compiler/src/utils/state-accessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ export function useStateMap<K extends Type, V>(

type StateSetGetter<K extends Type> = (program: Program, type: K) => boolean;
type StateSetSetter<K extends Type> = (program: Program, type: K) => void;
type StateSetMapGetter<K extends Type> = (program: Program) => Set<K>;

export function useStateSet<K extends Type>(key: symbol): [StateSetGetter<K>, StateSetSetter<K>] {
export function useStateSet<K extends Type>(
key: symbol,
): [StateSetGetter<K>, StateSetSetter<K>, StateSetMapGetter<K>] {
const getter = (program: Program, target: K) => program.stateSet(key).has(target);
const setter = (program: Program, target: K) => program.stateSet(key).add(target);
const setGetter = (program: Program) => program.stateSet(key);

return [getter, setter];
return [getter, setter, setGetter as any];
}
52 changes: 52 additions & 0 deletions packages/http-client/generated-defs/TypeSpec.HttpClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type {
DecoratorContext,
Enum,
EnumMember,
Interface,
Model,
ModelProperty,
Namespace,
Union,
UnionVariant,
} from "@typespec/compiler";

export interface ClientDecoratorOptions {
readonly name?: string;
readonly emitterScope?: string;
}

export interface ClientNameOptions {
readonly emitterScope?: string;
}

/**
* Create a TypeSpec.HttpClient.Client client out of a namespace or interface
*
* @example
* ```typespec
* @client
* namespace MyService {}
* ```
* @example
* ```typespec
* @client
* interface MyService {}
* ```
*/
export type ClientDecorator = (
context: DecoratorContext,
target: Namespace | Interface,
options?: ClientDecoratorOptions,
) => void;

export type ClientNameDecorator = (
context: DecoratorContext,
target: Namespace | Interface | Model | ModelProperty | Enum | EnumMember | Union | UnionVariant,
name: string,
options?: ClientNameOptions,
) => void;

export type TypeSpecHttpClientDecorators = {
client: ClientDecorator;
clientName: ClientNameDecorator;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// An error in the imports would mean that the decorator is not exported or
// doesn't have the right name.

import { $decorators } from "@typespec/http-client";
import type { TypeSpecHttpClientDecorators } from "./TypeSpec.HttpClient.js";

/**
* An error here would mean that the exported decorator is not using the same signature. Make sure to have export const $decName: DecNameDecorator = (...) => ...
*/
const _: TypeSpecHttpClientDecorators = $decorators["TypeSpec.HttpClient"];
45 changes: 45 additions & 0 deletions packages/http-client/lib/decorators.tsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using Reflection;

namespace TypeSpec.HttpClient;

model ClientDecoratorOptions {
/**
* The name of the client.
* If not specified, the name will be derived from the namespace or interface name.
*/
name?: string;

...EmitterScopeOptions;
}

model EmitterScopeOptions {
/**
* Specifies the target emitters that the decorator should apply to. If not set, the decorator will be applied to all emitters by default.
*/
emitterScope?: string;
}

/**
* Create a TypeSpec.HttpClient.Client client out of a namespace or interface
* @example
* ```typespec
* @client
* namespace MyService {}
* ```
* @example
* ```typespec
* @client
* interface MyService {}
* ```
*/
extern dec client(target: Namespace | Interface, options?: valueof ClientDecoratorOptions);

model ClientNameOptions {
...EmitterScopeOptions;
}

extern dec clientName(
target: Namespace | Interface | Model | ModelProperty | Enum | EnumMember | Union | UnionVariant,
name: valueof string,
options?: valueof ClientNameOptions
);
2 changes: 2 additions & 0 deletions packages/http-client/lib/main.tsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import "./decorators.tsp";
import "../dist/src/tsp-index.js";
11 changes: 8 additions & 3 deletions packages/http-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
"type": "module",
"main": "dist/src/index.js",
"license": "MIT",
"tspMain": "./lib/main.tsp",
"exports": {
".": {
"import": "./dist/src/index.js"
"import": "./dist/src/index.js",
"typespec": "./lib/main.tsp"
},
"./testing": {
"import": "./dist/src/testing/index.js"
Expand All @@ -23,7 +25,8 @@
"@alloy-js/typescript": "^0.18.0",
"@typespec/compiler": "workspace:^",
"@typespec/emitter-framework": "workspace:^",
"@typespec/http": "workspace:^"
"@typespec/http": "workspace:^",
"@typespec/versioning": "workspace:^"
},
"devDependencies": {
"@alloy-js/cli": "^0.18.0",
Expand All @@ -34,13 +37,15 @@
"@typespec/compiler": "workspace:^",
"@typespec/emitter-framework": "workspace:^",
"@typespec/http": "workspace:^",
"@typespec/tspd": "workspace:^",
"eslint": "^9.23.0",
"prettier": "~3.6.2",
"typescript": "~5.8.2",
"vitest": "^3.1.2"
},
"scripts": {
"build": "alloy build",
"build": "npm run gen-extern-signature && alloy build",
"gen-extern-signature": "tspd --enable-experimental gen-extern-signature .",
"clean": "rimraf ./dist",
"watch": "alloy build --watch",
"test": "vitest run",
Expand Down
30 changes: 15 additions & 15 deletions packages/http-client/src/client-library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import { Enum, Model, Namespace, Program, Union } from "@typespec/compiler";
import { unsafe_Mutator } from "@typespec/compiler/experimental";
import { $ } from "@typespec/compiler/typekit";
import { HttpOperation } from "@typespec/http";
import { Client, InternalClient } from "./interfaces.js";
import { _Client, _InternalClient } from "./interfaces.js";
import { reportDiagnostic } from "./lib.js";
import { collectDataTypes } from "./utils/type-collector.js";

export interface ClientLibrary {
topLevel: Client[];
topLevel: _Client[];
dataTypes: Array<Model | Union | Enum>;
getClientForOperation(operation: HttpOperation): Client | undefined;
getClientForOperation(operation: HttpOperation): _Client | undefined;
}

export interface CreateClientLibraryOptions {
Expand All @@ -27,9 +27,9 @@ function hasGlobalOperations(program: Program, namespace: Namespace): boolean {
return false;
}

function getUserDefinedSubClients(program: Program, namespace: Namespace): InternalClient[] {
function getUserDefinedSubClients(program: Program, namespace: Namespace): _InternalClient[] {
const tk = $(program);
const clients: InternalClient[] = [];
const clients: _InternalClient[] = [];

for (const subNs of namespace.namespaces.values()) {
if (!tk.type.isUserDefined(subNs)) {
Expand All @@ -44,14 +44,14 @@ function getUserDefinedSubClients(program: Program, namespace: Namespace): Inter
return clients;
}

function getEffectiveClient(program: Program, namespace: Namespace): InternalClient | undefined {
function getEffectiveClient(program: Program, namespace: Namespace): _InternalClient | undefined {
const tk = $(program);
if (namespace.operations.size > 0 || namespace.interfaces.size > 0) {
// It has content so it should be a client
return tk.client.getClient(namespace);
}

const effectiveClients: InternalClient[] = [];
const effectiveClients: _InternalClient[] = [];

// It has no content so we need to check its children
for (const subNs of namespace.namespaces.values()) {
Expand All @@ -73,7 +73,7 @@ function getEffectiveClient(program: Program, namespace: Namespace): InternalCli
return undefined;
}

const operationClientMap = new Map<Program, Map<HttpOperation, Client>>();
const operationClientMap = new Map<Program, Map<HttpOperation, _Client>>();

export function createClientLibrary(
program: Program,
Expand All @@ -82,10 +82,10 @@ export function createClientLibrary(
const tk = $(program);

if (!operationClientMap.has(program)) {
operationClientMap.set(program, new Map<HttpOperation, Client>());
operationClientMap.set(program, new Map<HttpOperation, _Client>());
}

let topLevel: InternalClient[] = [];
let topLevel: _InternalClient[] = [];
const dataTypes = new Set<Model | Union | Enum>();

// Need to find out if we need to create a client for the global namespace.
Expand All @@ -102,7 +102,7 @@ export function createClientLibrary(
}
}

const topLevelClients: Client[] = [];
const topLevelClients: _Client[] = [];

if (topLevel.length === 0) {
reportDiagnostic(tk.program, { code: "cant-find-client", target: globalNs });
Expand All @@ -126,18 +126,18 @@ export function createClientLibrary(

interface VisitClientOptions {
operationMutators?: unsafe_Mutator[];
parentClient?: Client;
parentClient?: _Client;
}
function visitClient(
program: Program,
client: InternalClient,
client: _InternalClient,
dataTypes: Set<Model | Union | Enum>,
options?: VisitClientOptions,
): Client {
): _Client {
const tk = $(program);
// First create a partial `Client` object.
// We’ll fill in subClients *after* we have `c`.
const currentClient: Client = {
const currentClient: _Client = {
...client,
operations: [],
subClients: [],
Expand Down
Loading
Loading