core/aggregators/field.aggregator.ts

core/aggregators/field.aggregator.ts is a file in GovLab Patterns. 56 lines of code and 13 definitions.

import { DataLoadError, isBlank, kindOf, primitiveOf, resolvePrimitive } from "#core/classifiers/schema.classifier";
import type { FieldSchema, Kind, Primitive } from "#types/schema.types";
import { heterogeneousKinds, unresolvedKind } from "#configuration/strings/schema.strings";

export class FieldAccumulator {
    private readonly kinds = new Set<Kind>();
    private readonly primitives = new Set<Primitive>();
    private readonly lengths = new Set<number>();
    private present = 0;
    private elementNumeric = true;
    private sawElement = false;

    public observe(value: unknown, floatField: boolean): void {
        if (isBlank(value)) {
            return;
        }
        this.present += 1;
        const kind = kindOf(value);
        this.kinds.add(kind);
        if (kind === "scalar") {
            this.primitives.add(primitiveOf(value, floatField));
        }
        if (Array.isArray(value)) {
            this.observeList(value);
        }
    }

    public result(name: string, total: number): FieldSchema {
        if (this.present === 0) {
            return { elementNumeric: false, fixedLength: null, kind: "scalar", name, nullable: true, primitive: null };
        }
        if (this.kinds.size !== 1) {
            const kinds = [...this.kinds].sort((a, b) => a.localeCompare(b)).join(", ");
            throw new DataLoadError(heterogeneousKinds(name, kinds));
        }
        const [kind] = [...this.kinds];
        if (kind === undefined) {
            throw new DataLoadError(unresolvedKind(name));
        }
        const [firstLength] = [...this.lengths];
        return {
            elementNumeric: this.sawElement && this.elementNumeric,
            fixedLength: this.lengths.size === 1 ? (firstLength ?? null) : null,
            kind,
            name,
            nullable: this.present < total,
            primitive: kind === "scalar" ? resolvePrimitive(name, this.primitives) : null,
        };
    }

    private observeList(value: readonly unknown[]): void {
        this.lengths.add(value.length);
        for (const element of value) {
            this.sawElement = true;
            if (typeof element !== "number") {
                this.elementNumeric = false;
            }
        }
    }
}