Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Durable Objects syntax idea #1

Closed
sor4chi opened this issue Sep 18, 2023 · 6 comments
Closed

Durable Objects syntax idea #1

sor4chi opened this issue Sep 18, 2023 · 6 comments

Comments

@sor4chi
Copy link
Owner

sor4chi commented Sep 18, 2023

Durable Object is a very good representation in that it can store state and mutations as a single object.
However, if we could provide a third party, we would be able to ensure more flexibility and maintainability of notation.

ref: honojs/examples#86

For now, I implemented the simplest method I can think of at the moment to generate a HonoObject in main and a sample of it.

As far as I know, Durable Object works by exporting the class itself, not the class instance.
Therefore, we tried to use the Constructor Function to generate dynamic classes while keeping the Hono interface easy to use.

If you have any ideas for a better interface, we'd love to hear your opinions and suggestions!

@sor4chi
Copy link
Owner Author

sor4chi commented Sep 18, 2023

Current Interface:

export const Counter = generateHonoObject("/counter", (app, state) => {
  const { storage } = state;

  app.post("/increment", async (c) => {
    const newVal = 1 + ((await storage.get<number>("value")) || 0);
    storage.put("value", newVal);
    return c.text(newVal.toString());
  });

  app.post("/decrement", async (c) => {
    const newVal = -1 + ((await storage.get<number>("value")) || 0);
    storage.put("value", newVal);
    return c.text(newVal.toString());
  });

  app.get("/", async (c) => {
    const value = (await storage.get<number>("value")) || 0;
    return c.text(value.toString());
  });
});

@sor4chi
Copy link
Owner Author

sor4chi commented Sep 18, 2023

Hono has an API called Variable in its context, and we are trying to map the contents of storage to this API to see if we can somehow achieve a clean interface.

I tried to build a simpler vue-like interface by using Proxy to define getter and setter, but it seems to be unusable because Variable is Readonly.

In the first place, get/set of storage itself is all Promise return value, so it seems impossible to make it Proxy.

@sor4chi
Copy link
Owner Author

sor4chi commented Sep 18, 2023

In #2, try to add React-like way to define the state!
How about this?

@geelen
Copy link

geelen commented Sep 19, 2023

Personally I really like this:

export const Counter = generateHonoObject("/counter", (app, state) => {
  const { storage } = state;

  app.post("/increment", async (c) => {
    const newVal = 1 + ((await storage.get<number>("value")) || 0);
    storage.put("value", newVal);
    return c.text(newVal.toString());
  });

  app.post("/decrement", async (c) => {
    const newVal = -1 + ((await storage.get<number>("value")) || 0);
    storage.put("value", newVal);
    return c.text(newVal.toString());
  });

  app.get("/", async (c) => {
    const value = (await storage.get<number>("value")) || 0;
    return c.text(value.toString());
  });
});

My thought would be to make the (app, state) => { callback async, then when you construct the object, wrap the Hono construction in blockConcurrencyWhile (see the example here: https://developers.cloudflare.com/durable-objects/learning/in-memory-state/)

That way you could do:

export const Counter = generateHonoObject("/counter", async (app, state) => {
  const { storage } = state;
  const config = await storage.get('__CONFIG') ?? {}

  app.post('/init', initValidator, async (c) => {
    Object.assign(config, await c.req.json())
    storage.put('__CONFIG', config)
  })

  app.use('/private/*',
    basicAuth({
      username: config.username,
      password: config.password,
    })
  )

  // ...
})

Does that make sense?

@sor4chi
Copy link
Owner Author

sor4chi commented Sep 19, 2023

Hi @geelen, I'm glad you like it.
That's a nice idea. I want to add it soon!

this is what you mean, right?

export function generateHonoObject<
  E extends Env = Env,
  S extends Schema = Record<string, never>,
  BasePath extends string = "/",
>(
  basePath: string,
- cb: (app: Hono<E, S, BasePath>, state: DurableObjectState) => void,
+ cb: (app: Hono<E, S, BasePath>, state: DurableObjectState) => Promise<void>,
) {
  const honoObject = function (
    this: HonoObject<E, S, BasePath>,
    state: DurableObjectState,
  ) {
    const app = new Hono<E, S, BasePath>().basePath(basePath);
    this.app = app;
-   cb(app, state);
+   state.blockConcurrencyWhile(async () => {
+     await cb(app, state);
+   });
  };

  honoObject.prototype.fetch = function (
    this: HonoObject<E, S, BasePath>,
    request: Request,
  ) {
    return this.app.fetch(request);
  };

  return honoObject;
}

@sor4chi
Copy link
Owner Author

sor4chi commented Sep 26, 2023

Hi, @geelen
I released your blockConcurrencyWhile idea since it is a backward compatible PR. Thank you so much.
You can use this as [email protected]!

@sor4chi sor4chi closed this as completed Oct 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants