Skip to content
File

Blob: src/workerd/api/kv.h

cpp283 lines
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#pragma once
6 
7#include <workerd/api/streams/readable.h>
8#include <workerd/api/worker-rpc.h>
9#include <workerd/io/limit-enforcer.h>
10#include <workerd/jsg/jsg.h>
11 
12namespace kj {
13class HttpClient;
14class HttpHeaders;
15} // namespace kj
16namespace workerd {
17class IoContext;
18}
19namespace workerd::api {
20 
21// A capability to a KV namespace.
22class KvNamespace: public jsg::Object {
23 public:
24 struct AdditionalHeader {
25 kj::String name;
26 kj::String value;
27 
28 JSG_MEMORY_INFO(AdditionalHeader) {
29 tracker.trackField("name", name);
30 tracker.trackField("value", value);
31 }
32 };
33 
34 // `subrequestChannel` is what to pass to IoContext::getHttpClient() to get an HttpClient
35 // representing this namespace. It is also used to construct fetcher for JSRPC methods.
36 // `additionalHeaders` is what gets appended to every outbound request.
37 explicit KvNamespace(
38 kj::String bindingName, kj::Array<AdditionalHeader> additionalHeaders, uint subrequestChannel)
39 : additionalHeaders(kj::mv(additionalHeaders)),
40 subrequestChannel(subrequestChannel),
41 bindingName(kj::mv(bindingName)) {}
42 
43 struct GetOptions {
44 jsg::Optional<kj::String> type;
45 jsg::Optional<int> cacheTtl;
46 
47 JSG_STRUCT(type, cacheTtl);
48 JSG_STRUCT_TS_OVERRIDE(KVNamespaceGetOptions<Type> {
49 type: Type;
50 });
51 };
52 
53 using GetResult = kj::Maybe<
54 kj::OneOf<jsg::Ref<ReadableStream>, kj::Array<byte>, kj::String, jsg::JsRef<jsg::JsValue>>>;
55 
56 jsg::Promise<KvNamespace::GetResult> getSingle(jsg::Lock& js,
57 IoContext& context,
58 TraceContext& traceContext,
59 kj::String name,
60 jsg::Optional<kj::OneOf<kj::String, GetOptions>> options);
61 
62 jsg::Promise<jsg::JsRef<jsg::JsMap>> getBulk(jsg::Lock& js,
63 IoContext& context,
64 TraceContext& traceContext,
65 kj::Array<kj::String> name,
66 jsg::Optional<kj::OneOf<kj::String, GetOptions>> options,
67 bool withMetadata);
68 
69 kj::String formBulkBodyString(jsg::Lock& js,
70 kj::Array<kj::String>& names,
71 bool withMetadata,
72 jsg::Optional<kj::OneOf<kj::String, GetOptions>>& options);
73 
74 kj::OneOf<jsg::Promise<KvNamespace::GetResult>, jsg::Promise<jsg::JsRef<jsg::JsMap>>> get(
75 jsg::Lock& js,
76 kj::OneOf<kj::String, kj::Array<kj::String>> name,
77 jsg::Optional<kj::OneOf<kj::String, GetOptions>> options);
78 
79 struct GetWithMetadataResult {
80 GetResult value;
81 kj::Maybe<jsg::JsRef<jsg::JsValue>> metadata;
82 kj::Maybe<jsg::JsRef<jsg::JsValue>> cacheStatus;
83 
84 JSG_STRUCT(value, metadata, cacheStatus);
85 JSG_STRUCT_TS_OVERRIDE(KVNamespaceGetWithMetadataResult<Value, Metadata> {
86 value: Value | null;
87 metadata: Metadata | null;
88 cacheStatus: string | null;
89 });
90 };
91 
92 jsg::Promise<GetWithMetadataResult> getWithMetadataImpl(jsg::Lock& js,
93 IoContext& context,
94 TraceContext& traceContext,
95 kj::String name,
96 jsg::Optional<kj::OneOf<kj::String, GetOptions>> options,
97 LimitEnforcer::KvOpType op);
98 
99 jsg::Promise<KvNamespace::GetWithMetadataResult> getWithMetadataSingle(jsg::Lock& js,
100 IoContext& context,
101 TraceContext& traceContext,
102 kj::String name,
103 jsg::Optional<kj::OneOf<kj::String, GetOptions>> options);
104 
105 kj::OneOf<jsg::Promise<KvNamespace::GetWithMetadataResult>, jsg::Promise<jsg::JsRef<jsg::JsMap>>>
106 getWithMetadata(jsg::Lock& js,
107 kj::OneOf<kj::Array<kj::String>, kj::String> name,
108 jsg::Optional<kj::OneOf<kj::String, GetOptions>> options);
109 struct ListOptions {
110 jsg::Optional<int> limit;
111 jsg::Optional<kj::Maybe<kj::String>> prefix;
112 jsg::Optional<kj::Maybe<kj::String>> cursor;
113 
114 JSG_STRUCT(limit, prefix, cursor);
115 JSG_STRUCT_TS_OVERRIDE(KVNamespaceListOptions);
116 };
117 
118 jsg::Promise<jsg::JsRef<jsg::JsValue>> list(jsg::Lock& js, jsg::Optional<ListOptions> options);
119 
120 // Optional parameter for passing options into a Fetcher::put. Initially
121 // intended for supporting expiration times in KV bindings.
122 struct PutOptions {
123 jsg::Optional<int> expiration;
124 jsg::Optional<int> expirationTtl;
125 jsg::Optional<kj::Maybe<jsg::JsRef<jsg::JsValue>>> metadata;
126 
127 JSG_STRUCT(expiration, expirationTtl, metadata);
128 JSG_STRUCT_TS_OVERRIDE(KVNamespacePutOptions);
129 };
130 
131 // We can't just list the supported types in this OneOf because if we did then arbitrary objects
132 // would get coerced into meaningless strings like "[object Object]". Instead we first use this
133 // OneOf to differentiate between primitives and objects, and check the object for the types that
134 // we specifically support later.
135 using PutBody = kj::OneOf<kj::String, jsg::JsObject>;
136 
137 using PutSupportedTypes = kj::OneOf<kj::String, kj::Array<byte>, jsg::Ref<ReadableStream>>;
138 
139 jsg::Promise<void> put(jsg::Lock& js,
140 kj::String name,
141 PutBody body,
142 jsg::Optional<PutOptions> options,
143 const jsg::TypeHandler<PutSupportedTypes>& putTypeHandler);
144 
145 jsg::Promise<void> delete_(jsg::Lock& js, kj::String name);
146 jsg::Ref<JsRpcPromise> deleteBulk(const v8::FunctionCallbackInfo<v8::Value>& args);
147 
148 JSG_RESOURCE_TYPE(KvNamespace, CompatibilityFlags::Reader flags) {
149 JSG_METHOD(get);
150 JSG_METHOD(list);
151 JSG_METHOD(put);
152 JSG_METHOD(getWithMetadata);
153 JSG_METHOD_NAMED(delete, delete_);
154 if (flags.getWorkerdExperimental()) {
155 // Temporary method for tests
156 JSG_METHOD(deleteBulk);
157 }
158 
159 JSG_TS_ROOT();
160 
161 JSG_TS_DEFINE(
162 interface KVNamespaceListKey<Metadata, Key extends string = string> {
163 name: Key;
164 expiration?: number;
165 metadata?: Metadata;
166 }
167 type KVNamespaceListResult<Metadata, Key extends string = string> =
168 | { list_complete: false; keys: KVNamespaceListKey<Metadata, Key>[]; cursor: string; cacheStatus: string | null; }
169 | { list_complete: true; keys: KVNamespaceListKey<Metadata, Key>[]; cacheStatus: string | null; };
170 );
171 // `Metadata` before `Key` type parameter for backwards-compatibility with `workers-types@3`.
172 // `Key` is also an optional type parameter, which must come after required parameters.
173 
174 if (flags.getWorkerdExperimental()) {
175 JSG_TS_OVERRIDE(KVNamespace<Key extends string = string> {
176 get(key: Key, options?: Partial<KVNamespaceGetOptions<undefined>>): Promise<string | null>;
177 get(key: Key, type: "text"): Promise<string | null>;
178 get<ExpectedValue = unknown>(key: Key, type: "json"): Promise<ExpectedValue | null>;
179 get(key: Key, type: "arrayBuffer"): Promise<ArrayBuffer | null>;
180 get(key: Key, type: "stream"): Promise<ReadableStream | null>;
181 get(key: Key, options?: KVNamespaceGetOptions<"text">): Promise<string | null>;
182 get<ExpectedValue = unknown>(key: Key, options?: KVNamespaceGetOptions<"json">): Promise<ExpectedValue | null>;
183 get(key: Key, options?: KVNamespaceGetOptions<"arrayBuffer">): Promise<ArrayBuffer | null>;
184 get(key: Key, options?: KVNamespaceGetOptions<"stream">): Promise<ReadableStream | null>;
185 
186 get(key: Array<Key>, type: "text"): Promise<Map<string, string | null>>;
187 get<ExpectedValue = unknown>(key: Array<Key>, type: "json"): Promise<Map<string, ExpectedValue | null>>;
188 get(key: Array<Key>, options?: Partial<KVNamespaceGetOptions<undefined>>): Promise<Map<string, string | null>>;
189 get(key: Array<Key>, options?: KVNamespaceGetOptions<"text">): Promise<Map<string, string | null>>;
190 get<ExpectedValue = unknown>(key: Array<Key>, options?: KVNamespaceGetOptions<"json">): Promise<Map<string, ExpectedValue | null>>;
191 
192 list<Metadata = unknown>(options?: KVNamespaceListOptions): Promise<KVNamespaceListResult<Metadata, Key>>;
193 
194 put(key: Key, value: string | ArrayBuffer | ArrayBufferView | ReadableStream, options?: KVNamespacePutOptions): Promise<void>;
195 
196 getWithMetadata<Metadata = unknown>(key: Key, options?: Partial<KVNamespaceGetOptions<undefined>>): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;
197 getWithMetadata<Metadata = unknown>(key: Key, type: "text"): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;
198 getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(key: Key, type: "json"): Promise<KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;
199 getWithMetadata<Metadata = unknown>(key: Key, type: "arrayBuffer"): Promise<KVNamespaceGetWithMetadataResult<ArrayBuffer, Metadata>>;
200 getWithMetadata<Metadata = unknown>(key: Key, type: "stream"): Promise<KVNamespaceGetWithMetadataResult<ReadableStream, Metadata>>;
201 getWithMetadata<Metadata = unknown>(key: Key, options: KVNamespaceGetOptions<"text">): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;
202 getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(key: Key, options: KVNamespaceGetOptions<"json">): Promise<KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;
203 getWithMetadata<Metadata = unknown>(key: Key, options: KVNamespaceGetOptions<"arrayBuffer">): Promise<KVNamespaceGetWithMetadataResult<ArrayBuffer, Metadata>>;
204 getWithMetadata<Metadata = unknown>(key: Key, options: KVNamespaceGetOptions<"stream">): Promise<KVNamespaceGetWithMetadataResult<ReadableStream, Metadata>>;
205 
206 getWithMetadata<Metadata = unknown>(key: Array<Key>, type: "text"): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>;
207 getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(key: Array<Key>, type: "json"): Promise<Map<string, KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;
208 getWithMetadata<Metadata = unknown>(key: Array<Key>, options?: Partial<KVNamespaceGetOptions<undefined>>): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>;
209 getWithMetadata<Metadata = unknown>(key: Array<Key>, options?: KVNamespaceGetOptions<"text">): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>;
210 getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(key: Array<Key>, options?: KVNamespaceGetOptions<"json">): Promise<Map<string, KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;
211 delete(key: Key): Promise<void>;
212 deleteBulk(keys: Key | Key[]): Promise<void>;
213 });
214 } else {
215 JSG_TS_OVERRIDE(KVNamespace<Key extends string = string> {
216 get(key: Key, options?: Partial<KVNamespaceGetOptions<undefined>>): Promise<string | null>;
217 get(key: Key, type: "text"): Promise<string | null>;
218 get<ExpectedValue = unknown>(key: Key, type: "json"): Promise<ExpectedValue | null>;
219 get(key: Key, type: "arrayBuffer"): Promise<ArrayBuffer | null>;
220 get(key: Key, type: "stream"): Promise<ReadableStream | null>;
221 get(key: Key, options?: KVNamespaceGetOptions<"text">): Promise<string | null>;
222 get<ExpectedValue = unknown>(key: Key, options?: KVNamespaceGetOptions<"json">): Promise<ExpectedValue | null>;
223 get(key: Key, options?: KVNamespaceGetOptions<"arrayBuffer">): Promise<ArrayBuffer | null>;
224 get(key: Key, options?: KVNamespaceGetOptions<"stream">): Promise<ReadableStream | null>;
225 
226 get(key: Array<Key>, type: "text"): Promise<Map<string, string | null>>;
227 get<ExpectedValue = unknown>(key: Array<Key>, type: "json"): Promise<Map<string, ExpectedValue | null>>;
228 get(key: Array<Key>, options?: Partial<KVNamespaceGetOptions<undefined>>): Promise<Map<string, string | null>>;
229 get(key: Array<Key>, options?: KVNamespaceGetOptions<"text">): Promise<Map<string, string | null>>;
230 get<ExpectedValue = unknown>(key: Array<Key>, options?: KVNamespaceGetOptions<"json">): Promise<Map<string, ExpectedValue | null>>;
231 
232 list<Metadata = unknown>(options?: KVNamespaceListOptions): Promise<KVNamespaceListResult<Metadata, Key>>;
233 
234 put(key: Key, value: string | ArrayBuffer | ArrayBufferView | ReadableStream, options?: KVNamespacePutOptions): Promise<void>;
235 
236 getWithMetadata<Metadata = unknown>(key: Key, options?: Partial<KVNamespaceGetOptions<undefined>>): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;
237 getWithMetadata<Metadata = unknown>(key: Key, type: "text"): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;
238 getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(key: Key, type: "json"): Promise<KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;
239 getWithMetadata<Metadata = unknown>(key: Key, type: "arrayBuffer"): Promise<KVNamespaceGetWithMetadataResult<ArrayBuffer, Metadata>>;
240 getWithMetadata<Metadata = unknown>(key: Key, type: "stream"): Promise<KVNamespaceGetWithMetadataResult<ReadableStream, Metadata>>;
241 getWithMetadata<Metadata = unknown>(key: Key, options: KVNamespaceGetOptions<"text">): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;
242 getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(key: Key, options: KVNamespaceGetOptions<"json">): Promise<KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;
243 getWithMetadata<Metadata = unknown>(key: Key, options: KVNamespaceGetOptions<"arrayBuffer">): Promise<KVNamespaceGetWithMetadataResult<ArrayBuffer, Metadata>>;
244 getWithMetadata<Metadata = unknown>(key: Key, options: KVNamespaceGetOptions<"stream">): Promise<KVNamespaceGetWithMetadataResult<ReadableStream, Metadata>>;
245 
246 getWithMetadata<Metadata = unknown>(key: Array<Key>, type: "text"): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>;
247 getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(key: Array<Key>, type: "json"): Promise<Map<string, KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;
248 getWithMetadata<Metadata = unknown>(key: Array<Key>, options?: Partial<KVNamespaceGetOptions<undefined>>): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>;
249 getWithMetadata<Metadata = unknown>(key: Array<Key>, options?: KVNamespaceGetOptions<"text">): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>;
250 getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(key: Array<Key>, options?: KVNamespaceGetOptions<"json">): Promise<Map<string, KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;
251 delete(key: Key): Promise<void>;
252 });
253 }
254 }
255 
256 void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {
257 tracker.trackField("additionalHeaders", additionalHeaders.asPtr());
258 }
259 
260 protected:
261 // Do the boilerplate work of constructing an HTTP client to KV. Setting a KvOptType causes
262 // the limiter for that op type to be checked. If a string is used, there isn't any limiter
263 // enforcement.
264 // NOTE: The urlStr is added to the headers as a non-owning reference and thus must outlive
265 // the usage of the headers.
266 kj::Own<kj::HttpClient> getHttpClient(IoContext& context,
267 kj::HttpHeaders& headers,
268 kj::OneOf<LimitEnforcer::KvOpType, kj::LiteralStringConst> opTypeOrName,
269 kj::StringPtr urlStr,
270 TraceContext& traceContext);
271 
272 private:
273 kj::Array<AdditionalHeader> additionalHeaders;
274 uint subrequestChannel;
275 kj::String bindingName;
276};
277 
278#define EW_KV_ISOLATE_TYPES \
279 api::KvNamespace, api::KvNamespace::ListOptions, api::KvNamespace::GetOptions, \
280 api::KvNamespace::PutOptions, api::KvNamespace::GetWithMetadataResult
281// The list of kv.h types that are added to worker.c++'s JSG_DECLARE_ISOLATE_TYPE
282} // namespace workerd::api