json_list_to_external_table = (get_json_list, fields0, methods = {}) => {
  const fields = fields0.map((f) =>
    f.constructor.name === Object.name ? new Field(f) : f
  );
  const getRows = async (where = {}, selopts = {}) => {
    let data_in = await get_json_list(where, selopts);
    const restricts = Object.entries(where);
    const sat =
      (x) =>
      ([k, v]) => {
        if (k === "or" && Array.isArray(v))
          return v.some((v1) => Object.entries(v1).every((kv1) => sat(x)(kv1)));
        else if (v?.in) return v.in.includes(x[k]);
        else if (Array.isArray(v)) return v.every((v1) => sat(x)([k, v1]));
        else if (k === "_fts")
          return JSON.stringify(x)
            .toLowerCase()
            .includes((v.searchTerm || "").toLowerCase());
        else if (v?.lt && v?.equal) return x[k] <= +v.lt;
        else if (v?.lt) return x[k] < +v.lt;
        else if (v?.gt && v?.equal) return x[k] >= +v.gt;
        else if (v?.gt) return x[k] > +v.gt;
        else if (v?.ilike) return (x[k] || "").includes(v.ilike);
        else return x[k] == v;
      };
    const data_filtered =
      restricts.length === 0
        ? data_in
        : data_in.filter((x) => restricts.every(sat(x)));
    if (selopts.orderBy && typeof selopts.orderBy === "string") {
      const cmp = selopts.orderDesc
        ? new Function(
            "a,b",
            `return b.${selopts.orderBy}-a.${selopts.orderBy}`
          )
        : new Function(
            "a,b",
            `return a.${selopts.orderBy}-b.${selopts.orderBy}`
          );
      data_filtered.sort(cmp);
    }
    if (selopts.limit)
      return data_filtered.slice(
        selopts.offset || 0,
        (selopts.offset || 0) + selopts.limit
      );
    else return data_filtered;
  };
  const tbl = {
    pk_name: fields.find((f) => f.primary_key)?.name,
    getFields() {
      return fields;
    },
    getForeignKeys() {
      return fields.filter((f) => f.is_fkey && f.type !== "File");
    },
    getField(fnm) {
      return fields.find((f) => f.name === fnm);
    },
    fields,
    getRows,
    async getRow(where, opts) {
      const rows = await getRows(where, opts);
      return rows.length ? rows[0] : null;
    },
    get min_role_read() {
      const roles = getState().getConfig("exttables_min_role_read", {});
      return roles[tbl.name] || 100;
    },
    getJoinedRows(opts = {}) {
      const { where, ...rest } = opts;
      return getRows(where || {}, rest || {});
    },
    async getJoinedRow(opts = {}) {
      const { where, ...rest } = opts;
      const rows = await getRows(where || {}, rest || {});
      return rows.length > 0 ? rows[0] : null;
    },
    async countRows(where, opts) {
      if (methods?.countRows) {
        return await methods?.countRows(where, opts);
      }
      let data_in = await get_json_list(where, opts);
      return data_in.length;
    },
    get_child_relations() {
      return { child_relations: [], child_field_list: [] };
    },
    get_parent_relations() {
      return { parent_relations: [], parent_field_list: [] };
    },
    get_relation_options() {
      return [];
    },
    get_relation_data() {
      return [];
    },
    get_join_field_options() {
      return [];
    },
    slug_options() {
      return [];
    },
    enable_fkey_constraints() {},
    ownership_options() {
      return [];
    },
    external: true,
    owner_fieldname() {
      return null;
    },
    async distinctValues(fldNm, opts) {
      let data_in = await get_json_list(opts || {});
      const s = new Set(data_in.map((x) => x[fldNm]));
      return [...s];
    },
    async getTags() {
      return await Tag.findWithEntries({ table_id: tbl.id });
    },
    async getForeignTables() {
      const tableNames = new Set(
        tbl.fields
          .filter((f) => f.is_fkey && f.reftable_name)
          .map((f) => f.reftable_name)
      );
      return Array.from(tableNames).map((tname) =>
        Table.findOne({ name: tname })
      );
    },
  };
  return tbl;
}
JSON List to external table
Hiiden "exttables_min_role_read" can be used to manage min role for external tables
@param {function} get_json_list
@param {object[]} fields0
@returns {object}