apiful
Version: 
Extensible, typed API tooling
206 lines (200 loc) • 5.85 kB
JavaScript
import { C as CODE_HEADER_DIRECTIVES } from '../shared/apiful.B_nvMJ_g.mjs';
async function jsonToTypeDefinition(data, options = {}) {
  const { compile } = await import('json-schema-to-typescript-lite').catch(() => {
    throw new Error('Missing dependency "json-schema-to-typescript-lite", please install it');
  });
  const resolvedOptions = resolveOptions(options);
  const schema = createJsonSchema(data, resolvedOptions);
  const output = await compile(schema, resolvedOptions.typeName);
  return `
${CODE_HEADER_DIRECTIVES}
${output}
`.trimStart();
}
function createJsonSchema(data, options) {
  if (Array.isArray(data)) {
    if (data.length === 0) {
      return {
        type: "array"
      };
    }
    const itemSchemas = data.map((item) => createJsonSchema(item, options));
    return {
      type: "array",
      items: mergeSchemas(itemSchemas)
    };
  } else if (typeof data === "object" && data !== null) {
    const properties = Object.fromEntries(
      Object.entries(data).map(([key, value]) => [
        key,
        createJsonSchema(value, options)
      ])
    );
    return {
      type: "object",
      properties,
      required: options.strictProperties ? Object.keys(properties) : false,
      additionalProperties: Object.keys(data).length === 0
    };
  } else if (data == null) {
    return {
      type: "any"
    };
  } else {
    return {
      type: typeof data
    };
  }
}
function mergeSchemas(schemas, options) {
  if (schemas.length === 0)
    return {};
  if (schemas.length === 1)
    return schemas[0];
  const types = new Set(schemas.map((schema) => schema.type));
  if (types.size !== 1) {
    return {
      anyOf: schemas.map((schema) => ({
        ...schema,
        additionalProperties: schema.type === "object" ? schema.additionalProperties : void 0
      }))
    };
  }
  const type = schemas[0].type;
  if (type === "object") {
    const propertySchemas = /* @__PURE__ */ new Map();
    const requiredProperties = /* @__PURE__ */ new Set();
    for (const schema of schemas) {
      if (!schema.properties)
        continue;
      for (const [key, value] of Object.entries(schema.properties)) {
        if (!propertySchemas.has(key))
          propertySchemas.set(key, []);
        propertySchemas.get(key).push(value);
      }
      if (Array.isArray(schema.required)) {
        for (const key of schema.required)
          requiredProperties.add(key);
      }
    }
    return {
      type: "object",
      properties: Object.fromEntries(
        Array.from(propertySchemas.entries()).map(([key, propertySchemas2]) => [
          key,
          mergeSchemas(propertySchemas2)
        ])
      ),
      required: requiredProperties.size > 0 ? Array.from(requiredProperties) : void 0,
      additionalProperties: propertySchemas.size === 0
    };
  } else if (type === "array") {
    const itemSchemas = schemas.map((schema) => schema.items).filter((items) => Boolean(items));
    return {
      type: "array",
      items: mergeSchemas(itemSchemas)
    };
  } else {
    return schemas[0];
  }
}
function resolveOptions(options) {
  return {
    typeName: options.typeName || "Root",
    strictProperties: options.strictProperties ?? false
  };
}
const validatorSymbol = Symbol.for("apiful.validator");
class TypeValidationError extends Error {
  value;
  cause;
  constructor({
    value,
    cause
  }) {
    super(
      `Type validation failed with value: ${JSON.stringify(value)}
Error message: ${getErrorMessage(cause)}`
    );
    this.value = value;
    this.cause = cause;
  }
}
function validator(validate) {
  return { [validatorSymbol]: true, validate };
}
function validateTypes({
  value,
  schema: inputSchema
}) {
  const result = safeValidateTypes({ value, schema: inputSchema });
  if (!result.success) {
    throw new TypeValidationError({ value, cause: result.error });
  }
  return result.value;
}
function safeValidateTypes({
  value,
  schema
}) {
  try {
    if (schema.validate == null) {
      return { success: true, value };
    }
    const result = schema.validate(value);
    if (result.success) {
      return result;
    }
    return {
      success: false,
      error: new TypeValidationError({ value, cause: result.error })
    };
  } catch (error) {
    return {
      success: false,
      error: new TypeValidationError({ value, cause: error })
    };
  }
}
function isValidator(value) {
  return typeof value === "object" && value !== null && validatorSymbol in value && value[validatorSymbol] === true && "validate" in value;
}
function getErrorMessage(error) {
  if (error == null) {
    return "unknown error";
  }
  if (typeof error === "string") {
    return error;
  }
  if (error instanceof Error) {
    return error.message;
  }
  return JSON.stringify(error);
}
const schemaSymbol = Symbol.for("apiful.schema");
function jsonSchema(jsonSchema2, { validate } = {}) {
  return {
    [schemaSymbol]: true,
    _type: void 0,
    // Should never be used directly
    [validatorSymbol]: true,
    jsonSchema: jsonSchema2,
    validate
  };
}
function isSchema(value) {
  return typeof value === "object" && value !== null && schemaSymbol in value && value[schemaSymbol] === true && "jsonSchema" in value && "validate" in value;
}
function base64ToUint8Array(base64String) {
  const base64Url = base64String.replaceAll("-", "+").replaceAll("_", "/");
  const latin1String = globalThis.atob(base64Url);
  return Uint8Array.from(latin1String, (byte) => byte.charCodeAt(0));
}
function uint8ArrayToBase64(array) {
  let latin1String = "";
  for (const byte of array) {
    latin1String += String.fromCharCode(byte);
  }
  return globalThis.btoa(latin1String);
}
export { TypeValidationError, base64ToUint8Array, isSchema, isValidator, jsonSchema, jsonToTypeDefinition, safeValidateTypes, uint8ArrayToBase64, validateTypes, validator, validatorSymbol };