1

I'm trying to make a JS Proxy that does this.

routes.api.posts => '/api/posts'
routes.api.posts('postId') => '/api/posts/postId'
routes.api.posts('postId', { name: 'alex' }) => '/api/posts/postId?name=alex'
routes.api.posts({ name: 'alex' }) => '/api/posts?name=alex'

I've referenced these

I've gotten this far.

var obj = {
  func: undefined,
  realFunc: (args) => args,
  prop: undefined,
  realProp: true
};

var handlers = {
  path: '',
  get: (target, name) => {
    const prop = target[name];
    if (prop != null) { return prop; }

    let isProp = true;
    Promise.resolve().then(() => {
      handlers.path = `${handlers.path}/${name}`
      console.log('handlers.path', handlers.path)
      if (isProp) {
        console.log(`props ${name}`)
      } else {
        // it's a function
        console.log(`props ${name}`)
      }
    });
    return new Proxy(() => {}, {
      get: handlers.get,
      apply: (a, b) => {
        isProp = false;
        return new Proxy(() => {}, handlers);
      }
    });
  }
};

var routes = new Proxy(obj, handlers)

var case1 = routes.api.posts
console.log(`routes.api.posts()`, case1 === '/api/posts', case1)

var case2 = routes.api.posts('postId')
console.log(`routes.api.posts('postId')`, case2 === '/api/posts/postId', case2)

var case3 = routes.api.posts('postId', { name: 'alex' })
console.log(`routes.api.posts('postId'`, case3 === '/api/posts/postId?name=alex', case3)

var case4 = routes.api.posts({ name: 'alex' })
console.log(`routes.api.posts({ name: 'alex' })`, case4 === '/api/posts?name=alex', case4)
user3840170
  • 26,597
  • 4
  • 30
  • 62
Alex Cory
  • 10,635
  • 10
  • 52
  • 62
  • Why `Promise.resolve`? Absolutely do not do that, there's nothing asynchronous in your code. – Bergi May 30 '21 at 20:32

1 Answers1

2

This seems to work well. get intercepts missing props and creates new routes objects on the fly, while apply takes care of function calls.

let makeRoutes = (prefix = '') => new Proxy(() => {}, {
    get(target, prop) {
        if (prop === 'toString')
            return () => prefix
        if (prop in target || typeof prop !== 'string')
            return target[prop]
        return makeRoutes(prefix + '/' + prop)
    },

    apply(target, thisArg, args) {
        let url = prefix
        for (let a of args)
            url += typeof a === 'object'
                ? '?' + new URLSearchParams(a)
                : '/' + a
        return url
    },
})


let routes = makeRoutes()

console.log('' + routes.api.posts)
console.log('' + routes.api.posts('postId'))
console.log('' + routes.api.posts('postId', {name: 'alex'}))
console.log('' + routes.api.posts({name: 'alex'}))

console.log('' + routes.this.should.work.too('param', {name: 'alex'}))
georg
  • 211,518
  • 52
  • 313
  • 390
  • You shouldn't need an `apply` trap. Simply put that code in the function itself. – Bergi May 30 '21 at 20:34
  • @Bergi how would you make it so `routes.api.posts` doesn't return a proxy? – Alex Cory May 31 '21 at 01:18
  • @AlexCory It still should return a proxy. But one with only a `get` trap: `return new Proxy((path, query) => { return prefix + … }, {get(target, key) { … }})` – Bergi May 31 '21 at 01:21