Skip to content

Commit 7196c85

Browse files
committed
update workerd version
1 parent c9f0edd commit 7196c85

File tree

3 files changed

+111
-20
lines changed

3 files changed

+111
-20
lines changed

packages/miniflare/src/workers/kv/namespace.worker.ts

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import {
33
DeferredPromise,
44
DELETE,
55
GET,
6-
POST,
76
HttpError,
7+
KeyValueEntry,
88
KeyValueStorage,
99
maybeApply,
1010
MiniflareDurableObject,
11+
POST,
1112
PUT,
1213
RouteHandler,
13-
KeyValueEntry,
1414
} from "miniflare:shared";
1515
import { KVHeaders, KVLimits, KVParams } from "./constants";
1616
import {
@@ -22,6 +22,7 @@ import {
2222
validatePutOptions,
2323
} from "./validator.worker";
2424

25+
const MAX_BULK_LENGTH = 100 * 1024 * 1024;
2526
interface KVParams {
2627
key: string;
2728
}
@@ -75,10 +76,14 @@ function secondsToMillis(seconds: number): number {
7576
return seconds * 1000;
7677
}
7778

78-
async function processKeyValue(obj: KeyValueEntry<unknown> | null, type: string = "text", withMetadata: boolean = false) {
79+
async function processKeyValue(
80+
obj: KeyValueEntry<unknown> | null,
81+
type: string = "text",
82+
withMetadata: boolean = false
83+
) {
7984
const decoder = new TextDecoder();
8085
let r = "";
81-
if(obj?.value) {
86+
if (obj?.value) {
8287
for await (const chunk of obj?.value) {
8388
r += decoder.decode(chunk, { stream: true });
8489
}
@@ -89,16 +94,22 @@ async function processKeyValue(obj: KeyValueEntry<unknown> | null, type: string
8994
try {
9095
val = obj?.value == null ? null : type === "json" ? JSON.parse(r) : r;
9196
} catch (err: any) {
92-
throw new HttpError(400, "At least of of the requested keys corresponds to a non-JSON value");
97+
throw new HttpError(
98+
400,
99+
"At least of of the requested keys corresponds to a non-JSON value"
100+
);
93101
}
94102
if (val == null) {
95103
return null;
96104
}
97105
if (withMetadata) {
98-
return { value: val, metadata: obj?.metadata ? JSON.stringify(obj?.metadata) : null };
106+
return {
107+
value: val,
108+
metadata: obj?.metadata ? JSON.stringify(obj?.metadata) : null,
109+
};
99110
}
100111
return val;
101-
}
112+
}
102113

103114
export class KVNamespaceObject extends MiniflareDurableObject {
104115
#storage?: KeyValueStorage;
@@ -110,13 +121,7 @@ export class KVNamespaceObject extends MiniflareDurableObject {
110121
@GET("/:key")
111122
@POST("/bulk/get")
112123
get: RouteHandler<KVParams> = async (req, params, url) => {
113-
// Decode URL parameters
114-
const key = decodeKey(params, url.searchParams);
115-
const cacheTtlParam = url.searchParams.get(KVParams.CACHE_TTL);
116-
const cacheTtl =
117-
cacheTtlParam === null ? undefined : parseInt(cacheTtlParam);
118-
if(req.body != null) { // get bulk
119-
// get bulk
124+
if (req.method === "POST" && req.body != null) {
120125
let r = "";
121126
const decoder = new TextDecoder();
122127
for await (const chunk of req.body) {
@@ -125,15 +130,33 @@ export class KVNamespaceObject extends MiniflareDurableObject {
125130
r += decoder.decode();
126131
const parsedBody = JSON.parse(r);
127132
const keys: string[] = parsedBody.keys;
128-
const obj: {[key: string]: any} = {};
129-
for(const key of keys) {
133+
const type = parsedBody?.type;
134+
if (type && type !== "text" && type !== "json") {
135+
return new Response("", { status: 400 });
136+
}
137+
const obj: { [key: string]: any } = {};
138+
if (keys.length > 100) {
139+
return new Response("", { status: 400 });
140+
}
141+
for (const key of keys) {
130142
validateGetOptions(key, { cacheTtl: parsedBody?.cacheTtl });
131143
const entry = await this.storage.get(key);
132-
obj[key] = await processKeyValue(entry, parsedBody?.type, parsedBody?.withMetadata);
144+
const value = await processKeyValue(
145+
entry,
146+
parsedBody?.type,
147+
parsedBody?.withMetadata
148+
);
149+
obj[key] = value;
133150
}
151+
134152
return new Response(JSON.stringify(obj));
135153
}
136154

155+
// Decode URL parameters
156+
const key = decodeKey(params, url.searchParams);
157+
const cacheTtlParam = url.searchParams.get(KVParams.CACHE_TTL);
158+
const cacheTtl =
159+
cacheTtlParam === null ? undefined : parseInt(cacheTtlParam);
137160
// Get value from storage
138161
validateGetOptions(key, { cacheTtl });
139162
const entry = await this.storage.get(key);
@@ -160,7 +183,6 @@ export class KVNamespaceObject extends MiniflareDurableObject {
160183
const rawExpiration = url.searchParams.get(KVParams.EXPIRATION);
161184
const rawExpirationTtl = url.searchParams.get(KVParams.EXPIRATION_TTL);
162185
const rawMetadata = req.headers.get(KVHeaders.METADATA);
163-
164186
// Validate key, expiration and metadata
165187
const now = millisToSeconds(this.timers.now());
166188
const { expiration, metadata } = validatePutOptions(key, {

packages/miniflare/test/plugins/kv/index.spec.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,57 @@ test("get: returns value", async (t) => {
122122
const result = await kv.get("key");
123123
t.is(result, "value");
124124
});
125+
126+
test("bulk get: returns value", async (t) => {
127+
const { kv } = t.context;
128+
await kv.put("key1", "value1");
129+
const result: any = await kv.get(["key1", "key2"]);
130+
const expectedResult = new Map([
131+
["key1", "value1"],
132+
["key2", null],
133+
]);
134+
135+
t.deepEqual(result, expectedResult);
136+
});
137+
138+
test("bulk get: check max keys", async (t) => {
139+
const { kv } = t.context;
140+
await kv.put("key1", "value1");
141+
const keyArray = [];
142+
for (let i = 0; i <= 100; i++) {
143+
keyArray.push(`key${i}`);
144+
}
145+
try {
146+
await kv.get(keyArray);
147+
} catch (error: any) {
148+
t.is(error.message, "KV GET_BULK failed: 400 Bad Request");
149+
}
150+
});
151+
152+
test.only("bulk get: request json type", async (t) => {
153+
const { kv } = t.context;
154+
await kv.put("key1", '{"example": "ex"}');
155+
await kv.put("key2", "example");
156+
let result: any = await kv.get(["key1"]);
157+
let expectedResult: any = new Map([["key1", '{"example": "ex"}']]);
158+
expectedResult = new Map([["key1", '{"example": "ex"}']]);
159+
t.deepEqual(result, expectedResult);
160+
161+
result = await kv.get(["key1"], "json");
162+
expectedResult = new Map([["key1", {"example": "ex"}]]);
163+
t.deepEqual(result, expectedResult);
164+
165+
try {
166+
await kv.get(["key1", "key2"], "json");
167+
} catch (error: any) {
168+
t.is(
169+
error.message,
170+
"KV GET_BULK failed: 400 At least of of the requested keys corresponds to a non-JSON value"
171+
);
172+
}
173+
174+
});
175+
125176
test("get: returns null for non-existent keys", async (t) => {
126177
const { kv } = t.context;
127178
t.is(await kv.get("key"), null);

packages/miniflare/test/test-shared/miniflare.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,23 @@ export function namespace<T>(ns: string, binding: T): Namespaced<T> {
3838
return (keys: unknown, ...args: unknown[]) => {
3939
if (typeof keys === "string") keys = ns + keys;
4040
if (Array.isArray(keys)) keys = keys.map((key) => ns + key);
41-
return (value as (...args: unknown[]) => unknown)(keys, ...args);
41+
const result = (value as (...args: unknown[]) => unknown)(
42+
keys,
43+
...args
44+
);
45+
if (result instanceof Promise) {
46+
return result.then((res) => {
47+
if (res instanceof Map) {
48+
let newResult = new Map<string, unknown>();
49+
for (const [key, value] of res) {
50+
newResult.set(key.slice(ns.length), value);
51+
}
52+
return newResult;
53+
}
54+
return res;
55+
});
56+
}
57+
return result;
4258
};
4359
}
4460
return value;
@@ -54,6 +70,8 @@ export function namespace<T>(ns: string, binding: T): Namespaced<T> {
5470
});
5571
}
5672

73+
74+
5775
export function miniflareTest<
5876
Env,
5977
Context extends MiniflareTestContext = MiniflareTestContext,
@@ -83,7 +101,7 @@ export function miniflareTest<
83101
status: 500,
84102
headers: { "MF-Experimental-Error-Stack": "true" },
85103
});
86-
}
104+
}
87105
}
88106
}
89107
`;

0 commit comments

Comments
 (0)