@@ -28,56 +28,56 @@ export async function run(
2828 ctx : ScriptContext ,
2929 req : Request ,
3030) : Promise < Response > {
31- // interface TokenBucket {
32- // tokens: number;
33- // lastRefill: Date;
34- // }
35- //
36- // // Update the token bucket
37- // //
38- // // `TokenBucket` is an unlogged table which are significantly faster to
39- // // write to than regular tables, but are not durable. This is important
40- // // because this script will be called on every request.
41- // const rows = await ctx.db.$queryRaw<TokenBucket[]>`
42- // WITH updated_bucket AS (
43- // UPDATE "TokenBuckets" b
44- // SET
45- // "tokens" = CASE
46- // -- Reset the bucket and consume 1 token
47- // WHEN now() > b."lastRefill" + make_interval(secs => ${req.period}) THEN ${
48- // req.requests - 1
49- // }
50- // -- Consume 1 token
51- // ELSE b.tokens - 1
52- // END,
53- // "lastRefill" = CASE
54- // WHEN now() > b."lastRefill" + make_interval(secs => ${req.period}) THEN now()
55- // ELSE b."lastRefill"
56- // END
57- // WHERE b."type" = ${req.type} AND b."key" = ${req.key}
58- // RETURNING b."tokens", b."lastRefill"
59- // ),
60- // inserted AS (
61- // INSERT INTO "TokenBuckets" ("type", "key", "tokens", "lastRefill")
62- // SELECT ${req.type}, ${req.key}, ${req.requests - 1}, now()
63- // WHERE NOT EXISTS (SELECT 1 FROM updated_bucket)
64- // RETURNING "tokens", "lastRefill"
65- // )
66- // SELECT * FROM updated_bucket
67- // UNION ALL
68- // SELECT * FROM inserted;
69- // `;
70- // const { tokens, lastRefill } = rows[0];
31+ interface TokenBucket {
32+ tokens : number ;
33+ lastRefill : Date ;
34+ }
35+
36+ // Update the token bucket
7137 //
72- // // If the bucket is empty, throw an error
73- // if (tokens < 0) {
74- // throw new RuntimeError("RATE_LIMIT_EXCEEDED", {
75- // meta: {
76- // retryAfter: new Date(lastRefill.getTime() + req.period * 1000)
77- // .toUTCString(),
78- // },
79- // });
80- // }
38+ // `TokenBucket` is an unlogged table which are significantly faster to
39+ // write to than regular tables, but are not durable. This is important
40+ // because this script will be called on every request.
41+ const rows = await ctx . db . $queryRaw < TokenBucket [ ] > `
42+ WITH updated_bucket AS (
43+ UPDATE "TokenBuckets" b
44+ SET
45+ "tokens" = CASE
46+ -- Reset the bucket and consume 1 token
47+ WHEN now() > b."lastRefill" + make_interval(secs => ${ req . period } ) THEN ${
48+ req . requests - 1
49+ }
50+ -- Consume 1 token
51+ ELSE b.tokens - 1
52+ END,
53+ "lastRefill" = CASE
54+ WHEN now() > b."lastRefill" + make_interval(secs => ${ req . period } ) THEN now()
55+ ELSE b."lastRefill"
56+ END
57+ WHERE b."type" = ${ req . type } AND b."key" = ${ req . key }
58+ RETURNING b."tokens", b."lastRefill"
59+ ),
60+ inserted AS (
61+ INSERT INTO "TokenBuckets" ("type", "key", "tokens", "lastRefill")
62+ SELECT ${ req . type } , ${ req . key } , ${ req . requests - 1 } , now()
63+ WHERE NOT EXISTS (SELECT 1 FROM updated_bucket)
64+ RETURNING "tokens", "lastRefill"
65+ )
66+ SELECT * FROM updated_bucket
67+ UNION ALL
68+ SELECT * FROM inserted;
69+ ` ;
70+ const { tokens, lastRefill } = rows [ 0 ] ;
71+
72+ // If the bucket is empty, throw an error
73+ if ( tokens < 0 ) {
74+ throw new RuntimeError ( "RATE_LIMIT_EXCEEDED" , {
75+ meta : {
76+ retryAfter : new Date ( lastRefill . getTime ( ) + req . period * 1000 )
77+ . toUTCString ( ) ,
78+ } ,
79+ } ) ;
80+ }
8181
8282 return { } ;
8383}
0 commit comments