Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e0998aa
feat: implement fire-and-forget for batch subgraph publish
wilsonrivera Jun 11, 2026
91e2dea
chore: add BullMQ job to delete job details after 24 hours
wilsonrivera Jun 11, 2026
0ff357c
chore: override `msgpackr` to address taskforcesh/bullmq#2747
wilsonrivera Jun 11, 2026
74b0e9c
feat: implement fifo lock for async batch publish
wilsonrivera Jun 11, 2026
8275b02
chore: add `.gitattributes`
wilsonrivera Jun 12, 2026
c70a6c0
chore: fix typo
wilsonrivera Jun 12, 2026
8f6b88a
chore: move `.gitattributes` to controlplane
wilsonrivera Jun 12, 2026
e491d4c
chore: fix test
wilsonrivera Jun 12, 2026
bb7610c
chore: implement rpc endpoint to get batch publish status
wilsonrivera Jun 12, 2026
f119b8a
chore: cleanup owned batch publish locks
wilsonrivera Jun 12, 2026
d8b52ee
chore: rename `jobStatus` to `status` and make it optional
wilsonrivera Jun 12, 2026
7ab73f9
chore: remove testing code
wilsonrivera Jun 12, 2026
60238ac
chore: switch to heartbeat expiration instead of locking for a full day
wilsonrivera Jun 12, 2026
a72b642
Merge branch 'main' into wilson/eng-9724-controlpanel-move-to-a-accep…
wilsonrivera Jun 12, 2026
99e6340
chore: use redis over postgres for distributed locking
wilsonrivera Jun 12, 2026
ec1dae1
chore: generate platform types
wilsonrivera Jun 12, 2026
32e1a59
chore: remove test code
wilsonrivera Jun 12, 2026
8067b2a
chore: replace `redlock-universal` with `redlock`
wilsonrivera Jun 12, 2026
200a2c3
chore: add `redlock` module definition
wilsonrivera Jun 12, 2026
af35ce5
chore: update `tsconfig.test.json`
wilsonrivera Jun 12, 2026
3e40625
chore: lock with controplace prefix
wilsonrivera Jun 13, 2026
8fcedaf
Merge branch 'main' into wilson/eng-9724-controlpanel-move-to-a-accep…
wilsonrivera Jun 15, 2026
b5f7d3b
Merge branch 'main' into wilson/eng-9724-controlpanel-move-to-a-accep…
wilsonrivera Jun 15, 2026
4aaebca
chore: cleanup `Redlock` definition
wilsonrivera Jun 15, 2026
a28fa5f
Merge branch 'main' into wilson/eng-9724-controlpanel-move-to-a-accep…
wilsonrivera Jun 15, 2026
5314bf6
chore: regenerate migrations
wilsonrivera Jun 15, 2026
514e23d
chore: update tests
wilsonrivera Jun 15, 2026
e84d658
chore: remove `msgpackr` override as `bullmq` has been updated
wilsonrivera Jun 15, 2026
ed3d363
chore: fix test
wilsonrivera Jun 15, 2026
093e325
feat: cli implement status fetch for accept first strategy (#2964)
wilsonrivera Jun 16, 2026
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
20 changes: 19 additions & 1 deletion cli/src/commands/subgraph/commands/batch-publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getBaseHeaders } from '../../../core/config.js';
import { handleCompositionResult } from '../../../handle-composition-result.js';
import { limitMaxValue } from '../../../constants.js';
import { fileExists } from '../../../utils.js';
import { pollBatchPublishStatus } from '../utils/poll-batch-publish-status.js';

const entrySchema = z.object({
name: z.string().trim().min(1, 'a non-empty "name" is required'),
Expand Down Expand Up @@ -57,6 +58,7 @@ export default (opts: BaseCommandOptions) => {
);
command.option('-r, --raw', 'Prints to the console in json format instead of table');
command.option('-j, --json', 'Prints to the console in json format instead of table');
command.option('--async', 'This flag enables polling the operation status');

command.action(async (options) => {
const configFile = resolve(options.config);
Expand Down Expand Up @@ -126,18 +128,34 @@ export default (opts: BaseCommandOptions) => {
spinner.start();
}

const resp = await opts.client.platform.publishFederatedSubgraphs(
let resp = await opts.client.platform.publishFederatedSubgraphs(
{
namespace: options.namespace,
subgraphs,
disableResolvabilityValidation: options.disableResolvabilityValidation,
limit,
async: options.async,
},
{
headers: getBaseHeaders(),
},
);

if (resp.jobId) {
const controller = new AbortController();
function cancelPolling() {
controller.abort();
}

process.on('SIGINT', cancelPolling);

try {
resp = await pollBatchPublishStatus(opts.client, resp.jobId, controller.signal);
} finally {
process.off('SIGINT', cancelPolling);
}
}

const total = subgraphs.length;
const changed = resp.updatedSubgraphNames?.length ?? 0;

Expand Down
83 changes: 83 additions & 0 deletions cli/src/commands/subgraph/utils/poll-batch-publish-status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {
PublishFederatedSubgraphsResponse,
BatchPublishJobStatus,
} from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb';
import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb';
import { Client } from '../../../core/client/client.js';
import { getBaseHeaders } from '../../../core/config.js';

export async function pollBatchPublishStatus(
client: Client,
jobId: string,
signal: AbortSignal,
): Promise<PublishFederatedSubgraphsResponse> {
let attempt = 0;
const headers = getBaseHeaders();
while (!signal.aborted) {
const resp = await client.platform.getBatchPublishJobStatus({ jobId }, { headers, signal });
if (resp.response?.code !== EnumStatusCode.OK) {
return new PublishFederatedSubgraphsResponse({
response: resp.response,
});
}

switch (resp.status) {
case BatchPublishJobStatus.PENDING:
case BatchPublishJobStatus.PROCESSING: {
await sleep(computeDelay(1000, 5000, attempt++, true), signal);
break;
}
case BatchPublishJobStatus.FAILED: {
return new PublishFederatedSubgraphsResponse({
response: {
code: EnumStatusCode.ERR_SUBGRAPH_COMPOSITION_FAILED,
details: resp.failureReason,
},
});
}
case BatchPublishJobStatus.COMPLETED: {
return new PublishFederatedSubgraphsResponse({
response: { code: EnumStatusCode.OK },
...resp,
});
}
}
}

/**
* The only reason we should realistically get here is due to `signal` being aborted; however, we still need
* to return a response object
*/
return new PublishFederatedSubgraphsResponse({
response: {
code: EnumStatusCode.ERR,
details: signal.aborted ? 'Operation was cancelled by the user.' : undefined,
},
});
}

function computeDelay(base: number, max: number, attempt: number, jitter: boolean): number {
const delay = Math.min(max, base * 2 ** attempt);
return jitter ? delay * (0.5 + Math.random() * 0.5) : delay;
}

function sleep(ms: number, signal?: AbortSignal): Promise<'aborted' | 'ok'> {
return new Promise((resolve) => {
if (signal?.aborted) {
resolve('aborted');
return;
}

const timer = setTimeout(() => {
signal?.removeEventListener('abort', onAbort);
resolve('ok');
}, ms);

function onAbort() {
clearTimeout(timer);
resolve('aborted');
}

signal?.addEventListener('abort', onAbort, { once: true });
});
}
Loading
Loading