Skip to content

Commit 3aea745

Browse files
committed
fix websocket type exporting
1 parent e62abe5 commit 3aea745

4 files changed

Lines changed: 81 additions & 14 deletions

File tree

packages/next/src/index.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,8 @@ async function initializeDev(
227227
try {
228228
const result = await _regenerateTypes(serverEntry, goRoot, baseUrl, true, "");
229229
generatedDts = result.types;
230-
const clientJs = nextClientJsTemplate(goPort, baseUrl, DEV_API_PREFIX);
230+
const hasWebSocket = result.asyncapiSpec != null;
231+
const clientJs = nextClientJsTemplate(goPort, baseUrl, DEV_API_PREFIX, { hasWebSocket });
231232
writeGeneratedFiles(configDir, generatedDts, baseUrl, { clientJsContent: clientJs, openapiSource, asyncapiSpec: result.asyncapiSpec });
232233
patchTsConfigPaths(projectRoot, configDir);
233234
console.log("[shiftapi] Types generated.");
@@ -260,7 +261,8 @@ async function initializeDev(
260261
);
261262
if (result.changed) {
262263
generatedDts = result.types;
263-
const clientJs = nextClientJsTemplate(goPort, baseUrl, DEV_API_PREFIX);
264+
const hasWebSocket = result.asyncapiSpec != null;
265+
const clientJs = nextClientJsTemplate(goPort, baseUrl, DEV_API_PREFIX, { hasWebSocket });
264266
writeGeneratedFiles(configDir, generatedDts, baseUrl, {
265267
clientJsContent: clientJs,
266268
openapiSource,
@@ -313,7 +315,8 @@ async function initializeBuild(
313315
): Promise<InitResult> {
314316
try {
315317
const result = await _regenerateTypes(serverEntry, goRoot, baseUrl, false, "");
316-
const clientJs = nextClientJsTemplate(basePort, baseUrl);
318+
const hasWebSocket = result.asyncapiSpec != null;
319+
const clientJs = nextClientJsTemplate(basePort, baseUrl, undefined, { hasWebSocket });
317320
writeGeneratedFiles(configDir, result.types, baseUrl, { clientJsContent: clientJs, openapiSource, asyncapiSpec: result.asyncapiSpec });
318321
patchTsConfigPaths(projectRoot, configDir);
319322
console.log("[shiftapi] Types generated for build.");

packages/shiftapi/src/__tests__/templates.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,31 @@ describe("dtsTemplate", () => {
7676
// Should not generate WSErrorFor when no channels have x-errors.
7777
expect(output).not.toContain("WSErrorFor");
7878
});
79+
80+
it("exports standalone type aliases for component schemas", () => {
81+
// Simulate openapi-typescript output with 4-space indentation
82+
const generatedTypes = `export interface components {
83+
schemas: {
84+
EchoReply: {
85+
text: string;
86+
};
87+
ChatMsg: {
88+
user: string;
89+
text: string;
90+
};
91+
};
92+
}`;
93+
94+
const output = dtsTemplate(generatedTypes);
95+
96+
expect(output).toContain('export type EchoReply = components["schemas"]["EchoReply"];');
97+
expect(output).toContain('export type ChatMsg = components["schemas"]["ChatMsg"];');
98+
});
99+
100+
it("handles generated types with no schemas gracefully", () => {
101+
const output = dtsTemplate("// no schemas here");
102+
expect(output).not.toContain("export type ");
103+
// Should still produce a valid module declaration
104+
expect(output).toContain('declare module "@shiftapi/client"');
105+
});
79106
});

packages/shiftapi/src/codegen.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ export async function regenerateTypes(
1616
const specs = extractSpecs(serverEntry, resolve(goRoot));
1717
const types = await generateTypes(specs.openapi as Record<string, unknown>);
1818
const changed = types !== previousTypes;
19+
const hasWebSocket = specs.asyncapi != null;
1920
const virtualModuleSource = virtualModuleTemplate(
2021
baseUrl,
2122
isDev ? DEV_API_PREFIX : undefined,
23+
{ hasWebSocket },
2224
);
2325
return { types, virtualModuleSource, changed, asyncapiSpec: specs.asyncapi };
2426
}
@@ -39,7 +41,8 @@ export function writeGeneratedFiles(
3941
}
4042

4143
writeFileSync(resolve(outDir, "client.d.ts"), dtsTemplate(generatedDts, options?.asyncapiSpec ?? null));
42-
writeFileSync(resolve(outDir, "client.js"), options?.clientJsContent ?? clientJsTemplate(baseUrl));
44+
const hasWebSocket = options?.asyncapiSpec != null;
45+
writeFileSync(resolve(outDir, "client.js"), options?.clientJsContent ?? clientJsTemplate(baseUrl, { hasWebSocket }));
4346
if (options?.openapiSource) {
4447
writeFileSync(resolve(outDir, "openapi-fetch.js"), options.openapiSource);
4548
}

packages/shiftapi/src/templates.ts

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,32 @@ function resolveEnvelopeFromSchema(schema: any, spec: any): string {
168168
return `{ type: ${typeName}; data: ${dataType} }`;
169169
}
170170

171+
/**
172+
* Extracts schema names from the openapi-typescript generated types string
173+
* and produces `export type Name = components["schemas"]["Name"];` aliases.
174+
*/
175+
function buildSchemaExports(generatedTypes: string): string {
176+
const schemaNames: string[] = [];
177+
// Match the schemas block inside `components { schemas: { ... } }`
178+
const schemasMatch = generatedTypes.match(/schemas:\s*\{([\s\S]*?)\n {4}\}/);
179+
if (schemasMatch) {
180+
// Each top-level schema key is at 8-space indent
181+
const re = /^ {8}(\w+)\s*:/gm;
182+
let m;
183+
while ((m = re.exec(schemasMatch[1])) !== null) {
184+
schemaNames.push(m[1]);
185+
}
186+
}
187+
if (schemaNames.length === 0) return "";
188+
return schemaNames
189+
.map((name) => ` export type ${name} = components["schemas"]["${name}"];`)
190+
.join("\n");
191+
}
192+
171193
export function dtsTemplate(generatedTypes: string, asyncapiSpec: object | null = null): string {
172194
const wsSection = buildWSChannelsType(asyncapiSpec);
173195
const wsImport = wsSection ? '\n import type { WSConnection } from "shiftapi/internal";\n import { WSError } from "shiftapi/internal";' : "";
196+
const schemaExports = buildSchemaExports(generatedTypes);
174197

175198
return `\
176199
// Auto-generated by shiftapi. Do not edit.
@@ -201,15 +224,18 @@ ${indent(generatedTypes)}
201224
${wsSection}
202225
export const client: ReturnType<typeof createClient<paths>>;
203226
export { createClient };${wsSection ? "\n export { WSError };" : ""}
204-
}
227+
${schemaExports ? "\n" + schemaExports + "\n" : ""}}
205228
`;
206229
}
207230

208-
export function clientJsTemplate(baseUrl: string): string {
231+
export function clientJsTemplate(baseUrl: string, options?: { hasWebSocket?: boolean }): string {
232+
const hasWS = options?.hasWebSocket ?? false;
233+
const wsImport = hasWS ? ", WSError" : "";
234+
const wsExport = hasWS ? ", WSError" : "";
209235
return `\
210236
// Auto-generated by shiftapi. Do not edit.
211237
import createClient from "openapi-fetch";
212-
import { createSSE, createWebSocket } from "shiftapi/internal";
238+
import { createSSE, createWebSocket${wsImport} } from "shiftapi/internal";
213239
214240
/** Pre-configured, fully-typed API client. */
215241
export const client = createClient({
@@ -220,20 +246,24 @@ export const client = createClient({
220246
export const sse = createSSE(${JSON.stringify(baseUrl)});
221247
export const websocket = createWebSocket(${JSON.stringify(baseUrl)});
222248
223-
export { createClient };
249+
export { createClient${wsExport} };
224250
`;
225251
}
226252

227253
export function nextClientJsTemplate(
228254
port: number,
229255
baseUrl: string,
230256
devApiPrefix?: string,
257+
options?: { hasWebSocket?: boolean },
231258
): string {
259+
const hasWS = options?.hasWebSocket ?? false;
260+
const wsImport = hasWS ? ", WSError" : "";
261+
const wsExport = hasWS ? ", WSError" : "";
232262
if (!devApiPrefix) {
233263
return `\
234264
// Auto-generated by @shiftapi/next. Do not edit.
235265
import createClient from "./openapi-fetch.js";
236-
import { createSSE, createWebSocket } from "shiftapi/internal";
266+
import { createSSE, createWebSocket${wsImport} } from "shiftapi/internal";
237267
238268
const baseUrl =
239269
process.env.NEXT_PUBLIC_SHIFTAPI_BASE_URL || ${JSON.stringify(baseUrl)};
@@ -247,15 +277,15 @@ export const client = createClient({
247277
export const sse = createSSE(baseUrl);
248278
export const websocket = createWebSocket(baseUrl);
249279
250-
export { createClient };
280+
export { createClient${wsExport} };
251281
`;
252282
}
253283

254284
const devServerUrl = `http://localhost:${port}`;
255285
return `\
256286
// Auto-generated by @shiftapi/next. Do not edit.
257287
import createClient from "./openapi-fetch.js";
258-
import { createSSE, createWebSocket } from "shiftapi/internal";
288+
import { createSSE, createWebSocket${wsImport} } from "shiftapi/internal";
259289
260290
const baseUrl =
261291
process.env.NEXT_PUBLIC_SHIFTAPI_BASE_URL ||
@@ -272,22 +302,26 @@ export const client = createClient({
272302
export const sse = createSSE(baseUrl);
273303
export const websocket = createWebSocket(baseUrl);
274304
275-
export { createClient };
305+
export { createClient${wsExport} };
276306
`;
277307
}
278308

279309
export function virtualModuleTemplate(
280310
baseUrl: string,
281311
devApiPrefix?: string,
312+
options?: { hasWebSocket?: boolean },
282313
): string {
314+
const hasWS = options?.hasWebSocket ?? false;
315+
const wsImport = hasWS ? ", WSError" : "";
316+
const wsExport = hasWS ? ", WSError" : "";
283317
const baseUrlExpr = devApiPrefix
284318
? `import.meta.env.VITE_SHIFTAPI_BASE_URL || (import.meta.env.DEV ? ${JSON.stringify(devApiPrefix)} : ${JSON.stringify(baseUrl)})`
285319
: `import.meta.env.VITE_SHIFTAPI_BASE_URL || ${JSON.stringify(baseUrl)}`;
286320

287321
return `\
288322
// Auto-generated by @shiftapi/vite-plugin
289323
import createClient from "openapi-fetch";
290-
import { createSSE, createWebSocket } from "shiftapi/internal";
324+
import { createSSE, createWebSocket${wsImport} } from "shiftapi/internal";
291325
292326
const baseUrl = ${baseUrlExpr};
293327
@@ -300,6 +334,6 @@ export const client = createClient({
300334
export const sse = createSSE(baseUrl);
301335
export const websocket = createWebSocket(baseUrl);
302336
303-
export { createClient };
337+
export { createClient${wsExport} };
304338
`;
305339
}

0 commit comments

Comments
 (0)