Skip to content

Commit

Permalink
feat: fastly.sdkVersion implementation (#776)
Browse files Browse the repository at this point in the history
  • Loading branch information
guybedford committed May 13, 2024
1 parent 1ab46a3 commit 3eb5a8f
Show file tree
Hide file tree
Showing 13 changed files with 126 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import { pass, assert } from "./assertions.js";
import { routes } from "./routes.js";
import { sdkVersion } from "fastly:experimental";

routes.set("/fastly/now", function () {
let error = assert(typeof fastly.now, 'function', 'typeof fastly.now')
Expand All @@ -22,3 +23,13 @@ routes.set("/fastly/now", function () {

return pass()
})

routes.set("/fastly/version", function () {
let error = assert(typeof fastly.sdkVersion, 'string', 'typeof fastly.sdkVersion')
if (error) { return error }

error = assert(fastly.sdkVersion, sdkVersion, 'fastly.sdkVersion matches fastly:experimental#sdkVersion')
if (error) { return error }

return pass()
})
2 changes: 1 addition & 1 deletion integration-tests/js-compute/fixtures/app/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import "./dynamic-backend.js"
import "./edge-rate-limiter.js"
import "./env.js"
import "./fanout.js"
import "./fastly-now.js"
import "./fastly-global.js"
import "./fetch-errors.js"
import "./geoip.js"
import "./headers.js"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@
"GET /backend/health/happy-path-backend-exists",
"GET /backend/health/happy-path-backend-does-not-exist",
"GET /env",
"GET /fastly/version",
"GET /multiple-set-cookie/response-init",
"GET /multiple-set-cookie/response-direct",
"GET /request/clone/called-as-constructor",
Expand Down
10 changes: 10 additions & 0 deletions integration-tests/js-compute/fixtures/app/tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -3058,6 +3058,16 @@
"status": 200
}
},
"GET /fastly/version": {
"environments": ["compute", "viceroy"],
"downstream_request": {
"method": "GET",
"pathname": "/fastly/version"
},
"downstream_response": {
"status": 200
}
},
"GET /fastly/getgeolocationforipaddress/interface": {
"environments": ["compute"],
"downstream_request": {
Expand Down
2 changes: 1 addition & 1 deletion integration-tests/js-compute/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ if (!local) {
const setupPath = join(fixturePath, 'setup.js')
if (existsSync(setupPath)) {
core.startGroup('Extra set-up steps for the service')
await zx`${setupPath}`
await zx`${setupPath}${starlingmonkey ? ' --starlingmonkey' : ''}`
await sleep(60)
core.endGroup()
}
Expand Down
135 changes: 73 additions & 62 deletions runtime/fastly/builtins/fastly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ const JSErrorFormatString *FastlyGetErrorMessage(void *userRef, unsigned errorNu

namespace {

api::Engine *ENGINE;

bool enableDebugLogging(JSContext *cx, unsigned argc, JS::Value *vp) {
JS::CallArgs args = CallArgsFromVp(argc, vp);
if (!args.requireAtLeast(cx, __func__, 1))
Expand All @@ -51,6 +49,13 @@ JS::PersistentRooted<JSObject *> Fastly::baseURL;
JS::PersistentRooted<JSString *> Fastly::defaultBackend;
bool Fastly::allowDynamicBackends = false;

bool Fastly::version_get(JSContext *cx, unsigned argc, JS::Value *vp) {
JS::CallArgs args = CallArgsFromVp(argc, vp);
JS::RootedString version_str(cx, JS_NewStringCopyN(cx, RUNTIME_VERSION, strlen(RUNTIME_VERSION)));
args.rval().setString(version_str);
return true;
}

bool Env::env_get(JSContext *cx, unsigned argc, JS::Value *vp) {
JS::CallArgs args = CallArgsFromVp(argc, vp);
if (!args.requireAtLeast(cx, "fastly.env.get", 1))
Expand Down Expand Up @@ -306,145 +311,151 @@ const JSPropertySpec Fastly::properties[] = {
JS_PSGS("defaultBackend", defaultBackend_get, defaultBackend_set, JSPROP_ENUMERATE),
JS_PSGS("allowDynamicBackends", allowDynamicBackends_get, allowDynamicBackends_set,
JSPROP_ENUMERATE),
JS_PSG("sdkVersion", version_get, JSPROP_ENUMERATE),
JS_PS_END};

bool install(api::Engine *engine) {
ENGINE = engine;
JS::RootedObject fastly(ENGINE->cx(), JS_NewPlainObject(ENGINE->cx()));
JS::RootedObject fastly(engine->cx(), JS_NewPlainObject(engine->cx()));
if (!fastly) {
return false;
}

Fastly::env.init(ENGINE->cx(), Env::create(ENGINE->cx()));
Fastly::env.init(engine->cx(), Env::create(engine->cx()));
if (!Fastly::env) {
return false;
}

Fastly::baseURL.init(ENGINE->cx());
Fastly::defaultBackend.init(ENGINE->cx());
Fastly::baseURL.init(engine->cx());
Fastly::defaultBackend.init(engine->cx());

if (!JS_DefineProperty(ENGINE->cx(), ENGINE->global(), "fastly", fastly, 0)) {
if (!JS_DefineProperty(engine->cx(), engine->global(), "fastly", fastly, 0)) {
return false;
}

// fastly:env
RootedValue env_get(ENGINE->cx());
if (!JS_GetProperty(ENGINE->cx(), Fastly::env, "get", &env_get)) {
RootedValue env_get(engine->cx());
if (!JS_GetProperty(engine->cx(), Fastly::env, "get", &env_get)) {
return false;
}
RootedObject env_builtin(ENGINE->cx(), JS_NewObject(ENGINE->cx(), nullptr));
if (!JS_SetProperty(ENGINE->cx(), env_builtin, "env", env_get)) {
RootedObject env_builtin(engine->cx(), JS_NewObject(engine->cx(), nullptr));
if (!JS_SetProperty(engine->cx(), env_builtin, "env", env_get)) {
return false;
}
RootedValue env_builtin_val(ENGINE->cx(), JS::ObjectValue(*env_builtin));
if (!ENGINE->define_builtin_module("fastly:env", env_builtin_val)) {
RootedValue env_builtin_val(engine->cx(), JS::ObjectValue(*env_builtin));
if (!engine->define_builtin_module("fastly:env", env_builtin_val)) {
return false;
}

// fastly:experimental
RootedObject experimental(ENGINE->cx(), JS_NewObject(ENGINE->cx(), nullptr));
RootedValue experimental_val(ENGINE->cx(), JS::ObjectValue(*experimental));
RootedObject experimental(engine->cx(), JS_NewObject(engine->cx(), nullptr));
RootedValue experimental_val(engine->cx(), JS::ObjectValue(*experimental));
// TODO(GB): implement includeBytes
if (!JS_SetProperty(ENGINE->cx(), experimental, "includeBytes", experimental_val)) {
if (!JS_SetProperty(engine->cx(), experimental, "includeBytes", experimental_val)) {
return false;
}
auto set_default_backend =
JS_NewFunction(ENGINE->cx(), &Fastly::defaultBackend_set, 1, 0, "setDefaultBackend");
RootedObject set_default_backend_obj(ENGINE->cx(), JS_GetFunctionObject(set_default_backend));
RootedValue set_default_backend_val(ENGINE->cx(), ObjectValue(*set_default_backend_obj));
if (!JS_SetProperty(ENGINE->cx(), experimental, "setDefaultBackend", set_default_backend_val)) {
JS_NewFunction(engine->cx(), &Fastly::defaultBackend_set, 1, 0, "setDefaultBackend");
RootedObject set_default_backend_obj(engine->cx(), JS_GetFunctionObject(set_default_backend));
RootedValue set_default_backend_val(engine->cx(), ObjectValue(*set_default_backend_obj));
if (!JS_SetProperty(engine->cx(), experimental, "setDefaultBackend", set_default_backend_val)) {
return false;
}
auto allow_dynamic_backends =
JS_NewFunction(ENGINE->cx(), &Fastly::allowDynamicBackends_set, 1, 0, "allowDynamicBackends");
RootedObject allow_dynamic_backends_obj(ENGINE->cx(),
JS_NewFunction(engine->cx(), &Fastly::allowDynamicBackends_set, 1, 0, "allowDynamicBackends");
RootedObject allow_dynamic_backends_obj(engine->cx(),
JS_GetFunctionObject(allow_dynamic_backends));
RootedValue allow_dynamic_backends_val(ENGINE->cx(), ObjectValue(*allow_dynamic_backends_obj));
if (!JS_SetProperty(ENGINE->cx(), experimental, "allowDynamicBackends",
RootedValue allow_dynamic_backends_val(engine->cx(), ObjectValue(*allow_dynamic_backends_obj));
if (!JS_SetProperty(engine->cx(), experimental, "allowDynamicBackends",
allow_dynamic_backends_val)) {
return false;
}
if (!ENGINE->define_builtin_module("fastly:experimental", experimental_val)) {
RootedString version_str(
engine->cx(), JS_NewStringCopyN(engine->cx(), RUNTIME_VERSION, strlen(RUNTIME_VERSION)));
RootedValue version_str_val(engine->cx(), StringValue(version_str));
if (!JS_SetProperty(engine->cx(), experimental, "sdkVersion", version_str_val)) {
return false;
}
if (!engine->define_builtin_module("fastly:experimental", experimental_val)) {
return false;
}

// TODO(GB): all of the following builtin modules are just placeholder shapes for now
if (!ENGINE->define_builtin_module("fastly:body", env_builtin_val)) {
if (!engine->define_builtin_module("fastly:body", env_builtin_val)) {
return false;
}
RootedObject cache(ENGINE->cx(), JS_NewObject(ENGINE->cx(), nullptr));
RootedValue cache_val(ENGINE->cx(), JS::ObjectValue(*cache));
if (!JS_SetProperty(ENGINE->cx(), cache, "CoreCache", cache_val)) {
RootedObject cache(engine->cx(), JS_NewObject(engine->cx(), nullptr));
RootedValue cache_val(engine->cx(), JS::ObjectValue(*cache));
if (!JS_SetProperty(engine->cx(), cache, "CoreCache", cache_val)) {
return false;
}
if (!JS_SetProperty(ENGINE->cx(), cache, "CacheEntry", cache_val)) {
if (!JS_SetProperty(engine->cx(), cache, "CacheEntry", cache_val)) {
return false;
}
if (!JS_SetProperty(ENGINE->cx(), cache, "CacheEntry", cache_val)) {
if (!JS_SetProperty(engine->cx(), cache, "CacheEntry", cache_val)) {
return false;
}
if (!JS_SetProperty(ENGINE->cx(), cache, "SimpleCache", cache_val)) {
if (!JS_SetProperty(engine->cx(), cache, "SimpleCache", cache_val)) {
return false;
}
if (!ENGINE->define_builtin_module("fastly:cache", cache_val)) {
if (!engine->define_builtin_module("fastly:cache", cache_val)) {
return false;
}
if (!ENGINE->define_builtin_module("fastly:config-store", env_builtin_val)) {
if (!engine->define_builtin_module("fastly:config-store", env_builtin_val)) {
return false;
}
RootedObject device_device(ENGINE->cx(), JS_NewObject(ENGINE->cx(), nullptr));
RootedValue device_device_val(ENGINE->cx(), JS::ObjectValue(*device_device));
if (!JS_SetProperty(ENGINE->cx(), device_device, "Device", device_device_val)) {
RootedObject device_device(engine->cx(), JS_NewObject(engine->cx(), nullptr));
RootedValue device_device_val(engine->cx(), JS::ObjectValue(*device_device));
if (!JS_SetProperty(engine->cx(), device_device, "Device", device_device_val)) {
return false;
}
if (!ENGINE->define_builtin_module("fastly:device", device_device_val)) {
if (!engine->define_builtin_module("fastly:device", device_device_val)) {
return false;
}
RootedObject dictionary(ENGINE->cx(), JS_NewObject(ENGINE->cx(), nullptr));
RootedValue dictionary_val(ENGINE->cx(), JS::ObjectValue(*dictionary));
if (!JS_SetProperty(ENGINE->cx(), dictionary, "Dictionary", dictionary_val)) {
RootedObject dictionary(engine->cx(), JS_NewObject(engine->cx(), nullptr));
RootedValue dictionary_val(engine->cx(), JS::ObjectValue(*dictionary));
if (!JS_SetProperty(engine->cx(), dictionary, "Dictionary", dictionary_val)) {
return false;
}
if (!ENGINE->define_builtin_module("fastly:dictionary", dictionary_val)) {
if (!engine->define_builtin_module("fastly:dictionary", dictionary_val)) {
return false;
}
RootedObject edge_rate_limiter(ENGINE->cx(), JS_NewObject(ENGINE->cx(), nullptr));
RootedValue edge_rate_limiter_val(ENGINE->cx(), JS::ObjectValue(*edge_rate_limiter));
if (!JS_SetProperty(ENGINE->cx(), edge_rate_limiter, "RateCounter", edge_rate_limiter_val)) {
RootedObject edge_rate_limiter(engine->cx(), JS_NewObject(engine->cx(), nullptr));
RootedValue edge_rate_limiter_val(engine->cx(), JS::ObjectValue(*edge_rate_limiter));
if (!JS_SetProperty(engine->cx(), edge_rate_limiter, "RateCounter", edge_rate_limiter_val)) {
return false;
}
if (!JS_SetProperty(ENGINE->cx(), edge_rate_limiter, "PenaltyBox", edge_rate_limiter_val)) {
if (!JS_SetProperty(engine->cx(), edge_rate_limiter, "PenaltyBox", edge_rate_limiter_val)) {
return false;
}
if (!JS_SetProperty(ENGINE->cx(), edge_rate_limiter, "EdgeRateLimiter", edge_rate_limiter_val)) {
if (!JS_SetProperty(engine->cx(), edge_rate_limiter, "EdgeRateLimiter", edge_rate_limiter_val)) {
return false;
}
if (!ENGINE->define_builtin_module("fastly:edge-rate-limiter", edge_rate_limiter_val)) {
if (!engine->define_builtin_module("fastly:edge-rate-limiter", edge_rate_limiter_val)) {
return false;
}
RootedObject fanout(ENGINE->cx(), JS_NewObject(ENGINE->cx(), nullptr));
RootedValue fanout_val(ENGINE->cx(), JS::ObjectValue(*fanout));
if (!JS_SetProperty(ENGINE->cx(), fanout, "createFanoutHandoff", fanout_val)) {
RootedObject fanout(engine->cx(), JS_NewObject(engine->cx(), nullptr));
RootedValue fanout_val(engine->cx(), JS::ObjectValue(*fanout));
if (!JS_SetProperty(engine->cx(), fanout, "createFanoutHandoff", fanout_val)) {
return false;
}
if (!ENGINE->define_builtin_module("fastly:fanout", fanout_val)) {
if (!engine->define_builtin_module("fastly:fanout", fanout_val)) {
return false;
}
if (!ENGINE->define_builtin_module("fastly:geolocation", env_builtin_val)) {
if (!engine->define_builtin_module("fastly:geolocation", env_builtin_val)) {
return false;
}
RootedObject kv_store(ENGINE->cx(), JS_NewObject(ENGINE->cx(), nullptr));
RootedValue kv_store_val(ENGINE->cx(), JS::ObjectValue(*kv_store));
if (!JS_SetProperty(ENGINE->cx(), kv_store, "KVStore", kv_store_val)) {
RootedObject kv_store(engine->cx(), JS_NewObject(engine->cx(), nullptr));
RootedValue kv_store_val(engine->cx(), JS::ObjectValue(*kv_store));
if (!JS_SetProperty(engine->cx(), kv_store, "KVStore", kv_store_val)) {
return false;
}
if (!ENGINE->define_builtin_module("fastly:kv-store", kv_store_val)) {
if (!engine->define_builtin_module("fastly:kv-store", kv_store_val)) {
return false;
}
if (!ENGINE->define_builtin_module("fastly:logger", env_builtin_val)) {
if (!engine->define_builtin_module("fastly:logger", env_builtin_val)) {
return false;
}
if (!ENGINE->define_builtin_module("fastly:secret-store", env_builtin_val)) {
if (!engine->define_builtin_module("fastly:secret-store", env_builtin_val)) {
return false;
}

Expand All @@ -463,8 +474,8 @@ bool install(api::Engine *engine) {
// options.getExperimentalHighResolutionTimeMethodsEnabled() ? nowfn : end,
end};

return JS_DefineFunctions(ENGINE->cx(), fastly, methods) &&
JS_DefineProperties(ENGINE->cx(), fastly, Fastly::properties);
return JS_DefineFunctions(engine->cx(), fastly, methods) &&
JS_DefineProperties(engine->cx(), fastly, Fastly::properties);
}

} // namespace fastly::fastly
3 changes: 3 additions & 0 deletions runtime/fastly/builtins/fastly.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ using namespace builtins;

namespace fastly::fastly {

#define RUNTIME_VERSION "starlingmonkey-dev"

class Env : public BuiltinNoConstructor<Env> {
private:
static bool env_get(JSContext *cx, unsigned argc, JS::Value *vp);
Expand Down Expand Up @@ -53,6 +55,7 @@ class Fastly : public BuiltinNoConstructor<Fastly> {
// static bool getGeolocationForIpAddress(JSContext *cx, unsigned argc, JS::Value *vp);
// static bool getLogger(JSContext *cx, unsigned argc, JS::Value *vp);
// static bool includeBytes(JSContext *cx, unsigned argc, JS::Value *vp);
static bool version_get(JSContext *cx, unsigned argc, JS::Value *vp);
static bool env_get(JSContext *cx, unsigned argc, JS::Value *vp);
static bool baseURL_get(JSContext *cx, unsigned argc, JS::Value *vp);
static bool baseURL_set(JSContext *cx, unsigned argc, JS::Value *vp);
Expand Down
8 changes: 8 additions & 0 deletions runtime/js-compute-runtime/builtins/fastly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,13 @@ bool Fastly::env_get(JSContext *cx, unsigned argc, JS::Value *vp) {
return true;
}

bool Fastly::version_get(JSContext *cx, unsigned argc, JS::Value *vp) {
JS::CallArgs args = CallArgsFromVp(argc, vp);
JS::RootedString version_str(cx, JS_NewStringCopyN(cx, RUNTIME_VERSION, strlen(RUNTIME_VERSION)));
args.rval().setString(version_str);
return true;
}

bool Fastly::baseURL_get(JSContext *cx, unsigned argc, JS::Value *vp) {
JS::CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setObjectOrNull(baseURL);
Expand Down Expand Up @@ -262,6 +269,7 @@ const JSPropertySpec Fastly::properties[] = {
JS_PSGS("defaultBackend", defaultBackend_get, defaultBackend_set, JSPROP_ENUMERATE),
JS_PSGS("allowDynamicBackends", allowDynamicBackends_get, allowDynamicBackends_set,
JSPROP_ENUMERATE),
JS_PSG("sdkVersion", version_get, JSPROP_ENUMERATE),
JS_PS_END};

bool Fastly::create(JSContext *cx, JS::HandleObject global, FastlyOptions options) {
Expand Down
1 change: 1 addition & 0 deletions runtime/js-compute-runtime/builtins/fastly.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class Fastly : public BuiltinNoConstructor<Fastly> {
static bool getLogger(JSContext *cx, unsigned argc, JS::Value *vp);
static bool includeBytes(JSContext *cx, unsigned argc, JS::Value *vp);
static bool env_get(JSContext *cx, unsigned argc, JS::Value *vp);
static bool version_get(JSContext *cx, unsigned argc, JS::Value *vp);
static bool baseURL_get(JSContext *cx, unsigned argc, JS::Value *vp);
static bool baseURL_set(JSContext *cx, unsigned argc, JS::Value *vp);
static bool defaultBackend_get(JSContext *cx, unsigned argc, JS::Value *vp);
Expand Down
2 changes: 2 additions & 0 deletions runtime/js-compute-runtime/js-compute-builtins.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const JSErrorFormatString js_ErrorFormatString[JSErrNum_Limit] = {

#include "host_interface/host_api.h"

#define RUNTIME_VERSION "3.13.2-dev"

const JSErrorFormatString *GetErrorMessage(void *userRef, unsigned errorNumber);

JSObject *PromiseRejectedWithPendingError(JSContext *cx);
Expand Down
1 change: 1 addition & 0 deletions src/bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const enableDebugLogging = globalThis.fastly.enableDebugLogging;
export const setBaseURL = Object.getOwnPropertyDescriptor(globalThis.fastly, 'baseURL').set;
export const setDefaultBackend = Object.getOwnPropertyDescriptor(globalThis.fastly, 'defaultBackend').set;
export const allowDynamicBackends = Object.getOwnPropertyDescriptor(globalThis.fastly, 'allowDynamicBackends').set;
export const sdkVersion = globalThis.fastly.sdkVersion;
`
}
}
Expand Down
7 changes: 7 additions & 0 deletions types/experimental.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
* @experimental
*/
declare module "fastly:experimental" {
/**
* JavaScript SDK version string for the JS runtime engine build.
*
* @experimental
* @hidden
*/
export const sdkVersion: string;
/**
* @experimental
* @hidden
Expand Down
Loading

0 comments on commit 3eb5a8f

Please sign in to comment.