Skip to content

Commit

Permalink
types: HyperDurable accepts T param to specify persisted data
Browse files Browse the repository at this point in the history
  • Loading branch information
Travis Frank committed Jun 14, 2022
1 parent 0423620 commit 4a93f00
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 45 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
35 changes: 20 additions & 15 deletions src/HyperDurable.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { Router } from 'itty-router';
import { HyperError } from './HyperError';

interface HyperState extends DurableObjectState {
dirty: Set<string>;
interface HyperState<T> extends DurableObjectState {
dirty: Set<Extract<keyof T, string>>;
initialized?: Promise<void>;
persisted: Set<string>;
persisted: Set<Extract<keyof T, string>>;
tempKey: string;
}

export class HyperDurable<Env = unknown> implements DurableObject {
export class HyperDurable<T extends object, Env = unknown> implements DurableObject {
readonly isProxy?: boolean;
readonly original?: any;
env: Env;
state: HyperState;
state: HyperState<T>;
storage: DurableObjectStorage;
router: Router;

Expand Down Expand Up @@ -51,7 +51,9 @@ export class HyperDurable<Env = unknown> 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'
Expand All @@ -62,9 +64,9 @@ export class HyperDurable<Env = unknown> 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<keyof T, string>);
} else {
this.state.dirty.add(this.state.tempKey);
this.state.dirty.add(this.state.tempKey as Extract<keyof T, string>);
}
}

Expand Down Expand Up @@ -202,13 +204,13 @@ export class HyperDurable<Env = unknown> implements DurableObject {

async load() {
if (this.state.persisted.size === 0) {
const persisted = await this.storage.get<Set<string>>('persisted');
const persisted = await this.storage.get<Set<Extract<keyof T, string>>>('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);
}
}

Expand All @@ -217,7 +219,9 @@ export class HyperDurable<Env = unknown> 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);
Expand All @@ -238,7 +242,7 @@ export class HyperDurable<Env = unknown> 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) {
Expand All @@ -248,14 +252,15 @@ export class HyperDurable<Env = unknown> 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;
}

Expand Down
22 changes: 11 additions & 11 deletions src/HyperNamespaceProxy.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { HyperDurable } from './HyperDurable';
import { HyperError } from './HyperError';

export class HyperNamespaceProxy<T extends HyperDurable<ENV>, ENV> implements DurableObjectNamespace {
export class HyperNamespaceProxy<DO extends HyperDurable<any, ENV>, ENV> implements DurableObjectNamespace {
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
) {
this.namespace = namespace;
// Create a reference of the DO to check for methods / properties
Expand Down Expand Up @@ -41,15 +41,15 @@ export class HyperNamespaceProxy<T extends HyperDurable<ENV>, 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<unknown>
: Promise<unknown>;
};
// All of our props have setters formatted as: setProperty()
type SetStub = {
[Prop in keyof T as T[Prop] extends Function ? never : `set${Capitalize<string & Prop>}`]?:
(newValue: T[Prop]) => Promise<unknown>
[Prop in keyof DO as DO[Prop] extends Function ? never : `set${Capitalize<string & Prop>}`]?:
(newValue: DO[Prop]) => Promise<unknown>
}
type HyperStub = DurableObjectStub & PromisedGetStub & SetStub;

Expand Down Expand Up @@ -115,12 +115,12 @@ export class HyperNamespaceProxy<T extends HyperDurable<ENV>, ENV> implements Du
}
}

export const proxyHyperDurables = <DO extends HyperDurable<ENV>, ENV>(
env: ENV,
doBindings: { [key: string]: new (state: DurableObjectState, env: ENV) => DO }
export const proxyHyperDurables = <DO extends HyperDurable<any, Env>, Env>(
env: Env,
doBindings: { [key: string]: new (state: DurableObjectState, env: Env) => DO }
) => {
const newEnv: {
[Prop in keyof typeof doBindings]?: HyperNamespaceProxy<DO, ENV>
[Prop in keyof typeof doBindings]?: HyperNamespaceProxy<DO, Env>
} = {};
for (const [key, value] of Object.entries(doBindings)) {
if (!(value.prototype instanceof HyperDurable)) {
Expand Down
34 changes: 17 additions & 17 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@

import { Router } from "itty-router";

export interface HyperState extends DurableObjectState {
dirty: Set<string>;
export interface HyperState<T> extends DurableObjectState {
dirty: Set<Extract<keyof T, string>>;
initialized?: Promise<void>;
persisted: Set<string>;
persisted: Set<Extract<keyof T, string>>;
tempKey: string;
}

export class HyperDurable<Env = unknown> {
export class HyperDurable<T extends object, Env = unknown> {
readonly isProxy?: boolean;
readonly original?: any;
env: Env;
state: HyperState;
state: HyperState<T>;
storage: DurableObjectStorage;
router: Router;

Expand All @@ -23,35 +23,35 @@ export class HyperDurable<Env = unknown> {
load(): Promise<void>;
persist(): Promise<void>;
destroy(): Promise<void>
toObject(): object;
toObject(): T;
fetch(request: Request): Promise<Response>;
}

export class HyperNamespaceProxy<T extends HyperDurable<ENV>, ENV> {
export class HyperNamespaceProxy<DO extends HyperDurable<any, Env>, 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<unknown>
: Promise<unknown>;
} & {
[Prop in keyof T as T[Prop] extends Function ? never : `set${Capitalize<string & Prop>}`]:
(newValue: T[Prop]) => Promise<unknown>
[Prop in keyof DO as DO[Prop] extends Function ? never : `set${Capitalize<string & Prop>}`]:
(newValue: DO[Prop]) => Promise<unknown>
};
}

export function proxyHyperDurables<DO extends HyperDurable<ENV>, ENV>(
env: ENV,
doBindings: { [key: string]: new (state: DurableObjectState, env: ENV) => DO }
export function proxyHyperDurables<DO extends HyperDurable<any, Env>, Env>(
env: Env,
doBindings: { [key: string]: new (state: DurableObjectState, env: Env) => DO }
): {
[Prop in keyof typeof doBindings]: HyperNamespaceProxy<DO, ENV>
[Prop in keyof typeof doBindings]: HyperNamespaceProxy<DO, Env>
}
9 changes: 8 additions & 1 deletion test/counter.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { HyperDurable } from '../src/HyperDurable';

export class Counter extends HyperDurable<Environment> {
type CounterData = {
abc?: number;
counter: number;
objectLikeProp: string[];
}

export class Counter extends HyperDurable<CounterData, Environment> implements CounterData {
abc?: number;
counter: number;
objectLikeProp: string[];

constructor(state: DurableObjectState, env: Environment) {
super(state, env);

this.counter = 1;
this.objectLikeProp = [];
}
Expand Down

0 comments on commit 4a93f00

Please sign in to comment.