All files / sheaves/src metadata.ts

100% Statements 23/23
100% Branches 16/16
100% Functions 5/5
100% Lines 23/23

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86              6x   6x 91x 91x                 6x     95x 1x   94x 2x       92x 1x       91x 1x       90x                 6x   77x               6x   8x                         6x       101x 6x   95x 101x    
/**
 * MetadataSpec constructors and evaluation helpers.
 */
 
import type { MetadataSpec } from './types.ts';
 
const metadataPlainObjectHint =
  'Sheaf metadata must be a plain object; use e.g. { value: myValue } if you need to attach a primitive.';
 
const isPlainObjectRecord = (value: object): boolean => {
  const proto = Object.getPrototypeOf(value);
  return proto === null || proto === Object.prototype;
};
 
/**
 * Normalize evaluated metadata: empty sentinel is `{}`; invalid shapes throw.
 *
 * @param raw - Result from constant value or callable, before validation.
 * @returns A plain object suitable for candidate metadata.
 */
const normalizeEvaluatedSheafMetadata = (
  raw: unknown,
): Record<string, unknown> => {
  if (raw === undefined || raw === null) {
    return {};
  }
  if (typeof raw !== 'object') {
    throw new Error(
      `sheafify: metadata cannot be a primitive (${typeof raw}). ${metadataPlainObjectHint}`,
    );
  }
  if (Array.isArray(raw)) {
    throw new Error(
      `sheafify: metadata cannot be an array. ${metadataPlainObjectHint}`,
    );
  }
  if (!isPlainObjectRecord(raw)) {
    throw new Error(
      `sheafify: metadata must be a plain object. ${metadataPlainObjectHint}`,
    );
  }
  return raw as Record<string, unknown>;
};
 
/**
 * Wrap a static value as a constant metadata spec.
 *
 * @param value - The static metadata value.
 * @returns A constant MetadataSpec wrapping the value.
 */
export const constant = <M extends Record<string, unknown>>(
  value: M,
): MetadataSpec<M> => harden({ kind: 'constant', value });
 
/**
 * Wrap a live function as a callable metadata spec.
 *
 * @param fn - Function from invocation args to metadata value.
 * @returns A callable metadata spec.
 */
export const callable = <M extends Record<string, unknown>>(
  fn: (args: unknown[]) => M,
): MetadataSpec<M> => harden({ kind: 'callable', fn });
 
/**
 * Evaluate a metadata spec against the invocation args.
 *
 * Missing spec yields `{}` (no metadata). Callable/constant results must be plain objects;
 * `undefined`/`null` from the producer normalize to `{}`. Primitives, arrays, and non-plain
 * objects throw with guidance to use an explicit record such as `{ value: myValue }`.
 *
 * @param spec - The spec to evaluate, or undefined.
 * @param args - The invocation arguments.
 * @returns The evaluated metadata object (possibly empty).
 */
export const evaluateMetadata = <MetaData extends Record<string, unknown>>(
  spec: MetadataSpec<MetaData> | undefined,
  args: unknown[],
): MetaData => {
  if (spec === undefined) {
    return {} as MetaData;
  }
  const raw = spec.kind === 'constant' ? spec.value : spec.fn(args);
  return normalizeEvaluatedSheafMetadata(raw) as MetaData;
};