diff --git a/integration-tests/js-compute/fixtures/app/src/fastly-now.js b/integration-tests/js-compute/fixtures/app/src/fastly-global.js similarity index 67% rename from integration-tests/js-compute/fixtures/app/src/fastly-now.js rename to integration-tests/js-compute/fixtures/app/src/fastly-global.js index 22b10bc4ce..a0898048d8 100644 --- a/integration-tests/js-compute/fixtures/app/src/fastly-now.js +++ b/integration-tests/js-compute/fixtures/app/src/fastly-global.js @@ -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') @@ -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() +}) diff --git a/integration-tests/js-compute/fixtures/app/src/index.js b/integration-tests/js-compute/fixtures/app/src/index.js index 6a87224abf..7de740a5ef 100644 --- a/integration-tests/js-compute/fixtures/app/src/index.js +++ b/integration-tests/js-compute/fixtures/app/src/index.js @@ -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" diff --git a/integration-tests/js-compute/fixtures/app/tests-starlingmonkey.json b/integration-tests/js-compute/fixtures/app/tests-starlingmonkey.json index 73f71a4ebf..23a5756db2 100644 --- a/integration-tests/js-compute/fixtures/app/tests-starlingmonkey.json +++ b/integration-tests/js-compute/fixtures/app/tests-starlingmonkey.json @@ -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", diff --git a/integration-tests/js-compute/fixtures/app/tests.json b/integration-tests/js-compute/fixtures/app/tests.json index 97b63f8353..5fe9ef6a9e 100644 --- a/integration-tests/js-compute/fixtures/app/tests.json +++ b/integration-tests/js-compute/fixtures/app/tests.json @@ -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": { diff --git a/integration-tests/js-compute/test.js b/integration-tests/js-compute/test.js index c1bdfeb88a..4aeb1f03fa 100755 --- a/integration-tests/js-compute/test.js +++ b/integration-tests/js-compute/test.js @@ -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() } diff --git a/runtime/fastly/builtins/fastly.cpp b/runtime/fastly/builtins/fastly.cpp index 01c019416e..a7372ff1c0 100644 --- a/runtime/fastly/builtins/fastly.cpp +++ b/runtime/fastly/builtins/fastly.cpp @@ -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)) @@ -51,6 +49,13 @@ JS::PersistentRooted Fastly::baseURL; JS::PersistentRooted 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)) @@ -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; } @@ -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 diff --git a/runtime/fastly/builtins/fastly.h b/runtime/fastly/builtins/fastly.h index 4fea0f2b27..925974bd36 100644 --- a/runtime/fastly/builtins/fastly.h +++ b/runtime/fastly/builtins/fastly.h @@ -13,6 +13,8 @@ using namespace builtins; namespace fastly::fastly { +#define RUNTIME_VERSION "starlingmonkey-dev" + class Env : public BuiltinNoConstructor { private: static bool env_get(JSContext *cx, unsigned argc, JS::Value *vp); @@ -53,6 +55,7 @@ class Fastly : public BuiltinNoConstructor { // 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); diff --git a/runtime/js-compute-runtime/builtins/fastly.cpp b/runtime/js-compute-runtime/builtins/fastly.cpp index a6d76b19f4..714940276a 100644 --- a/runtime/js-compute-runtime/builtins/fastly.cpp +++ b/runtime/js-compute-runtime/builtins/fastly.cpp @@ -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); @@ -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) { diff --git a/runtime/js-compute-runtime/builtins/fastly.h b/runtime/js-compute-runtime/builtins/fastly.h index e7b3321770..16d795b177 100644 --- a/runtime/js-compute-runtime/builtins/fastly.h +++ b/runtime/js-compute-runtime/builtins/fastly.h @@ -31,6 +31,7 @@ class Fastly : public BuiltinNoConstructor { 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); diff --git a/runtime/js-compute-runtime/js-compute-builtins.h b/runtime/js-compute-runtime/js-compute-builtins.h index 103a61d2a7..86ae6be1c0 100644 --- a/runtime/js-compute-runtime/js-compute-builtins.h +++ b/runtime/js-compute-runtime/js-compute-builtins.h @@ -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); diff --git a/src/bundle.js b/src/bundle.js index 73c96734b3..12d564a517 100644 --- a/src/bundle.js +++ b/src/bundle.js @@ -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; ` } } diff --git a/types/experimental.d.ts b/types/experimental.d.ts index 36ca8c08cd..e67f4a6dc8 100644 --- a/types/experimental.d.ts +++ b/types/experimental.d.ts @@ -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 diff --git a/types/globals.d.ts b/types/globals.d.ts index 000258b452..db808fa1b0 100644 --- a/types/globals.d.ts +++ b/types/globals.d.ts @@ -751,6 +751,7 @@ declare interface Fastly { /** * Property to access the environment variables for the Fastly Compute service. * @hidden + * @experimental */ env: { /** @@ -766,6 +767,12 @@ declare interface Fastly { get(name: string): string; }; + /** + * JavaScript SDK version string for the JS runtime build. + * @hidden + */ + sdkVersion: string, + /** * Creates a new {@linkcode Logger} instance for the given * [named log endpoint](https://developer.fastly.com/learning/integrations/logging).