diff --git a/package.json b/package.json index fec20ca..74d5c4c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ticketbridge/hyper-durable", - "version": "0.1.10", + "version": "0.1.11", "description": "Object-like access to Durable Object properties and methods", "repository": { "type": "git", diff --git a/src/HyperDurable.ts b/src/HyperDurable.ts index a82327d..4da066f 100644 --- a/src/HyperDurable.ts +++ b/src/HyperDurable.ts @@ -1,18 +1,18 @@ import { Router } from 'itty-router'; import { HyperError } from './HyperError'; -interface HyperState extends DurableObjectState { - dirty: Set; +interface HyperState extends DurableObjectState { + dirty: Set>; initialized?: Promise; - persisted: Set; + persisted: Set>; tempKey: string; } -export class HyperDurable implements DurableObject { +export class HyperDurable implements DurableObject { readonly isProxy?: boolean; readonly original?: any; env: Env; - state: HyperState; + state: HyperState; storage: DurableObjectStorage; router: Router; @@ -51,7 +51,9 @@ export class HyperDurable implements DurableObject { // If we're getting a proxied top-level property of the Durable Object, // save the key to persist the deeply-nested property - if (target[key].isProxy && this === target) this.state.tempKey = key; + if (target[key].isProxy && this === target && !reservedKeys.has(key)) { + this.state.tempKey = key; + } // If prop is a function, bind `this` return typeof target[key] === 'function' @@ -62,9 +64,9 @@ export class HyperDurable implements DurableObject { // Add key to persist data if (target[key] !== value) { if (this === target) { - this.state.dirty.add(key); + this.state.dirty.add(key as Extract); } else { - this.state.dirty.add(this.state.tempKey); + this.state.dirty.add(this.state.tempKey as Extract); } } @@ -202,13 +204,13 @@ export class HyperDurable implements DurableObject { async load() { if (this.state.persisted.size === 0) { - const persisted = await this.storage.get>('persisted'); + const persisted = await this.storage.get>>('persisted'); if (persisted) { this.state.persisted = persisted; } } for (let key of this.state.persisted) { - this[key] = await this.storage.get(key); + this[key as string] = await this.storage.get(key); } } @@ -217,7 +219,9 @@ export class HyperDurable implements DurableObject { try { let newProps = false; for (let key of this.state.dirty) { - const value = this[key].isProxy ? this[key].original : this[key]; + const value = this[key as string].isProxy ? + this[key as string].original : + this[key as string]; await this.storage.put(key, value); if (!this.state.persisted.has(key)) { this.state.persisted.add(key); @@ -238,7 +242,7 @@ export class HyperDurable implements DurableObject { await this.storage.deleteAll(); this.state.dirty.clear(); for (let key of this.state.persisted) { - delete this[key]; + delete this[key as string]; this.state.persisted.delete(key); } } catch(e) { @@ -248,14 +252,15 @@ export class HyperDurable implements DurableObject { } } - toObject(): object { + toObject(): T { const output = {}; for (let key of this.state.persisted) { - output[key] = this[key]; + output[key as string] = this[key as string]; } for (let key of this.state.dirty) { - output[key] = this[key]; + output[key as string] = this[key as string]; } + // @ts-ignore return output; } diff --git a/src/HyperNamespaceProxy.ts b/src/HyperNamespaceProxy.ts index f74806c..b9a9d5e 100644 --- a/src/HyperNamespaceProxy.ts +++ b/src/HyperNamespaceProxy.ts @@ -1,9 +1,9 @@ import { HyperDurable } from './HyperDurable'; import { HyperError } from './HyperError'; -export class HyperNamespaceProxy, ENV> implements DurableObjectNamespace { +export class HyperNamespaceProxy, ENV> implements DurableObjectNamespace { namespace: DurableObjectNamespace; - ref: T; + ref: DO; newUniqueId: (_options?: DurableObjectNamespaceNewUniqueIdOptions) => DurableObjectId; idFromName: (name: string) => DurableObjectId; @@ -11,7 +11,7 @@ export class HyperNamespaceProxy, ENV> implements Du constructor( namespace: DurableObjectNamespace, - ref: new (state: DurableObjectState, env: ENV) => T + ref: new (state: DurableObjectState, env: ENV) => DO ) { this.namespace = namespace; // Create a reference of the DO to check for methods / properties @@ -41,15 +41,15 @@ export class HyperNamespaceProxy, ENV> implements Du // All of our prop getters & methods return Promises, since everything uses the // fetch interface. type PromisedGetStub = { - [Prop in keyof T]?: - T[Prop] extends Function + [Prop in keyof DO]?: + DO[Prop] extends Function ? () => Promise : Promise; }; // All of our props have setters formatted as: setProperty() type SetStub = { - [Prop in keyof T as T[Prop] extends Function ? never : `set${Capitalize}`]?: - (newValue: T[Prop]) => Promise + [Prop in keyof DO as DO[Prop] extends Function ? never : `set${Capitalize}`]?: + (newValue: DO[Prop]) => Promise } type HyperStub = DurableObjectStub & PromisedGetStub & SetStub; @@ -115,12 +115,12 @@ export class HyperNamespaceProxy, ENV> implements Du } } -export const proxyHyperDurables = , ENV>( - env: ENV, - doBindings: { [key: string]: new (state: DurableObjectState, env: ENV) => DO } +export const proxyHyperDurables = , Env>( + env: Env, + doBindings: { [key: string]: new (state: DurableObjectState, env: Env) => DO } ) => { const newEnv: { - [Prop in keyof typeof doBindings]?: HyperNamespaceProxy + [Prop in keyof typeof doBindings]?: HyperNamespaceProxy } = {}; for (const [key, value] of Object.entries(doBindings)) { if (!(value.prototype instanceof HyperDurable)) { diff --git a/src/index.d.ts b/src/index.d.ts index 4fe7f5b..3d296a5 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -2,18 +2,18 @@ import { Router } from "itty-router"; -export interface HyperState extends DurableObjectState { - dirty: Set; +export interface HyperState extends DurableObjectState { + dirty: Set>; initialized?: Promise; - persisted: Set; + persisted: Set>; tempKey: string; } -export class HyperDurable { +export class HyperDurable { readonly isProxy?: boolean; readonly original?: any; env: Env; - state: HyperState; + state: HyperState; storage: DurableObjectStorage; router: Router; @@ -23,35 +23,35 @@ export class HyperDurable { load(): Promise; persist(): Promise; destroy(): Promise - toObject(): object; + toObject(): T; fetch(request: Request): Promise; } -export class HyperNamespaceProxy, ENV> { +export class HyperNamespaceProxy, Env> { namespace: DurableObjectNamespace; - ref: T; + ref: DO; newUniqueId: (_options?: DurableObjectNamespaceNewUniqueIdOptions) => DurableObjectId; idFromName: (name: string) => DurableObjectId; idFromString: (hexId: string) => DurableObjectId; constructor( namespace: DurableObjectNamespace, - ref: new (state: DurableObjectState, env: ENV) => T + ref: new (state: DurableObjectState, env: Env) => DO ); get(id: DurableObjectId): DurableObjectStub & { - [Prop in keyof T]: - T[Prop] extends Function + [Prop in keyof DO]: + DO[Prop] extends Function ? () => Promise : Promise; } & { - [Prop in keyof T as T[Prop] extends Function ? never : `set${Capitalize}`]: - (newValue: T[Prop]) => Promise + [Prop in keyof DO as DO[Prop] extends Function ? never : `set${Capitalize}`]: + (newValue: DO[Prop]) => Promise }; } -export function proxyHyperDurables, ENV>( - env: ENV, - doBindings: { [key: string]: new (state: DurableObjectState, env: ENV) => DO } +export function proxyHyperDurables, Env>( + env: Env, + doBindings: { [key: string]: new (state: DurableObjectState, env: Env) => DO } ): { - [Prop in keyof typeof doBindings]: HyperNamespaceProxy + [Prop in keyof typeof doBindings]: HyperNamespaceProxy } diff --git a/test/counter.ts b/test/counter.ts index bb651f8..40a2b34 100644 --- a/test/counter.ts +++ b/test/counter.ts @@ -1,12 +1,19 @@ import { HyperDurable } from '../src/HyperDurable'; -export class Counter extends HyperDurable { +type CounterData = { + abc?: number; + counter: number; + objectLikeProp: string[]; +} + +export class Counter extends HyperDurable implements CounterData { abc?: number; counter: number; objectLikeProp: string[]; constructor(state: DurableObjectState, env: Environment) { super(state, env); + this.counter = 1; this.objectLikeProp = []; }