Search code examples
javascriptclassreflectionproxychaining

Intercept previous methods with class method chain conditionally in javascript


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!


Solution

  • 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
                queueMicrotask(() => 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>