// Copyright (c) 2017-2022 Cloudflare, Inc. // Licensed under the Apache 2.0 license found in the LICENSE file or at: // https://opensource.org/licenses/Apache-2.0 #pragma once #include #include #include #include namespace kj { class HttpClient; class HttpHeaders; } // namespace kj namespace workerd { class IoContext; } namespace workerd::api { // A capability to a KV namespace. class KvNamespace: public jsg::Object { public: struct AdditionalHeader { kj::String name; kj::String value; JSG_MEMORY_INFO(AdditionalHeader) { tracker.trackField("name", name); tracker.trackField("value", value); } }; // `subrequestChannel` is what to pass to IoContext::getHttpClient() to get an HttpClient // representing this namespace. It is also used to construct fetcher for JSRPC methods. // `additionalHeaders` is what gets appended to every outbound request. explicit KvNamespace( kj::String bindingName, kj::Array additionalHeaders, uint subrequestChannel) : additionalHeaders(kj::mv(additionalHeaders)), subrequestChannel(subrequestChannel), bindingName(kj::mv(bindingName)) {} struct GetOptions { jsg::Optional type; jsg::Optional cacheTtl; JSG_STRUCT(type, cacheTtl); JSG_STRUCT_TS_OVERRIDE(KVNamespaceGetOptions { type: Type; }); }; using GetResult = kj::Maybe< kj::OneOf, kj::Array, kj::String, jsg::JsRef>>; jsg::Promise getSingle(jsg::Lock& js, IoContext& context, TraceContext& traceContext, kj::String name, jsg::Optional> options); jsg::Promise> getBulk(jsg::Lock& js, IoContext& context, TraceContext& traceContext, kj::Array name, jsg::Optional> options, bool withMetadata); kj::String formBulkBodyString(jsg::Lock& js, kj::Array& names, bool withMetadata, jsg::Optional>& options); kj::OneOf, jsg::Promise>> get( jsg::Lock& js, kj::OneOf> name, jsg::Optional> options); struct GetWithMetadataResult { GetResult value; kj::Maybe> metadata; kj::Maybe> cacheStatus; JSG_STRUCT(value, metadata, cacheStatus); JSG_STRUCT_TS_OVERRIDE(KVNamespaceGetWithMetadataResult { value: Value | null; metadata: Metadata | null; cacheStatus: string | null; }); }; jsg::Promise getWithMetadataImpl(jsg::Lock& js, IoContext& context, TraceContext& traceContext, kj::String name, jsg::Optional> options, LimitEnforcer::KvOpType op); jsg::Promise getWithMetadataSingle(jsg::Lock& js, IoContext& context, TraceContext& traceContext, kj::String name, jsg::Optional> options); kj::OneOf, jsg::Promise>> getWithMetadata(jsg::Lock& js, kj::OneOf, kj::String> name, jsg::Optional> options); struct ListOptions { jsg::Optional limit; jsg::Optional> prefix; jsg::Optional> cursor; JSG_STRUCT(limit, prefix, cursor); JSG_STRUCT_TS_OVERRIDE(KVNamespaceListOptions); }; jsg::Promise> list(jsg::Lock& js, jsg::Optional options); // Optional parameter for passing options into a Fetcher::put. Initially // intended for supporting expiration times in KV bindings. struct PutOptions { jsg::Optional expiration; jsg::Optional expirationTtl; jsg::Optional>> metadata; JSG_STRUCT(expiration, expirationTtl, metadata); JSG_STRUCT_TS_OVERRIDE(KVNamespacePutOptions); }; // We can't just list the supported types in this OneOf because if we did then arbitrary objects // would get coerced into meaningless strings like "[object Object]". Instead we first use this // OneOf to differentiate between primitives and objects, and check the object for the types that // we specifically support later. using PutBody = kj::OneOf; using PutSupportedTypes = kj::OneOf, jsg::Ref>; jsg::Promise put(jsg::Lock& js, kj::String name, PutBody body, jsg::Optional options, const jsg::TypeHandler& putTypeHandler); jsg::Promise delete_(jsg::Lock& js, kj::String name); jsg::Ref deleteBulk(const v8::FunctionCallbackInfo& args); JSG_RESOURCE_TYPE(KvNamespace, CompatibilityFlags::Reader flags) { JSG_METHOD(get); JSG_METHOD(list); JSG_METHOD(put); JSG_METHOD(getWithMetadata); JSG_METHOD_NAMED(delete, delete_); if (flags.getWorkerdExperimental()) { // Temporary method for tests JSG_METHOD(deleteBulk); } JSG_TS_ROOT(); JSG_TS_DEFINE( interface KVNamespaceListKey { name: Key; expiration?: number; metadata?: Metadata; } type KVNamespaceListResult = | { list_complete: false; keys: KVNamespaceListKey[]; cursor: string; cacheStatus: string | null; } | { list_complete: true; keys: KVNamespaceListKey[]; cacheStatus: string | null; }; ); // `Metadata` before `Key` type parameter for backwards-compatibility with `workers-types@3`. // `Key` is also an optional type parameter, which must come after required parameters. if (flags.getWorkerdExperimental()) { JSG_TS_OVERRIDE(KVNamespace { get(key: Key, options?: Partial>): Promise; get(key: Key, type: "text"): Promise; get(key: Key, type: "json"): Promise; get(key: Key, type: "arrayBuffer"): Promise; get(key: Key, type: "stream"): Promise; get(key: Key, options?: KVNamespaceGetOptions<"text">): Promise; get(key: Key, options?: KVNamespaceGetOptions<"json">): Promise; get(key: Key, options?: KVNamespaceGetOptions<"arrayBuffer">): Promise; get(key: Key, options?: KVNamespaceGetOptions<"stream">): Promise; get(key: Array, type: "text"): Promise>; get(key: Array, type: "json"): Promise>; get(key: Array, options?: Partial>): Promise>; get(key: Array, options?: KVNamespaceGetOptions<"text">): Promise>; get(key: Array, options?: KVNamespaceGetOptions<"json">): Promise>; list(options?: KVNamespaceListOptions): Promise>; put(key: Key, value: string | ArrayBuffer | ArrayBufferView | ReadableStream, options?: KVNamespacePutOptions): Promise; getWithMetadata(key: Key, options?: Partial>): Promise>; getWithMetadata(key: Key, type: "text"): Promise>; getWithMetadata(key: Key, type: "json"): Promise>; getWithMetadata(key: Key, type: "arrayBuffer"): Promise>; getWithMetadata(key: Key, type: "stream"): Promise>; getWithMetadata(key: Key, options: KVNamespaceGetOptions<"text">): Promise>; getWithMetadata(key: Key, options: KVNamespaceGetOptions<"json">): Promise>; getWithMetadata(key: Key, options: KVNamespaceGetOptions<"arrayBuffer">): Promise>; getWithMetadata(key: Key, options: KVNamespaceGetOptions<"stream">): Promise>; getWithMetadata(key: Array, type: "text"): Promise>; getWithMetadata(key: Array, type: "json"): Promise>; getWithMetadata(key: Array, options?: Partial>): Promise>; getWithMetadata(key: Array, options?: KVNamespaceGetOptions<"text">): Promise>; getWithMetadata(key: Array, options?: KVNamespaceGetOptions<"json">): Promise>; delete(key: Key): Promise; deleteBulk(keys: Key | Key[]): Promise; }); } else { JSG_TS_OVERRIDE(KVNamespace { get(key: Key, options?: Partial>): Promise; get(key: Key, type: "text"): Promise; get(key: Key, type: "json"): Promise; get(key: Key, type: "arrayBuffer"): Promise; get(key: Key, type: "stream"): Promise; get(key: Key, options?: KVNamespaceGetOptions<"text">): Promise; get(key: Key, options?: KVNamespaceGetOptions<"json">): Promise; get(key: Key, options?: KVNamespaceGetOptions<"arrayBuffer">): Promise; get(key: Key, options?: KVNamespaceGetOptions<"stream">): Promise; get(key: Array, type: "text"): Promise>; get(key: Array, type: "json"): Promise>; get(key: Array, options?: Partial>): Promise>; get(key: Array, options?: KVNamespaceGetOptions<"text">): Promise>; get(key: Array, options?: KVNamespaceGetOptions<"json">): Promise>; list(options?: KVNamespaceListOptions): Promise>; put(key: Key, value: string | ArrayBuffer | ArrayBufferView | ReadableStream, options?: KVNamespacePutOptions): Promise; getWithMetadata(key: Key, options?: Partial>): Promise>; getWithMetadata(key: Key, type: "text"): Promise>; getWithMetadata(key: Key, type: "json"): Promise>; getWithMetadata(key: Key, type: "arrayBuffer"): Promise>; getWithMetadata(key: Key, type: "stream"): Promise>; getWithMetadata(key: Key, options: KVNamespaceGetOptions<"text">): Promise>; getWithMetadata(key: Key, options: KVNamespaceGetOptions<"json">): Promise>; getWithMetadata(key: Key, options: KVNamespaceGetOptions<"arrayBuffer">): Promise>; getWithMetadata(key: Key, options: KVNamespaceGetOptions<"stream">): Promise>; getWithMetadata(key: Array, type: "text"): Promise>; getWithMetadata(key: Array, type: "json"): Promise>; getWithMetadata(key: Array, options?: Partial>): Promise>; getWithMetadata(key: Array, options?: KVNamespaceGetOptions<"text">): Promise>; getWithMetadata(key: Array, options?: KVNamespaceGetOptions<"json">): Promise>; delete(key: Key): Promise; }); } } void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { tracker.trackField("additionalHeaders", additionalHeaders.asPtr()); } protected: // Do the boilerplate work of constructing an HTTP client to KV. Setting a KvOptType causes // the limiter for that op type to be checked. If a string is used, there isn't any limiter // enforcement. // NOTE: The urlStr is added to the headers as a non-owning reference and thus must outlive // the usage of the headers. kj::Own getHttpClient(IoContext& context, kj::HttpHeaders& headers, kj::OneOf opTypeOrName, kj::StringPtr urlStr, TraceContext& traceContext); private: kj::Array additionalHeaders; uint subrequestChannel; kj::String bindingName; }; #define EW_KV_ISOLATE_TYPES \ api::KvNamespace, api::KvNamespace::ListOptions, api::KvNamespace::GetOptions, \ api::KvNamespace::PutOptions, api::KvNamespace::GetWithMetadataResult // The list of kv.h types that are added to worker.c++'s JSG_DECLARE_ISOLATE_TYPE } // namespace workerd::api