Skip to content

Commit 0b3d0de

Browse files
committed
Implement counter
1 parent 7bed41b commit 0b3d0de

File tree

2 files changed

+125
-0
lines changed

2 files changed

+125
-0
lines changed

src/counter.ts

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { DurableObject } from 'cloudflare:workers';
2+
3+
class InvalidState extends Error {
4+
constructor(message: string) {
5+
super(message);
6+
7+
this.name = 'InvalidState';
8+
}
9+
}
10+
11+
export default class Counter extends DurableObject {
12+
private isDestroyed = false;
13+
14+
async increment() {
15+
if (this.isDestroyed) {
16+
throw new InvalidState('cannot increment counter as it is pending to be destroyed');
17+
}
18+
19+
// ctx is defined by the super class
20+
let value: number = (await this.ctx.storage.get('value')) ?? 0;
21+
22+
value++;
23+
await this.ctx.storage.put('value', value);
24+
25+
return value;
26+
}
27+
28+
/**
29+
* Clears data stored in transactional storage, the object will be evicted when it is idle for a while
30+
*/
31+
async destroy() {
32+
await this.ctx.storage.delete('value');
33+
this.isDestroyed = true;
34+
}
35+
}

src/index.ts

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import Counter from './counter';
2+
3+
function generateJSONResponse(
4+
body: Record<string, unknown>,
5+
status = 200,
6+
additionalHeaders: Record<string, unknown> = {}
7+
) {
8+
return new Response(JSON.stringify(body), {
9+
status,
10+
headers: {
11+
'Content-Type': 'application/json',
12+
...additionalHeaders,
13+
},
14+
});
15+
}
16+
17+
function getCounterObjectStub(
18+
pathname: string,
19+
counterNamespace: DurableObjectNamespace<Counter>
20+
) {
21+
const counterID: DurableObjectId = counterNamespace.idFromName(pathname);
22+
return counterNamespace.get(counterID);
23+
}
24+
25+
async function handleGetRequest(
26+
pathname: string,
27+
counterNamespace: DurableObjectNamespace<Counter>
28+
) {
29+
const counterObjectStub = getCounterObjectStub(pathname, counterNamespace);
30+
const newValue = await counterObjectStub.increment();
31+
32+
return generateJSONResponse({ newValue });
33+
}
34+
35+
async function handleDeleteRequest(
36+
pathname: string,
37+
counterNamespace: DurableObjectNamespace<Counter>
38+
) {
39+
const counterObjectStub = getCounterObjectStub(pathname, counterNamespace);
40+
await counterObjectStub.destroy();
41+
42+
return generateJSONResponse({
43+
message: 'The counter is scheduled to be destroyed.',
44+
});
45+
}
46+
47+
async function handleRequest(
48+
pathname: string,
49+
method: string,
50+
counterNamespace: DurableObjectNamespace<Counter>
51+
) {
52+
switch (method) {
53+
case 'GET':
54+
return await handleGetRequest(pathname, counterNamespace);
55+
case 'DELETE':
56+
return await handleDeleteRequest(pathname, counterNamespace);
57+
default:
58+
return generateJSONResponse(
59+
{ message: 'The method used in this request is not supported.' },
60+
405,
61+
{
62+
Allow: 'GET, DELETE',
63+
}
64+
);
65+
}
66+
}
67+
68+
export default {
69+
async fetch(request, env): Promise<Response> {
70+
const counterNamespace = env.COUNTER as DurableObjectNamespace<Counter>;
71+
72+
const { url, method } = request;
73+
const { pathname } = new URL(url);
74+
75+
try {
76+
return await handleRequest(pathname, method, counterNamespace);
77+
} catch (error) {
78+
if (error instanceof Error && error.message.startsWith('InvalidState')) {
79+
return generateJSONResponse({ message: error.message }, 400);
80+
}
81+
82+
return generateJSONResponse(
83+
{ message: 'Unknown internal server error occured.' },
84+
500
85+
);
86+
}
87+
},
88+
} satisfies ExportedHandler<Env>;
89+
90+
export { Counter };

0 commit comments

Comments
 (0)