Closed sor4chi closed 9 months ago
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());
});
});
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.
In #2, try to add React-like way to define the state! How about this?
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?
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;
}
Hi, @geelen
I released your blockConcurrencyWhile
idea since it is a backward compatible PR. Thank you so much.
You can use this as hono-do@0.1.0
!
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: https://github.com/honojs/examples/issues/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!