Skip to content

Commit 96b40b5

Browse files
committed
Improve external composer ux
1 parent a8b775f commit 96b40b5

File tree

10 files changed

+150
-66
lines changed

10 files changed

+150
-66
lines changed

packages/services/api/src/modules/schema/providers/schema-manager.ts

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -447,15 +447,10 @@ export class SchemaManager {
447447
},
448448
});
449449

450-
const [project, organization] = await Promise.all([
451-
this.storage.getProject({
452-
organizationId: selector.organizationId,
453-
projectId: selector.projectId,
454-
}),
455-
this.storage.getOrganization({
456-
organizationId: selector.organizationId,
457-
}),
458-
]);
450+
const project = await this.storage.getProject({
451+
organizationId: selector.organizationId,
452+
projectId: selector.projectId,
453+
});
459454

460455
if (project.type !== ProjectType.FEDERATION) {
461456
throw new HiveError(
@@ -481,11 +476,8 @@ export class SchemaManager {
481476
],
482477
{
483478
external: project.externalComposition,
484-
native: this.checkProjectNativeFederationSupport({
485-
project,
486-
organization,
487-
targetId: null,
488-
}),
479+
// when testing external composition, do not use native composition during the check
480+
native: false,
489481
contracts: null,
490482
},
491483
);

packages/services/schema/src/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ export const schemaBuilderApiRouter = t.router({
105105
abortSignal,
106106
requestId: ctx.req.id,
107107
});
108+
108109
return result.result;
109110
},
110111
result =>

packages/services/schema/src/composition-scheduler.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ export class CompositionScheduler {
171171

172172
const time = process.hrtime();
173173

174+
// starts the composition for this task
174175
worker.postMessage({
175176
event: 'composition',
176177
id: taskId,

packages/services/schema/src/composition-worker.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import * as crypto from 'node:crypto';
22
import type { MessagePort } from 'node:worker_threads';
3+
// IMPORTANT: Must use "type" to avoid runtime dependency
4+
import type { FastifyBaseLogger } from 'fastify';
35
import type { Logger } from '@hive/api';
46
import { CompositionResponse } from './api';
57
import { createComposeFederation, type ComposeFederationArgs } from './composition/federation';
@@ -51,11 +53,10 @@ export function createCompositionWorker(args: {
5153
if (message.data.type === 'federation') {
5254
const composeFederation = createComposeFederation({
5355
decrypt,
54-
logger: baseLogger.child({ reqId: message.data.args.requestId }) as any,
56+
logger: baseLogger.child({ reqId: message.data.args.requestId }) as FastifyBaseLogger,
5557
requestTimeoutMs: message.data.requestTimeoutMs,
5658
});
5759
const composed = await composeFederation(message.data.args);
58-
5960
args.port.postMessage({
6061
event: 'compositionResult',
6162
id: message.id,

packages/services/schema/src/composition/federation.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ export const createComposeFederation = (deps: ComposeFederationDeps) =>
134134
if (args.native) {
135135
deps.logger.debug(
136136
'Using built-in Federation v2 composition service (schemas=%s)',
137-
args.schemas.length,
137+
subgraphs.length,
138138
);
139139
compose = subgraphs => Promise.resolve(composeFederationV2(subgraphs, deps.logger));
140140
} else if (args.external) {
@@ -151,7 +151,7 @@ export const createComposeFederation = (deps: ComposeFederationDeps) =>
151151
} else {
152152
deps.logger.debug(
153153
'Using built-in Federation v1 composition service (schemas=%s)',
154-
args.schemas.length,
154+
subgraphs.length,
155155
);
156156
compose = subgraphs => Promise.resolve(composeFederationV1(subgraphs));
157157
}

packages/services/schema/src/lib/compose.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,24 +32,21 @@ const EXTERNAL_COMPOSITION_RESULT = z.union([
3232
supergraph: z.string(),
3333
sdl: z.string(),
3434
}),
35-
includesNetworkError: z.boolean().optional().default(false),
35+
includesNetworkError: z.boolean().default(false),
3636
}),
3737
z.object({
3838
type: z.literal('failure'),
3939
result: z.object({
40-
supergraph: z.string().optional(),
41-
sdl: z.string().optional(),
40+
supergraph: z.string().nullish(),
41+
sdl: z.string().nullish(),
4242
errors: z.array(
4343
z.object({
4444
message: z.string(),
45-
source: z
46-
.union([z.literal('composition'), z.literal('graphql')])
47-
.optional()
48-
.transform(value => value ?? 'graphql'),
45+
source: z.union([z.literal('composition'), z.literal('graphql')]).default('graphql'),
4946
}),
5047
),
5148
}),
52-
includesNetworkError: z.boolean().optional().default(false),
49+
includesNetworkError: z.boolean().default(false),
5350
}),
5451
]);
5552

@@ -198,8 +195,20 @@ export async function composeExternalFederation(args: {
198195

199196
if (!parseResult.success) {
200197
args.logger.error('External composition failure: invalid shape of data: %o', parseResult.error);
201-
202-
throw new Error(`External composition failure: invalid shape of data`);
198+
return {
199+
type: 'failure',
200+
result: {
201+
supergraph: null,
202+
sdl: null,
203+
errors: [
204+
{
205+
message: 'External composition failure: invalid shape of data',
206+
source: 'composition',
207+
},
208+
],
209+
},
210+
includesNetworkError: false,
211+
};
203212
}
204213

205214
if (parseResult.data.type === 'success') {
@@ -314,6 +323,10 @@ async function callExternalService(
314323
},
315324
timeout: {
316325
request: timeoutMs,
326+
// connecting should be quick
327+
lookup: 10_000,
328+
connect: 10_000,
329+
secureConnect: 10_000,
317330
},
318331
});
319332

@@ -329,7 +342,7 @@ async function callExternalService(
329342
span.setAttribute('error.type', error.name);
330343

331344
logger.error(
332-
'Network error without response. (errorName=%s, errorMessage=%s)',
345+
'Network error during external composition without response. (errorName=%s, errorMessage=%s)',
333346
error.name,
334347
error.message,
335348
);
@@ -341,7 +354,7 @@ async function callExternalService(
341354
supergraph: null,
342355
errors: [
343356
{
344-
message: `External composition network failure. Is the service reachable?`,
357+
message: `A network error occurred during external composition: "${error.message}"`,
345358
source: 'graphql',
346359
},
347360
],

0 commit comments

Comments
 (0)