Skip to content

Commit 21abccf

Browse files
Swati Kumarclaude
authored andcommitted
Add Alloy infrastructure, context system, and field components
- Build: @alloy-js/graphql (linked local), JSX transpilation via alloy build, rollup plugin for vitest - Context: GraphQLSchemaContext provider with TypeGraph from mutation engine (single source of truth, no duplicate interface) - Components: Field and OperationField — thin translators from mutated types to Alloy GraphQL SDL components. Use resolveGraphQLTypeName for type names and decorator-based nullable state. No type-expression render-prop indirection (mutation pipeline handles all transforms). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4b64ab4 commit 21abccf

9 files changed

Lines changed: 484 additions & 9 deletions

File tree

packages/graphql/package.json

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,16 @@
3030
"node": ">=20.0.0"
3131
},
3232
"dependencies": {
33-
"@alloy-js/core": "^0.11.0",
34-
"@alloy-js/typescript": "^0.11.0",
33+
"@alloy-js/core": "^0.22.0",
34+
"@alloy-js/graphql": "link:../../alloy/packages/graphql",
35+
"@alloy-js/typescript": "^0.22.0",
3536
"change-case": "^5.4.4",
3637
"graphql": "^16.9.0"
3738
},
3839
"scripts": {
3940
"clean": "rimraf ./dist ./temp",
40-
"build": "tsc -p .",
41-
"watch": "tsc --watch",
41+
"build": "alloy build",
42+
"watch": "alloy build --watch",
4243
"test": "vitest run",
4344
"test:watch": "vitest -w",
4445
"lint": "eslint . --max-warnings=0",
@@ -56,6 +57,8 @@
5657
"@typespec/mutator-framework": "workspace:~"
5758
},
5859
"devDependencies": {
60+
"@alloy-js/cli": "^0.22.0",
61+
"@alloy-js/rollup-plugin": "^0.1.0",
5962
"@types/node": "~22.13.13",
6063
"@typespec/compiler": "workspace:~",
6164
"@typespec/emitter-framework": "workspace:~",
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { type ModelProperty, getDeprecationDetails, isArrayModelType } from "@typespec/compiler";
2+
import * as gql from "@alloy-js/graphql";
3+
import { useTsp } from "@typespec/emitter-framework";
4+
import { resolveGraphQLTypeName } from "../../lib/graphql-type-name.js";
5+
import { hasNullableElements, isNullable } from "../../lib/nullable.js";
6+
7+
export interface FieldProps {
8+
property: ModelProperty;
9+
isInput: boolean;
10+
}
11+
12+
export function Field(props: FieldProps) {
13+
const { $, program } = useTsp();
14+
15+
const doc = $.type.getDoc(props.property);
16+
const deprecation = getDeprecationDetails(program, props.property);
17+
const nullable = isNullable(props.property) || props.property.optional;
18+
const type = props.property.type;
19+
20+
if (type.kind === "Model" && isArrayModelType(type)) {
21+
const elemNullable = hasNullableElements(props.property);
22+
const typeName = resolveGraphQLTypeName(type.indexer.value);
23+
24+
if (props.isInput) {
25+
return (
26+
<gql.InputField
27+
name={props.property.name}
28+
type={typeName}
29+
nonNull={!elemNullable}
30+
description={doc}
31+
deprecated={deprecation?.message}
32+
>
33+
<gql.InputField.List nonNull={!nullable} />
34+
</gql.InputField>
35+
);
36+
}
37+
38+
return (
39+
<gql.Field
40+
name={props.property.name}
41+
type={typeName}
42+
nonNull={!elemNullable}
43+
description={doc}
44+
deprecated={deprecation?.message}
45+
>
46+
<gql.Field.List nonNull={!nullable} />
47+
</gql.Field>
48+
);
49+
}
50+
51+
if (props.isInput) {
52+
return (
53+
<gql.InputField
54+
name={props.property.name}
55+
type={resolveGraphQLTypeName(type)}
56+
nonNull={!nullable}
57+
description={doc}
58+
deprecated={deprecation?.message}
59+
/>
60+
);
61+
}
62+
63+
return (
64+
<gql.Field
65+
name={props.property.name}
66+
type={resolveGraphQLTypeName(type)}
67+
nonNull={!nullable}
68+
description={doc}
69+
deprecated={deprecation?.message}
70+
/>
71+
);
72+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { Field, type FieldProps } from "./field.js";
2+
export { OperationField, type OperationFieldProps } from "./operation-field.js";
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { type Operation, getDeprecationDetails, isArrayModelType } from "@typespec/compiler";
2+
import * as gql from "@alloy-js/graphql";
3+
import { useTsp } from "@typespec/emitter-framework";
4+
import { resolveGraphQLTypeName } from "../../lib/graphql-type-name.js";
5+
import { hasNullableElements, isNullable } from "../../lib/nullable.js";
6+
7+
export interface OperationFieldProps {
8+
operation: Operation;
9+
}
10+
11+
export function OperationField(props: OperationFieldProps) {
12+
const { $, program } = useTsp();
13+
14+
const doc = $.type.getDoc(props.operation);
15+
const deprecation = getDeprecationDetails(program, props.operation);
16+
const returnType = props.operation.returnType;
17+
const nullable = isNullable(props.operation);
18+
const params = Array.from(props.operation.parameters.properties.values());
19+
20+
const isList = returnType.kind === "Model" && isArrayModelType(returnType);
21+
const typeName = isList
22+
? resolveGraphQLTypeName(returnType.indexer.value)
23+
: resolveGraphQLTypeName(returnType);
24+
const elemNullable = isList && hasNullableElements(props.operation);
25+
26+
return (
27+
<gql.Field
28+
name={props.operation.name}
29+
type={typeName}
30+
nonNull={isList ? !elemNullable : !nullable}
31+
description={doc}
32+
deprecated={deprecation?.message}
33+
>
34+
{isList ? <gql.Field.List nonNull={!nullable} /> : undefined}
35+
{params.map((param) => {
36+
const paramNullable = isNullable(param) || param.optional;
37+
const paramType = param.type;
38+
const paramIsList = paramType.kind === "Model" && isArrayModelType(paramType);
39+
const paramElemNullable = paramIsList && hasNullableElements(param);
40+
const paramTypeName = paramIsList
41+
? resolveGraphQLTypeName(paramType.indexer.value)
42+
: resolveGraphQLTypeName(paramType);
43+
44+
return (
45+
<gql.InputValue
46+
name={param.name}
47+
type={paramTypeName}
48+
nonNull={paramIsList ? !paramElemNullable : !paramNullable}
49+
description={$.type.getDoc(param)}
50+
deprecated={getDeprecationDetails(program, param)?.message}
51+
>
52+
{paramIsList ? <gql.InputValue.List nonNull={!paramNullable} /> : undefined}
53+
</gql.InputValue>
54+
);
55+
})}
56+
</gql.Field>
57+
);
58+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { type ComponentContext, createNamedContext, useContext } from "@alloy-js/core";
2+
import type { TypeGraph } from "../mutation-engine/type-graph.js";
3+
4+
export interface GraphQLSchemaContextValue {
5+
typeGraph: TypeGraph;
6+
}
7+
8+
export const GraphQLSchemaContext: ComponentContext<GraphQLSchemaContextValue> =
9+
createNamedContext<GraphQLSchemaContextValue>("GraphQLSchema");
10+
11+
export function useGraphQLSchema(): GraphQLSchemaContextValue {
12+
const context = useContext(GraphQLSchemaContext);
13+
14+
if (!context) {
15+
throw new Error(
16+
"useGraphQLSchema must be used within GraphQLSchemaContext.Provider.",
17+
);
18+
}
19+
20+
return context;
21+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export {
2+
GraphQLSchemaContext,
3+
useGraphQLSchema,
4+
type GraphQLSchemaContextValue,
5+
} from "./graphql-schema-context.js";

packages/graphql/tsconfig.json

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
{
22
"extends": "../../tsconfig.base.json",
33
"compilerOptions": {
4-
"useDefineForClassFields": true,
4+
"module": "NodeNext",
5+
"moduleResolution": "NodeNext",
6+
"target": "es2022",
7+
"skipLibCheck": true,
8+
"isolatedModules": true,
9+
"verbatimModuleSyntax": true,
10+
"jsx": "preserve",
11+
"jsxImportSource": "@alloy-js/core",
12+
"emitDeclarationOnly": true,
513
"rootDir": ".",
6-
"outDir": "dist",
7-
"verbatimModuleSyntax": true
14+
"outDir": "dist"
815
},
9-
"include": ["src", "test"]
16+
"include": ["src/**/*.ts", "src/**/*.tsx", "test/**/*.ts", "test/**/*.tsx"],
17+
"exclude": ["node_modules", "dist"]
1018
}

packages/graphql/vitest.config.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1+
import alloyPlugin from "@alloy-js/rollup-plugin";
12
import { defineConfig, mergeConfig } from "vitest/config";
23
import { defaultTypeSpecVitestConfig } from "../../vitest.config.js";
34

4-
export default mergeConfig(defaultTypeSpecVitestConfig, defineConfig({}));
5+
export default mergeConfig(
6+
defaultTypeSpecVitestConfig,
7+
defineConfig({
8+
esbuild: {
9+
jsx: "preserve",
10+
sourcemap: "both",
11+
},
12+
plugins: [alloyPlugin()],
13+
}),
14+
);

0 commit comments

Comments
 (0)