From e45d9bc00f98278cf6ec5e3e0d982f1fb0be9e58 Mon Sep 17 00:00:00 2001 From: Kevin Whitley Date: Wed, 12 Jun 2024 09:35:55 -0500 Subject: [PATCH] default persist delay of 2000, calling $persist() writes immediately --- ...52a54aaa34407d131736142cce26645a13c.sqlite | Bin 0 -> 8192 bytes ...cb16683602779f21c4d90f93bcc07c41819.sqlite | Bin 0 -> 8192 bytes ...683602779f21c4d90f93bcc07c41819.sqlite-shm | Bin 0 -> 32768 bytes ...683602779f21c4d90f93bcc07c41819.sqlite-wal | 0 ...7abf9818cabfdc8f34cc5056096dc8e4874.sqlite | Bin 0 -> 8192 bytes ...d73a61955d73366abd030d642eba562ea78.sqlite | Bin 0 -> 8192 bytes .../tmp/bundle-7e8VMq/checked-fetch.js | 30 ++ .../middleware-insertion-facade.js | 11 + .../bundle-7e8VMq/middleware-loader.entry.ts | 133 +++++++ .../counter/.wrangler/tmp/dev-caNuv7/index.js | 346 ++++++++++++++++++ .../.wrangler/tmp/dev-caNuv7/index.js.map | 8 + examples/counter/Counter.ts | 25 +- examples/counter/index.ts | 15 +- examples/counter/package.json | 1 + examples/counter/yarn.lock | 5 + src/IttyDurable.ts | 77 ++-- src/itty-router/withDO.ts | 2 +- 17 files changed, 607 insertions(+), 46 deletions(-) create mode 100644 examples/counter/.wrangler/state/v3/do/durable-rpc-Counter/277dda905bc63838280e7a300c36652a54aaa34407d131736142cce26645a13c.sqlite create mode 100644 examples/counter/.wrangler/state/v3/do/durable-rpc-Counter/36515cee978e9313732757a355a6ecb16683602779f21c4d90f93bcc07c41819.sqlite create mode 100644 examples/counter/.wrangler/state/v3/do/durable-rpc-Counter/36515cee978e9313732757a355a6ecb16683602779f21c4d90f93bcc07c41819.sqlite-shm create mode 100644 examples/counter/.wrangler/state/v3/do/durable-rpc-Counter/36515cee978e9313732757a355a6ecb16683602779f21c4d90f93bcc07c41819.sqlite-wal create mode 100644 examples/counter/.wrangler/state/v3/do/durable-rpc-Counter/a7c0906685086ac64614ec0ba4e607abf9818cabfdc8f34cc5056096dc8e4874.sqlite create mode 100644 examples/counter/.wrangler/state/v3/do/durable-rpc-Counter/dec48d3e295e387106f4c28080054d73a61955d73366abd030d642eba562ea78.sqlite create mode 100644 examples/counter/.wrangler/tmp/bundle-7e8VMq/checked-fetch.js create mode 100644 examples/counter/.wrangler/tmp/bundle-7e8VMq/middleware-insertion-facade.js create mode 100644 examples/counter/.wrangler/tmp/bundle-7e8VMq/middleware-loader.entry.ts create mode 100644 examples/counter/.wrangler/tmp/dev-caNuv7/index.js create mode 100644 examples/counter/.wrangler/tmp/dev-caNuv7/index.js.map diff --git a/examples/counter/.wrangler/state/v3/do/durable-rpc-Counter/277dda905bc63838280e7a300c36652a54aaa34407d131736142cce26645a13c.sqlite b/examples/counter/.wrangler/state/v3/do/durable-rpc-Counter/277dda905bc63838280e7a300c36652a54aaa34407d131736142cce26645a13c.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..45c4ac43d219532234755199d2617f3a652543fd GIT binary patch literal 8192 zcmeI$KTE?v7zXh7E=r*waVs4PFPT~p7bh1n(E~#w(RfsIggMbc^c zb7Wr$_nT}X5P$##AOHafKmY;|fB*y_009UP8PVzW`(krmWwS*-ofp&SX*bKORQX!5 z_JdgQ?u>_BZt|9YuvX8S@6zzrPbVCy$w_;+$`;Fk% z{Uuun1Rwwb2tWV=5P$##AOHafKmY>&R$$<`7pp(lc!$kTK=}3IIfXHvGt1VWatl`^ BFvS1> literal 0 HcmV?d00001 diff --git a/examples/counter/.wrangler/state/v3/do/durable-rpc-Counter/36515cee978e9313732757a355a6ecb16683602779f21c4d90f93bcc07c41819.sqlite b/examples/counter/.wrangler/state/v3/do/durable-rpc-Counter/36515cee978e9313732757a355a6ecb16683602779f21c4d90f93bcc07c41819.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..68b6a7db221de8a327fd8c545318b84b413fff55 GIT binary patch literal 8192 zcmeIuy9&ZE6b9haAP9o!Cf&}cATCaBwi>XsQmygQsZ?n}tQ4dQjy{2};G^2x?k@jN za!zuR>D#QGa~5%&-mYQBW9pI+G-W0tQSZE!(em1;&qXWlzanUK`?T|4TIWsqLLdME z2tWV=5P$##AOHafKmY;|AWF4nPuI2ABnyv86rAF~-c?o1JZUJ&$}kV ztmHxQ%CnY+=d&&SVg2?9lY7Lb&Ebs-X7A4mJ literal 0 HcmV?d00001 diff --git a/examples/counter/.wrangler/state/v3/do/durable-rpc-Counter/36515cee978e9313732757a355a6ecb16683602779f21c4d90f93bcc07c41819.sqlite-shm b/examples/counter/.wrangler/state/v3/do/durable-rpc-Counter/36515cee978e9313732757a355a6ecb16683602779f21c4d90f93bcc07c41819.sqlite-shm new file mode 100644 index 0000000000000000000000000000000000000000..fe9ac2845eca6fe6da8a63cd096d9cf9e24ece10 GIT binary patch literal 32768 zcmeIuAr62r38E>13Dq9=x?MANgiQ%NNOv2sviL7lp}`4#+Z zei3_3vpWmV!+ZDMzv(yL=q`Dx3NG#PC9n9D4oFJ6U?w8j4A<%iH@BMgwr%%+MN)tH z-FB~p`XW~d1Rwwb2tWV=5P$##AOHafKmYX&MX|K6k<%`#Xqa=;$gA<;_<|e&2oTZca%6IBQ z{g5jJ0uX=z1Rwwb2tWV=5P$##AOL}XDRAtm>myH{uYQ#c_UfO2_|p%((%QhA=kFhK E17ZI#>Hq)$ literal 0 HcmV?d00001 diff --git a/examples/counter/.wrangler/state/v3/do/durable-rpc-Counter/dec48d3e295e387106f4c28080054d73a61955d73366abd030d642eba562ea78.sqlite b/examples/counter/.wrangler/state/v3/do/durable-rpc-Counter/dec48d3e295e387106f4c28080054d73a61955d73366abd030d642eba562ea78.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..5125c77046f416c02c8d003417eff98376636542 GIT binary patch literal 8192 zcmeI#Pf7zZ6bA5@j3N|D+?6hb$E+$8H?9N`XEfl@kh3zA?=csbjnObvYBr25w31E`{lAc zZ;GUY*;m`W66%xOKp+4C2tWV=5P$##AOHafKmY;|AhKeo)9s3nqq>+(%ke`s9^9>} zJj-=tG@B?+HLvct*J7Wi@|BIgH++>Rmr*|Afgbgn-n^K;lpLp7>@W6tn3#*~)^MH; zlk=8Sm+G6;k1GfSAOHafKmY;|fB*y_009U<00RG0fP>Dl|Kk5D8*cku(hGyi+Ax?D RZ|`z1P$%=X=0dD@{{SXUJ5c}t literal 0 HcmV?d00001 diff --git a/examples/counter/.wrangler/tmp/bundle-7e8VMq/checked-fetch.js b/examples/counter/.wrangler/tmp/bundle-7e8VMq/checked-fetch.js new file mode 100644 index 0000000..cf630c0 --- /dev/null +++ b/examples/counter/.wrangler/tmp/bundle-7e8VMq/checked-fetch.js @@ -0,0 +1,30 @@ +const urls = new Set(); + +function checkURL(request, init) { + const url = + request instanceof URL + ? request + : new URL( + (typeof request === "string" + ? new Request(request, init) + : request + ).url + ); + if (url.port && url.port !== "443" && url.protocol === "https:") { + if (!urls.has(url.toString())) { + urls.add(url.toString()); + console.warn( + `WARNING: known issue with \`fetch()\` requests to custom HTTPS ports in published Workers:\n` + + ` - ${url.toString()} - the custom port will be ignored when the Worker is published using the \`wrangler deploy\` command.\n` + ); + } + } +} + +globalThis.fetch = new Proxy(globalThis.fetch, { + apply(target, thisArg, argArray) { + const [request, init] = argArray; + checkURL(request, init); + return Reflect.apply(target, thisArg, argArray); + }, +}); diff --git a/examples/counter/.wrangler/tmp/bundle-7e8VMq/middleware-insertion-facade.js b/examples/counter/.wrangler/tmp/bundle-7e8VMq/middleware-insertion-facade.js new file mode 100644 index 0000000..c70eb87 --- /dev/null +++ b/examples/counter/.wrangler/tmp/bundle-7e8VMq/middleware-insertion-facade.js @@ -0,0 +1,11 @@ + import worker, * as OTHER_EXPORTS from "/Users/kevinwhitley/dev/kwhitley/itty-durable/examples/counter/index.ts"; + import * as __MIDDLEWARE_0__ from "/Users/kevinwhitley/.nvm/versions/node/v20.10.0/lib/node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts"; +import * as __MIDDLEWARE_1__ from "/Users/kevinwhitley/.nvm/versions/node/v20.10.0/lib/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts"; + + worker.middleware = [ + __MIDDLEWARE_0__.default,__MIDDLEWARE_1__.default, + ...(worker.middleware ?? []), + ].filter(Boolean); + + export * from "/Users/kevinwhitley/dev/kwhitley/itty-durable/examples/counter/index.ts"; + export default worker; \ No newline at end of file diff --git a/examples/counter/.wrangler/tmp/bundle-7e8VMq/middleware-loader.entry.ts b/examples/counter/.wrangler/tmp/bundle-7e8VMq/middleware-loader.entry.ts new file mode 100644 index 0000000..30ce774 --- /dev/null +++ b/examples/counter/.wrangler/tmp/bundle-7e8VMq/middleware-loader.entry.ts @@ -0,0 +1,133 @@ +// This loads all middlewares exposed on the middleware object and then starts +// the invocation chain. The big idea is that we can add these to the middleware +// export dynamically through wrangler, or we can potentially let users directly +// add them as a sort of "plugin" system. + +import ENTRY from "/Users/kevinwhitley/dev/kwhitley/itty-durable/examples/counter/.wrangler/tmp/bundle-7e8VMq/middleware-insertion-facade.js"; +import { __facade_invoke__, __facade_register__, Dispatcher } from "/Users/kevinwhitley/.nvm/versions/node/v20.10.0/lib/node_modules/wrangler/templates/middleware/common.ts"; +import type { + WithMiddleware, + WorkerEntrypointConstructor, +} from "/Users/kevinwhitley/dev/kwhitley/itty-durable/examples/counter/.wrangler/tmp/bundle-7e8VMq/middleware-insertion-facade.js"; + +// Preserve all the exports from the worker +export * from "/Users/kevinwhitley/dev/kwhitley/itty-durable/examples/counter/.wrangler/tmp/bundle-7e8VMq/middleware-insertion-facade.js"; + +class __Facade_ScheduledController__ implements ScheduledController { + readonly #noRetry: ScheduledController["noRetry"]; + + constructor( + readonly scheduledTime: number, + readonly cron: string, + noRetry: ScheduledController["noRetry"] + ) { + this.#noRetry = noRetry; + } + + noRetry() { + if (!(this instanceof __Facade_ScheduledController__)) { + throw new TypeError("Illegal invocation"); + } + // Need to call native method immediately in case uncaught error thrown + this.#noRetry(); + } +} + +function wrapExportedHandler( + worker: WithMiddleware +): ExportedHandler { + // If we don't have any middleware defined, just return the handler as is + if (worker.middleware === undefined || worker.middleware.length === 0) { + return worker; + } + // Otherwise, register all middleware once + for (const middleware of worker.middleware) { + __facade_register__(middleware); + } + + const fetchDispatcher: ExportedHandlerFetchHandler = function ( + request, + env, + ctx + ) { + if (worker.fetch === undefined) { + throw new Error("Handler does not export a fetch() function."); + } + return worker.fetch(request, env, ctx); + }; + + return { + ...worker, + fetch(request, env, ctx) { + const dispatcher: Dispatcher = function (type, init) { + if (type === "scheduled" && worker.scheduled !== undefined) { + const controller = new __Facade_ScheduledController__( + Date.now(), + init.cron ?? "", + () => {} + ); + return worker.scheduled(controller, env, ctx); + } + }; + return __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher); + }, + }; +} + +function wrapWorkerEntrypoint( + klass: WithMiddleware +): WorkerEntrypointConstructor { + // If we don't have any middleware defined, just return the handler as is + if (klass.middleware === undefined || klass.middleware.length === 0) { + return klass; + } + // Otherwise, register all middleware once + for (const middleware of klass.middleware) { + __facade_register__(middleware); + } + + // `extend`ing `klass` here so other RPC methods remain callable + return class extends klass { + #fetchDispatcher: ExportedHandlerFetchHandler> = ( + request, + env, + ctx + ) => { + this.env = env; + this.ctx = ctx; + if (super.fetch === undefined) { + throw new Error("Entrypoint class does not define a fetch() function."); + } + return super.fetch(request); + }; + + #dispatcher: Dispatcher = (type, init) => { + if (type === "scheduled" && super.scheduled !== undefined) { + const controller = new __Facade_ScheduledController__( + Date.now(), + init.cron ?? "", + () => {} + ); + return super.scheduled(controller); + } + }; + + fetch(request: Request) { + return __facade_invoke__( + request, + this.env, + this.ctx, + this.#dispatcher, + this.#fetchDispatcher + ); + } + }; +} + +let WRAPPED_ENTRY: ExportedHandler | WorkerEntrypointConstructor | undefined; +if (typeof ENTRY === "object") { + WRAPPED_ENTRY = wrapExportedHandler(ENTRY); +} else if (typeof ENTRY === "function") { + WRAPPED_ENTRY = wrapWorkerEntrypoint(ENTRY); +} +export default WRAPPED_ENTRY; diff --git a/examples/counter/.wrangler/tmp/dev-caNuv7/index.js b/examples/counter/.wrangler/tmp/dev-caNuv7/index.js new file mode 100644 index 0000000..eaee72c --- /dev/null +++ b/examples/counter/.wrangler/tmp/dev-caNuv7/index.js @@ -0,0 +1,346 @@ +// .wrangler/tmp/bundle-7e8VMq/checked-fetch.js +var urls = /* @__PURE__ */ new Set(); +function checkURL(request, init) { + const url = request instanceof URL ? request : new URL( + (typeof request === "string" ? new Request(request, init) : request).url + ); + if (url.port && url.port !== "443" && url.protocol === "https:") { + if (!urls.has(url.toString())) { + urls.add(url.toString()); + console.warn( + `WARNING: known issue with \`fetch()\` requests to custom HTTPS ports in published Workers: + - ${url.toString()} - the custom port will be ignored when the Worker is published using the \`wrangler deploy\` command. +` + ); + } + } +} +globalThis.fetch = new Proxy(globalThis.fetch, { + apply(target, thisArg, argArray) { + const [request, init] = argArray; + checkURL(request, init); + return Reflect.apply(target, thisArg, argArray); + } +}); + +// node_modules/itty-router/index.mjs +var t = ({ base: e = "", routes: t2 = [], ...r2 } = {}) => ({ __proto__: new Proxy({}, { get: (r3, o2, a2, s2) => (r4, ...c2) => t2.push([o2.toUpperCase?.(), RegExp(`^${(s2 = (e + r4).replace(/\/+(\/|$)/g, "$1")).replace(/(\/?\.?):(\w+)\+/g, "($1(?<$2>*))").replace(/(\/?\.?):(\w+)/g, "($1(?<$2>[^$1/]+?))").replace(/\./g, "\\.").replace(/(\/?)\*/g, "($1.*)?")}/*$`), c2, s2]) && a2 }), routes: t2, ...r2, async fetch(e2, ...o2) { + let a2, s2, c2 = new URL(e2.url), n2 = e2.query = { __proto__: null }; + for (let [e3, t3] of c2.searchParams) + n2[e3] = n2[e3] ? [].concat(n2[e3], t3) : t3; + e: + try { + for (let t3 of r2.before || []) + if (null != (a2 = await t3(e2.proxy ?? e2, ...o2))) + break e; + t: + for (let [r3, n3, l, i] of t2) + if ((r3 == e2.method || "ALL" == r3) && (s2 = c2.pathname.match(n3))) { + e2.params = s2.groups || {}, e2.route = i; + for (let t3 of l) + if (null != (a2 = await t3(e2.proxy ?? e2, ...o2))) + break t; + } + } catch (t3) { + if (!r2.catch) + throw t3; + a2 = await r2.catch(t3, e2.proxy ?? e2, ...o2); + } + try { + for (let t3 of r2.finally || []) + a2 = await t3(a2, e2.proxy ?? e2, ...o2) ?? a2; + } catch (t3) { + if (!r2.catch) + throw t3; + a2 = await r2.catch(t3, e2.proxy ?? e2, ...o2); + } + return a2; +} }); +var r = (e = "text/plain; charset=utf-8", t2) => (r2, o2 = {}) => { + if (void 0 === r2 || r2 instanceof Response) + return r2; + const a2 = new Response(t2?.(r2) ?? r2, o2.url ? void 0 : o2); + return a2.headers.set("content-type", e), a2; +}; +var o = r("application/json; charset=utf-8", JSON.stringify); +var a = (e) => ({ 400: "Bad Request", 401: "Unauthorized", 403: "Forbidden", 404: "Not Found", 500: "Internal Server Error" })[e] || "Unknown Error"; +var s = (e = 500, t2) => { + if (e instanceof Error) { + const { message: r2, ...o2 } = e; + e = e.status || 500, t2 = { error: r2 || a(e), ...o2 }; + } + return t2 = { status: e, ..."object" == typeof t2 ? t2 : { error: t2 || a(e) } }, o(t2, { status: e }); +}; +var c = (e) => { + e.proxy = new Proxy(e.proxy ?? e, { get: (t2, r2) => t2[r2]?.bind?.(e) ?? t2[r2] ?? t2?.params?.[r2] }); +}; +var n = ({ format: e = o, missing: r2 = () => s(404), finally: a2 = [], before: n2 = [], ...l } = {}) => t({ before: [c, ...n2], catch: s, finally: [(e2, ...t2) => e2 ?? r2(e2, ...t2), e, ...a2], ...l }); +var p = r("text/plain; charset=utf-8", String); +var f = r("text/html"); +var u = r("image/jpeg"); +var h = r("image/png"); +var g = r("image/webp"); + +// ../../src/IttyDurable.ts +import { DurableObject } from "cloudflare:workers"; +var WRITE_DELAY = 2e3; +var IttyDurable = class extends DurableObject { + // private attributes + #persistTimer; + #persistLoaded; + // in-memory attributes + $ = {}; + // default store + $store = { + get: () => this.ctx.storage.get("v"), + put: (value) => this.ctx.storage.put("v", value) + }; + constructor(...args) { + super(...args); + return new Proxy(this, { + get: (obj, prop, receiver, target = obj[prop], fn = target?.bind?.(obj)) => { + if (!fn) + return target; + return async (...args2) => { + await this.#sync(); + const result = await fn(...args2); + this.$persist(WRITE_DELAY); + return result; + }; + } + }); + } + // PRIVATE: internal storage sync + async #sync() { + this.#persistLoaded ||= Object.assign(this, await this.$store.get.call(this)); + } + // PUBLIC: persist() to manually persist + $persist(delay = 0) { + clearTimeout(this.#persistTimer); + this.#persistTimer = setTimeout( + () => { + const { $, ...persistable } = this.$props(); + this.$store.put.call(this, persistable); + }, + delay + ); + } + // PUBLIC: toJSON() strips the CF specific objects to display the rest + $props() { + const { ctx, env, $store, ...other } = this; + return other; + } +}; + +// ../../src/itty-router/withDO.ts +var withDO = (request, env) => { + for (const [k, v] of Object.entries(env)) { + if (v?.idFromName) + request[k] = request[k] ?? ((id) => typeof id == "string" ? v.get(v.idFromName(id)) : v.get(id)); + } +}; + +// Counter.ts +var CustomStore = { + get() { + console.log("syncing from store"); + return this.ctx.storage.get("value"); + }, + put(value) { + console.log("saving to store"); + return this.ctx.storage.put("value", value); + } +}; +var Counter = class extends IttyDurable { + value = 20; + foo = "baz"; + $store = CustomStore; + increment(by = 1) { + this.value += by; + return this.$props(); + } +}; + +// index.ts +var router = n({ + before: [withDO] + // upstream middleware +}); +router.get( + "/:name/increment/:by", + ({ Counter: Counter2, name, by }) => Counter2(name).increment(Number(by)) +).get( + ":name/reset", + ({ Counter: Counter2, name }) => Counter2(name).reset() +).get( + "/:name/inspect", + ({ Counter: Counter2, name }) => Counter2(name).$props() +); +var counter_default = { ...router }; + +// ../../../../../.nvm/versions/node/v20.10.0/lib/node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts +var drainBody = async (request, env, _ctx, middlewareCtx) => { + try { + return await middlewareCtx.next(request, env); + } finally { + try { + if (request.body !== null && !request.bodyUsed) { + const reader = request.body.getReader(); + while (!(await reader.read()).done) { + } + } + } catch (e) { + console.error("Failed to drain the unused request body.", e); + } + } +}; +var middleware_ensure_req_body_drained_default = drainBody; + +// ../../../../../.nvm/versions/node/v20.10.0/lib/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts +function reduceError(e) { + return { + name: e?.name, + message: e?.message ?? String(e), + stack: e?.stack, + cause: e?.cause === void 0 ? void 0 : reduceError(e.cause) + }; +} +var jsonError = async (request, env, _ctx, middlewareCtx) => { + try { + return await middlewareCtx.next(request, env); + } catch (e) { + const error = reduceError(e); + return Response.json(error, { + status: 500, + headers: { "MF-Experimental-Error-Stack": "true" } + }); + } +}; +var middleware_miniflare3_json_error_default = jsonError; + +// .wrangler/tmp/bundle-7e8VMq/middleware-insertion-facade.js +counter_default.middleware = [ + middleware_ensure_req_body_drained_default, + middleware_miniflare3_json_error_default, + ...counter_default.middleware ?? [] +].filter(Boolean); +var middleware_insertion_facade_default = counter_default; + +// ../../../../../.nvm/versions/node/v20.10.0/lib/node_modules/wrangler/templates/middleware/common.ts +var __facade_middleware__ = []; +function __facade_register__(...args) { + __facade_middleware__.push(...args.flat()); +} +function __facade_invokeChain__(request, env, ctx, dispatch, middlewareChain) { + const [head, ...tail] = middlewareChain; + const middlewareCtx = { + dispatch, + next(newRequest, newEnv) { + return __facade_invokeChain__(newRequest, newEnv, ctx, dispatch, tail); + } + }; + return head(request, env, ctx, middlewareCtx); +} +function __facade_invoke__(request, env, ctx, dispatch, finalMiddleware) { + return __facade_invokeChain__(request, env, ctx, dispatch, [ + ...__facade_middleware__, + finalMiddleware + ]); +} + +// .wrangler/tmp/bundle-7e8VMq/middleware-loader.entry.ts +var __Facade_ScheduledController__ = class { + constructor(scheduledTime, cron, noRetry) { + this.scheduledTime = scheduledTime; + this.cron = cron; + this.#noRetry = noRetry; + } + #noRetry; + noRetry() { + if (!(this instanceof __Facade_ScheduledController__)) { + throw new TypeError("Illegal invocation"); + } + this.#noRetry(); + } +}; +function wrapExportedHandler(worker) { + if (worker.middleware === void 0 || worker.middleware.length === 0) { + return worker; + } + for (const middleware of worker.middleware) { + __facade_register__(middleware); + } + const fetchDispatcher = function(request, env, ctx) { + if (worker.fetch === void 0) { + throw new Error("Handler does not export a fetch() function."); + } + return worker.fetch(request, env, ctx); + }; + return { + ...worker, + fetch(request, env, ctx) { + const dispatcher = function(type, init) { + if (type === "scheduled" && worker.scheduled !== void 0) { + const controller = new __Facade_ScheduledController__( + Date.now(), + init.cron ?? "", + () => { + } + ); + return worker.scheduled(controller, env, ctx); + } + }; + return __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher); + } + }; +} +function wrapWorkerEntrypoint(klass) { + if (klass.middleware === void 0 || klass.middleware.length === 0) { + return klass; + } + for (const middleware of klass.middleware) { + __facade_register__(middleware); + } + return class extends klass { + #fetchDispatcher = (request, env, ctx) => { + this.env = env; + this.ctx = ctx; + if (super.fetch === void 0) { + throw new Error("Entrypoint class does not define a fetch() function."); + } + return super.fetch(request); + }; + #dispatcher = (type, init) => { + if (type === "scheduled" && super.scheduled !== void 0) { + const controller = new __Facade_ScheduledController__( + Date.now(), + init.cron ?? "", + () => { + } + ); + return super.scheduled(controller); + } + }; + fetch(request) { + return __facade_invoke__( + request, + this.env, + this.ctx, + this.#dispatcher, + this.#fetchDispatcher + ); + } + }; +} +var WRAPPED_ENTRY; +if (typeof middleware_insertion_facade_default === "object") { + WRAPPED_ENTRY = wrapExportedHandler(middleware_insertion_facade_default); +} else if (typeof middleware_insertion_facade_default === "function") { + WRAPPED_ENTRY = wrapWorkerEntrypoint(middleware_insertion_facade_default); +} +var middleware_loader_entry_default = WRAPPED_ENTRY; +export { + Counter, + middleware_loader_entry_default as default +}; +//# sourceMappingURL=index.js.map diff --git a/examples/counter/.wrangler/tmp/dev-caNuv7/index.js.map b/examples/counter/.wrangler/tmp/dev-caNuv7/index.js.map new file mode 100644 index 0000000..fb8669b --- /dev/null +++ b/examples/counter/.wrangler/tmp/dev-caNuv7/index.js.map @@ -0,0 +1,8 @@ +{ + "version": 3, + "sources": ["../bundle-7e8VMq/checked-fetch.js", "../../../node_modules/src/src/IttyRouter.ts", "../../../node_modules/src/src/Router.ts", "../../../node_modules/src/src/createResponse.ts", "../../../node_modules/src/src/json.ts", "../../../node_modules/src/src/error.ts", "../../../node_modules/src/src/withParams.ts", "../../../node_modules/src/src/AutoRouter.ts", "../../../node_modules/src/src/StatusError.ts", "../../../node_modules/src/src/status.ts", "../../../node_modules/src/src/text.ts", "../../../node_modules/src/src/html.ts", "../../../node_modules/src/src/jpeg.ts", "../../../node_modules/src/src/png.ts", "../../../node_modules/src/src/webp.ts", "../../../node_modules/src/src/withContent.ts", "../../../node_modules/src/src/withCookies.ts", "../../../node_modules/src/src/cors.ts", "../../../../../src/IttyDurable.ts", "../../../../../src/itty-router/withDO.ts", "../../../Counter.ts", "../../../index.ts", "../../../../../../../../.nvm/versions/node/v20.10.0/lib/node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts", "../../../../../../../../.nvm/versions/node/v20.10.0/lib/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts", "../bundle-7e8VMq/middleware-insertion-facade.js", "../../../../../../../../.nvm/versions/node/v20.10.0/lib/node_modules/wrangler/templates/middleware/common.ts", "../bundle-7e8VMq/middleware-loader.entry.ts"], + "sourceRoot": "/Users/kevinwhitley/dev/kwhitley/itty-durable/examples/counter/.wrangler/tmp/dev-caNuv7", + "sourcesContent": ["const urls = new Set();\n\nfunction checkURL(request, init) {\n\tconst url =\n\t\trequest instanceof URL\n\t\t\t? request\n\t\t\t: new URL(\n\t\t\t\t\t(typeof request === \"string\"\n\t\t\t\t\t\t? new Request(request, init)\n\t\t\t\t\t\t: request\n\t\t\t\t\t).url\n\t\t\t );\n\tif (url.port && url.port !== \"443\" && url.protocol === \"https:\") {\n\t\tif (!urls.has(url.toString())) {\n\t\t\turls.add(url.toString());\n\t\t\tconsole.warn(\n\t\t\t\t`WARNING: known issue with \\`fetch()\\` requests to custom HTTPS ports in published Workers:\\n` +\n\t\t\t\t\t` - ${url.toString()} - the custom port will be ignored when the Worker is published using the \\`wrangler deploy\\` command.\\n`\n\t\t\t);\n\t\t}\n\t}\n}\n\nglobalThis.fetch = new Proxy(globalThis.fetch, {\n\tapply(target, thisArg, argArray) {\n\t\tconst [request, init] = argArray;\n\t\tcheckURL(request, init);\n\t\treturn Reflect.apply(target, thisArg, argArray);\n\t},\n});\n", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "import { DurableObject } from 'cloudflare:workers'\n\nconst WRITE_DELAY = 2000\n\ntype ContextType = {\n ctx: {\n storage: {\n get(key: string): any\n put(key: string, value: any): void\n }\n }\n}\n\nexport type DurableStore = {\n get(this: any): any\n put(this: any, value: any): any\n}\n\nexport class IttyDurable extends DurableObject {\n // private attributes\n #persistTimer: any\n #persistLoaded: undefined | true\n\n // in-memory attributes\n $: any = {}\n\n // default store\n $store: DurableStore = {\n get: () => this.ctx.storage.get('v'),\n put: (value: any) => this.ctx.storage.put('v', value)\n }\n\n constructor(...args: any) {\n // @ts-ignore\n super(...args)\n\n return new Proxy(this, {\n get: (\n obj,\n prop,\n receiver,\n // @ts-ignore\n target = obj[prop], // the target of the proxy call\n fn = target?.bind?.(obj), // an attempt to find a callable function\n ) => {\n // return properties directly (no callable function found)\n if (!fn) return target\n\n // otherwise return a function that:\n // 1. syncs from storage\n // 2. calls defined function\n // 3. persists on debounce\n return async (...args: any) => {\n await this.#sync()\n const result = await fn(...args)\n this.$persist(WRITE_DELAY)\n return result\n }\n }\n })\n }\n\n // PRIVATE: internal storage sync\n async #sync() {\n // only re-assign this #persistLoaded if undefined\n this.#persistLoaded ||= Object.assign(this, await this.$store.get.call(this))\n }\n\n // PUBLIC: persist() to manually persist\n $persist(delay: number = 0) {\n // remove previous timer\n clearTimeout(this.#persistTimer)\n\n // add a new delayed/debounced timer for persistence\n this.#persistTimer = setTimeout(\n () => {\n const { $, ...persistable } = this.$props()\n this.$store.put.call(this, persistable)\n },\n delay\n )\n }\n\n // PUBLIC: toJSON() strips the CF specific objects to display the rest\n $props(): any {\n const { ctx, env, $store, ...other } = this\n return other\n }\n}\n", "export const withDO = (request: any, env: Record) => {\n for (const [k, v] of Object.entries(env)) {\n if (v?.idFromName)\n request[k] = request[k] ?? (\n (id: any) =>\n typeof id == 'string'\n ? v.get(v.idFromName(id))\n : v.get(id)\n )\n }\n}\n", "// import { IttyDurable } from 'itty-durable'\nimport { IttyDurable, DurableStore } from '../../src'\n\nconst CustomStore: DurableStore = {\n get() {\n console.log('syncing from store')\n return this.ctx.storage.get('value')\n },\n put(value: any) {\n console.log('saving to store')\n return this.ctx.storage.put('value', value)\n }\n}\n\nexport class Counter extends IttyDurable {\n value = 20\n foo = 'baz'\n\n $store = CustomStore\n\n increment(by: number = 1) {\n this.value += by\n\n return this.$props()\n }\n}\n", "import { AutoRouter } from './node_modules/itty-router'\nexport { Counter } from './Counter'\nimport { withDO } from '../../src/index' // middleware for itty-router\n\nconst router = AutoRouter({\n before: [withDO], // upstream middleware\n})\n\nrouter\n .get('/:name/increment/:by',\n ({ Counter, name, by }) => Counter(name).increment(Number(by))\n )\n .get(':name/reset',\n ({ Counter, name }) => Counter(name).reset()\n )\n .get('/:name/inspect',\n ({ Counter, name }) => Counter(name).$props()\n )\n\nexport default { ...router }\n", "import type { Middleware } from \"./common\";\n\nconst drainBody: Middleware = async (request, env, _ctx, middlewareCtx) => {\n\ttry {\n\t\treturn await middlewareCtx.next(request, env);\n\t} finally {\n\t\ttry {\n\t\t\tif (request.body !== null && !request.bodyUsed) {\n\t\t\t\tconst reader = request.body.getReader();\n\t\t\t\twhile (!(await reader.read()).done) {}\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.error(\"Failed to drain the unused request body.\", e);\n\t\t}\n\t}\n};\n\nexport default drainBody;\n", "import type { Middleware } from \"./common\";\n\ninterface JsonError {\n\tmessage?: string;\n\tname?: string;\n\tstack?: string;\n\tcause?: JsonError;\n}\n\nfunction reduceError(e: any): JsonError {\n\treturn {\n\t\tname: e?.name,\n\t\tmessage: e?.message ?? String(e),\n\t\tstack: e?.stack,\n\t\tcause: e?.cause === undefined ? undefined : reduceError(e.cause),\n\t};\n}\n\n// See comment in `bundle.ts` for details on why this is needed\nconst jsonError: Middleware = async (request, env, _ctx, middlewareCtx) => {\n\ttry {\n\t\treturn await middlewareCtx.next(request, env);\n\t} catch (e: any) {\n\t\tconst error = reduceError(e);\n\t\treturn Response.json(error, {\n\t\t\tstatus: 500,\n\t\t\theaders: { \"MF-Experimental-Error-Stack\": \"true\" },\n\t\t});\n\t}\n};\n\nexport default jsonError;\n", "\t\t\t\timport worker, * as OTHER_EXPORTS from \"/Users/kevinwhitley/dev/kwhitley/itty-durable/examples/counter/index.ts\";\n\t\t\t\timport * as __MIDDLEWARE_0__ from \"/Users/kevinwhitley/.nvm/versions/node/v20.10.0/lib/node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts\";\nimport * as __MIDDLEWARE_1__ from \"/Users/kevinwhitley/.nvm/versions/node/v20.10.0/lib/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts\";\n\t\t\t\t\n\t\t\t\tworker.middleware = [\n\t\t\t\t\t__MIDDLEWARE_0__.default,__MIDDLEWARE_1__.default,\n\t\t\t\t\t...(worker.middleware ?? []),\n\t\t\t\t].filter(Boolean);\n\t\t\t\t\n\t\t\t\texport * from \"/Users/kevinwhitley/dev/kwhitley/itty-durable/examples/counter/index.ts\";\n\t\t\t\texport default worker;", "export type Awaitable = T | Promise;\n// TODO: allow dispatching more events?\nexport type Dispatcher = (\n\ttype: \"scheduled\",\n\tinit: { cron?: string }\n) => Awaitable;\n\nexport type IncomingRequest = Request<\n\tunknown,\n\tIncomingRequestCfProperties\n>;\n\nexport interface MiddlewareContext {\n\tdispatch: Dispatcher;\n\tnext(request: IncomingRequest, env: any): Awaitable;\n}\n\nexport type Middleware = (\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tmiddlewareCtx: MiddlewareContext\n) => Awaitable;\n\nconst __facade_middleware__: Middleware[] = [];\n\n// The register functions allow for the insertion of one or many middleware,\n// We register internal middleware first in the stack, but have no way of controlling\n// the order that addMiddleware is run in service workers so need an internal function.\nexport function __facade_register__(...args: (Middleware | Middleware[])[]) {\n\t__facade_middleware__.push(...args.flat());\n}\nexport function __facade_registerInternal__(\n\t...args: (Middleware | Middleware[])[]\n) {\n\t__facade_middleware__.unshift(...args.flat());\n}\n\nfunction __facade_invokeChain__(\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tdispatch: Dispatcher,\n\tmiddlewareChain: Middleware[]\n): Awaitable {\n\tconst [head, ...tail] = middlewareChain;\n\tconst middlewareCtx: MiddlewareContext = {\n\t\tdispatch,\n\t\tnext(newRequest, newEnv) {\n\t\t\treturn __facade_invokeChain__(newRequest, newEnv, ctx, dispatch, tail);\n\t\t},\n\t};\n\treturn head(request, env, ctx, middlewareCtx);\n}\n\nexport function __facade_invoke__(\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tdispatch: Dispatcher,\n\tfinalMiddleware: Middleware\n): Awaitable {\n\treturn __facade_invokeChain__(request, env, ctx, dispatch, [\n\t\t...__facade_middleware__,\n\t\tfinalMiddleware,\n\t]);\n}\n", "// This loads all middlewares exposed on the middleware object and then starts\n// the invocation chain. The big idea is that we can add these to the middleware\n// export dynamically through wrangler, or we can potentially let users directly\n// add them as a sort of \"plugin\" system.\n\nimport ENTRY from \"/Users/kevinwhitley/dev/kwhitley/itty-durable/examples/counter/.wrangler/tmp/bundle-7e8VMq/middleware-insertion-facade.js\";\nimport { __facade_invoke__, __facade_register__, Dispatcher } from \"/Users/kevinwhitley/.nvm/versions/node/v20.10.0/lib/node_modules/wrangler/templates/middleware/common.ts\";\nimport type {\n\tWithMiddleware,\n\tWorkerEntrypointConstructor,\n} from \"/Users/kevinwhitley/dev/kwhitley/itty-durable/examples/counter/.wrangler/tmp/bundle-7e8VMq/middleware-insertion-facade.js\";\n\n// Preserve all the exports from the worker\nexport * from \"/Users/kevinwhitley/dev/kwhitley/itty-durable/examples/counter/.wrangler/tmp/bundle-7e8VMq/middleware-insertion-facade.js\";\n\nclass __Facade_ScheduledController__ implements ScheduledController {\n\treadonly #noRetry: ScheduledController[\"noRetry\"];\n\n\tconstructor(\n\t\treadonly scheduledTime: number,\n\t\treadonly cron: string,\n\t\tnoRetry: ScheduledController[\"noRetry\"]\n\t) {\n\t\tthis.#noRetry = noRetry;\n\t}\n\n\tnoRetry() {\n\t\tif (!(this instanceof __Facade_ScheduledController__)) {\n\t\t\tthrow new TypeError(\"Illegal invocation\");\n\t\t}\n\t\t// Need to call native method immediately in case uncaught error thrown\n\t\tthis.#noRetry();\n\t}\n}\n\nfunction wrapExportedHandler(\n\tworker: WithMiddleware\n): ExportedHandler {\n\t// If we don't have any middleware defined, just return the handler as is\n\tif (worker.middleware === undefined || worker.middleware.length === 0) {\n\t\treturn worker;\n\t}\n\t// Otherwise, register all middleware once\n\tfor (const middleware of worker.middleware) {\n\t\t__facade_register__(middleware);\n\t}\n\n\tconst fetchDispatcher: ExportedHandlerFetchHandler = function (\n\t\trequest,\n\t\tenv,\n\t\tctx\n\t) {\n\t\tif (worker.fetch === undefined) {\n\t\t\tthrow new Error(\"Handler does not export a fetch() function.\");\n\t\t}\n\t\treturn worker.fetch(request, env, ctx);\n\t};\n\n\treturn {\n\t\t...worker,\n\t\tfetch(request, env, ctx) {\n\t\t\tconst dispatcher: Dispatcher = function (type, init) {\n\t\t\t\tif (type === \"scheduled\" && worker.scheduled !== undefined) {\n\t\t\t\t\tconst controller = new __Facade_ScheduledController__(\n\t\t\t\t\t\tDate.now(),\n\t\t\t\t\t\tinit.cron ?? \"\",\n\t\t\t\t\t\t() => {}\n\t\t\t\t\t);\n\t\t\t\t\treturn worker.scheduled(controller, env, ctx);\n\t\t\t\t}\n\t\t\t};\n\t\t\treturn __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher);\n\t\t},\n\t};\n}\n\nfunction wrapWorkerEntrypoint(\n\tklass: WithMiddleware\n): WorkerEntrypointConstructor {\n\t// If we don't have any middleware defined, just return the handler as is\n\tif (klass.middleware === undefined || klass.middleware.length === 0) {\n\t\treturn klass;\n\t}\n\t// Otherwise, register all middleware once\n\tfor (const middleware of klass.middleware) {\n\t\t__facade_register__(middleware);\n\t}\n\n\t// `extend`ing `klass` here so other RPC methods remain callable\n\treturn class extends klass {\n\t\t#fetchDispatcher: ExportedHandlerFetchHandler> = (\n\t\t\trequest,\n\t\t\tenv,\n\t\t\tctx\n\t\t) => {\n\t\t\tthis.env = env;\n\t\t\tthis.ctx = ctx;\n\t\t\tif (super.fetch === undefined) {\n\t\t\t\tthrow new Error(\"Entrypoint class does not define a fetch() function.\");\n\t\t\t}\n\t\t\treturn super.fetch(request);\n\t\t};\n\n\t\t#dispatcher: Dispatcher = (type, init) => {\n\t\t\tif (type === \"scheduled\" && super.scheduled !== undefined) {\n\t\t\t\tconst controller = new __Facade_ScheduledController__(\n\t\t\t\t\tDate.now(),\n\t\t\t\t\tinit.cron ?? \"\",\n\t\t\t\t\t() => {}\n\t\t\t\t);\n\t\t\t\treturn super.scheduled(controller);\n\t\t\t}\n\t\t};\n\n\t\tfetch(request: Request) {\n\t\t\treturn __facade_invoke__(\n\t\t\t\trequest,\n\t\t\t\tthis.env,\n\t\t\t\tthis.ctx,\n\t\t\t\tthis.#dispatcher,\n\t\t\t\tthis.#fetchDispatcher\n\t\t\t);\n\t\t}\n\t};\n}\n\nlet WRAPPED_ENTRY: ExportedHandler | WorkerEntrypointConstructor | undefined;\nif (typeof ENTRY === \"object\") {\n\tWRAPPED_ENTRY = wrapExportedHandler(ENTRY);\n} else if (typeof ENTRY === \"function\") {\n\tWRAPPED_ENTRY = wrapWorkerEntrypoint(ENTRY);\n}\nexport default WRAPPED_ENTRY;\n"], + "mappings": "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", + "names": ["Router", "base", "routes", "other", "__proto__", "Proxy", "get", "target", "prop", "receiver", "path", "route", "handlers", "push", "toUpperCase", "RegExp", "replace", "async", "request", "args", "response", "match", "url", "URL", "query", "k", "v", "searchParams", "concat", "t", "handler", "before", "proxy", "outer", "method", "regex", "pathname", "params", "groups", "err", "catch", "finally", "createResponse", "format", "transform", "body", "options", "Response", "headers", "set", "json", "JSON", "stringify", "getMessage", "code", "error", "a", "b", "Error", "message", "status", "withParams", "obj", "bind", "AutoRouter", "missing", "f", "r", "text", "createResponse", "String", "html", "jpeg", "png", "webp", "args", "Counter"] +} diff --git a/examples/counter/Counter.ts b/examples/counter/Counter.ts index ebe5a42..a54b1ec 100644 --- a/examples/counter/Counter.ts +++ b/examples/counter/Counter.ts @@ -1,11 +1,26 @@ -import { IttyDurable } from '../../dist/index.mjs' +// import { IttyDurable } from 'itty-durable' +import { IttyDurable, DurableStore } from '../../src' + +const CustomStore: DurableStore = { + get() { + console.log('syncing from store') + return this.ctx.storage.get('value') + }, + put(value: any) { + console.log('saving to store') + return this.ctx.storage.put('value', value) + } +} export class Counter extends IttyDurable { - value = 0 + value = 20 + foo = 'baz' + + $store = CustomStore - increment(by) { - this.value += by || 1 + increment(by: number = 1) { + this.value += by - return this.toJSON() + return this.$props() } } diff --git a/examples/counter/index.ts b/examples/counter/index.ts index d63bbfe..64d9bd5 100644 --- a/examples/counter/index.ts +++ b/examples/counter/index.ts @@ -1,17 +1,20 @@ import { AutoRouter } from './node_modules/itty-router' export { Counter } from './Counter' -import { withDO } from '../../dist/index.mjs' +import { withDO } from '../../src/index' // middleware for itty-router const router = AutoRouter({ - before: [withDO], + before: [withDO], // upstream middleware }) router - .get('/increment/:by', ({ Counter, by }) => - Counter('foo').increment(Number(by)) + .get('/:name/increment/:by', + ({ Counter, name, by }) => Counter(name).increment(Number(by)) ) - .get('/reset', ({ Counter }) => - Counter('foo').reset() + .get(':name/reset', + ({ Counter, name }) => Counter(name).reset() + ) + .get('/:name/inspect', + ({ Counter, name }) => Counter(name).$props() ) export default { ...router } diff --git a/examples/counter/package.json b/examples/counter/package.json index 3ce9107..ee3e292 100644 --- a/examples/counter/package.json +++ b/examples/counter/package.json @@ -8,6 +8,7 @@ "author": "", "license": "ISC", "dependencies": { + "itty-durable": "^3.0.0-next.0", "itty-router": "^5.0.17", "wrangler": "^3.55.0" }, diff --git a/examples/counter/yarn.lock b/examples/counter/yarn.lock index e15aa28..ffd236a 100644 --- a/examples/counter/yarn.lock +++ b/examples/counter/yarn.lock @@ -401,6 +401,11 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +itty-durable@^3.0.0-next.0: + version "3.0.0-next.0" + resolved "https://registry.yarnpkg.com/itty-durable/-/itty-durable-3.0.0-next.0.tgz#0e80ea8a31ef8bc4824b817be0ca157bb112be5c" + integrity sha512-g/88NsrYFVkPvTXi+OknTruP8ULHPdDarYg/WH19Clr3CPqRHKSKPjvva8EbimfVpfW3hd4ggTxk89B8md+5RQ== + itty-router@^5.0.17: version "5.0.17" resolved "https://registry.yarnpkg.com/itty-router/-/itty-router-5.0.17.tgz#e5a015756bfc420ea20f09da80935a7feb8c4ef8" diff --git a/src/IttyDurable.ts b/src/IttyDurable.ts index f43fbc8..bcced5c 100644 --- a/src/IttyDurable.ts +++ b/src/IttyDurable.ts @@ -1,10 +1,19 @@ import { DurableObject } from 'cloudflare:workers' -const PERSIST_DELAY = 5000 +const WRITE_DELAY = 2000 -type Store = { - get: () => any, - put: (value: any) => any, +type ContextType = { + ctx: { + storage: { + get(key: string): any + put(key: string, value: any): void + } + } +} + +export type DurableStore = { + get(this: any): any + put(this: any, value: any): any } export class IttyDurable extends DurableObject { @@ -12,16 +21,13 @@ export class IttyDurable extends DurableObject { #persistTimer: any #persistLoaded: undefined | true - // public attributes - id?: any - persisted: any + // in-memory attributes + $: any = {} // default store - store: Store = { - // @ts-ignore - get: () => this.ctx.storage.get('i'), - // @ts-ignore - put: (value: any) => this.ctx.storage.put('i', value) + $store: DurableStore = { + get: () => this.ctx.storage.get('v'), + put: (value: any) => this.ctx.storage.put('v', value) } constructor(...args: any) { @@ -34,47 +40,50 @@ export class IttyDurable extends DurableObject { prop, receiver, // @ts-ignore - target = obj[prop], - fn = target?.bind?.(obj), + target = obj[prop], // the target of the proxy call + fn = target?.bind?.(obj), // an attempt to find a callable function ) => { - // allow chainable id setting before calling other methods - if (prop == '$id') return (id: any) => (this.id = this.id ?? id) && this - - // return properties directly + // return properties directly (no callable function found) if (!fn) return target - // if method, start the persist debounce and call the function - // CONSIDER: moving the persist to after fn is called - this.persist() - return async (...args: any) => this.#sync().then(() => fn(args)) + // otherwise return a function that: + // 1. syncs from storage + // 2. calls defined function + // 3. persists on debounce + return async (...args: any) => { + await this.#sync() + const result = await fn(...args) + this.$persist(WRITE_DELAY) + return result + } } }) } // PRIVATE: internal storage sync async #sync() { - if (this.#persistLoaded) return - this.persisted = await this.store.get.call(this) - this.#persistLoaded = true + // only re-assign this #persistLoaded if undefined + this.#persistLoaded ||= Object.assign(this, await this.$store.get.call(this)) } // PUBLIC: persist() to manually persist - persist(delay?: number) { - if (!this.persisted) return // skip if nothing to persist - + $persist(delay: number = 0) { // remove previous timer clearTimeout(this.#persistTimer) // add a new delayed/debounced timer for persistence - this.#persistTimer = setTimeout(() => { - this.store.put.call(this, this.persisted) - }, delay ?? PERSIST_DELAY) + this.#persistTimer = setTimeout( + () => { + const { $, ...persistable } = this.$props() + this.$store.put.call(this, persistable) + }, + delay + ) } // PUBLIC: toJSON() strips the CF specific objects to display the rest - toJSON() { - // @ts-ignore - const { ctx, env, store, ...other } = this + $props(): any { + const { ctx, env, $store, ...other } = this return other } } diff --git a/src/itty-router/withDO.ts b/src/itty-router/withDO.ts index c582395..2299b52 100644 --- a/src/itty-router/withDO.ts +++ b/src/itty-router/withDO.ts @@ -4,7 +4,7 @@ export const withDO = (request: any, env: Record) => { request[k] = request[k] ?? ( (id: any) => typeof id == 'string' - ? v.get(v.idFromName(id))//.$id(id) + ? v.get(v.idFromName(id)) : v.get(id) ) }