From 1a2b66f0a9b18cfdd918d6f06e1ac19cd7207d7f Mon Sep 17 00:00:00 2001 From: Clifford Tawiah Date: Sat, 5 Oct 2024 13:47:11 -0400 Subject: [PATCH 1/7] Upgraded edgekv bundled files to 0.6.3 --- .../sdk/akamai-edgekv/src/edgekv/edgekv.js | 759 ++++++++---------- 1 file changed, 350 insertions(+), 409 deletions(-) diff --git a/packages/sdk/akamai-edgekv/src/edgekv/edgekv.js b/packages/sdk/akamai-edgekv/src/edgekv/edgekv.js index 5ed3c8e741..245788278b 100644 --- a/packages/sdk/akamai-edgekv/src/edgekv/edgekv.js +++ b/packages/sdk/akamai-edgekv/src/edgekv/edgekv.js @@ -1,6 +1,6 @@ /* (c) Copyright 2020 Akamai Technologies, Inc. Licensed under Apache 2 license. -Version: 0.6.0 +Version: 0.6.3 Purpose: Provide a helper class to simplify the interaction with EdgeKV in an EdgeWorker. Repo: https://github.com/akamai/edgeworkers-examples/tree/master/edgekv/lib */ @@ -12,433 +12,374 @@ import { httpRequest } from 'http-request'; * You must include edgekv_tokens.js in your bundle for this class to function properly. * edgekv_tokens.js must include all namespaces you are going to use in the bundle. */ -import { edgekv_access_tokens } from 'edgekv_tokens.js'; +import { edgekv_access_tokens } from './edgekv_tokens.js'; export class EdgeKV { - #namespace; - #group; - #edgekv_uri; - #token_override; - #num_retries_on_timeout; - #sandbox_id; - #sandbox_fallback; + #namespace; + #group; + #edgekv_uri; + #token_override; + #num_retries_on_timeout; + #sandbox_id; + #sandbox_fallback; - /** - * Constructor to allow setting default namespace and group - * These defaults can be overriden when making individual GET, PUT, and DELETE operations - * @param {string} [$0.namepsace="default"] the default namespace to use for all GET, PUT, and DELETE operations - * Namespace must be 32 characters or less, consisting of A-Z a-z 0-9 _ or - - * @param {string} [$0.group="default"] the default group to use for all GET, PUT, and DELETE operations - * Group must be 128 characters or less, consisting of A-Z a-z 0-9 _ or - - * @param {number} [$0.num_retries_on_timeout=0] the number of times to retry a GET requests when the sub request times out - * @param {object} [$0.ew_request=null] passes the request object from the EdgeWorkers event handler to enable access to EdgeKV data in sandbox environments - * @param {boolean} [$0.sandbox_fallback=false] whether to fallback to retrieving staging data if the sandbox data does not exist, instead of returning null or the specified default value - */ - constructor(namespace = 'default', group = 'default') { - if (typeof namespace === 'object') { - this.#namespace = namespace.namespace || 'default'; - this.#group = namespace.group || 'default'; - this.#edgekv_uri = namespace.edgekv_uri || 'https://edgekv.akamai-edge-svcs.net'; - this.#token_override = namespace.token_override || null; - this.#num_retries_on_timeout = namespace.num_retries_on_timeout || 0; - this.#sandbox_id = namespace.ew_request ? namespace.ew_request.sandboxId || null : null; - this.#sandbox_fallback = namespace.sandbox_fallback || false; - } else { - this.#namespace = namespace; - this.#group = group; - this.#edgekv_uri = 'https://edgekv.akamai-edge-svcs.net'; - this.#token_override = null; - this.#num_retries_on_timeout = 0; - this.#sandbox_id = null; - this.#sandbox_fallback = false; - } - } + /** + * Constructor to allow setting default namespace and group + * These defaults can be overridden when making individual GET, PUT, and DELETE operations + * + * @typedef {Object} Opts + * @property {string} [namespace="default"] the default namespace to use for all GET, PUT, and DELETE operations + * Namespace must be 32 characters or fewer, consisting of A-Z a-z 0-9 _ or - + * @property {string} [group="default"] the default group to use for all GET, PUT, and DELETE operations + * Group must be 128 characters or fewer, consisting of A-Z a-z 0-9 _ or - + * @property {number} [num_retries_on_timeout=0] the number of times to retry a GET requests when the sub request times out + * @property {object} [ew_request=null] passes the request object from the EdgeWorkers event handler to enable access to EdgeKV data in sandbox environments + * @property {boolean} [sandbox_fallback=false] whether to fall back to retrieving staging data if the sandbox data does not exist, instead of returning null or the specified default value + * + * @param {Opts|string} [namespace="default"] the default namespace to use for all GET, PUT, and DELETE operations + * @param {string} [group="default"] the default group to use for all GET, PUT, and DELETE operations + * Group must be 128 characters or fewer, consisting of A-Z a-z 0-9 _ or - + */ + constructor(namespace = "default", group = "default") { + if (typeof namespace === "object") { + this.#namespace = namespace.namespace || "default"; + this.#group = namespace.group || "default"; + this.#edgekv_uri = namespace.edgekv_uri || "https://edgekv.akamai-edge-svcs.net"; + this.#token_override = namespace.token_override || null; + this.#num_retries_on_timeout = namespace.num_retries_on_timeout || 0; + this.#sandbox_id = (namespace.ew_request ? (namespace.ew_request.sandboxId || null) : null); + this.#sandbox_fallback = namespace.sandbox_fallback || false; + } else { + this.#namespace = namespace; + this.#group = group; + this.#edgekv_uri = "https://edgekv.akamai-edge-svcs.net"; + this.#token_override = null; + this.#num_retries_on_timeout = 0; + this.#sandbox_id = null; + this.#sandbox_fallback = false; + } + } - throwError(failed_reason, status, body) { - throw { - failed: failed_reason, - status: status, - body: body, - toString: function () { - return JSON.stringify(this); - }, - }; - } + /** + * if EdgeKV operation was not successful, an object describing the non-200 response + * @typedef {Object} EdgeKVError + * @property {string} failed - Failure reason. + * @property {number} status - HTTP status code. + * @property {*} body - Response body. + */ - async requestHandlerTemplate( - http_request, - handler_200, - handler_large_200, - error_text, - default_value, - num_retries_on_timeout - ) { - try { - let response = await http_request(); - switch (response.status) { - case 200: - // need to handle content length > 128000 bytes differently in EdgeWorkers - let contentLength = response.getHeader('Content-Length'); - if (!contentLength || contentLength.length == 0 || contentLength[0] >= 128000) { - return handler_large_200(response); - } else { - return handler_200(response); - } - case 404: - return default_value; - default: - let content = ''; - try { - content = await response.text(); - content = JSON.parse(content); - } catch (error) {} - throw { status: response.status, body: content }; // to be caught in surrounding catch block - } - } catch (error) { - if ( - num_retries_on_timeout > 0 && - /^.*subrequest to URL.*timed out.*$/.test(error.toString()) - ) { - return this.requestHandlerTemplate( - http_request, - handler_200, - handler_large_200, - error_text, - default_value, - num_retries_on_timeout - 1 - ); - } - if (error.hasOwnProperty('status')) { - this.throwError(error_text + ' FAILED', error.status, error.body); - } - this.throwError(error_text + ' FAILED', 0, error.toString()); - } - } + throwError(failed_reason, status, body) { + throw { + failed: failed_reason, + status: status, + body: body, + toString: function () { return JSON.stringify(this); } + }; + } - validate({ namespace = null, group = null, item = null }) { - if (!namespace || !/^[A-Za-z0-9_-]{1,32}$/.test(namespace)) { - throw 'Namespace is not valid. Must be 32 characters or less, consisting of A-Z a-z 0-9 _ or -'; - } - if (!group || !/^[A-Za-z0-9_-]{1,128}$/.test(group)) { - throw 'Group is not valid. Must be 128 characters or less, consisting of A-Z a-z 0-9 _ or -'; - } - if (!item || !/^[A-Za-z0-9_-]{1,512}$/.test(item)) { - throw 'Item is not valid. Must be 512 characters or less, consisting of A-Z a-z 0-9 _ or -'; - } - } + async requestHandlerTemplate(http_request, handler_200, handler_large_200, error_text, default_value, num_retries_on_timeout) { + try { + let response = await http_request(); + switch (response.status) { + case 200: + // need to handle content length > 128000 bytes differently in EdgeWorkers + let contentLength = response.getHeader('Content-Length'); + if (!contentLength || contentLength.length === 0 || contentLength[0] >= 128000) { + return handler_large_200(response); + } else { + return handler_200(response); + } + case 404: + return default_value; + default: + let content = ""; + try { + content = await response.text(); + content = JSON.parse(content); + } catch (error) { } + throw { status: response.status, body: content }; // to be caught in surrounding catch block + } + } catch (error) { + if (num_retries_on_timeout > 0 && /^.*subrequest to URL.*timed out.*$/.test(error.toString())) { + return this.requestHandlerTemplate(http_request, handler_200, handler_large_200, error_text, default_value, num_retries_on_timeout - 1); + } + if (error.hasOwnProperty('status')) { + this.throwError(error_text + " FAILED", error.status, error.body); + } + this.throwError(error_text + " FAILED", 0, error.toString()); + } + } - getNamespaceToken(namespace) { - if (this.#token_override) { - return this.#token_override; - } - let name = 'namespace-' + namespace; - if (!(name in edgekv_access_tokens)) { - throw ( - "MISSING ACCESS TOKEN. No EdgeKV Access Token defined for namespace '" + namespace + "'." - ); - } - return edgekv_access_tokens[name]['value']; - } + validate({ namespace = null, group = null, item = null }) { + if (!namespace || !/^[A-Za-z0-9_-]{1,32}$/.test(namespace)) { + throw "Namespace is not valid. Must be 32 characters or less, consisting of A-Z a-z 0-9 _ or -"; + } + if (!group || !/^[A-Za-z0-9_-]{1,128}$/.test(group)) { + throw "Group is not valid. Must be 128 characters or less, consisting of A-Z a-z 0-9 _ or -"; + } + if (!item || !/^[A-Za-z0-9_-]{1,512}$/.test(item)) { + throw "Item is not valid. Must be 512 characters or less, consisting of A-Z a-z 0-9 _ or -"; + } + } - addTimeout(options, timeout) { - if ( - timeout && - (typeof timeout !== 'number' || !isFinite(timeout) || timeout <= 0 || timeout > 1000) - ) { - throw 'Timeout is not valid. Must be a number greater than 0 and less than 1000.'; - } - if (timeout) { - options.timeout = timeout; - } - return options; - } + getNamespaceTokenHeader(namespace) { + if (this.#token_override) { + return this.#token_override; + } + let name = "namespace-" + namespace; + if (!(name in edgekv_access_tokens)) { + throw "MISSING ACCESS TOKEN. No EdgeKV Access Token defined for namespace '" + namespace + "'."; + } + if ("value" in edgekv_access_tokens[name]) { + return { 'X-Akamai-EdgeDB-Auth': [edgekv_access_tokens[name]["value"]]}; + } else if ("reference" in edgekv_access_tokens[name]) { + return { 'X-Akamai-EdgeDB-Auth-Ref': [edgekv_access_tokens[name]["reference"]]}; + } else { + throw "MISSING ACCESS TOKEN. No EdgeKV Access Token value or reference defined for namespace '" + namespace + "'."; + } + } - addSandboxId(uri) { - if (this.#sandbox_id) { - uri = uri + '?sandboxId=' + this.#sandbox_id; - if (this.#sandbox_fallback) { - uri = uri + '&sandboxFallback=true'; - } - } - return uri; - } + addTimeout(options, timeout) { + if (timeout && (typeof timeout !== 'number' || !isFinite(timeout) || timeout <= 0 || timeout > 4000)) { + throw "Timeout is not valid. Must be a number greater than 0 and less than 4000."; + } + if (timeout) { + options.timeout = timeout; + } + return options; + } - async streamText(response_body) { - let result = ''; - await response_body.pipeThrough(new TextDecoderStream()).pipeTo( - new WritableStream({ - write(chunk) { - result += chunk; - }, - }), - { preventAbort: true } - ); - return result; - } + addSandboxId(uri) { + if (this.#sandbox_id) { + uri = uri + "?sandboxId=" + this.#sandbox_id; + if (this.#sandbox_fallback) { + uri = uri + "&sandboxFallback=true"; + } + } + return uri; + } - async streamJson(response_body) { - return JSON.parse(await this.streamText(response_body)); - } + async streamText(response_body) { + let result = ""; + await response_body + .pipeThrough(new TextDecoderStream()) + .pipeTo(new WritableStream({ + write(chunk) { + result += chunk; + } + }), { preventAbort: true }); + return result; + } - putRequest({ - namespace = this.#namespace, - group = this.#group, - item, - value, - timeout = null, - } = {}) { - this.validate({ namespace: namespace, group: group, item: item }); - let uri = - this.#edgekv_uri + '/api/v1/namespaces/' + namespace + '/groups/' + group + '/items/' + item; - return httpRequest( - this.addSandboxId(uri), - this.addTimeout( - { - method: 'PUT', - body: typeof value === 'object' ? JSON.stringify(value) : value, - headers: { 'X-Akamai-EdgeDB-Auth': [this.getNamespaceToken(namespace)] }, - }, - timeout - ) - ); - } + async streamJson(response_body) { + return JSON.parse(await this.streamText(response_body)); + } - /** - * async PUT text into an item in the EdgeKV. - * @param {string} [$0.namepsace=this.#namespace] specify a namespace other than the default - * @param {string} [$0.group=this.#group] specify a group other than the default - * @param {string} $0.item item key to put into the EdgeKV - * @param {string} $0.value text value to put into the EdgeKV - * @param {number} [$0.timeout=null] the maximum time, between 1 and 1000 milliseconds, to wait for the response - * @returns {Promise} if the operation was successful, the response from the EdgeKV - * @throws {object} if the operation was not successful, - * an object describing the non-200 response from the EdgeKV: {failed, status, body} - */ - async putText({ - namespace = this.#namespace, - group = this.#group, - item, - value, - timeout = null, - } = {}) { - return this.requestHandlerTemplate( - () => - this.putRequest({ - namespace: namespace, - group: group, - item: item, - value: value, - timeout: timeout, - }), - (response) => response.text(), - (response) => this.streamText(response.body), - 'PUT', - null, - 0 - ); - } + putRequest(args) { + const { namespace = this.#namespace, group = this.#group, item, value, timeout = null } = args || {}; + this.validate({ namespace: namespace, group: group, item: item }); + let uri = this.#edgekv_uri + "/api/v1/namespaces/" + namespace + "/groups/" + group + "/items/" + item; + return httpRequest(this.addSandboxId(uri), this.addTimeout({ + method: "PUT", + body: typeof value === "object" ? JSON.stringify(value) : value, + headers: { ...this.getNamespaceTokenHeader(namespace) } + }, timeout)); + } - /** - * PUT text into an item in the EdgeKV while only waiting for the request to send and not for the response. - * @param {string} [$0.namepsace=this.#namespace] specify a namespace other than the default - * @param {string} [$0.group=this.#group] specify a group other than the default - * @param {string} $0.item item key to put into the EdgeKV - * @param {string} $0.value text value to put into the EdgeKV - * @throws {object} if the operation was not successful at sending the request, - * an object describing the error: {failed, status, body} - */ - putTextNoWait({ namespace = this.#namespace, group = this.#group, item, value } = {}) { - try { - this.putRequest({ namespace: namespace, group: group, item: item, value: value }); - } catch (error) { - this.throwError('PUT FAILED', 0, error.toString()); - } - } + /** + * async PUT text into an item in the EdgeKV. + * @param {Object} args + * @param {string} [args.namespace=this.#namespace] specify a namespace other than the default + * @param {string} [args.group=this.#group] specify a group other than the default + * @param {string} args.item item key to put into the EdgeKV + * @param {string} args.value text value to put into the EdgeKV + * @param {number?} [args.timeout=null] the maximum time, between 1 and 4000 milliseconds, to wait for the response + * @returns {Promise} if the operation was successful, the response from the EdgeKV + * @throws {EdgeKVError} if the operation was not successful, + * an object describing the non-200 response from the EdgeKV: {failed, status, body} + */ + async putText(args) { + const { namespace = this.#namespace, group = this.#group, item, value, timeout = null } = args || {}; + return this.requestHandlerTemplate( + () => this.putRequest({ namespace: namespace, group: group, item: item, value: value, timeout: timeout }), + (response) => response.text(), + (response) => this.streamText(response.body), + "PUT", + null, + 0 + ); + } - /** - * async PUT json into an item in the EdgeKV. - * @param {string} [$0.namepsace=this.#namespace] specify a namespace other than the default - * @param {string} [$0.group=this.#group] specify a group other than the default - * @param {string} $0.item item key to put into the EdgeKV - * @param {object} $0.value json value to put into the EdgeKV - * @param {number} [$0.timeout=null] the maximum time, between 1 and 1000 milliseconds, to wait for the response - * @returns {Promise} if the operation was successful, the response from the EdgeKV - * @throws {object} if the operation was not successful, - * an object describing the non-200 response from the EdgeKV: {failed, status, body} - */ - async putJson({ - namespace = this.#namespace, - group = this.#group, - item, - value, - timeout = null, - } = {}) { - return this.requestHandlerTemplate( - () => - this.putRequest({ - namespace: namespace, - group: group, - item: item, - value: JSON.stringify(value), - timeout: timeout, - }), - (response) => response.text(), - (response) => this.streamText(response.body), - 'PUT', - null, - 0 - ); - } + /** + * PUT text into an item in the EdgeKV while only waiting for the request to send and not for the response. + * @param {Object} args + * @param {string} [args.namespace=this.#namespace] specify a namespace other than the default + * @param {string} [args.group=this.#group] specify a group other than the default + * @param {string} args.item item key to put into the EdgeKV + * @param {string} args.value text value to put into the EdgeKV + * @throws {EdgeKVError} if the operation was not successful at sending the request, + * an object describing the error: {failed, status, body} + */ + putTextNoWait(args) { + const { namespace = this.#namespace, group = this.#group, item, value } = args || {}; + try { + this.putRequest({ namespace: namespace, group: group, item: item, value: value }); + } catch (error) { + this.throwError("PUT FAILED", 0, error.toString()); + } + } - /** - * PUT json into an item in the EdgeKV while only waiting for the request to send and not for the response. - * @param {string} [$0.namepsace=this.#namespace] specify a namespace other than the default - * @param {string} [$0.group=this.#group] specify a group other than the default - * @param {string} $0.item item key to put into the EdgeKV - * @param {object} $0.value json value to put into the EdgeKV - * @throws {object} if the operation was not successful at sending the request, - * an object describing the error: {failed, status, body} - */ - putJsonNoWait({ namespace = this.#namespace, group = this.#group, item, value } = {}) { - try { - this.putRequest({ - namespace: namespace, - group: group, - item: item, - value: JSON.stringify(value), - }); - } catch (error) { - this.throwError('PUT FAILED', 0, error.toString()); - } - } + /** + * async PUT json into an item in the EdgeKV. + * @param {Object} args + * @param {string} [args.namespace=this.#namespace] specify a namespace other than the default + * @param {string} [args.group=this.#group] specify a group other than the default + * @param {string} args.item item key to put into the EdgeKV + * @param {Object} args.value json value to put into the EdgeKV + * @param {number?} [args.timeout=null] the maximum time, between 1 and 4000 milliseconds, to wait for the response + * @returns {Promise} if the operation was successful, the response from the EdgeKV + * @throws {EdgeKVError} if the operation was not successful, + * an object describing the non-200 response from the EdgeKV: {failed, status, body} + */ + async putJson(args) { + const { namespace = this.#namespace, group = this.#group, item, value, timeout = null } = args || {}; + return this.requestHandlerTemplate( + () => this.putRequest({ namespace: namespace, group: group, item: item, value: JSON.stringify(value), timeout: timeout }), + (response) => response.text(), + (response) => this.streamText(response.body), + "PUT", + null, + 0 + ); + } - getRequest({ namespace = this.#namespace, group = this.#group, item, timeout = null } = {}) { - this.validate({ namespace: namespace, group: group, item: item }); - let uri = - this.#edgekv_uri + '/api/v1/namespaces/' + namespace + '/groups/' + group + '/items/' + item; - return httpRequest( - this.addSandboxId(uri), - this.addTimeout( - { - method: 'GET', - headers: { 'X-Akamai-EdgeDB-Auth': [this.getNamespaceToken(namespace)] }, - }, - timeout - ) - ); - } + /** + * PUT json into an item in the EdgeKV while only waiting for the request to send and not for the response. + * @param {Object} args + * @param {string} [args.namespace=this.#namespace] specify a namespace other than the default + * @param {string} [args.group=this.#group] specify a group other than the default + * @param {string} args.item item key to put into the EdgeKV + * @param {object} args.value json value to put into the EdgeKV + * @throws {EdgeKVError} if the operation was not successful at sending the request, + * an object describing the error: {failed, status, body} + */ + putJsonNoWait(args) { + const { namespace = this.#namespace, group = this.#group, item, value } = args || {}; + try { + this.putRequest({ namespace: namespace, group: group, item: item, value: JSON.stringify(value) }); + } catch (error) { + this.throwError("PUT FAILED", 0, error.toString()); + } + } - /** - * async GET text from an item in the EdgeKV. - * @param {string} [$0.namepsace=this.#namespace] specify a namespace other than the default - * @param {string} [$0.group=this.#group] specify a group other than the default - * @param {string} $0.item item key to get from the EdgeKV - * @param {string} [$0.default_value=null] the default value to return if a 404 response is returned from EdgeKV - * @param {number} [$0.timeout=null] the maximum time, between 1 and 1000 milliseconds, to wait for the response - * @param {number} [$0.num_retries_on_timeout=null] the number of times to retry a requests when the sub request times out - * @returns {Promise} if the operation was successful, the text response from the EdgeKV or the default_value on 404 - * @throws {object} if the operation was not successful, - * an object describing the non-200 and non-404 response from the EdgeKV: {failed, status, body} - */ - async getText({ - namespace = this.#namespace, - group = this.#group, - item, - default_value = null, - timeout = null, - num_retries_on_timeout = null, - } = {}) { - return this.requestHandlerTemplate( - () => this.getRequest({ namespace: namespace, group: group, item: item, timeout: timeout }), - (response) => response.text(), - (response) => this.streamText(response.body), - 'GET TEXT', - default_value, - num_retries_on_timeout ?? this.#num_retries_on_timeout - ); - } + getRequest(args) { + const { namespace = this.#namespace, group = this.#group, item, timeout = null } = args || {}; + this.validate({ namespace: namespace, group: group, item: item }); + let uri = this.#edgekv_uri + "/api/v1/namespaces/" + namespace + "/groups/" + group + "/items/" + item; + return httpRequest(this.addSandboxId(uri), this.addTimeout({ + method: "GET", + headers: { ...this.getNamespaceTokenHeader(namespace) } + }, timeout)); + } - /** - * async GET json from an item in the EdgeKV. - * @param {string} [$0.namepsace=this.#namespace] specify a namespace other than the default - * @param {string} [$0.group=this.#group] specify a group other than the default - * @param {string} $0.item item key to get from the EdgeKV - * @param {object} [$0.default_value=null] the default value to return if a 404 response is returned from EdgeKV - * @param {number} [$0.timeout=null] the maximum time, between 1 and 1000 milliseconds, to wait for the response - * @param {number} [$0.num_retries_on_timeout=null] the number of times to retry a requests when the sub request times out - * @returns {Promise} if the operation was successful, the json response from the EdgeKV or the default_value on 404 - * @throws {object} if the operation was not successful, - * an object describing the non-200 and non-404 response from the EdgeKV: {failed, status, body} - */ - async getJson({ - namespace = this.#namespace, - group = this.#group, - item, - default_value = null, - timeout = null, - num_retries_on_timeout = null, - } = {}) { - return this.requestHandlerTemplate( - () => this.getRequest({ namespace: namespace, group: group, item: item, timeout: timeout }), - (response) => response.json(), - (response) => this.streamJson(response.body), - 'GET JSON', - default_value, - num_retries_on_timeout ?? this.#num_retries_on_timeout - ); - } + /** + * async GET text from an item in the EdgeKV. + * @param {Object} args + * @param {string} [args.namespace=this.#namespace] specify a namespace other than the default + * @param {string} [args.group=this.#group] specify a group other than the default + * @param {string} args.item item key to get from the EdgeKV + * @param {string?} [args.default_value=null] the default value to return if a 404 response is returned from EdgeKV + * @param {number?} [args.timeout=null] the maximum time, between 1 and 4000 milliseconds, to wait for the response + * @param {number?} [args.num_retries_on_timeout=null] the number of times to retry a requests when the sub request times out + * @returns {Promise} if the operation was successful, the text response from the EdgeKV or the default_value on 404 + * @throws {EdgeKVError} if the operation was not successful, + * an object describing the non-200 and non-404 response from the EdgeKV: {failed, status, body} + */ + async getText(args) { + const { namespace = this.#namespace, group = this.#group, item, default_value = null, timeout = null, num_retries_on_timeout = null } = args || {}; + return this.requestHandlerTemplate( + () => this.getRequest({ namespace: namespace, group: group, item: item, timeout: timeout }), + (response) => response.text(), + (response) => this.streamText(response.body), + "GET TEXT", + default_value, + num_retries_on_timeout ?? this.#num_retries_on_timeout + ); + } - deleteRequest({ namespace = this.#namespace, group = this.#group, item, timeout = null } = {}) { - this.validate({ namespace: namespace, group: group, item: item }); - let uri = - this.#edgekv_uri + '/api/v1/namespaces/' + namespace + '/groups/' + group + '/items/' + item; - return httpRequest( - this.addSandboxId(uri), - this.addTimeout( - { - method: 'DELETE', - headers: { 'X-Akamai-EdgeDB-Auth': [this.getNamespaceToken(namespace)] }, - }, - timeout - ) - ); - } + /** + * async GET json from an item in the EdgeKV. + * @param {Object} args + * @param {string} [args.namespace=this.#namespace] specify a namespace other than the default + * @param {string} [args.group=this.#group] specify a group other than the default + * @param {string} args.item item key to get from the EdgeKV + * @param {Object} [args.default_value=null] the default value to return if a 404 response is returned from EdgeKV + * @param {number?} [args.timeout=null] the maximum time, between 1 and 4000 milliseconds, to wait for the response + * @param {number?} [args.num_retries_on_timeout=null] the number of times to retry a requests when the sub request times out + * @returns {Promise} if the operation was successful, the json response from the EdgeKV or the default_value on 404 + * @throws {EdgeKVError} if the operation was not successful, + * an object describing the non-200 and non-404 response from the EdgeKV: {failed, status, body} + */ + async getJson(args) { + const { namespace = this.#namespace, group = this.#group, item, default_value = null, timeout = null, num_retries_on_timeout = null } = args || {}; + return this.requestHandlerTemplate( + () => this.getRequest({ namespace: namespace, group: group, item: item, timeout: timeout }), + (response) => response.json(), + (response) => this.streamJson(response.body), + "GET JSON", + default_value, + num_retries_on_timeout ?? this.#num_retries_on_timeout + ); + } - /** - * async DELETE an item in the EdgeKV. - * @param {string} [$0.namepsace=this.#namespace] specify a namespace other than the default - * @param {string} [$0.group=this.#group] specify a group other than the default - * @param {string} $0.item item key to delete from the EdgeKV - * @param {number} [$0.timeout=null] the maximum time, between 1 and 1000 milliseconds, to wait for the response - * @returns {Promise} if the operation was successful, the text response from the EdgeKV - * @throws {object} if the operation was not successful, - * an object describing the non-200 response from the EdgeKV: {failed, status, body} - */ - async delete({ namespace = this.#namespace, group = this.#group, item, timeout = null } = {}) { - return this.requestHandlerTemplate( - () => - this.deleteRequest({ namespace: namespace, group: group, item: item, timeout: timeout }), - (response) => response.text(), - (response) => this.streamText(response.body), - 'DELETE', - null, - 0 - ); - } + deleteRequest(args) { + const { namespace = this.#namespace, group = this.#group, item, timeout = null } = args || {}; + this.validate({ namespace: namespace, group: group, item: item }); + let uri = this.#edgekv_uri + "/api/v1/namespaces/" + namespace + "/groups/" + group + "/items/" + item; + return httpRequest(this.addSandboxId(uri), this.addTimeout({ + method: "DELETE", + headers: { ...this.getNamespaceTokenHeader(namespace) } + }, timeout)); + } - /** - * DELETE an item in the EdgeKV while only waiting for the request to send and not for the response. - * @param {string} [$0.namepsace=this.#namespace] specify a namespace other than the default - * @param {string} [$0.group=this.#group] specify a group other than the default - * @param {string} $0.item item key to delete from the EdgeKV - * @throws {object} if the operation was not successful at sending the request, - * an object describing the error: {failed, status, body} - */ - deleteNoWait({ namespace = this.#namespace, group = this.#group, item } = {}) { - try { - this.delete({ namespace: namespace, group: group, item: item }); - } catch (error) { - this.throwError('DELETE FAILED', 0, error.toString()); - } - } -} + /** + * async DELETE an item in the EdgeKV. + * @param {Object} args + * @param {string} [args.namespace=this.#namespace] specify a namespace other than the default + * @param {string} [args.group=this.#group] specify a group other than the default + * @param {string} args.item item key to delete from the EdgeKV + * @param {number?} [args.timeout=null] the maximum time, between 1 and 4000 milliseconds, to wait for the response + * @returns {Promise} if the operation was successful, the text response from the EdgeKV + * @throws {EdgeKVError} if the operation was not successful, + * an object describing the non-200 response from the EdgeKV: {failed, status, body} + */ + async delete(args) { + const { namespace = this.#namespace, group = this.#group, item, timeout = null } = args || {}; + return this.requestHandlerTemplate( + () => this.deleteRequest({ namespace: namespace, group: group, item: item, timeout: timeout }), + (response) => response.text(), + (response) => this.streamText(response.body), + "DELETE", + null, + 0 + ); + } + + /** + * DELETE an item in the EdgeKV while only waiting for the request to send and not for the response. + * @param {Object} args + * @param {string} [args.namespace=this.#namespace] specify a namespace other than the default + * @param {string} [args.group=this.#group] specify a group other than the default + * @param {string} args.item item key to delete from the EdgeKV + * @throws {EdgeKVError} if the operation was not successful at sending the request, + * an object describing the error: {failed, status, body} + */ + deleteNoWait(args) { + const { namespace = this.#namespace, group = this.#group, item } = args || {}; + try { + this.delete({ namespace: namespace, group: group, item: item }); + } catch (error) { + this.throwError("DELETE FAILED", 0, error.toString()); + } + } +} \ No newline at end of file From 18bb6f53abc866dab139a2510023e72b64d807ee Mon Sep 17 00:00:00 2001 From: Clifford Tawiah Date: Sat, 5 Oct 2024 13:51:39 -0400 Subject: [PATCH 2/7] fixed edgekv import --- packages/sdk/akamai-edgekv/src/edgekv/edgekv.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/akamai-edgekv/src/edgekv/edgekv.js b/packages/sdk/akamai-edgekv/src/edgekv/edgekv.js index 245788278b..722d22325c 100644 --- a/packages/sdk/akamai-edgekv/src/edgekv/edgekv.js +++ b/packages/sdk/akamai-edgekv/src/edgekv/edgekv.js @@ -12,7 +12,7 @@ import { httpRequest } from 'http-request'; * You must include edgekv_tokens.js in your bundle for this class to function properly. * edgekv_tokens.js must include all namespaces you are going to use in the bundle. */ -import { edgekv_access_tokens } from './edgekv_tokens.js'; +import { edgekv_access_tokens } from 'edgekv_tokens.js'; export class EdgeKV { #namespace; From 6f95c1f02b5ec75c09147ecaca003ac0cc46d30d Mon Sep 17 00:00:00 2001 From: Clifford Tawiah Date: Tue, 15 Oct 2024 11:10:51 -0400 Subject: [PATCH 3/7] fix: added logging when retrieving values from with edgkv lib fails --- packages/sdk/akamai-edgekv/src/edgekv/edgeKVProvider.ts | 9 ++++++--- packages/sdk/akamai-edgekv/src/index.ts | 8 ++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/sdk/akamai-edgekv/src/edgekv/edgeKVProvider.ts b/packages/sdk/akamai-edgekv/src/edgekv/edgeKVProvider.ts index 97c8806f01..b85655307a 100644 --- a/packages/sdk/akamai-edgekv/src/edgekv/edgeKVProvider.ts +++ b/packages/sdk/akamai-edgekv/src/edgekv/edgeKVProvider.ts @@ -1,24 +1,27 @@ -import { EdgeProvider } from '@launchdarkly/akamai-edgeworker-sdk-common'; +import { BasicLogger, EdgeProvider } from '@launchdarkly/js-server-sdk-common'; import { EdgeKV } from './edgekv'; type EdgeKVProviderParams = { namespace: string; group: string; + logger: BasicLogger; }; export default class EdgeKVProvider implements EdgeProvider { private edgeKv: EdgeKV; + private logger: BasicLogger; - constructor({ namespace, group }: EdgeKVProviderParams) { + constructor({ namespace, group, logger }: EdgeKVProviderParams) { this.edgeKv = new EdgeKV({ namespace, group } as any); + this.logger = logger; } async get(rootKey: string): Promise { try { return await this.edgeKv.getText({ item: rootKey } as any); } catch (e) { - /* empty */ + this.logger?.error(`Error getting value from EdgeKV: ${e}`); } return undefined; } diff --git a/packages/sdk/akamai-edgekv/src/index.ts b/packages/sdk/akamai-edgekv/src/index.ts index d5780ba399..3cb01199c4 100644 --- a/packages/sdk/akamai-edgekv/src/index.ts +++ b/packages/sdk/akamai-edgekv/src/index.ts @@ -11,6 +11,7 @@ import { init as initEdge, LDClient, LDOptions } from '@launchdarkly/akamai-edgeworker-sdk-common'; import EdgeKVProvider from './edgekv/edgeKVProvider'; +import { BasicLogger } from '@launchdarkly/js-server-sdk-common'; export * from '@launchdarkly/akamai-edgeworker-sdk-common'; @@ -32,11 +33,14 @@ export const init = ({ options = {}, sdkKey, }: AkamaiLDClientParams): LDClient => { - const edgekvProvider = new EdgeKVProvider({ namespace, group }); + const logger = options.logger ?? BasicLogger.get(); + + const edgekvProvider = new EdgeKVProvider({ namespace, group, logger }); + return initEdge({ sdkKey, - options, + options: { ...options, logger }, featureStoreProvider: edgekvProvider, platformName: 'Akamai EdgeWorker', sdkName: '@launchdarkly/akamai-server-edgekv-sdk', From 0c21ee2d024c853003c3d5311fe722b8a2db3689 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Tue, 15 Oct 2024 08:30:56 -0700 Subject: [PATCH 4/7] Set beta version --- packages/sdk/akamai-edgekv/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/akamai-edgekv/package.json b/packages/sdk/akamai-edgekv/package.json index 0ab4f6b50b..a89fae56ce 100644 --- a/packages/sdk/akamai-edgekv/package.json +++ b/packages/sdk/akamai-edgekv/package.json @@ -1,6 +1,6 @@ { "name": "@launchdarkly/akamai-server-edgekv-sdk", - "version": "1.1.16", + "version": "1.1.17-beta.1", "description": "Akamai LaunchDarkly EdgeWorker SDK for EdgeKV feature store", "homepage": "https://github.com/launchdarkly/js-core/tree/main/packages/sdk/akamai-edgekv", "repository": { From 4bfea395f07e1f92fb486ebc368c00b7015cd921 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Tue, 15 Oct 2024 08:34:40 -0700 Subject: [PATCH 5/7] Lint --- packages/sdk/akamai-edgekv/src/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/sdk/akamai-edgekv/src/index.ts b/packages/sdk/akamai-edgekv/src/index.ts index 3cb01199c4..acdd85b58e 100644 --- a/packages/sdk/akamai-edgekv/src/index.ts +++ b/packages/sdk/akamai-edgekv/src/index.ts @@ -9,9 +9,9 @@ * @packageDocumentation */ import { init as initEdge, LDClient, LDOptions } from '@launchdarkly/akamai-edgeworker-sdk-common'; +import { BasicLogger } from '@launchdarkly/js-server-sdk-common'; import EdgeKVProvider from './edgekv/edgeKVProvider'; -import { BasicLogger } from '@launchdarkly/js-server-sdk-common'; export * from '@launchdarkly/akamai-edgeworker-sdk-common'; @@ -37,7 +37,6 @@ export const init = ({ const edgekvProvider = new EdgeKVProvider({ namespace, group, logger }); - return initEdge({ sdkKey, options: { ...options, logger }, From 814821ac5550446fe890d62a2c7289f44f820a64 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Tue, 15 Oct 2024 08:41:47 -0700 Subject: [PATCH 6/7] Fix build issues. --- .../__tests__/edgekv/edgeKVProvider.test.ts | 13 +++++++++++-- .../sdk/akamai-edgekv/src/edgekv/edgeKVProvider.ts | 7 ++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/sdk/akamai-edgekv/__tests__/edgekv/edgeKVProvider.test.ts b/packages/sdk/akamai-edgekv/__tests__/edgekv/edgeKVProvider.test.ts index b97366dd27..a785aea8c9 100644 --- a/packages/sdk/akamai-edgekv/__tests__/edgekv/edgeKVProvider.test.ts +++ b/packages/sdk/akamai-edgekv/__tests__/edgekv/edgeKVProvider.test.ts @@ -1,3 +1,5 @@ +import { LDLogger } from '@launchdarkly/js-server-sdk-common'; + import { EdgeKV } from '../../src/edgekv/edgekv'; import EdgeKVProvider from '../../src/edgekv/edgeKVProvider'; @@ -11,14 +13,21 @@ const NAMESPACE = 'namespace'; const GROUP = 'group'; describe('EdgeKVProvider', () => { + let logger: LDLogger; beforeEach(() => { mockEdgeKV.mockImplementation(() => ({ getText: jest.fn().mockResolvedValue('some-text'), })); + logger = { + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }; }); it('get string from edgeKV', async () => { - const edgeKVProvider = new EdgeKVProvider({ namespace: NAMESPACE, group: GROUP }); + const edgeKVProvider = new EdgeKVProvider({ namespace: NAMESPACE, group: GROUP, logger }); expect(await edgeKVProvider.get('rootKey')).toEqual('some-text'); }); @@ -28,7 +37,7 @@ describe('EdgeKVProvider', () => { getText: jest.fn().mockRejectedValueOnce(expectedError), })); - const edgeKVProvider = new EdgeKVProvider({ namespace: NAMESPACE, group: GROUP }); + const edgeKVProvider = new EdgeKVProvider({ namespace: NAMESPACE, group: GROUP, logger }); const result = await edgeKVProvider.get('rootKey'); expect(result).toBe(undefined); }); diff --git a/packages/sdk/akamai-edgekv/src/edgekv/edgeKVProvider.ts b/packages/sdk/akamai-edgekv/src/edgekv/edgeKVProvider.ts index b85655307a..19a4d10690 100644 --- a/packages/sdk/akamai-edgekv/src/edgekv/edgeKVProvider.ts +++ b/packages/sdk/akamai-edgekv/src/edgekv/edgeKVProvider.ts @@ -1,16 +1,17 @@ -import { BasicLogger, EdgeProvider } from '@launchdarkly/js-server-sdk-common'; +import { EdgeProvider } from '@launchdarkly/akamai-edgeworker-sdk-common'; +import { BasicLogger, LDLogger } from '@launchdarkly/js-server-sdk-common'; import { EdgeKV } from './edgekv'; type EdgeKVProviderParams = { namespace: string; group: string; - logger: BasicLogger; + logger: LDLogger; }; export default class EdgeKVProvider implements EdgeProvider { private edgeKv: EdgeKV; - private logger: BasicLogger; + private logger: LDLogger; constructor({ namespace, group, logger }: EdgeKVProviderParams) { this.edgeKv = new EdgeKV({ namespace, group } as any); From 9956f6d98796ea1ed8af4f3dc354d140a87ddc47 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Tue, 15 Oct 2024 08:44:23 -0700 Subject: [PATCH 7/7] More lint --- packages/sdk/akamai-edgekv/src/edgekv/edgeKVProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/akamai-edgekv/src/edgekv/edgeKVProvider.ts b/packages/sdk/akamai-edgekv/src/edgekv/edgeKVProvider.ts index 19a4d10690..f82ef0bb03 100644 --- a/packages/sdk/akamai-edgekv/src/edgekv/edgeKVProvider.ts +++ b/packages/sdk/akamai-edgekv/src/edgekv/edgeKVProvider.ts @@ -1,5 +1,5 @@ import { EdgeProvider } from '@launchdarkly/akamai-edgeworker-sdk-common'; -import { BasicLogger, LDLogger } from '@launchdarkly/js-server-sdk-common'; +import { LDLogger } from '@launchdarkly/js-server-sdk-common'; import { EdgeKV } from './edgekv';