// Copyright (c) Cratis. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. import { describe, expect, it } from 'vitest'; import { eventType } from '../../events/eventTypeDecorator.js'; import { FromBuilder } from './FromBuilder.js'; import { IAddChildBuilder } from './IAddChildBuilder.js'; import { IReadModelPropertiesBuilder } from './IReadModelPropertiesBuilder.js'; import { JoinBuilder } from './JoinBuilder.js'; import { ProjectionBuilderFor } from './ProjectionBuilderFor.js'; class Line { id = ''; } class Base { id = ''; } class Derived extends Base { derivedId = ''; } class LineAdded { line = new Line(); } eventType()(LineAdded); class Order { id = ''; lines: Line[] = []; nullableLines: Line[] | null = null; bases: Base[] = []; } // This checks the shared public interface independently of either concrete builder. function acceptOriginalCallSites(builder: IReadModelPropertiesBuilder): void { builder.addChild(model => model.nullableLines, event => event.line); builder.addChild(model => model.nullableLines, child => child.identifiedBy(item => item.id)); builder.addChild(model => model.bases, (event: LineAdded) => event.line); builder.addChild(model => model.bases, (child: IAddChildBuilder) => child .identifiedBy(item => item.derivedId) .usingKeyFromContext('sequenceNumber')); builder.addChild(model => model.lines, (event: LineAdded) => event.line); } describe('addChild source compatibility', () => { it('should accept nullable targets and explicitly narrower child types on the shared interface', () => { const builder: IReadModelPropertiesBuilder = new FromBuilder(); acceptOriginalCallSites(builder); }); it('should accept the original call shapes on the concrete from builder', () => { const builder = new FromBuilder(); builder.addChild(model => model.nullableLines, event => event.line); builder.addChild(model => model.bases, (child: IAddChildBuilder) => child.identifiedBy(item => item.derivedId)); builder.addChild(model => model.bases, (event: LineAdded) => event.line); builder.addChild(model => model.lines, (event: LineAdded) => event.line); expect(builder.entry.children).toHaveLength(4); }); it('should accept the original call shapes on the concrete join builder', () => { const builder = new JoinBuilder(); builder.addChild(model => model.nullableLines, event => event.line); builder.addChild(model => model.bases, (child: IAddChildBuilder) => child.identifiedBy(item => item.derivedId)); builder.addChild(model => model.bases, (event: LineAdded) => event.line); builder.addChild(model => model.lines, (event: LineAdded) => event.line); expect(builder.entry.children).toHaveLength(4); }); it('should contextually type the unannotated builder callback in the documentation example', () => { const projection = new ProjectionBuilderFor(); projection.from(LineAdded, from => from .addChild(model => model.lines, child => child .identifiedBy(item => item.id) .usingKeyFromContext('sequenceNumber') .usingParentKeyFromContext('eventSourceId'))); expect(projection.build('order', 'Order').Children).toHaveProperty('lines'); }); });