var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __decorateClass = (decorators, target, key, kind) => { for (var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target, i = decorators.length - 1, decorator; i >= 0; i--) (decorator = decorators[i]) && (result = (kind ? decorator(target, key, result) : decorator(result)) || result); return kind && result && __defProp(target, key, result), result; }; // src/workers/kv/namespace.worker.ts import assert from "node:assert"; import { DeferredPromise, DELETE, GET, HttpError as HttpError2, KeyValueStorage, maybeApply, MiniflareDurableObject, PUT } from "miniflare:shared"; // src/workers/kv/constants.ts import { testRegExps } from "miniflare:shared"; var KVLimits = { MIN_CACHE_TTL: 60, MAX_LIST_KEYS: 1e3, MAX_KEY_SIZE: 512, MAX_VALUE_SIZE: 25 * 1024 * 1024, MAX_VALUE_SIZE_TEST: 1024, MAX_METADATA_SIZE: 1024 }, KVParams = { URL_ENCODED: "urlencoded", CACHE_TTL: "cache_ttl", EXPIRATION: "expiration", EXPIRATION_TTL: "expiration_ttl", LIST_LIMIT: "key_count_limit", LIST_PREFIX: "prefix", LIST_CURSOR: "cursor" }, KVHeaders = { EXPIRATION: "CF-Expiration", METADATA: "CF-KV-Metadata" }; // src/workers/kv/validator.worker.ts import { Buffer as Buffer2 } from "node:buffer"; import { HttpError } from "miniflare:shared"; function decodeKey({ key }, query) { if (query.get(KVParams.URL_ENCODED)?.toLowerCase() !== "true") return key; try { return decodeURIComponent(key); } catch (e) { throw e instanceof URIError ? new HttpError(400, "Could not URL-decode key name") : e; } } function validateKey(key) { if (key === "") throw new HttpError(400, "Key names must not be empty"); if (key === "." || key === "..") throw new HttpError( 400, `Illegal key name "${key}". Please use a different name.` ); validateKeyLength(key); } function validateKeyLength(key) { let keyLength = Buffer2.byteLength(key); if (keyLength > KVLimits.MAX_KEY_SIZE) throw new HttpError( 414, `UTF-8 encoded length of ${keyLength} exceeds key length limit of ${KVLimits.MAX_KEY_SIZE}.` ); } function validateGetOptions(key, options) { validateKey(key); let cacheTtl = options?.cacheTtl; if (cacheTtl !== void 0 && (isNaN(cacheTtl) || cacheTtl < KVLimits.MIN_CACHE_TTL)) throw new HttpError( 400, `Invalid ${KVParams.CACHE_TTL} of ${cacheTtl}. Cache TTL must be at least ${KVLimits.MIN_CACHE_TTL}.` ); } function validatePutOptions(key, options) { let { now, rawExpiration, rawExpirationTtl, rawMetadata } = options; validateKey(key); let expiration; if (rawExpirationTtl !== null) { let expirationTtl = parseInt(rawExpirationTtl); if (Number.isNaN(expirationTtl) || expirationTtl <= 0) throw new HttpError( 400, `Invalid ${KVParams.EXPIRATION_TTL} of ${rawExpirationTtl}. Please specify integer greater than 0.` ); if (expirationTtl < KVLimits.MIN_CACHE_TTL) throw new HttpError( 400, `Invalid ${KVParams.EXPIRATION_TTL} of ${rawExpirationTtl}. Expiration TTL must be at least ${KVLimits.MIN_CACHE_TTL}.` ); expiration = now + expirationTtl; } else if (rawExpiration !== null) { if (expiration = parseInt(rawExpiration), Number.isNaN(expiration) || expiration <= now) throw new HttpError( 400, `Invalid ${KVParams.EXPIRATION} of ${rawExpiration}. Please specify integer greater than the current number of seconds since the UNIX epoch.` ); if (expiration < now + KVLimits.MIN_CACHE_TTL) throw new HttpError( 400, `Invalid ${KVParams.EXPIRATION} of ${rawExpiration}. Expiration times must be at least ${KVLimits.MIN_CACHE_TTL} seconds in the future.` ); } let metadata; if (rawMetadata !== null) { let metadataLength = Buffer2.byteLength(rawMetadata); if (metadataLength > KVLimits.MAX_METADATA_SIZE) throw new HttpError( 413, `Metadata length of ${metadataLength} exceeds limit of ${KVLimits.MAX_METADATA_SIZE}.` ); metadata = JSON.parse(rawMetadata); } return { expiration, metadata }; } function decodeListOptions(url) { let limitParam = url.searchParams.get(KVParams.LIST_LIMIT), limit = limitParam === null ? KVLimits.MAX_LIST_KEYS : parseInt(limitParam), prefix = url.searchParams.get(KVParams.LIST_PREFIX) ?? void 0, cursor = url.searchParams.get(KVParams.LIST_CURSOR) ?? void 0; return { limit, prefix, cursor }; } function validateListOptions(options) { let limit = options.limit; if (limit !== void 0) { if (isNaN(limit) || limit < 1) throw new HttpError( 400, `Invalid ${KVParams.LIST_LIMIT} of ${limit}. Please specify an integer greater than 0.` ); if (limit > KVLimits.MAX_LIST_KEYS) throw new HttpError( 400, `Invalid ${KVParams.LIST_LIMIT} of ${limit}. Please specify an integer less than ${KVLimits.MAX_LIST_KEYS}.` ); } let prefix = options.prefix; prefix != null && validateKeyLength(prefix); } // src/workers/kv/namespace.worker.ts function createMaxValueSizeError(length, maxValueSize) { return new HttpError2( 413, `Value length of ${length} exceeds limit of ${maxValueSize}.` ); } var MaxLengthStream = class extends TransformStream { signal; length; constructor(maxLength) { let abortController = new AbortController(), lengthPromise = new DeferredPromise(), length = 0; super({ transform(chunk, controller) { length += chunk.byteLength, length <= maxLength ? controller.enqueue(chunk) : abortController.signal.aborted || abortController.abort(); }, flush() { lengthPromise.resolve(length); } }), this.signal = abortController.signal, this.length = lengthPromise; } }; function millisToSeconds(millis) { return Math.floor(millis / 1e3); } function secondsToMillis(seconds) { return seconds * 1e3; } var KVNamespaceObject = class extends MiniflareDurableObject { #storage; get storage() { return this.#storage ??= new KeyValueStorage(this); } get = async (req, params, url) => { let key = decodeKey(params, url.searchParams), cacheTtlParam = url.searchParams.get(KVParams.CACHE_TTL), cacheTtl = cacheTtlParam === null ? void 0 : parseInt(cacheTtlParam); validateGetOptions(key, { cacheTtl }); let entry = await this.storage.get(key); if (entry === null) throw new HttpError2(404, "Not Found"); let headers = new Headers(); return entry.expiration !== void 0 && headers.set( KVHeaders.EXPIRATION, millisToSeconds(entry.expiration).toString() ), entry.metadata !== void 0 && headers.set(KVHeaders.METADATA, JSON.stringify(entry.metadata)), new Response(entry.value, { headers }); }; put = async (req, params, url) => { let key = decodeKey(params, url.searchParams), rawExpiration = url.searchParams.get(KVParams.EXPIRATION), rawExpirationTtl = url.searchParams.get(KVParams.EXPIRATION_TTL), rawMetadata = req.headers.get(KVHeaders.METADATA), now = millisToSeconds(this.timers.now()), { expiration, metadata } = validatePutOptions(key, { now, rawExpiration, rawExpirationTtl, rawMetadata }), value = req.body, contentLength = parseInt(req.headers.get("Content-Length")), valueLengthHint; Number.isNaN(contentLength) ? value === null && (valueLengthHint = 0) : valueLengthHint = contentLength, value ??= new ReadableStream({ start(controller) { controller.close(); } }); let maxValueSize = this.beingTested ? KVLimits.MAX_VALUE_SIZE_TEST : KVLimits.MAX_VALUE_SIZE, maxLengthStream; if (valueLengthHint !== void 0 && valueLengthHint > maxValueSize) throw createMaxValueSizeError(valueLengthHint, maxValueSize); maxLengthStream = new MaxLengthStream(maxValueSize), value = value.pipeThrough(maxLengthStream); try { await this.storage.put({ key, value, expiration: maybeApply(secondsToMillis, expiration), metadata, signal: maxLengthStream?.signal }); } catch (e) { if (typeof e == "object" && e !== null && "name" in e && e.name === "AbortError") { assert(maxLengthStream !== void 0); let length = await maxLengthStream.length; throw createMaxValueSizeError(length, maxValueSize); } else throw e; } return new Response(); }; delete = async (req, params, url) => { let key = decodeKey(params, url.searchParams); return validateKey(key), await this.storage.delete(key), new Response(); }; list = async (req, params, url) => { let options = decodeListOptions(url); validateListOptions(options); let res = await this.storage.list(options), keys = res.keys.map((key) => ({ name: key.key, expiration: maybeApply(millisToSeconds, key.expiration), // workerd expects metadata to be a JSON-serialised string metadata: maybeApply(JSON.stringify, key.metadata) })), result; return res.cursor === void 0 ? result = { keys, list_complete: !0, cacheStatus: null } : result = { keys, list_complete: !1, cursor: res.cursor, cacheStatus: null }, Response.json(result); }; }; __decorateClass([ GET("/:key") ], KVNamespaceObject.prototype, "get", 2), __decorateClass([ PUT("/:key") ], KVNamespaceObject.prototype, "put", 2), __decorateClass([ DELETE("/:key") ], KVNamespaceObject.prototype, "delete", 2), __decorateClass([ GET("/") ], KVNamespaceObject.prototype, "list", 2); export { KVNamespaceObject }; //# sourceMappingURL=namespace.worker.js.map