feat: restore per-type guards for union narrowing

Signed-off-by: benjdlambert <ben@blam.sh>
This commit is contained in:
benjdlambert
2026-05-19 12:14:25 +02:00
parent e5671e44c0
commit a185d7e0f8
4 changed files with 88 additions and 0 deletions
@@ -485,6 +485,16 @@ export const isAiResourceEntity: (
entity: Entity,
) => entity is AiResourceEntityV1alpha1;
// @alpha
export const isRuleAiResourceEntity: (
entity: Entity,
) => entity is RuleAiResourceEntityV1alpha1;
// @alpha
export const isSkillAiResourceEntity: (
entity: Entity,
) => entity is SkillAiResourceEntityV1alpha1;
// @public
export type KindValidator = {
check(entity: Entity): Promise<boolean>;
+2
View File
@@ -38,6 +38,8 @@ export {
skillAiResourceEntityV1alpha1Validator,
ruleAiResourceEntityV1alpha1Validator,
isAiResourceEntity,
isSkillAiResourceEntity,
isRuleAiResourceEntity,
aiResourceEntityModel,
} from './kinds/AiResourceEntityV1alpha1';
export * from './model';
@@ -23,6 +23,8 @@ import {
skillAiResourceEntityV1alpha1Validator as skillValidator,
ruleAiResourceEntityV1alpha1Validator as ruleValidator,
isAiResourceEntity,
isSkillAiResourceEntity,
isRuleAiResourceEntity,
} from './AiResourceEntityV1alpha1';
describe('AiResourceV1alpha1 default validator', () => {
@@ -301,3 +303,57 @@ describe('isAiResourceEntity', () => {
expect(isAiResourceEntity(entity)).toBe(false);
});
});
describe('isSkillAiResourceEntity', () => {
it('returns true for a skill AiResource', () => {
const entity: Entity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'AiResource',
metadata: { name: 'test' },
spec: { type: 'skill' },
};
expect(isSkillAiResourceEntity(entity)).toBe(true);
});
it('returns false for a non-skill AiResource', () => {
const entity: Entity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'AiResource',
metadata: { name: 'test' },
spec: { type: 'rule' },
};
expect(isSkillAiResourceEntity(entity)).toBe(false);
});
it('returns false for a different kind', () => {
const entity: Entity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: { name: 'test' },
spec: { type: 'skill' },
};
expect(isSkillAiResourceEntity(entity)).toBe(false);
});
});
describe('isRuleAiResourceEntity', () => {
it('returns true for a rule AiResource', () => {
const entity: Entity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'AiResource',
metadata: { name: 'test' },
spec: { type: 'rule' },
};
expect(isRuleAiResourceEntity(entity)).toBe(true);
});
it('returns false for a non-rule AiResource', () => {
const entity: Entity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'AiResource',
metadata: { name: 'test' },
spec: { type: 'skill' },
};
expect(isRuleAiResourceEntity(entity)).toBe(false);
});
});
@@ -125,6 +125,26 @@ export const isAiResourceEntity = (
): entity is AiResourceEntityV1alpha1 =>
entity.apiVersion === 'backstage.io/v1alpha1' && entity.kind === 'AiResource';
/**
* Type guard for {@link SkillAiResourceEntityV1alpha1}.
*
* @alpha
*/
export const isSkillAiResourceEntity = (
entity: Entity,
): entity is SkillAiResourceEntityV1alpha1 =>
isAiResourceEntity(entity) && entity.spec?.type === 'skill';
/**
* Type guard for {@link RuleAiResourceEntityV1alpha1}.
*
* @alpha
*/
export const isRuleAiResourceEntity = (
entity: Entity,
): entity is RuleAiResourceEntityV1alpha1 =>
isAiResourceEntity(entity) && entity.spec?.type === 'rule';
const ruleValidator = entityKindSchemaValidator(ruleJsonSchema);
/**