import type { PreviousCell } from './cell-staleness'; export type StepProgramDatasetOptions = { runIf?: ( row: Record, index: number, ) => boolean | Promise; recompute?: boolean; recomputeOnError?: boolean; staleAfterSeconds?: number; }; export type StepProgramDatasetColumnRunInput = { row: Record; ctx: unknown; index: number; previousCell?: PreviousCell; }; export type StepProgramDatasetColumnDefinition = StepProgramDatasetOptions & { run: ( input: StepProgramDatasetColumnRunInput, ) => Value | Promise; }; export type StepProgramDatasetColumnInput = | TResolver | StepProgramDatasetColumnDefinition; function isStepProgramDatasetColumnDefinition( value: unknown, ): value is StepProgramDatasetColumnDefinition { return ( value !== null && typeof value === 'object' && typeof (value as { run?: unknown }).run === 'function' ); } function isRecord(value: unknown): value is Record { return value !== null && typeof value === 'object' && !Array.isArray(value); } function isPreviousCell(value: unknown): value is PreviousCell { return ( isRecord(value) && Object.prototype.hasOwnProperty.call(value, 'value') ); } export type StepProgramDatasetStep = { name: string; resolver: TResolver; }; export type StepProgramDatasetProgram = { kind: 'steps'; steps: readonly TStep[]; continueOnProviderUnavailable?: boolean; }; export type StepProgramDatasetConditionalResolver = { kind: 'conditional'; when: ( row: Record, index: number, ) => boolean | Promise; run: TResolver; elseValue?: unknown; }; export function isStepProgramDatasetProgram( value: unknown, ): value is StepProgramDatasetProgram { return ( value !== null && typeof value === 'object' && (value as { kind?: unknown }).kind === 'steps' && Array.isArray((value as { steps?: unknown }).steps) ); } export function isStepProgramDatasetConditionalResolver( value: unknown, ): value is StepProgramDatasetConditionalResolver { return ( value !== null && typeof value === 'object' && (value as { kind?: unknown }).kind === 'conditional' && typeof (value as { when?: unknown }).when === 'function' && typeof (value as { run?: unknown }).run === 'function' ); } function continuesOnProviderUnavailable( program: StepProgramDatasetProgram, ): boolean { return ( program.continueOnProviderUnavailable === true || program.steps.some( (step) => isStepProgramDatasetConditionalResolver( (step as StepProgramDatasetStep).resolver, ) && ( ( step as StepProgramDatasetStep< StepProgramDatasetConditionalResolver > ).resolver.when as { __deeplineProviderWaterfall?: unknown; } ).__deeplineProviderWaterfall === true, ) ); } export class StepProgramDatasetBuilder< TStep extends StepProgramDatasetStep, TResolver, TRunOptions, TResult, > { private readonly program: StepProgramDatasetProgram = { kind: 'steps', steps: [], }; constructor( private readonly runProgram: ( program: StepProgramDatasetProgram, options?: TRunOptions, ) => TResult, private readonly messages: { emptyColumnName: string; invalidColumnsProgram: string; legacyStep: string; }, ) {} withColumn( name: string, columnInput: StepProgramDatasetColumnInput, options?: StepProgramDatasetOptions, ): this { if (!name.trim()) { throw new Error(this.messages.emptyColumnName); } const normalized = this.normalizeColumnInput(columnInput, options); this.program.steps = [ ...this.program.steps, { name, ...(normalized.options?.recompute === true ? { recompute: true } : {}), ...(normalized.options?.recomputeOnError === true ? { recomputeOnError: true } : {}), ...(typeof normalized.options?.staleAfterSeconds === 'number' ? { staleAfterSeconds: normalized.options.staleAfterSeconds } : {}), resolver: this.applyDerivationOptions( normalized.resolver, normalized.options, ), } as TStep, ]; return this; } withColumns(program: StepProgramDatasetProgram): this { if (!isStepProgramDatasetProgram(program)) { throw new Error(this.messages.invalidColumnsProgram); } const continueOnProviderUnavailable = continuesOnProviderUnavailable(program); this.program.steps = [ ...this.program.steps, ...program.steps.map((step) => continueOnProviderUnavailable ? ({ ...step, continueOnProviderUnavailable: true } as TStep) : step, ), ]; return this; } step(): never { throw new Error(this.messages.legacyStep); } run(options?: TRunOptions): TResult { return this.runProgram(this.program, options); } private applyDerivationOptions( resolver: TResolver, options?: StepProgramDatasetOptions, ): TResolver { if ( !options?.runIf || isStepProgramDatasetConditionalResolver(resolver) ) { return resolver; } return { kind: 'conditional', when: options.runIf, run: resolver, elseValue: null, } as TResolver; } private normalizeColumnInput( columnInput: StepProgramDatasetColumnInput, options?: StepProgramDatasetOptions, ): { resolver: TResolver; options?: StepProgramDatasetOptions } { if (!isStepProgramDatasetColumnDefinition(columnInput)) { return { resolver: columnInput, options }; } const { run, ...definitionOptions } = columnInput; const resolver = (( row: Record, ctx: unknown, third?: unknown, fourth?: unknown, fifth?: unknown, ) => { const index = typeof third === 'number' ? third : typeof fourth === 'number' ? fourth : 0; const rowForRun = isRecord(third) && typeof fourth === 'number' ? third : row; const previousCell = isPreviousCell(fifth) ? fifth : isPreviousCell(fourth) ? fourth : undefined; return run({ row: rowForRun, ctx, index, ...(previousCell ? { previousCell } : {}), }); }) as TResolver; return { resolver, options: { ...definitionOptions, ...(options ?? {}), }, }; } }