@@ -17,6 +17,10 @@ using fastly::FastlyGetErrorMessage;
1717using fastly::fastly::convertBodyInit;
1818using fastly::fetch::RequestOrResponse;
1919
20+ namespace {
21+ api::Engine *GLOBAL_ENGINE;
22+ }
23+
2024namespace fastly ::cache_simple {
2125
2226template <RequestOrResponse::BodyReadResult result_type>
@@ -394,62 +398,39 @@ bool get_or_set_catch_handler(JSContext *cx, JS::HandleObject lookup_state,
394398 return true ;
395399}
396400
397- } // namespace
398-
399- // static getOrSet(key: string, set: () => Promise<{value: BodyInit, ttl: number}>):
400- // SimpleCacheEntry | null; static getOrSet(key: string, set: () => Promise<{value: ReadableStream,
401- // ttl: number, length: number}>): SimpleCacheEntry | null;
402- bool SimpleCache::getOrSet (JSContext *cx, unsigned argc, JS::Value *vp) {
403- REQUEST_HANDLER_ONLY (" The SimpleCache builtin" );
404- JS::CallArgs args = JS::CallArgsFromVp (argc, vp);
405- if (!args.requireAtLeast (cx, " SimpleCache.getOrSet" , 2 )) {
406- return false ;
407- }
408-
409- // Convert key parameter into a string and check the value adheres to our validation rules.
410- auto key_chars = core::encode (cx, args.get (0 ));
411- if (!key_chars) {
401+ bool process_pending_cache_lookup (JSContext *cx, host_api::CacheHandle::Handle handle,
402+ JS::HandleObject context_obj, JS::HandleValue) {
403+ host_api::CacheHandle pending_lookup (handle);
404+ JS::RootedValue key_val (cx);
405+ if (!JS_GetProperty (cx, context_obj, " key" , &key_val)) {
412406 return false ;
413407 }
414-
415- if (key_chars. len == 0 ) {
416- JS_ReportErrorASCII ( cx, " SimpleCache.getOrSet: key can not be an empty string " );
408+ auto key_chars = core::encode (cx, key_val);
409+ JS::RootedValue set_function_val (cx);
410+ if (! JS_GetProperty ( cx, context_obj, " set_function " , &set_function_val)) {
417411 return false ;
418412 }
419- if (key_chars.len > 8135 ) {
420- JS_ReportErrorASCII (
421- cx, " SimpleCache.getOrSet: key is too long, the maximum allowed length is 8135." );
413+ JS::RootedValue promise_val (cx);
414+ if (!JS_GetProperty (cx, context_obj, " promise" , &promise_val)) {
422415 return false ;
423416 }
417+ JS::RootedObject promise_obj (cx, &promise_val.toObject ());
424418
425- JS::RootedObject promise (cx, JS::NewPromiseObject (cx, nullptr ));
426- if (!promise) {
427- return ReturnPromiseRejectedWithPendingError (cx, args);
428- }
429-
430- auto res = host_api::CacheHandle::transaction_lookup (key_chars, host_api::CacheLookupOptions{});
431- if (auto *err = res.to_err ()) {
432- HANDLE_ERROR (cx, *err);
433- return false ;
434- }
435-
436- auto handle = res.unwrap ();
437- BEGIN_TRANSACTION (transaction, cx, promise, handle);
419+ BEGIN_TRANSACTION (transaction, cx, promise_obj, pending_lookup);
438420
439421 // Check if a fresh cache item was found, if that's the case, then we will resolve
440422 // with a SimpleCacheEntry containing the value. Else, call the content-provided
441423 // function in the `set` parameter and insert it's returned value property into the
442424 // cache under the provided `key`, and then we will resolve with a SimpleCacheEntry
443425 // containing the value.
444- auto state_res = handle .get_state ();
426+ auto state_res = pending_lookup .get_state ();
445427 if (auto *err = state_res.to_err ()) {
446428 return false ;
447429 }
448430
449431 auto state = state_res.unwrap ();
450- args.rval ().setObject (*promise);
451432 if (state.is_usable ()) {
452- auto body_res = handle .get_body (host_api::CacheGetBodyOptions{});
433+ auto body_res = pending_lookup .get_body (host_api::CacheGetBodyOptions{});
453434 if (auto *err = body_res.to_err ()) {
454435 return false ;
455436 }
@@ -461,16 +442,15 @@ bool SimpleCache::getOrSet(JSContext *cx, unsigned argc, JS::Value *vp) {
461442
462443 JS::RootedValue result (cx);
463444 result.setObject (*entry);
464- JS::ResolvePromise (cx, promise , result);
445+ JS::ResolvePromise (cx, promise_obj , result);
465446 return true ;
466447 } else {
467- auto arg1 = args.get (1 );
468- if (!arg1.isObject () || !JS::IsCallable (&arg1.toObject ())) {
448+ if (!set_function_val.isObject () || !JS::IsCallable (&set_function_val.toObject ())) {
469449 JS_ReportErrorLatin1 (cx, " SimpleCache.getOrSet: set argument is not a function" );
470450 return false ;
471451 }
472452 JS::RootedValueArray<0 > fnargs (cx);
473- JS::RootedObject fn (cx, &arg1 .toObject ());
453+ JS::RootedObject fn (cx, &set_function_val .toObject ());
474454 JS::RootedValue result (cx);
475455 if (!JS::Call (cx, JS::NullHandleValue, fn, fnargs, &result)) {
476456 return false ;
@@ -483,7 +463,7 @@ bool SimpleCache::getOrSet(JSContext *cx, unsigned argc, JS::Value *vp) {
483463
484464 // JS::RootedObject owner(cx, JS_NewPlainObject(cx));
485465 JS::RootedObject lookup_state (cx, JS_NewPlainObject (cx));
486- JS::RootedValue handle_val (cx, JS::NumberValue (handle. handle ));
466+ JS::RootedValue handle_val (cx, JS::NumberValue (handle));
487467 if (!JS_SetProperty (cx, lookup_state, " handle" , handle_val)) {
488468 return false ;
489469 }
@@ -492,7 +472,7 @@ bool SimpleCache::getOrSet(JSContext *cx, unsigned argc, JS::Value *vp) {
492472 if (!JS_SetProperty (cx, lookup_state, " key" , keyVal)) {
493473 return false ;
494474 }
495- JS::RootedValue promise_val (cx, JS::ObjectValue (*promise ));
475+ JS::RootedValue promise_val (cx, JS::ObjectValue (*promise_obj ));
496476 if (!JS_SetProperty (cx, lookup_state, " promise" , promise_val)) {
497477 return false ;
498478 }
@@ -517,6 +497,73 @@ bool SimpleCache::getOrSet(JSContext *cx, unsigned argc, JS::Value *vp) {
517497 }
518498}
519499
500+ } // namespace
501+
502+ // static getOrSet(key: string, set: () => Promise<{value: BodyInit, ttl: number}>):
503+ // SimpleCacheEntry | null; static getOrSet(key: string, set: () => Promise<{value: ReadableStream,
504+ // ttl: number, length: number}>): SimpleCacheEntry | null;
505+ bool SimpleCache::getOrSet (JSContext *cx, unsigned argc, JS::Value *vp) {
506+ REQUEST_HANDLER_ONLY (" The SimpleCache builtin" );
507+ JS::CallArgs args = JS::CallArgsFromVp (argc, vp);
508+ if (!args.requireAtLeast (cx, " SimpleCache.getOrSet" , 2 )) {
509+ return false ;
510+ }
511+
512+ // Convert key parameter into a string and check the value adheres to our validation rules.
513+ auto key_chars = core::encode (cx, args.get (0 ));
514+ if (!key_chars) {
515+ return false ;
516+ }
517+
518+ if (key_chars.len == 0 ) {
519+ JS_ReportErrorASCII (cx, " SimpleCache.getOrSet: key can not be an empty string" );
520+ return false ;
521+ }
522+ if (key_chars.len > 8135 ) {
523+ JS_ReportErrorASCII (
524+ cx, " SimpleCache.getOrSet: key is too long, the maximum allowed length is 8135." );
525+ return false ;
526+ }
527+
528+ JS::RootedObject promise (cx, JS::NewPromiseObject (cx, nullptr ));
529+ if (!promise) {
530+ return ReturnPromiseRejectedWithPendingError (cx, args);
531+ }
532+
533+ auto res = host_api::CacheHandle::transaction_lookup (key_chars, host_api::CacheLookupOptions{});
534+ if (auto *err = res.to_err ()) {
535+ HANDLE_ERROR (cx, *err);
536+ return false ;
537+ }
538+
539+ // The async task requires some extra state: the key, the `set` function, and the promise.
540+ // We wrap this all up into one object, so `process_pending_cache_lookup` can retrieve everything.
541+ // This could be avoided with some changes to `FastlyAsyncTask`, see
542+ // (https://github.com/fastly/js-compute-runtime/issues/1245)
543+ JS::RootedObject context_obj (cx, JS_NewPlainObject (cx));
544+ if (!context_obj) {
545+ return false ;
546+ }
547+ JS::RootedValue key_val (cx, args.get (0 ));
548+ if (!JS_SetProperty (cx, context_obj, " key" , key_val)) {
549+ return false ;
550+ }
551+ JS::RootedValue set_func_val (cx, args.get (1 ));
552+ if (!JS_SetProperty (cx, context_obj, " set_function" , set_func_val)) {
553+ return false ;
554+ }
555+ JS::RootedValue promise_val (cx, JS::ObjectValue (*promise));
556+ if (!JS_SetProperty (cx, context_obj, " promise" , promise_val)) {
557+ return false ;
558+ }
559+ auto handle = res.unwrap ();
560+ GLOBAL_ENGINE->queue_async_task (new FastlyAsyncTask (
561+ handle.handle , context_obj, JS::UndefinedHandleValue, process_pending_cache_lookup));
562+
563+ args.rval ().setObject (*promise);
564+ return true ;
565+ }
566+
520567// static set(key: string, value: BodyInit, ttl: number): undefined;
521568// static set(key: string, value: ReadableStream, ttl: number, length: number): undefined;
522569bool SimpleCache::set (JSContext *cx, unsigned argc, JS::Value *vp) {
@@ -791,6 +838,8 @@ const JSPropertySpec SimpleCache::properties[] = {
791838 JS_STRING_SYM_PS (toStringTag, " SimpleCache" , JSPROP_READONLY), JS_PS_END};
792839
793840bool install (api::Engine *engine) {
841+ GLOBAL_ENGINE = engine;
842+
794843 if (!SimpleCacheEntry::init_class_impl (engine->cx (), engine->global ())) {
795844 return false ;
796845 }
0 commit comments