Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c5181a5
feat: add new endpoints for fetching feature flags and feature subgra…
JivusAyrus Apr 1, 2026
41890f4
fix: lint
JivusAyrus Apr 1, 2026
440588e
chore: improve `getWorkspace` and `getFederatedByGraphName` performance
wilsonrivera Apr 3, 2026
c856c7a
chore: remove `console.log` and query `protobufSchemaVersion` with `l…
wilsonrivera Apr 3, 2026
ab799f9
Merge branch 'main' into suvij/eng-9327-controlplane-split-feature-fl…
wilsonrivera Apr 3, 2026
a06c07b
chore: fix tests
wilsonrivera Apr 3, 2026
02f7330
chore: fix tests
wilsonrivera Apr 5, 2026
e7531db
chore: improve `getFeatureSubgraphsByFederatedGraph` performance
wilsonrivera Apr 7, 2026
7be4f14
Merge branch 'main' into suvij/eng-9327-controlplane-split-feature-fl…
wilsonrivera Apr 7, 2026
fdb61cf
chore: small fixes
wilsonrivera Apr 7, 2026
9f97de8
chore: linting
wilsonrivera Apr 7, 2026
c718280
chore: remove unused comment
wilsonrivera Apr 7, 2026
6ba0ab3
Merge branch 'main' into suvij/eng-9327-controlplane-split-feature-fl…
wilsonrivera Apr 9, 2026
43461ef
Merge branch 'main' into suvij/eng-9327-controlplane-split-feature-fl…
wilsonrivera Apr 9, 2026
d6ad6ce
Merge branch 'main' into suvij/eng-9327-controlplane-split-feature-fl…
wilsonrivera Apr 13, 2026
9fa032c
chore: prevent sending multiple requests to same endpoint
wilsonrivera Apr 13, 2026
99c83da
Merge branch 'main' into suvij/eng-9327-controlplane-split-feature-fl…
wilsonrivera Apr 14, 2026
40aecdc
chore: linting
wilsonrivera Apr 14, 2026
2eecefd
Merge remote-tracking branch 'origin/suvij/eng-9327-controlplane-spli…
wilsonrivera Apr 14, 2026
8e12b6c
Merge branch 'main' of github.com:wundergraph/cosmo into suvij/eng-93…
JivusAyrus Apr 15, 2026
e7d77fa
Merge branch 'main' into suvij/eng-9327-controlplane-split-feature-fl…
wilsonrivera Apr 15, 2026
70bea30
Merge branch 'main' into suvij/eng-9327-controlplane-split-feature-fl…
JivusAyrus Apr 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
15,745 changes: 8,050 additions & 7,695 deletions connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

24 changes: 23 additions & 1 deletion connect/src/wg/cosmo/platform/v1/platform_connect.ts

Large diffs are not rendered by default.

215 changes: 200 additions & 15 deletions connect/src/wg/cosmo/platform/v1/platform_pb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3593,19 +3593,6 @@ export class GetFederatedGraphByNameResponse extends Message<GetFederatedGraphBy
*/
graphRequestToken = "";

/**
* @generated from field: repeated wg.cosmo.platform.v1.FeatureFlag featureFlagsInLatestValidComposition = 5;
*/
featureFlagsInLatestValidComposition: FeatureFlag[] = [];

/**
* includes all the feature subgraphs that are part of the federated graph;
* even the ones that are not part of the latest composition
*
* @generated from field: repeated wg.cosmo.platform.v1.Subgraph featureSubgraphs = 6;
*/
featureSubgraphs: Subgraph[] = [];

constructor(data?: PartialMessage<GetFederatedGraphByNameResponse>) {
super();
proto3.util.initPartial(data, this);
Expand All @@ -3618,8 +3605,6 @@ export class GetFederatedGraphByNameResponse extends Message<GetFederatedGraphBy
{ no: 2, name: "graph", kind: "message", T: FederatedGraph },
{ no: 3, name: "subgraphs", kind: "message", T: Subgraph, repeated: true },
{ no: 4, name: "graphRequestToken", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 5, name: "featureFlagsInLatestValidComposition", kind: "message", T: FeatureFlag, repeated: true },
{ no: 6, name: "featureSubgraphs", kind: "message", T: Subgraph, repeated: true },
]);

static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): GetFederatedGraphByNameResponse {
Expand Down Expand Up @@ -19824,6 +19809,206 @@ export class GetFeatureFlagsByFederatedGraphResponse extends Message<GetFeatureF
}
}

/**
* @generated from message wg.cosmo.platform.v1.GetFeatureFlagsInLatestCompositionByFederatedGraphRequest
*/
export class GetFeatureFlagsInLatestCompositionByFederatedGraphRequest extends Message<GetFeatureFlagsInLatestCompositionByFederatedGraphRequest> {
/**
* @generated from field: string federated_graph_name = 1;
*/
federatedGraphName = "";

/**
* @generated from field: string namespace = 2;
*/
namespace = "";

constructor(data?: PartialMessage<GetFeatureFlagsInLatestCompositionByFederatedGraphRequest>) {
super();
proto3.util.initPartial(data, this);
}

static readonly runtime: typeof proto3 = proto3;
static readonly typeName = "wg.cosmo.platform.v1.GetFeatureFlagsInLatestCompositionByFederatedGraphRequest";
static readonly fields: FieldList = proto3.util.newFieldList(() => [
{ no: 1, name: "federated_graph_name", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 2, name: "namespace", kind: "scalar", T: 9 /* ScalarType.STRING */ },
]);

static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): GetFeatureFlagsInLatestCompositionByFederatedGraphRequest {
return new GetFeatureFlagsInLatestCompositionByFederatedGraphRequest().fromBinary(bytes, options);
}

static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): GetFeatureFlagsInLatestCompositionByFederatedGraphRequest {
return new GetFeatureFlagsInLatestCompositionByFederatedGraphRequest().fromJson(jsonValue, options);
}

static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): GetFeatureFlagsInLatestCompositionByFederatedGraphRequest {
return new GetFeatureFlagsInLatestCompositionByFederatedGraphRequest().fromJsonString(jsonString, options);
}

static equals(a: GetFeatureFlagsInLatestCompositionByFederatedGraphRequest | PlainMessage<GetFeatureFlagsInLatestCompositionByFederatedGraphRequest> | undefined, b: GetFeatureFlagsInLatestCompositionByFederatedGraphRequest | PlainMessage<GetFeatureFlagsInLatestCompositionByFederatedGraphRequest> | undefined): boolean {
return proto3.util.equals(GetFeatureFlagsInLatestCompositionByFederatedGraphRequest, a, b);
}
}

/**
* @generated from message wg.cosmo.platform.v1.GetFeatureFlagsInLatestCompositionByFederatedGraphResponse
*/
export class GetFeatureFlagsInLatestCompositionByFederatedGraphResponse extends Message<GetFeatureFlagsInLatestCompositionByFederatedGraphResponse> {
/**
* @generated from field: wg.cosmo.platform.v1.Response response = 1;
*/
response?: Response;

/**
* feature flags that are part of the latest valid composition of the federated graph
*
* @generated from field: repeated wg.cosmo.platform.v1.FeatureFlag feature_flags = 2;
*/
featureFlags: FeatureFlag[] = [];

constructor(data?: PartialMessage<GetFeatureFlagsInLatestCompositionByFederatedGraphResponse>) {
super();
proto3.util.initPartial(data, this);
}

static readonly runtime: typeof proto3 = proto3;
static readonly typeName = "wg.cosmo.platform.v1.GetFeatureFlagsInLatestCompositionByFederatedGraphResponse";
static readonly fields: FieldList = proto3.util.newFieldList(() => [
{ no: 1, name: "response", kind: "message", T: Response },
{ no: 2, name: "feature_flags", kind: "message", T: FeatureFlag, repeated: true },
]);

static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): GetFeatureFlagsInLatestCompositionByFederatedGraphResponse {
return new GetFeatureFlagsInLatestCompositionByFederatedGraphResponse().fromBinary(bytes, options);
}

static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): GetFeatureFlagsInLatestCompositionByFederatedGraphResponse {
return new GetFeatureFlagsInLatestCompositionByFederatedGraphResponse().fromJson(jsonValue, options);
}

static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): GetFeatureFlagsInLatestCompositionByFederatedGraphResponse {
return new GetFeatureFlagsInLatestCompositionByFederatedGraphResponse().fromJsonString(jsonString, options);
}

static equals(a: GetFeatureFlagsInLatestCompositionByFederatedGraphResponse | PlainMessage<GetFeatureFlagsInLatestCompositionByFederatedGraphResponse> | undefined, b: GetFeatureFlagsInLatestCompositionByFederatedGraphResponse | PlainMessage<GetFeatureFlagsInLatestCompositionByFederatedGraphResponse> | undefined): boolean {
return proto3.util.equals(GetFeatureFlagsInLatestCompositionByFederatedGraphResponse, a, b);
}
}

/**
* @generated from message wg.cosmo.platform.v1.GetFeatureSubgraphsByFederatedGraphRequest
*/
export class GetFeatureSubgraphsByFederatedGraphRequest extends Message<GetFeatureSubgraphsByFederatedGraphRequest> {
/**
* @generated from field: string federated_graph_name = 1;
*/
federatedGraphName = "";

/**
* @generated from field: string namespace = 2;
*/
namespace = "";

/**
* @generated from field: int32 limit = 3;
*/
limit = 0;

/**
* @generated from field: int32 offset = 4;
*/
offset = 0;

/**
* @generated from field: optional string query = 5;
*/
query?: string;

constructor(data?: PartialMessage<GetFeatureSubgraphsByFederatedGraphRequest>) {
super();
proto3.util.initPartial(data, this);
}

static readonly runtime: typeof proto3 = proto3;
static readonly typeName = "wg.cosmo.platform.v1.GetFeatureSubgraphsByFederatedGraphRequest";
static readonly fields: FieldList = proto3.util.newFieldList(() => [
{ no: 1, name: "federated_graph_name", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 2, name: "namespace", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 3, name: "limit", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
{ no: 4, name: "offset", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
{ no: 5, name: "query", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true },
]);

static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): GetFeatureSubgraphsByFederatedGraphRequest {
return new GetFeatureSubgraphsByFederatedGraphRequest().fromBinary(bytes, options);
}

static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): GetFeatureSubgraphsByFederatedGraphRequest {
return new GetFeatureSubgraphsByFederatedGraphRequest().fromJson(jsonValue, options);
}

static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): GetFeatureSubgraphsByFederatedGraphRequest {
return new GetFeatureSubgraphsByFederatedGraphRequest().fromJsonString(jsonString, options);
}

static equals(a: GetFeatureSubgraphsByFederatedGraphRequest | PlainMessage<GetFeatureSubgraphsByFederatedGraphRequest> | undefined, b: GetFeatureSubgraphsByFederatedGraphRequest | PlainMessage<GetFeatureSubgraphsByFederatedGraphRequest> | undefined): boolean {
return proto3.util.equals(GetFeatureSubgraphsByFederatedGraphRequest, a, b);
}
}

/**
* @generated from message wg.cosmo.platform.v1.GetFeatureSubgraphsByFederatedGraphResponse
*/
export class GetFeatureSubgraphsByFederatedGraphResponse extends Message<GetFeatureSubgraphsByFederatedGraphResponse> {
/**
* @generated from field: wg.cosmo.platform.v1.Response response = 1;
*/
response?: Response;

/**
* all unique feature subgraphs across all feature flags of the federated graph
*
* @generated from field: repeated wg.cosmo.platform.v1.Subgraph feature_subgraphs = 2;
*/
featureSubgraphs: Subgraph[] = [];

/**
* @generated from field: int32 total_count = 3;
*/
totalCount = 0;

constructor(data?: PartialMessage<GetFeatureSubgraphsByFederatedGraphResponse>) {
super();
proto3.util.initPartial(data, this);
}

static readonly runtime: typeof proto3 = proto3;
static readonly typeName = "wg.cosmo.platform.v1.GetFeatureSubgraphsByFederatedGraphResponse";
static readonly fields: FieldList = proto3.util.newFieldList(() => [
{ no: 1, name: "response", kind: "message", T: Response },
{ no: 2, name: "feature_subgraphs", kind: "message", T: Subgraph, repeated: true },
{ no: 3, name: "total_count", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
]);

static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): GetFeatureSubgraphsByFederatedGraphResponse {
return new GetFeatureSubgraphsByFederatedGraphResponse().fromBinary(bytes, options);
}

static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): GetFeatureSubgraphsByFederatedGraphResponse {
return new GetFeatureSubgraphsByFederatedGraphResponse().fromJson(jsonValue, options);
}

static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): GetFeatureSubgraphsByFederatedGraphResponse {
return new GetFeatureSubgraphsByFederatedGraphResponse().fromJsonString(jsonString, options);
}

static equals(a: GetFeatureSubgraphsByFederatedGraphResponse | PlainMessage<GetFeatureSubgraphsByFederatedGraphResponse> | undefined, b: GetFeatureSubgraphsByFederatedGraphResponse | PlainMessage<GetFeatureSubgraphsByFederatedGraphResponse> | undefined): boolean {
return proto3.util.equals(GetFeatureSubgraphsByFederatedGraphResponse, a, b);
}
}

/**
* @generated from message wg.cosmo.platform.v1.GetOrganizationWebhookHistoryRequest
*/
Expand Down
10 changes: 10 additions & 0 deletions controlplane/src/core/bufservices/PlatformService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ import { enableFeatureFlag } from './feature-flag/enableFeatureFlag.js';
import { getFeatureFlagByName } from './feature-flag/getFeatureFlagByName.js';
import { getFeatureFlags } from './feature-flag/getFeatureFlags.js';
import { getFeatureFlagsByFederatedGraph } from './feature-flag/getFeatureFlagsByFederatedGraph.js';
import { getFeatureFlagsInLatestCompositionByFederatedGraph } from './feature-flag/getFeatureFlagsInLatestCompositionByFederatedGraph.js';
import { getFeatureSubgraphs } from './feature-flag/getFeatureSubgraphs.js';
import { getFeatureSubgraphsByFederatedGraph } from './feature-flag/getFeatureSubgraphsByFederatedGraph.js';
import { getFeatureSubgraphsByFeatureFlag } from './feature-flag/getFeatureSubgraphsByFeatureFlag.js';
import { updateFeatureFlag } from './feature-flag/updateFeatureFlag.js';
import { checkFederatedGraph } from './federated-graph/checkFederatedGraph.js';
Expand Down Expand Up @@ -755,6 +757,14 @@ export default function (opts: RouterOptions): Partial<ServiceImpl<typeof Platfo
return getFeatureFlagsByFederatedGraph(opts, req, ctx);
},

getFeatureFlagsInLatestCompositionByFederatedGraph: (req, ctx) => {
return getFeatureFlagsInLatestCompositionByFederatedGraph(opts, req, ctx);
},

getFeatureSubgraphsByFederatedGraph: (req, ctx) => {
return getFeatureSubgraphsByFederatedGraph(opts, req, ctx);
},

getOrganizationWebhookHistory: (req, ctx) => {
return getOrganizationWebhookHistory(opts, req, ctx);
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { PlainMessage } from '@bufbuild/protobuf';
import { HandlerContext } from '@connectrpc/connect';
import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb';
import {
GetFeatureFlagsInLatestCompositionByFederatedGraphRequest,
GetFeatureFlagsInLatestCompositionByFederatedGraphResponse,
} from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb';
import { FeatureFlagDTO } from '../../../types/index.js';
import { FeatureFlagRepository } from '../../repositories/FeatureFlagRepository.js';
import { FederatedGraphRepository } from '../../repositories/FederatedGraphRepository.js';
import { NamespaceRepository } from '../../repositories/NamespaceRepository.js';
import type { RouterOptions } from '../../routes.js';
import { enrichLogger, getLogger, handleError } from '../../util.js';
import { UnauthorizedError } from '../../errors/errors.js';

export function getFeatureFlagsInLatestCompositionByFederatedGraph(
opts: RouterOptions,
req: GetFeatureFlagsInLatestCompositionByFederatedGraphRequest,
ctx: HandlerContext,
): Promise<PlainMessage<GetFeatureFlagsInLatestCompositionByFederatedGraphResponse>> {
let logger = getLogger(ctx, opts.logger);

return handleError<PlainMessage<GetFeatureFlagsInLatestCompositionByFederatedGraphResponse>>(
ctx,
logger,
async () => {
const authContext = await opts.authenticator.authenticate(ctx.requestHeader);
logger = enrichLogger(ctx, logger, authContext);
const featureFlagRepo = new FeatureFlagRepository(logger, opts.db, authContext.organizationId);
const fedGraphRepo = new FederatedGraphRepository(logger, opts.db, authContext.organizationId);
const namespaceRepo = new NamespaceRepository(opts.db, authContext.organizationId);

const namespace = await namespaceRepo.byName(req.namespace);
if (!namespace) {
return {
response: {
code: EnumStatusCode.ERR_NOT_FOUND,
details: `Namespace ${req.namespace} not found`,
},
featureFlags: [],
};
}

const federatedGraph = await fedGraphRepo.byName(req.federatedGraphName, req.namespace);
if (!federatedGraph) {
return {
response: {
code: EnumStatusCode.ERR_NOT_FOUND,
details: `Federated Graph '${req.federatedGraphName}' not found`,
},
featureFlags: [],
};
}

if (!authContext.rbac.hasFederatedGraphReadAccess(federatedGraph)) {
throw new UnauthorizedError();
}

if (!federatedGraph.schemaVersionId) {
return {
response: {
code: EnumStatusCode.OK,
},
featureFlags: [],
};
}

// Get feature flag IDs from the latest valid composition
const ffsInLatestValidComposition = await featureFlagRepo.getFeatureFlagSchemaVersionsByBaseSchemaVersion({
baseSchemaVersionId: federatedGraph.schemaVersionId,
});

const featureFlags: FeatureFlagDTO[] = [];
if (ffsInLatestValidComposition) {
for (const ff of ffsInLatestValidComposition) {
if (!ff.featureFlagId) {
continue;
}
const flag = await featureFlagRepo.getFeatureFlagById({
featureFlagId: ff.featureFlagId,
namespaceId: namespace.id,
includeSubgraphs: false,
});
if (flag) {
featureFlags.push(flag);
}
}
}

return {
response: {
code: EnumStatusCode.OK,
},
featureFlags,
};
},
);
}
Loading
Loading