// Copyright (c) Cratis. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. import { PropertyAccessor, PropertyPathResolverProxyHandler } from '@cratis/fundamentals'; import { constantValueExpression } from '../constantValueExpression.js'; import { eventContextPropertyExpression } from '../eventContextPropertyExpression.js'; import { AddBuilder } from './AddBuilder.js'; import { AddChildBuilder, ChildAdditionEntry } from './AddChildBuilder.js'; import { CompositeKeyBuilder } from './CompositeKeyBuilder.js'; import { IAddBuilder } from './IAddBuilder.js'; import { IAddChildBuilder } from './IAddChildBuilder.js'; import { ICompositeKeyBuilder } from './ICompositeKeyBuilder.js'; import { IFromBuilder } from './IFromBuilder.js'; import { ISetBuilder } from './ISetBuilder.js'; import { ISubtractBuilder } from './ISubtractBuilder.js'; import { SetBuilder } from './SetBuilder.js'; import { SubtractBuilder } from './SubtractBuilder.js'; import { recordKeyDeclaration } from './projectionBuilderProvenance.js'; /** * Accumulated property mapping for a from clause. */ export interface FromEntry { properties: Record; key: string; parentKey: string; children: ChildAdditionEntry[]; } /** * Concrete implementation of {@link IFromBuilder}. * @template TReadModel - The read model type. * @template TEvent - The event type. */ export class FromBuilder implements IFromBuilder { readonly entry: FromEntry = { properties: {}, key: '$eventSourceId', parentKey: '', children: [] }; /** @inheritdoc */ set(readModelPropertyAccessor: PropertyAccessor): ISetBuilder> { const handler = new PropertyPathResolverProxyHandler(); const proxy = new Proxy({}, handler); readModelPropertyAccessor(proxy as TReadModel); const rmProp = handler.property; return new SetBuilder>( rmProp, (rp, expr) => { this.entry.properties[rp] = expr; }, this ); } /** @inheritdoc */ usingKey(keyAccessor: PropertyAccessor): this { const handler = new PropertyPathResolverProxyHandler(); const proxy = new Proxy({}, handler); keyAccessor(proxy as TEvent); this.entry.key = handler.property; recordKeyDeclaration(this, 'Key', '.from().usingKey'); return this; } /** @inheritdoc */ usingKeyFromContext(contextPropertyName: string): this { this.entry.key = eventContextPropertyExpression(contextPropertyName); recordKeyDeclaration(this, 'Key', '.from().usingKeyFromContext'); return this; } /** @inheritdoc */ usingParentKey(keyAccessor: PropertyAccessor): this { const handler = new PropertyPathResolverProxyHandler(); const proxy = new Proxy({}, handler); keyAccessor(proxy as TEvent); this.entry.parentKey = handler.property; recordKeyDeclaration(this, 'ParentKey', '.from().usingParentKey'); return this; } /** @inheritdoc */ usingParentKeyFromContext(contextPropertyName: string): this { this.entry.parentKey = eventContextPropertyExpression(contextPropertyName); recordKeyDeclaration(this, 'ParentKey', '.from().usingParentKeyFromContext'); return this; } /** @inheritdoc */ usingCompositeKey(builderCallback: (builder: ICompositeKeyBuilder) => void): this { const compositeKeyBuilder = new CompositeKeyBuilder(); builderCallback(compositeKeyBuilder); this.entry.key = compositeKeyBuilder.build(); recordKeyDeclaration(this, 'Key', '.from().usingCompositeKey'); return this; } /** @inheritdoc */ usingParentCompositeKey(builderCallback: (builder: ICompositeKeyBuilder) => void): this { const compositeKeyBuilder = new CompositeKeyBuilder(); builderCallback(compositeKeyBuilder); this.entry.parentKey = compositeKeyBuilder.build(); recordKeyDeclaration(this, 'ParentKey', '.from().usingParentCompositeKey'); return this; } /** @inheritdoc */ usingConstantKey(value: string): this { this.entry.key = constantValueExpression(value); recordKeyDeclaration(this, 'Key', '.from().usingConstantKey'); return this; } /** @inheritdoc */ usingConstantParentKey(value: string): this { this.entry.parentKey = constantValueExpression(value); recordKeyDeclaration(this, 'ParentKey', '.from().usingConstantParentKey'); return this; } /** @inheritdoc */ increment(readModelPropertyAccessor: PropertyAccessor): this { const handler = new PropertyPathResolverProxyHandler(); const proxy = new Proxy({}, handler); readModelPropertyAccessor(proxy as TReadModel); this.entry.properties[handler.property] = '$increment'; return this; } /** @inheritdoc */ decrement(readModelPropertyAccessor: PropertyAccessor): this { const handler = new PropertyPathResolverProxyHandler(); const proxy = new Proxy({}, handler); readModelPropertyAccessor(proxy as TReadModel); this.entry.properties[handler.property] = '$decrement'; return this; } /** @inheritdoc */ add(readModelPropertyAccessor: PropertyAccessor): IAddBuilder { const handler = new PropertyPathResolverProxyHandler(); const proxy = new Proxy({}, handler); readModelPropertyAccessor(proxy as TReadModel); return new AddBuilder( handler.property, (property, expression) => { this.entry.properties[property] = expression; }, this ); } /** @inheritdoc */ subtract(readModelPropertyAccessor: PropertyAccessor): ISubtractBuilder { const handler = new PropertyPathResolverProxyHandler(); const proxy = new Proxy({}, handler); readModelPropertyAccessor(proxy as TReadModel); return new SubtractBuilder( handler.property, (property, expression) => { this.entry.properties[property] = expression; }, this ); } /** @inheritdoc */ count(readModelPropertyAccessor: PropertyAccessor): this { const handler = new PropertyPathResolverProxyHandler(); const proxy = new Proxy({}, handler); readModelPropertyAccessor(proxy as TReadModel); this.entry.properties[handler.property] = '$count'; return this; } /** @inheritdoc */ addChild(targetPropertyAccessor: (model: TReadModel) => readonly TChildModel[] | null | undefined, builderCallback: (builder: IAddChildBuilder & TEvent) => void): this; /** @inheritdoc */ addChild(targetPropertyAccessor: (model: TReadModel) => readonly TChildModel[] | null | undefined, eventPropertyAccessor: PropertyAccessor): this; /** @inheritdoc */ addChild( targetPropertyAccessor: PropertyAccessor, eventPropertyAccessorOrBuilderCallback: PropertyAccessor | ((builder: IAddChildBuilder) => void) ): this; addChild( targetPropertyAccessor: PropertyAccessor, eventPropertyAccessorOrBuilderCallback: PropertyAccessor | ((builder: IAddChildBuilder) => void) ): this { const targetHandler = new PropertyPathResolverProxyHandler(); const targetProxy = new Proxy({}, targetHandler); targetPropertyAccessor(targetProxy as TReadModel); const probe = new AddChildBuilder(); const probeProxy = new Proxy({}, probe); (eventPropertyAccessorOrBuilderCallback as (value: unknown) => void)(probeProxy); this.entry.children.push(probe.usedAsBuilder ? { targetProperty: targetHandler.property, identifiedBy: probe.identifiedByProperty, usingKey: probe.usingKeyProperty, usingParentKey: probe.usingParentKeyProperty } : { targetProperty: targetHandler.property, fromEventProperty: probe.capturedEventProperty }); return this; } /** @inheritdoc */ setThisValue(): ISetBuilder { return new SetBuilder( '$this', (rp, expr) => { this.entry.properties[rp] = expr; }, this ); } }