/** The interned `{ type: }` schemas, built on demand. */ import type { JSONSchemaObj, JSONSchemaTypes } from "@commonfabric/api"; import { FabricPrimitive, type FabricValue } from "@commonfabric/data-model"; import { internSchema } from "./schema-intern.ts"; /** * Map from `JSONSchema` type names (and special names) to corresponding * interned schemas. Populated lazily. */ const BASIC_SCHEMAS: Record = {}; /** * Helper for `schemaForValueType()` and `emptySchemaObject()`, which does * the lookup and interning as necessary. */ function getBasicSchema(key: string) { const found = BASIC_SCHEMAS[key]; if (found) { return found; } else { const result = BASIC_SCHEMAS[key] = internSchema({ type: key as JSONSchemaTypes, }); return result; } } /** * Gets the basic `{ type: name }` schema for a given value. Returns `undefined` * if there is no well-defined type for the value. The result is always interned * (and frozen). * * **Note:** `undefined` (as a value) is in an "intermediate" state in the * codebase as of this writing, and _this_ function treats it as not having a * well-defined type. */ export function schemaForValueType( value: FabricValue, ): JSONSchemaObj | undefined { // TODO(danfuzz): This is a place that will need to get smarter once we // actually want to accept values beyond what's strictly allowed in JSON. This // notably includes `undefined` and all the other non-plain-object // `FabricValue` possibilities. const type = typeof value; switch (type) { case "object": { if (value === null) { return getBasicSchema("null"); } else if (Array.isArray(value)) { return getBasicSchema("array"); } else if (value instanceof FabricPrimitive) { // A `FabricPrimitive` gets its specific type name (e.g. // "FabricBytes") rather than "object": it is an opaque leaf, so // "object" would invite structural keywords that cannot apply. return getBasicSchema(value.schemaType); } break; } case "number": { if (Number.isInteger(value)) { return getBasicSchema("integer"); } break; } case "bigint": case "symbol": case "undefined": { // Not accepted yet, even though the intention is to accept most or all // of these. return undefined; } } return getBasicSchema(type); } /** Gets the standard interned empty schema _object_, a literal `{}`. */ export function emptySchemaObject() { const key = "emptySchema"; const found = BASIC_SCHEMAS[key]; if (found) { return found; } else { const result = BASIC_SCHEMAS[key] = internSchema({}); return result; } }