Intercept previous methods with class method chain conditionally in javascript

52 Views Asked by At

I'm just wondering if it's possible to intercept previous methods in a class chain, i have these classes

class And {
  client;
  table;
  condition;

  constructor(client, table, condition) {
    this.client = client;
    this.table = table;
    this.condition = condition;
  }

  and(anotherCondition) {
    return this.client.query(
      `SELECT * FROM "${this.table}" WHERE ${this.condition} AND ${anotherCondition};`
    );
  }
}

class Where {
  client;
  table;

  constructor(client, table) {
    this.client = client;
    this.table = table;
  }

  where(condition) {
    return this.client.query(
      `SELECT * FROM "${this.table}" WHERE ${condition};`
    );
  }
}

class Select {
  client;

  constructor(client) {
    this.client = client;
  }

  from(table) {
    return this.client.query(`SELECT * FROM "${table}";`);
  }
}

class Database {
  client;

  constructor(client) {
    this.client = client;
  }

  select() {
    return new Select(this.client);
  }
}

would it be possible to do something like this?

const db = new Database(client);

await db.select().from(users);
//> return all users

await db.select().from(users).where("id = 1");
//> intercept from() method and return all users with a where condition

await db.select().from(users).where("id = 1").and("email like '%gmail%'");
//> intercept previous two methods and run query with additional and condition

await db.select().from(users).where("id = 1").and("email like '%gmail%'").and("type = 'END_USER'");
//> similar with infinite `and` chains

what i want is being able to chain methods but it also depends on what methods are chained and return the result according to that.

i've read about Proxy and Reflect but i couldn't make any sense from it, any help would be much appreciated!

1

There are 1 best solutions below

1
Alexander Nenashev On BEST ANSWER

Since we are using promises here, it's easy just to postpone our decision to check whether our promise was chained and act accordingly. I think some generic wrapper is possible to avoid manual work here, but yoga is waiting me unfortunately:

class Where {
    client;
    table;

    constructor(client, table) {
        this.client = client;
        this.table = table;
    }

    where(condition) {
        return this.client.query(
            `SELECT * FROM "${this.table}" WHERE ${condition};`
        );
    }
}

class Select {
    client;

    constructor(client) {
        this.client = client;
    }

    from(table) {

        let chained = false;

        const promise = new Promise(resolve => {
            // postpone into a microtask
            Promise.resolve().then(() => resolve(chained || this.client.query(`SELECT * FROM "${table}";`)));
        });

        promise.where = condition => {
            chained = true;
            return new Where(this.client, table).where(condition)
        };

        return promise;
    }
}

class Database {
    client;

    constructor(client) {
        this.client = client;
    }

    select() {
        return new Select(this.client);
    }
}

const client = {
    query(query) {
        console.log('executing query:', query);
        return new Promise(r => setTimeout(() => r(query.includes('id = 1') ? { fname: 'Alexander', lname: 'Nenashev' } : [{ fname: 'Alexander', lname: 'Nenashev' }]), 1000));
    }
}
<script type="module">

const db = new Database(client);

console.log(await db.select().from('users'));

console.log(await db.select().from('users').where('id = 1'));

</script>