diff --git a/.github/workflows/package-unit-tests.yml b/.github/workflows/package-unit-tests.yml index 3b1b3c803b6..9d29f71db42 100644 --- a/.github/workflows/package-unit-tests.yml +++ b/.github/workflows/package-unit-tests.yml @@ -26,7 +26,6 @@ jobs: matrix: workspace: - realm - - '@realm/network-transport' - '@realm/babel-plugin' - '@realm/react' include: diff --git a/.github/workflows/pr-realm-web.yml b/.github/workflows/pr-realm-web.yml index 587027e0dc0..dc5e44b4e82 100644 --- a/.github/workflows/pr-realm-web.yml +++ b/.github/workflows/pr-realm-web.yml @@ -6,7 +6,6 @@ on: # Only run when the PR makes changes to the packages - "packages/realm-web/**" - "packages/realm-web-integration-tests/**" - - "packages/realm-network-transport/**" - "packages/realm-common/**" - "packages/realm-app-importer/**" # Changing types might also affect Realm Web diff --git a/.github/workflows/prepare-package-release.yml b/.github/workflows/prepare-package-release.yml index dd8d531f380..1e1204b644e 100644 --- a/.github/workflows/prepare-package-release.yml +++ b/.github/workflows/prepare-package-release.yml @@ -15,7 +15,6 @@ on: options: - '@realm/babel-plugin' - '@realm/common' - - '@realm/network-transport' - '@realm/react' - '@realm/tools' - '@realm/web' @@ -41,11 +40,6 @@ jobs: "PACKAGE_NAME": "@realm/common", "VERSION_PREFIX": "realm-common-" }, - "@realm/network-transport": { - "PACKAGE_PATH": "packages/realm-network-transport", - "PACKAGE_NAME": "@realm/network-transport", - "VERSION_PREFIX": "realm-network-transport-" - }, "@realm/react": { "PACKAGE_PATH": "packages/realm-react", "PACKAGE_NAME": "@realm/react", diff --git a/.github/workflows/publish-package-release.yml b/.github/workflows/publish-package-release.yml index bf056cceaaf..cef828c5369 100644 --- a/.github/workflows/publish-package-release.yml +++ b/.github/workflows/publish-package-release.yml @@ -20,7 +20,6 @@ on: options: - '@realm/babel-plugin' - '@realm/common' - - '@realm/network-transport' - '@realm/react' - '@realm/tools' - '@realm/web' @@ -50,13 +49,6 @@ jobs: "RELEASE_TITLE": "Realm Common", "SDK_NAME": "Common" }, - "@realm/network-transport": { - "PACKAGE_PATH": "packages/realm-network-transport", - "PACKAGE_NAME": "@realm/network-transport", - "VERSION_PREFIX": "realm-network-transport-", - "RELEASE_TITLE": "Realm Network Transport", - "SDK_NAME": "Network Transport" - }, "@realm/react": { "PACKAGE_PATH": "packages/realm-react", "PACKAGE_NAME": "@realm/react", diff --git a/CHANGELOG.md b/CHANGELOG.md index 0be6c00a935..d165e57bf9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ * None ### Enhancements -* None +* Added an optional `fetch` parameter to the `AppConfiguration`. Use this to specify a custom implementation of the `fetch` function used by the app to perform network requests. ### Fixed * ([#????](https://github.com/realm/realm-js/issues/????), since v?.?.?) diff --git a/integration-tests/environments/electron/package.json b/integration-tests/environments/electron/package.json index eaa982631e1..88ca0f57c95 100644 --- a/integration-tests/environments/electron/package.json +++ b/integration-tests/environments/electron/package.json @@ -27,8 +27,7 @@ "dependencies": [ "../../../packages/realm:bundle", "../../../packages/realm:build:node", - "../../../packages/mocha-reporter:bundle", - "../../../packages/realm-network-transport:bundle" + "../../../packages/mocha-reporter:bundle" ] }, "test:renderer": { @@ -36,8 +35,7 @@ "dependencies": [ "../../../packages/realm:bundle", "../../../packages/realm:build:node", - "../../../packages/mocha-reporter:bundle", - "../../../packages/realm-network-transport:bundle" + "../../../packages/mocha-reporter:bundle" ] } }, diff --git a/integration-tests/environments/node/package.json b/integration-tests/environments/node/package.json index 57fcb27f557..da1eadfba7d 100644 --- a/integration-tests/environments/node/package.json +++ b/integration-tests/environments/node/package.json @@ -15,8 +15,7 @@ "dependencies": [ "../../../packages/realm:bundle", "../../../packages/realm:build:node", - "../../../packages/mocha-reporter:bundle", - "../../../packages/realm-network-transport:bundle" + "../../../packages/mocha-reporter:bundle" ] }, "test:commonjs": { @@ -24,8 +23,7 @@ "dependencies": [ "../../../packages/realm:bundle", "../../../packages/realm:build:node", - "../../../packages/mocha-reporter:bundle", - "../../../packages/realm-network-transport:bundle" + "../../../packages/mocha-reporter:bundle" ] } }, diff --git a/integration-tests/environments/react-native/package.json b/integration-tests/environments/react-native/package.json index ccb8797d339..e8b4681644b 100644 --- a/integration-tests/environments/react-native/package.json +++ b/integration-tests/environments/react-native/package.json @@ -99,8 +99,7 @@ "dependencies": [ "../../../packages/realm:build:android", "../../../packages/realm:bundle", - "../../../packages/mocha-reporter:bundle", - "../../../packages/realm-network-transport:bundle" + "../../../packages/mocha-reporter:bundle" ], "env": { "PLATFORM": "android", @@ -118,8 +117,7 @@ "dependencies": [ "pod-install:simulator", "../../../packages/realm:bundle", - "../../../packages/mocha-reporter:bundle", - "../../../packages/realm-network-transport:bundle" + "../../../packages/mocha-reporter:bundle" ], "env": { "PLATFORM": "ios", @@ -131,8 +129,7 @@ "dependencies": [ "pod-install:catalyst", "../../../packages/realm:bundle", - "../../../packages/mocha-reporter:bundle", - "../../../packages/realm-network-transport:bundle" + "../../../packages/mocha-reporter:bundle" ], "env": { "PLATFORM": "catalyst", @@ -159,8 +156,7 @@ "dependencies": [ "../../../packages/realm:build:android", "../../../packages/realm:bundle", - "../../../packages/mocha-reporter:bundle", - "../../../packages/realm-network-transport:bundle" + "../../../packages/mocha-reporter:bundle" ], "env": { "PLATFORM": "android", @@ -173,8 +169,7 @@ "pod-install:simulator", "../../../packages/realm:build:ios:debug:simulator", "../../../packages/realm:bundle", - "../../../packages/mocha-reporter:bundle", - "../../../packages/realm-network-transport:bundle" + "../../../packages/mocha-reporter:bundle" ], "env": { "PLATFORM": "ios", @@ -187,8 +182,7 @@ "pod-install:catalyst", "../../../packages/realm:build:ios:debug:catalyst", "../../../packages/realm:bundle", - "../../../packages/mocha-reporter:bundle", - "../../../packages/realm-network-transport:bundle" + "../../../packages/mocha-reporter:bundle" ], "env": { "PLATFORM": "catalyst", diff --git a/integration-tests/tests/package.json b/integration-tests/tests/package.json index a8296590521..8c3d5bffbe7 100644 --- a/integration-tests/tests/package.json +++ b/integration-tests/tests/package.json @@ -1,101 +1,97 @@ { - "name": "@realm/integration-tests", - "version": "0.1.0", - "description": "A set of tests that can run in different environments", - "main": "src/index.ts", - "exports": { - ".": "./src/index.ts", - "./node": "./src/node/index.ts" - }, - "private": true, - "scripts": { - "build": "wireit", - "start": "wireit", - "test": "wireit", - "lint": "wireit", - "coverage": "wireit", - "ci:coverage": "wireit" - }, - "wireit": { - "lint": { - "command": "eslint --ext .js,.ts . && tsc --noEmit", - "dependencies": [ - "../../packages/realm-network-transport:bundle", - "../../packages/realm:bundle" - ] - }, - "build": { - "command": "tsc", - "dependencies": [ - "../../packages/realm:bundle", - "build-dependencies" - ] - }, - "start": { - "command": "mocha --watch", - "dependencies": [ - "../../packages/realm:bundle", - "build-dependencies" - ] - }, - "test": { - "command": "mocha --exit", - "dependencies": [ - "../../packages/realm:bundle", - "build-dependencies" - ] - }, - "coverage": { - "command": "nyc mocha --exit", - "dependencies": [ - "../../packages/realm:bundle:coverage", - "build-dependencies" - ] - }, - "ci:coverage": { - "command": "nyc --reporter=lcov -- mocha --exit", - "dependencies": [ - "../../packages/realm:bundle:coverage", - "build-dependencies" - ] - }, - "build-dependencies": { - "dependencies": [ - "../../packages/realm:build:node", - "../../packages/mocha-reporter:bundle", - "../../packages/realm-network-transport:bundle" - ] - } - }, - "peerDependencies": { - "realm": "*" - }, - "devDependencies": { - "@istanbuljs/nyc-config-typescript": "^1.0.2", - "@realm/app-importer": "*", - "@realm/mocha-reporter": "*", - "@thi.ng/bench": "^3.1.16", - "@types/chai": "^4.3.3", - "@types/chai-as-promised": "^7.1.5", - "@types/jsrsasign": "^10.5.4", - "@types/mocha": "^10.0.0", - "@types/node": "^18.15.10", - "concurrently": "^6.5.1", - "mocha": "^10.1.0", - "node-fetch": "^2.6.9", - "nyc": "^15.1.0", - "platform": "^1.3.6", - "realm": "*" - }, - "dependencies": { - "@realm/network-transport": "^0.7.2", - "@thi.ng/bench": "^3.1.16", - "chai": "4.3.6", - "chai-as-promised": "^7.1.1", - "concurrently": "^6.0.2", - "jsrsasign": "^11.0.0" - }, - "files": [ - "/src" - ] -} + "name": "@realm/integration-tests", + "version": "0.1.0", + "description": "A set of tests that can run in different environments", + "main": "src/index.ts", + "exports": { + ".": "./src/index.ts", + "./node": "./src/node/index.ts" + }, + "private": true, + "scripts": { + "build": "wireit", + "start": "wireit", + "test": "wireit", + "lint": "wireit", + "coverage": "wireit", + "ci:coverage": "wireit" + }, + "wireit": { + "lint": { + "command": "eslint --ext .js,.ts . && tsc --noEmit", + "dependencies": [ + "../../packages/realm:bundle" + ] + }, + "build": { + "command": "tsc", + "dependencies": [ + "../../packages/realm:bundle", + "build-dependencies" + ] + }, + "start": { + "command": "mocha --watch", + "dependencies": [ + "../../packages/realm:bundle", + "build-dependencies" + ] + }, + "test": { + "command": "mocha --exit", + "dependencies": [ + "../../packages/realm:bundle", + "build-dependencies" + ] + }, + "coverage": { + "command": "nyc mocha --exit", + "dependencies": [ + "../../packages/realm:bundle:coverage", + "build-dependencies" + ] + }, + "ci:coverage": { + "command": "nyc --reporter=lcov -- mocha --exit", + "dependencies": [ + "../../packages/realm:bundle:coverage", + "build-dependencies" + ] + }, + "build-dependencies": { + "dependencies": [ + "../../packages/realm:build:node", + "../../packages/mocha-reporter:bundle" + ] + } + }, + "peerDependencies": { + "realm": "*" + }, + "devDependencies": { + "@istanbuljs/nyc-config-typescript": "^1.0.2", + "@realm/app-importer": "*", + "@realm/mocha-reporter": "*", + "@thi.ng/bench": "^3.1.16", + "@types/chai": "^4.3.3", + "@types/chai-as-promised": "^7.1.5", + "@types/jsrsasign": "^10.5.4", + "@types/mocha": "^10.0.0", + "concurrently": "^6.5.1", + "mocha": "^10.1.0", + "nyc": "^15.1.0", + "platform": "^1.3.6", + "realm": "*" + }, + "dependencies": { + "@thi.ng/bench": "^3.1.16", + "chai": "4.3.6", + "chai-as-promised": "^7.1.1", + "concurrently": "^6.0.2", + "jsrsasign": "^11.0.0", + "node-fetch": "^3.3.2" + }, + "files": [ + "/src" + ] +} \ No newline at end of file diff --git a/integration-tests/tests/src/node/index.ts b/integration-tests/tests/src/node/index.ts index fa47a091eca..73ec5a42c8e 100644 --- a/integration-tests/tests/src/node/index.ts +++ b/integration-tests/tests/src/node/index.ts @@ -24,3 +24,4 @@ import "./path"; import "./sync-proxy"; import "./custom-inspect"; import "./ssl"; +import "./node-fetch"; diff --git a/packages/realm-network-transport/src/node/index.ts b/integration-tests/tests/src/node/node-fetch.ts similarity index 53% rename from packages/realm-network-transport/src/node/index.ts rename to integration-tests/tests/src/node/node-fetch.ts index 0b584e0db7b..b0527d1af96 100644 --- a/packages/realm-network-transport/src/node/index.ts +++ b/integration-tests/tests/src/node/node-fetch.ts @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////// // -// Copyright 2020 Realm Inc. +// Copyright 2024 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,15 +16,19 @@ // //////////////////////////////////////////////////////////////////////////// -export * from "../index"; +import { expect } from "chai"; +import nodeFetch from "node-fetch"; +import Realm from "realm"; -import { DefaultNetworkTransport } from "../DefaultNetworkTransport"; -import { Fetch, AbortController } from "../types"; -import { makeRequestBodyIterable } from "../IterableReadableStream"; +import { importAppBefore } from "../hooks"; +import { buildAppConfig } from "../utils/build-app-config"; +import { baseUrl } from "../utils/import-app"; -import fetch from "node-fetch"; -import NodeAbortController from "abort-controller"; +describe.skipIf(environment.missingServer, "passing node-fetch to AppConfiguration", () => { + importAppBefore(buildAppConfig().anonAuth()); -DefaultNetworkTransport.fetch = fetch as Fetch; -DefaultNetworkTransport.AbortController = NodeAbortController as AbortController; -DefaultNetworkTransport.transformResponse = makeRequestBodyIterable; + it("is supported", async function (this: AppContext) { + const app = new Realm.App({ id: this.app.id, baseUrl, fetch: nodeFetch }); + await app.logIn(Realm.Credentials.anonymous()); + }); +}); diff --git a/integration-tests/tests/src/tests/sync/app.ts b/integration-tests/tests/src/tests/sync/app.ts index 56da6d53c13..57dfd30786f 100644 --- a/integration-tests/tests/src/tests/sync/app.ts +++ b/integration-tests/tests/src/tests/sync/app.ts @@ -104,6 +104,14 @@ describe("App", () => { expect(() => new Realm.App(1234)).throws("Expected 'config' to be an object, got a number"); }); + it("uses fetch passed through config", async function () { + async function fakeFetch(): Promise { + throw new Error("Boom!"); + } + const app = new Realm.App({ id: "test-app", fetch: fakeFetch }); + await expect(app.logIn(Realm.Credentials.anonymous())).eventually.rejectedWith("Boom!"); + }); + it("logging in throws on invalid baseURL", async function () { const invalidUrlConf = { id: missingAppConfig.id, baseUrl: "http://localhost:9999" }; const app = new Realm.App(invalidUrlConf); diff --git a/integration-tests/tests/src/utils/fetch.ts b/integration-tests/tests/src/utils/fetch.ts deleted file mode 100644 index 5a03103cabd..00000000000 --- a/integration-tests/tests/src/utils/fetch.ts +++ /dev/null @@ -1,21 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -import { DefaultNetworkTransport } from "@realm/network-transport"; - -export const fetch = DefaultNetworkTransport.fetch; diff --git a/integration-tests/tests/tsconfig.json b/integration-tests/tests/tsconfig.json index ea9ecc0943e..e3144490200 100644 --- a/integration-tests/tests/tsconfig.json +++ b/integration-tests/tests/tsconfig.json @@ -13,7 +13,6 @@ "es2020" ], "types": [ - "@realm/network-transport", "realm", "bson", "buffer", diff --git a/package-lock.json b/package-lock.json index 23cbb910964..8851a5575ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,10 @@ "packages/realm/bindgen/", "packages/realm/bindgen/vendor/realm-core/", "packages/babel-plugin", + "packages/fetch", "packages/realm", "packages/realm-web", "packages/realm-common", - "packages/realm-network-transport", "packages/realm-app-importer", "packages/realm-react", "packages/realm-tools", @@ -374,12 +374,12 @@ "name": "@realm/integration-tests", "version": "0.1.0", "dependencies": { - "@realm/network-transport": "^0.7.2", "@thi.ng/bench": "^3.1.16", "chai": "4.3.6", "chai-as-promised": "^7.1.1", "concurrently": "^6.0.2", - "jsrsasign": "11.0.0" + "jsrsasign": "^11.0.0", + "node-fetch": "^3.3.2" }, "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.2", @@ -390,10 +390,8 @@ "@types/chai-as-promised": "^7.1.5", "@types/jsrsasign": "^10.5.4", "@types/mocha": "^10.0.0", - "@types/node": "^18.15.10", "concurrently": "^6.5.1", "mocha": "^10.1.0", - "node-fetch": "^2.6.9", "nyc": "^15.1.0", "platform": "^1.3.6", "realm": "*" @@ -550,6 +548,23 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "integration-tests/tests/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "integration-tests/tests/node_modules/serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -2790,7 +2805,6 @@ "version": "10.5.0", "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz", "integrity": "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==", - "dev": true, "dependencies": { "@chevrotain/gast": "10.5.0", "@chevrotain/types": "10.5.0", @@ -2801,7 +2815,6 @@ "version": "10.5.0", "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.5.0.tgz", "integrity": "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==", - "dev": true, "dependencies": { "@chevrotain/types": "10.5.0", "lodash": "4.17.21" @@ -2810,20 +2823,17 @@ "node_modules/@chevrotain/types": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.5.0.tgz", - "integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==", - "dev": true + "integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==" }, "node_modules/@chevrotain/utils": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.5.0.tgz", - "integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==", - "dev": true + "integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==" }, "node_modules/@commander-js/extra-typings": { "version": "10.0.3", "resolved": "https://registry.npmjs.org/@commander-js/extra-typings/-/extra-typings-10.0.3.tgz", "integrity": "sha512-OIw28QV/GlP8k0B5CJTRsl8IyNvd0R8C8rfo54Yz9P388vCNDgdNrFlKxZTGqps+5j6lSw3Ss9JTQwcur1w1oA==", - "dev": true, "peerDependencies": { "commander": "10.0.x" } @@ -2832,7 +2842,6 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -2844,7 +2853,6 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -5905,6 +5913,10 @@ "resolved": "integration-tests/environments/electron", "link": true }, + "node_modules/@realm/fetch": { + "resolved": "packages/fetch", + "link": true + }, "node_modules/@realm/integration-tests": { "resolved": "integration-tests/tests", "link": true @@ -5917,10 +5929,6 @@ "resolved": "packages/mocha-reporter", "link": true }, - "node_modules/@realm/network-transport": { - "resolved": "packages/realm-network-transport", - "link": true - }, "node_modules/@realm/node-tests": { "resolved": "integration-tests/environments/node", "link": true @@ -6342,25 +6350,33 @@ "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" + }, + "node_modules/@tsconfig/node18": { + "version": "18.2.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node18/-/node18-18.2.2.tgz", + "integrity": "sha512-d6McJeGsuoRlwWZmVIeE8CUA27lu6jLjvv1JzqmpsytOYYbVi1tHZEnwCNVOXnj4pyLvneZlFlpXUK+X9wBWyw==", + "dev": true + }, + "node_modules/@tsconfig/recommended": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/recommended/-/recommended-1.0.3.tgz", + "integrity": "sha512-+jby/Guq9H8O7NWgCv6X8VAiQE8Dr/nccsCtL74xyHKhu2Knu5EAKmOZj3nLCnLm1KooUzKY+5DsnGVqhM8/wQ==", "dev": true }, "node_modules/@types/babel__core": { @@ -6675,23 +6691,13 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.19.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.6.tgz", - "integrity": "sha512-X36s5CXMrrJOs2lQCdDF68apW4Rfx9ixYMawlepwmE4Anezv/AV2LSpKD1Ub8DAc+urp5bk0BGZ6NtmBitfnsg==", + "version": "18.19.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.8.tgz", + "integrity": "sha512-g1pZtPhsvGVTwmeVoexWZLTQaOvXwoSq//pTL0DHeNzUDrFnir4fgETdhjhIxjVnN+hKOuh98+E1eMLnUXstFg==", "dependencies": { "undici-types": "~5.26.4" } }, - "node_modules/@types/node-fetch": { - "version": "2.6.10", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.10.tgz", - "integrity": "sha512-PPpPK6F9ALFTn59Ka3BaL+qGuipRfxNE8qVgkp0bVixeiR2c2/L+IVOiBdu9JhhT22sWnQEp6YyHGI2b2+CMcA==", - "dev": true, - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" - } - }, "node_modules/@types/node-forge": { "version": "1.3.11", "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", @@ -8026,8 +8032,7 @@ "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" }, "node_modules/argparse": { "version": "2.0.1", @@ -9160,7 +9165,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dev": true, "dependencies": { "pascal-case": "^3.1.2", "tslib": "^2.0.3" @@ -9197,7 +9201,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", - "dev": true, "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", @@ -9280,7 +9283,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", - "dev": true, "dependencies": { "camel-case": "^4.1.2", "capital-case": "^1.0.4", @@ -9328,7 +9330,6 @@ "version": "10.5.0", "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz", "integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==", - "dev": true, "dependencies": { "@chevrotain/cst-dts-gen": "10.5.0", "@chevrotain/gast": "10.5.0", @@ -9961,7 +9962,6 @@ "version": "10.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, "peer": true, "engines": { "node": ">=14" @@ -10286,7 +10286,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", - "dev": true, "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", @@ -11185,8 +11184,7 @@ "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, "node_modules/cross-env": { "version": "7.0.3", @@ -11389,7 +11387,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true, "engines": { "node": ">= 12" } @@ -11965,7 +11962,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dev": true, "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" @@ -13461,7 +13457,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dev": true, "funding": [ { "type": "github", @@ -13820,7 +13815,6 @@ "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dev": true, "dependencies": { "fetch-blob": "^3.1.2" }, @@ -14592,7 +14586,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", - "dev": true, "dependencies": { "capital-case": "^1.0.4", "tslib": "^2.0.3" @@ -18670,7 +18663,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, "dependencies": { "tslib": "^2.0.3" } @@ -18735,8 +18727,7 @@ "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" }, "node_modules/makeerror": { "version": "1.0.12", @@ -20037,7 +20028,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" @@ -20117,7 +20107,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "dev": true, "funding": [ { "type": "github", @@ -21482,7 +21471,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dev": true, "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -21547,7 +21535,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dev": true, "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" @@ -21571,7 +21558,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", - "dev": true, "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -21580,8 +21566,7 @@ "node_modules/path-equal": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/path-equal/-/path-equal-1.2.5.tgz", - "integrity": "sha512-i73IctDr3F2W+bsOWDyyVm/lqsXO47aY9nsFZUjTT/aljSbkxHxxCoyZ9UUrM8jK0JVod+An+rl48RCsvWM+9g==", - "dev": true + "integrity": "sha512-i73IctDr3F2W+bsOWDyyVm/lqsXO47aY9nsFZUjTT/aljSbkxHxxCoyZ9UUrM8jK0JVod+An+rl48RCsvWM+9g==" }, "node_modules/path-exists": { "version": "3.0.0", @@ -23288,8 +23273,7 @@ "node_modules/regexp-to-ast": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", - "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==", - "dev": true + "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==" }, "node_modules/regexp.prototype.flags": { "version": "1.5.1", @@ -23910,7 +23894,6 @@ "version": "2.4.3", "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", - "dev": true, "engines": { "node": ">=10" } @@ -24082,7 +24065,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", - "dev": true, "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", @@ -24471,7 +24453,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", - "dev": true, "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -25679,7 +25660,6 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -25722,7 +25702,6 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -25731,7 +25710,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, "engines": { "node": ">=0.3.1" } @@ -25995,7 +25973,6 @@ "version": "0.55.0", "resolved": "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.55.0.tgz", "integrity": "sha512-BXaivYecUdiXWWNiUqXgY6A9cMWerwmhtO+lQE7tDZGs7Mf38sORDeQZugfYOZOHPZ9ulsD+w0LWjFDOQoXcwg==", - "dev": true, "dependencies": { "@types/json-schema": "^7.0.9", "@types/node": "^16.9.2", @@ -26013,14 +25990,12 @@ "node_modules/typescript-json-schema/node_modules/@types/node": { "version": "16.18.70", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.70.tgz", - "integrity": "sha512-8eIk20G5VVVQNZNouHjLA2b8utE2NvGybLjMaF4lyhA9uhGwnmXF8o+icdXKGSQSNANJewXva/sFUoZLwAaYAg==", - "dev": true + "integrity": "sha512-8eIk20G5VVVQNZNouHjLA2b8utE2NvGybLjMaF4lyhA9uhGwnmXF8o+icdXKGSQSNANJewXva/sFUoZLwAaYAg==" }, "node_modules/typescript-json-schema/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -26040,7 +26015,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -26052,7 +26026,6 @@ "version": "4.8.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", - "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -26252,7 +26225,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", - "dev": true, "dependencies": { "tslib": "^2.0.3" } @@ -26261,7 +26233,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", - "dev": true, "dependencies": { "tslib": "^2.0.3" } @@ -26343,8 +26314,7 @@ "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" }, "node_modules/v8-to-istanbul": { "version": "8.1.1", @@ -27351,7 +27321,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, "engines": { "node": ">=6" } @@ -27685,6 +27654,17 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "packages/fetch": { + "name": "@realm/fetch", + "version": "0.1.0", + "devDependencies": { + "@tsconfig/node18": "^18.2.2", + "@tsconfig/recommended": "^1.0.3" + }, + "engines": { + "node": ">=18" + } + }, "packages/metro-config": { "name": "@realm/metro-config", "version": "0.1.0", @@ -27702,22 +27682,7 @@ "mocha-github-actions-reporter": "^0.2.4" }, "devDependencies": { - "@types/mocha": "^8", - "@types/node": "^18.15.10", - "typescript": "^4.7.4" - } - }, - "packages/mocha-reporter/node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" + "@types/mocha": "^8" } }, "packages/realm": { @@ -27727,16 +27692,15 @@ "dependencies": { "bson": "^4.7.2", "debug": "^4.3.4", - "node-fetch": "^2.6.9", "node-machine-id": "^1.1.12", "prebuild-install": "^7.1.1" }, "devDependencies": { "@realm/bindgen": "^0.1.0", - "@realm/network-transport": "^0.7.2", + "@realm/fetch": "^0.1.0", "@types/chai": "^4.3.3", "@types/mocha": "^10.0.0", - "@types/node": "^18.15.10", + "@types/node": "^18.19.8", "@types/path-browserify": "^1.0.0", "chai": "4.3.6", "cmake-js": "6.3.2", @@ -27748,6 +27712,9 @@ "react-native": "0.73.2", "typedoc-plugin-missing-exports": "^2.0.1" }, + "engines": { + "node": ">=18" + }, "peerDependencies": { "react-native": ">=0.71.0" }, @@ -27762,22 +27729,19 @@ "version": "0.1.0", "license": "Apache-2.0", "dependencies": { - "@realm/network-transport": "*", + "@realm/fetch": "^0.1.0", "body-parser": "^1.20.1", "chalk": "^4.1.2", "debug": "^4.3.4", "deepmerge": "^4.2.2", "fs-extra": "^10.1.0", "glob": "^8.0.3", - "node-fetch": "^2.6.9", "yargs": "^17.6.0" }, "devDependencies": { "@types/body-parser": "^1.19.2", "@types/fs-extra": "^9.0.13", "@types/glob": "^8.0.0", - "@types/node": "^18.15.10", - "@types/node-fetch": "^2.6.2", "@types/yargs": "^17.0.13" } }, @@ -27945,157 +27909,18 @@ "packages/realm-network-transport": { "name": "@realm/network-transport", "version": "0.7.2", + "extraneous": true, "license": "Apache-2.0", "dependencies": { - "@realm/common": "^0.1.4", - "abort-controller": "^3.0.0", - "node-fetch": "^2.6.9" + "@realm/common": "^0.1.4" }, "devDependencies": { "@types/chai": "^4.2.10", "@types/mocha": "^5", - "@types/node-fetch": "^2.6.2", "chai": "4.3.6", "mocha": "^5.2.0" } }, - "packages/realm-network-transport/node_modules/@types/mocha": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", - "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", - "dev": true - }, - "packages/realm-network-transport/node_modules/commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, - "packages/realm-network-transport/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "packages/realm-network-transport/node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "packages/realm-network-transport/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "packages/realm-network-transport/node_modules/glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "packages/realm-network-transport/node_modules/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==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "packages/realm-network-transport/node_modules/he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha512-z/GDPjlRMNOa2XJiB4em8wJpuuBfrFOlYKTZxtpkdr1uPdibHI8rYA3MY0KDObpVyaes0e/aunid/t88ZI2EKA==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "packages/realm-network-transport/node_modules/minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", - "dev": true - }, - "packages/realm-network-transport/node_modules/mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", - "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", - "dev": true, - "dependencies": { - "minimist": "0.0.8" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "packages/realm-network-transport/node_modules/mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", - "dev": true, - "dependencies": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "packages/realm-network-transport/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "packages/realm-network-transport/node_modules/supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "packages/realm-react": { "name": "@realm/react", "version": "0.6.2", @@ -29258,22 +29083,7 @@ "realm-schema": "dist/realm-schema.js" }, "devDependencies": { - "@types/command-line-args": "^5.2.0", - "@types/node": "^18.15.10", - "typescript": "^4.6.4" - } - }, - "packages/realm-tools/node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" + "@types/command-line-args": "^5.2.0" } }, "packages/realm-web": { @@ -29286,21 +29096,17 @@ "js-base64": "^3.7.2" }, "devDependencies": { - "@realm/network-transport": "^0.7.2", + "@realm/fetch": "^0.1.0", "@types/chai": "^4.2.9", "@types/fs-extra": "^8.1.0", "@types/js-base64": "^3.3.1", "@types/mocha": "^10.0.6", - "@types/node": "^18.15.10", - "abort-controller": "^3.0.0", "chai": "4.3.6", "fs-extra": "^10.0.0", - "mocha": "^10.2.0", - "node-fetch": "^3.3.2" + "mocha": "^10.2.0" }, - "optionalDependencies": { - "abort-controller": "^3", - "node-fetch": "^3" + "engines": { + "node": ">=18" } }, "packages/realm-web-integration-tests": { @@ -29321,14 +29127,12 @@ "@types/chai": "^4.3.1", "@types/fs-extra": "^9.0.13", "@types/mocha": "^8", - "@types/node-fetch": "^2.6.2", "@types/puppeteer": "^5.4.6", "@types/webpack": "^5.28.0", "@types/webpack-env": "^1.17.0", "fs-extra": "^10.1.0", "html-webpack-plugin": "^5.5.0", "mongodb-realm-cli": "^1.3.2", - "node-fetch": "^3.2.5", "source-map-loader": "^4.0.2", "ts-loader": "^9.3.0", "webpack-cli": "^4.9.2" @@ -29485,24 +29289,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "packages/realm-web-integration-tests/node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dev": true, - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, "packages/realm-web-integration-tests/node_modules/serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -29726,24 +29512,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "packages/realm-web/node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dev": true, - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, "packages/realm-web/node_modules/supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", @@ -29762,7 +29530,6 @@ "packages/realm/bindgen/vendor/realm-core": { "name": "@realm/bindgen", "version": "0.1.0", - "dev": true, "dependencies": { "@commander-js/extra-typings": "^10.0.2", "@types/node": "^18.15.10", diff --git a/package.json b/package.json index a98216f5690..77a9b6657de 100644 --- a/package.json +++ b/package.json @@ -56,10 +56,10 @@ "packages/realm/bindgen/", "packages/realm/bindgen/vendor/realm-core/", "packages/babel-plugin", + "packages/fetch", "packages/realm", "packages/realm-web", "packages/realm-common", - "packages/realm-network-transport", "packages/realm-app-importer", "packages/realm-react", "packages/realm-tools", diff --git a/packages/fetch/.eslintrc.json b/packages/fetch/.eslintrc.json new file mode 100644 index 00000000000..6fe37da85b0 --- /dev/null +++ b/packages/fetch/.eslintrc.json @@ -0,0 +1,15 @@ +{ + "parser": "@typescript-eslint/parser", + "extends": [ + "plugin:@typescript-eslint/recommended" + ], + "rules": { + "no-restricted-globals": "off" + }, + "parserOptions": { + "sourceType": "module" + }, + "ignorePatterns": [ + "dist" + ] +} \ No newline at end of file diff --git a/packages/fetch/.gitignore b/packages/fetch/.gitignore new file mode 100644 index 00000000000..849ddff3b7e --- /dev/null +++ b/packages/fetch/.gitignore @@ -0,0 +1 @@ +dist/ diff --git a/packages/fetch/package.json b/packages/fetch/package.json new file mode 100644 index 00000000000..9472a8dcd1b --- /dev/null +++ b/packages/fetch/package.json @@ -0,0 +1,41 @@ +{ + "name": "@realm/fetch", + "version": "0.1.0", + "module": "true", + "private": true, + "scripts": { + "build": "wireit" + }, + "wireit": { + "build": { + "command": "tsc --build", + "files": [ + "src", + "types.d.ts", + "tsconfig.json", + "tsconfig.*.json" + ], + "output": [ + "dist" + ] + } + }, + "exports": { + "node": { + "import": "./dist/node-esm/node.js", + "require": "./dist/node-cjs/node.js" + }, + "react-native": "./dist/react-native/react-native.js", + "browser": "./dist/browser/browser.js", + "types": "./dist/types/types.d.ts" + }, + "react-native": "./dist/react-native/react-native.js", + "types": "./dist/types/types.d.ts", + "devDependencies": { + "@tsconfig/node18": "^18.2.2", + "@tsconfig/recommended": "^1.0.3" + }, + "engines": { + "node": ">=18" + } +} \ No newline at end of file diff --git a/packages/realm-network-transport/src/IterableReadableStream.ts b/packages/fetch/src/IterableReadableStream.ts similarity index 92% rename from packages/realm-network-transport/src/IterableReadableStream.ts rename to packages/fetch/src/IterableReadableStream.ts index 06086624fc4..eb1b9eac384 100644 --- a/packages/realm-network-transport/src/IterableReadableStream.ts +++ b/packages/fetch/src/IterableReadableStream.ts @@ -16,7 +16,7 @@ // //////////////////////////////////////////////////////////////////////////// -import { FetchResponse, ReadableStream } from "."; +import { Response, ReadableStream } from "./types"; // Falling back on a known string used in code transpiled by Babel const asyncIteratorSymbol = Symbol.asyncIterator || "@@asyncIterator"; @@ -56,7 +56,7 @@ export function makeStreamIterable(stream: ReadableStream): ReadableStream { } } -const RESPONSE_HANDLER: ProxyHandler = { +const RESPONSE_HANDLER: ProxyHandler = { get(target, prop, receiver) { if (prop === "body") { const body = target.body; @@ -74,6 +74,6 @@ const RESPONSE_HANDLER: ProxyHandler = { * @param response * @returns An async iterator. */ -export function makeRequestBodyIterable(response: FetchResponse): FetchResponse { +export function makeRequestBodyIterable(response: Response): Response { return new Proxy(response, RESPONSE_HANDLER); } diff --git a/packages/fetch/src/browser.ts b/packages/fetch/src/browser.ts new file mode 100644 index 00000000000..6d21367f650 --- /dev/null +++ b/packages/fetch/src/browser.ts @@ -0,0 +1,37 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2024 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +import type * as types from "./types"; + +// The sole purpose of this line is to verify types +const TypeTest: unknown = {}; + +TypeTest as ReadableStream satisfies types.ReadableStream; +// To ensure users cannot pass a request body that the platform cannot handle +TypeTest as types.RequestBody satisfies BodyInit; + +export const Headers = globalThis.Headers satisfies typeof types.Headers; +export const AbortSignal = globalThis.AbortSignal satisfies typeof types.AbortSignal; +export const AbortController = globalThis.AbortController satisfies typeof types.AbortController; +// Binding the function to avoid "Failed to execute 'fetch' on 'Window': Illegal invocation". +export const fetch = globalThis.fetch.bind(globalThis) satisfies typeof types.fetch< + BodyInit, + Headers, + AbortSignal, + Response["body"] +>; diff --git a/packages/fetch/src/node.ts b/packages/fetch/src/node.ts new file mode 100644 index 00000000000..340c0a36eaf --- /dev/null +++ b/packages/fetch/src/node.ts @@ -0,0 +1,42 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2024 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +import type { BodyInit } from "undici-types"; +import type { ReadableStream } from "node:stream/web"; + +import type * as types from "./types"; + +// The sole purpose of this line is to verify types +const TypeTest: unknown = {}; + +TypeTest as ReadableStream satisfies types.ReadableStream; +// To ensure users cannot pass a request body that the platform cannot handle +TypeTest as types.RequestBody satisfies BodyInit; + +export const Headers = globalThis.Headers satisfies typeof types.Headers; +export const AbortSignal = globalThis.AbortSignal satisfies typeof types.AbortSignal; +export const AbortController = globalThis.AbortController satisfies typeof types.AbortController; +// Binding the function to avoid "Failed to execute 'fetch' on 'Window': Illegal invocation". +// This happens when the "node" export of "realm" is imported from an Electron renderer process. +// It could be revisited if / when "realm" gets a "browser" export condition. +export const fetch = globalThis.fetch.bind(globalThis) satisfies typeof types.fetch< + BodyInit, + Headers, + AbortSignal, + Response["body"] +>; diff --git a/packages/realm-network-transport/src/status-text.ts b/packages/fetch/src/react-native.ts similarity index 52% rename from packages/realm-network-transport/src/status-text.ts rename to packages/fetch/src/react-native.ts index 4ddc10880ff..53c1ebd8670 100644 --- a/packages/realm-network-transport/src/status-text.ts +++ b/packages/fetch/src/react-native.ts @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////// // -// Copyright 2023 Realm Inc. +// Copyright 2024 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,6 +16,56 @@ // //////////////////////////////////////////////////////////////////////////// +import type * as types from "./types"; + +// The sole purpose of this line is to verify types +const TypeTest: unknown = {}; + +// This type assertion is left as a comment, because "react-native"'s fetch implementation lacks a ReadableStream. +// The MongoDB "watch" implementation needs the `request.body` being `ReadableStream`. +// For this, end-users must install the fetch polyfill installable via https://www.npmjs.com/package/react-native-polyfill-globals +// TypeTest as ReadableStream satisfies types.ReadableStream; + +// To ensure users cannot pass a request body that the platform cannot handle +TypeTest as types.RequestBody satisfies BodyInit_; + +// The sole purpose of this line is to verify types +globalThis.fetch satisfies typeof types.fetch; + +type ReactNativeRequestInit = { + reactNative?: { textStreaming: boolean }; +} & RequestInit; + +export async function fetch(input: RequestInfo, init: ReactNativeRequestInit = {}) { + // Setting additional options to signal to the RN fetch polyfill that it shouldn't consider the response a "blob" + // see https://github.com/react-native-community/fetch/issues/15 + init.reactNative = { textStreaming: true }; + + const response = await globalThis.fetch(input, init); + if (!response.statusText) { + Object.assign(response, { statusText: getStatusText(response.status) }); + } + return response; +} + +export const Headers = globalThis.Headers satisfies typeof types.Headers; + +class PolyfilledAbortSignal extends AbortSignal { + static timeout(ms: number): PolyfilledAbortSignal { + const controller = new AbortController(); + setTimeout(() => { + controller.abort(); + }, ms); + return controller.signal; + } +} + +PolyfilledAbortSignal satisfies typeof types.AbortSignal; +export { PolyfilledAbortSignal as AbortSignal }; + +const ReactNativeAbortController = AbortController satisfies typeof types.AbortController; +export { ReactNativeAbortController as AbortController }; + /** * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status */ @@ -75,6 +125,6 @@ const HTTP_STATUS_TEXTS: Record = { 511: "Network Authentication Required", }; -export function deriveStatusText(status: number): string | undefined { +export function getStatusText(status: number): string | undefined { return HTTP_STATUS_TEXTS[status]; } diff --git a/packages/fetch/src/types.ts b/packages/fetch/src/types.ts new file mode 100644 index 00000000000..3afd5e23fcd --- /dev/null +++ b/packages/fetch/src/types.ts @@ -0,0 +1,223 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2024 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +export type ResponseType = "basic" | "cors" | "default" | "error" | "opaque" | "opaqueredirect"; +export type RequestCredentials = "include" | "omit" | "same-origin"; +export type RequestMode = "cors" | "navigate" | "no-cors" | "same-origin"; + +export type TypedArray = + | Int8Array + | Uint8Array + | Uint8ClampedArray + | Int16Array + | Uint16Array + | Int32Array + | Uint32Array + | Float32Array + | Float64Array + | BigInt64Array + | BigUint64Array; + +export type RequestBody = ArrayBuffer | TypedArray | string; + +export declare class AbortSignal { + static timeout(time: number): AbortSignal; + /** + * Returns true if this AbortSignal's AbortController has signaled to abort, and false otherwise. + */ + readonly aborted: boolean; + + // The following properties are optional because they're missing from the react-native types + readonly reason?: unknown; + throwIfAborted?(): void; +} + +export declare class AbortController { + /** + * Returns the AbortSignal object associated with this object. + */ + readonly signal: PlatformAbortSignal; + + /** + * Invoking this method will set this object's AbortSignal's aborted flag and signal to any observers that the associated activity is to be aborted. + */ + abort(): void; +} + +export declare class Headers { + append(name: string, value: string): void; + delete(name: string): void; + get(name: string): string | null; + has(name: string): boolean; + set(name: string, value: string): void; + forEach(callbackfn: (value: string, key: string, parent: this) => void): void; + getSetCookie?(): string[]; + keys?(): IterableIterator; + values?(): IterableIterator; + entries?(): IterableIterator<[string, string]>; + [Symbol.iterator]?(): Iterator<[string, string]>; +} + +export type Blob = unknown; +export type FormData = unknown; + +export declare class Response { + // constructor (body?: BodyInit, init?: ResponseInit); + + readonly headers: PlatformHeaders; + readonly ok: boolean; + readonly status: number; + readonly statusText: string; + readonly type: ResponseType; + readonly url: string; + readonly redirected: boolean; + + readonly body?: PlatformResponseBody; + readonly bodyUsed: boolean; + + readonly arrayBuffer: () => Promise; + readonly blob: () => Promise; + readonly formData: () => Promise; + readonly json: () => Promise; + readonly text: () => Promise; + + readonly clone: () => this; + + /* + // Not all our supported runtimes implement these statics + // see https://developer.mozilla.org/en-US/docs/Web/API/Response + + static error(): Response; + static json(data: any, init?: ResponseInit): Response; + static redirect(url: string | URL, status: ResponseRedirectStatus): Response; + */ +} + +/** + * Minimal types for a ReadableStream. + * @todo Missing declarations for `constructor`, `pipeThrough`, `pipeTo` and `tee`. + */ + +export type ReadableStream = { + /** + * Creates a reader and locks the stream to it. + * While the stream is locked, no other reader can be acquired until this one is released. + * @see https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/getReader + */ + getReader(): ReadableStreamDefaultReader; + getReader(options: { mode: "byob" }): unknown; + + /** + * Cancel is used when you've completely finished with the stream and don't need any more data from it, even if there are chunks enqueued waiting to be read. + * @return a Promise that resolves when the stream is canceled. + * @see https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/cancel + */ + cancel: (reason?: string) => Promise; +}; // & AsyncIterable; +// Should be [AsyncIterable](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream#async_iteration), but not all our supported runtimes implement this. + +export type ReadableStreamReadDoneResult = { + done: true; + // TODO: Make this `value: undefined` when https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1676 is merged and released + value?: unknown; +}; + +export type ReadableStreamReadValueResult = { + done: false; + value: T; +}; + +export type ReadableStreamReadResult = + | ReadableStreamReadValueResult + | ReadableStreamReadDoneResult; + +export type ReadableStreamDefaultReader = { + /** + * a Promise that fulfills when the stream closes, or rejects if the stream throws an error or the reader's lock is released. + */ + closed: Promise; + /** + * Cancel is used when you've completely finished with the stream and don't need any more data from it, even if there are chunks enqueued waiting to be read. + * @param reason A human-readable reason for the cancellation. + * @returns a Promise that resolves when the stream is canceled. Calling this method signals a loss of interest in the stream by a consumer. + * @see https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/cancel + */ + cancel(reason?: string): Promise; + /** + * @returns a Promise providing access to the next chunk in the stream's internal queue. + * @see https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/read + */ + read(): Promise>; + /** + * Releases the reader's lock on the stream. + * @see https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/releaseLock + */ + releaseLock(): void; +}; + +export declare function fetch< + PlatformRequestBody = RequestBody, + PlatformHeaders extends Headers = Headers, + PlatformAbortSignal extends AbortSignal = AbortSignal, + PlatformResponseBody = ReadableStream | null, +>( + input: string, + init?: RequestInit, +): Promise>; + +/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ +export type AnyFetch = typeof fetch; + +export interface RequestInit< + PlatformRequestBody = RequestBody, + PlatformHeaders = Headers, + PlatformAbortSignal = AbortSignal, +> { + /** + * The request's body. + */ + body?: PlatformRequestBody; + /** + * A string indicating whether credentials will be sent with the request always, never, or only when sent to a same-origin URL. Sets request's credentials. + */ + credentials?: RequestCredentials; + /** + * The request's headers. + */ + headers?: PlatformHeaders | Record; + /** + * A cryptographic hash of the resource to be fetched by request. Sets request's integrity. + */ + integrity?: string; + /** + * A boolean to set request's keepalive. + */ + keepalive?: boolean; + /** + * A string to set request's method. + */ + method?: string; + /** + * A string to indicate whether the request will use CORS, or will be restricted to same-origin URLs. Sets request's mode. + */ + mode?: RequestMode; + /** + * An AbortSignal to set request's signal. + */ + signal?: PlatformAbortSignal; +} diff --git a/packages/fetch/tsconfig.base.json b/packages/fetch/tsconfig.base.json new file mode 100644 index 00000000000..16864f7a616 --- /dev/null +++ b/packages/fetch/tsconfig.base.json @@ -0,0 +1,9 @@ +{ + "extends": "@tsconfig/recommended/tsconfig.json", + "compilerOptions": { + "composite": true, + "outDir": "./dist", + "rootDir": "./src", + "module": "ES2022" + } +} \ No newline at end of file diff --git a/packages/fetch/tsconfig.browser.json b/packages/fetch/tsconfig.browser.json new file mode 100644 index 00000000000..101a4a5c0e0 --- /dev/null +++ b/packages/fetch/tsconfig.browser.json @@ -0,0 +1,18 @@ +{ + "extends": [ + "./tsconfig.base.json", + ], + "compilerOptions": { + "outDir": "./dist/browser", + "lib": ["DOM", "ES2023"], + "types": [], + "noEmit": false, + "allowImportingTsExtensions": false + }, + "files": [ + "src/browser.ts" + ], + "references": [ + { "path": "tsconfig.types.json" } + ] +} \ No newline at end of file diff --git a/packages/fetch/tsconfig.json b/packages/fetch/tsconfig.json new file mode 100644 index 00000000000..495f4c0591a --- /dev/null +++ b/packages/fetch/tsconfig.json @@ -0,0 +1,9 @@ +{ + "references": [ + { "path": "tsconfig.node.cjs.json" }, + { "path": "tsconfig.node.esm.json" }, + { "path": "tsconfig.browser.json" }, + { "path": "tsconfig.react-native.json" }, + ], + "files": [] +} \ No newline at end of file diff --git a/packages/fetch/tsconfig.node.base.json b/packages/fetch/tsconfig.node.base.json new file mode 100644 index 00000000000..97a15ad5c16 --- /dev/null +++ b/packages/fetch/tsconfig.node.base.json @@ -0,0 +1,9 @@ +{ + "extends": [ + "./tsconfig.base.json", + "@tsconfig/node18/tsconfig.json", + ], + "files": [ + "src/node.ts" + ] +} \ No newline at end of file diff --git a/packages/fetch/tsconfig.node.cjs.json b/packages/fetch/tsconfig.node.cjs.json new file mode 100644 index 00000000000..37941373997 --- /dev/null +++ b/packages/fetch/tsconfig.node.cjs.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.node.base.json", + "compilerOptions": { + "outDir": "./dist/node-cjs", + "module": "CommonJS" + }, + "references": [ + { "path": "tsconfig.types.json" } + ] +} \ No newline at end of file diff --git a/packages/fetch/tsconfig.node.esm.json b/packages/fetch/tsconfig.node.esm.json new file mode 100644 index 00000000000..6d83d110054 --- /dev/null +++ b/packages/fetch/tsconfig.node.esm.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.node.base.json", + "compilerOptions": { + "outDir": "./dist/node-esm", + "module": "ES2022" + }, + "references": [ + { "path": "tsconfig.types.json" } + ] +} \ No newline at end of file diff --git a/packages/fetch/tsconfig.react-native.json b/packages/fetch/tsconfig.react-native.json new file mode 100644 index 00000000000..944be3d2f33 --- /dev/null +++ b/packages/fetch/tsconfig.react-native.json @@ -0,0 +1,18 @@ +{ + "extends": [ + "./tsconfig.base.json", + "@react-native/typescript-config/tsconfig.json", + ], + "compilerOptions": { + "outDir": "./dist/react-native", + "types": ["react-native"], + "noEmit": false, + "allowImportingTsExtensions": false + }, + "files": [ + "src/react-native.ts" + ], + "references": [ + { "path": "tsconfig.types.json" } + ] +} \ No newline at end of file diff --git a/packages/fetch/tsconfig.types.json b/packages/fetch/tsconfig.types.json new file mode 100644 index 00000000000..3fc20bd2851 --- /dev/null +++ b/packages/fetch/tsconfig.types.json @@ -0,0 +1,16 @@ +{ + "extends": [ + "./tsconfig.base.json" + ], + "compilerOptions": { + "outDir": "./dist/types", + "types": [], + "noResolve": true, + "lib": [ + "ES2023" + ] + }, + "files": [ + "./src/types.ts" + ] +} \ No newline at end of file diff --git a/packages/mocha-reporter/package.json b/packages/mocha-reporter/package.json index 76efb1a64fb..0eaa36ac0dd 100644 --- a/packages/mocha-reporter/package.json +++ b/packages/mocha-reporter/package.json @@ -40,8 +40,6 @@ "mocha-github-actions-reporter": "^0.2.4" }, "devDependencies": { - "@types/mocha": "^8", - "@types/node": "^18.15.10", - "typescript": "^4.7.4" + "@types/mocha": "^8" } } diff --git a/packages/realm-app-importer/package.json b/packages/realm-app-importer/package.json index fcc5ab4d0d1..0f7ef1b47b4 100644 --- a/packages/realm-app-importer/package.json +++ b/packages/realm-app-importer/package.json @@ -6,7 +6,19 @@ "main": "./src/index.ts", "scripts": { "lint": "eslint --ext .js,.ts .", - "test": "mocha" + "test": "mocha", + "type-check": "wireit" + }, + "wireit": { + "type-check": { + "command": "tsc", + "files": [ + "tsconfig.json" + ], + "dependencies": [ + "../fetch:build" + ] + } }, "files": [ "dist", @@ -31,22 +43,19 @@ }, "license": "Apache-2.0", "dependencies": { - "@realm/network-transport": "*", + "@realm/fetch": "^0.1.0", "body-parser": "^1.20.1", "chalk": "^4.1.2", "debug": "^4.3.4", "deepmerge": "^4.2.2", "fs-extra": "^10.1.0", "glob": "^8.0.3", - "node-fetch": "^2.6.9", "yargs": "^17.6.0" }, "devDependencies": { "@types/body-parser": "^1.19.2", "@types/fs-extra": "^9.0.13", "@types/glob": "^8.0.0", - "@types/node": "^18.15.10", - "@types/node-fetch": "^2.6.2", "@types/yargs": "^17.0.13" } } diff --git a/packages/realm-app-importer/src/AdminApiClient.ts b/packages/realm-app-importer/src/AdminApiClient.ts index 0db54dd90e6..b548bde2372 100644 --- a/packages/realm-app-importer/src/AdminApiClient.ts +++ b/packages/realm-app-importer/src/AdminApiClient.ts @@ -16,7 +16,7 @@ // //////////////////////////////////////////////////////////////////////////// -import { DefaultNetworkTransport, FetchRequestInit } from "@realm/network-transport"; +import { fetch, RequestInit } from "@realm/fetch"; import { App, AppResponse, @@ -52,10 +52,11 @@ export type Credentials = export type AuthenticationMode = "access" | "refresh" | "none"; -type ClientFetchRequest = Omit & { +type ClientFetchRequest = Omit & { route: string[]; headers?: Record; authentication?: AuthenticationMode; + body?: unknown; }; type AdminApiClientConfig = { @@ -362,9 +363,9 @@ export class AdminApiClient { } private async fetch(request: ClientFetchRequest): Promise { + const { route, body, headers = {}, authentication = "access", ...rest } = request; + const url = [this.config.baseUrl, AdminApiClient.baseRoute, ...route].join("/"); try { - const { route, body, headers = {}, authentication = "access", ...rest } = request; - const url = [this.config.baseUrl, AdminApiClient.baseRoute, ...route].join("/"); if (authentication === "access") { if (!this.accessToken) { throw new Error("Not yet authenticated"); @@ -379,14 +380,11 @@ export class AdminApiClient { if (typeof body === "object") { headers["content-type"] = "application/json"; } - const response = await DefaultNetworkTransport.fetch(url, { + const response = await fetch(url, { ...rest, - body: JSON.stringify(body), + body: body === undefined || body === "" ? undefined : JSON.stringify(body), headers, - // Setting additional options to signal to the RN fetch polyfill that it shouldn't consider the response a "blob" - // see https://github.com/react-native-community/fetch/issues/15 - reactNative: { textStreaming: true }, - } as FetchRequestInit); + }); if (response.ok) { if (response.headers.get("content-type") === "application/json") { return response.json(); diff --git a/packages/realm-app-importer/src/fetch.ts b/packages/realm-app-importer/src/fetch.ts deleted file mode 100644 index 2f671e7630c..00000000000 --- a/packages/realm-app-importer/src/fetch.ts +++ /dev/null @@ -1,20 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// -import { DefaultNetworkTransport } from "@realm/network-transport"; - -export const fetch = DefaultNetworkTransport.fetch; diff --git a/packages/realm-app-importer/src/tests/tsconfig.json b/packages/realm-app-importer/src/tests/tsconfig.json index 2ed8165fb58..56829525613 100644 --- a/packages/realm-app-importer/src/tests/tsconfig.json +++ b/packages/realm-app-importer/src/tests/tsconfig.json @@ -1,11 +1,16 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "noResolve": false, - "types": [ - "mocha", - "chai", - "node" - ] - } + "composite": true, + "noResolve": false, + "types": [ + "mocha", + "chai", + "node" + ] + }, + "include": [ + "**/*.ts" + ], + "exclude": [] } \ No newline at end of file diff --git a/packages/realm-app-importer/tsconfig.json b/packages/realm-app-importer/tsconfig.json index 6a3e5f901ae..b4f40b722be 100644 --- a/packages/realm-app-importer/tsconfig.json +++ b/packages/realm-app-importer/tsconfig.json @@ -7,13 +7,22 @@ "module": "es2022", "moduleResolution": "node", "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, "noResolve": true, + "noEmit": true, + "types": [ + "@realm/fetch", + "debug" + ], "lib": [ - "es2019", + "es2022", "dom" ] }, "include": [ "src/**/*.ts" ], -} + "exclude": [ + "src/tests/*.ts" + ] +} \ No newline at end of file diff --git a/packages/realm-network-transport/.eslintignore b/packages/realm-network-transport/.eslintignore deleted file mode 100644 index 72760ebe4dc..00000000000 --- a/packages/realm-network-transport/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -/dist/ -/types/ diff --git a/packages/realm-network-transport/.eslintrc.json b/packages/realm-network-transport/.eslintrc.json deleted file mode 100644 index 88610f52481..00000000000 --- a/packages/realm-network-transport/.eslintrc.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "parser": "@typescript-eslint/parser", - "extends": [ - "plugin:@typescript-eslint/recommended" - ], - "rules": { - "@typescript-eslint/explicit-function-return-type": "off" - } -} \ No newline at end of file diff --git a/packages/realm-network-transport/.gitignore b/packages/realm-network-transport/.gitignore deleted file mode 100644 index ab8e4cb9c3f..00000000000 --- a/packages/realm-network-transport/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -dist/ -types/generated/ diff --git a/packages/realm-network-transport/.watchmanconfig b/packages/realm-network-transport/.watchmanconfig deleted file mode 100644 index 9e26dfeeb6e..00000000000 --- a/packages/realm-network-transport/.watchmanconfig +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/packages/realm-network-transport/LICENSE b/packages/realm-network-transport/LICENSE deleted file mode 120000 index 30cff7403da..00000000000 --- a/packages/realm-network-transport/LICENSE +++ /dev/null @@ -1 +0,0 @@ -../../LICENSE \ No newline at end of file diff --git a/packages/realm-network-transport/README.md b/packages/realm-network-transport/README.md deleted file mode 100644 index 0120b3e79aa..00000000000 --- a/packages/realm-network-transport/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Realm Network Transport - -Provides a shared abstraction defining how Realm JS (and its sub-packages) communicate over HTTP. - -This is an internal package of Realm JS, not intended for public consumption. diff --git a/packages/realm-network-transport/package.json b/packages/realm-network-transport/package.json deleted file mode 100644 index baab89716cb..00000000000 --- a/packages/realm-network-transport/package.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "name": "@realm/network-transport", - "version": "0.7.2", - "description": "Implements cross-platform fetching used by Realm JS", - "main": "./dist/bundle.js", - "module": "./dist/bundle.mjs", - "types": "./dist/bundle.d.ts", - "react-native": "./dist/bundle.react-native.mjs", - "browser": { - "./dist/bundle.js": "./dist/bundle.dom.js", - "./dist/bundle.mjs": "./dist/bundle.dom.mjs", - "./dist/bundle.d.ts": "./dist/bundle.dom.d.ts" - }, - "scripts": { - "bundle": "wireit", - "lint": "eslint --ext .js,.ts .", - "test": "mocha 'src/**/*.test.ts'" - }, - "wireit": { - "bundle": { - "command": "rollup --config", - "dependencies": [ - "generate-types", - "../realm-common:bundle" - ], - "files": [ - "rollup.config.mjs", - "src/**/*.ts" - ], - "output": [ - "dist/**" - ] - }, - "generate-types": { - "command": "tsc --project tsconfig.types.json --declaration --emitDeclarationOnly --declarationDir types/generated", - "files": [ - "src/**/*.ts" - ], - "output": [ - "types/generated/**/*.d.ts" - ] - } - }, - "files": [ - "dist" - ], - "author": { - "name": "MongoDB", - "email": "help@realm.io", - "url": "https://www.mongodb.com/docs/realm/" - }, - "repository": { - "type": "git", - "url": "https://github.com/realm/realm-js.git" - }, - "bugs": { - "url": "https://github.com/realm/realm-js/issues" - }, - "license": "Apache-2.0", - "dependencies": { - "@realm/common": "^0.1.4", - "abort-controller": "^3.0.0", - "node-fetch": "^2.6.9" - }, - "devDependencies": { - "@types/chai": "^4.2.10", - "@types/mocha": "^5", - "@types/node-fetch": "^2.6.2", - "chai": "4.3.6", - "mocha": "^5.2.0" - } -} diff --git a/packages/realm-network-transport/rollup.config.mjs b/packages/realm-network-transport/rollup.config.mjs deleted file mode 100644 index cccaf1fee20..00000000000 --- a/packages/realm-network-transport/rollup.config.mjs +++ /dev/null @@ -1,101 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -import typescript from "@rollup/plugin-typescript"; -import nodeResolve from "@rollup/plugin-node-resolve"; -import commonjs from "@rollup/plugin-commonjs"; -import dts from "rollup-plugin-dts"; - -import pkg from "./package.json" assert { type: "json" }; - -export default [ - { - input: "src/node/index.ts", - output: [ - { - file: pkg.main, - format: "cjs", - interop: "auto", - }, - { - file: pkg.module, - format: "es", - }, - ], - plugins: [ - commonjs(), - typescript({ - tsconfig: "src/node/tsconfig.json", - noEmitOnError: true, - }), - ], - external: ["abort-controller", "node-fetch"], - }, - { - input: "src/dom/index.ts", - output: [ - { - file: pkg.browser[pkg.main], - format: "cjs", - interop: "auto", - }, - { - file: pkg.browser[pkg.module], - format: "es", - }, - ], - plugins: [ - typescript({ - tsconfig: "src/dom/tsconfig.json", - noEmitOnError: true, - }), - nodeResolve({ - browser: true, - resolveOnly: ["@realm/common"], - }), - ], - }, - { - input: "src/react-native/index.ts", - output: [ - { - file: pkg["react-native"], - format: "es", - }, - ], - plugins: [ - nodeResolve({ - mainFields: ["react-native", "browser", "module", "main"], - exportConditions: ["react-native", "browser", "module", "main"], - resolveOnly: ["@realm/common"], - }), - typescript({ - tsconfig: "src/react-native/tsconfig.json", - noEmitOnError: true, - }), - ], - }, - { - input: "types/generated/index.d.ts", - output: { - file: pkg.types, - format: "es", - }, - plugins: [dts(), nodeResolve()], - }, -]; diff --git a/packages/realm-network-transport/src/DefaultNetworkTransport.ts b/packages/realm-network-transport/src/DefaultNetworkTransport.ts deleted file mode 100644 index a1f936669da..00000000000 --- a/packages/realm-network-transport/src/DefaultNetworkTransport.ts +++ /dev/null @@ -1,117 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -import { deriveStatusText } from "./status-text"; -import type { - NetworkTransport, - Request, - ResponseHandler, - Headers, - Fetch, - AbortController, - FetchResponse, -} from "./types"; - -export class DefaultNetworkTransport implements NetworkTransport { - public static fetch: Fetch; - public static AbortController: AbortController; - // A hook to transform responses before being returned from fetch - public static transformResponse: (response: FetchResponse) => FetchResponse = (response) => response; - public static extraFetchOptions: Record | undefined; - - public static DEFAULT_HEADERS = { - "Content-Type": "application/json", - }; - - private static createTimeoutSignal(timeoutMs: number | undefined) { - if (typeof timeoutMs === "number") { - const controller = new DefaultNetworkTransport.AbortController(); - // Call abort after a specific number of milliseconds - const timeout = setTimeout(() => { - controller.abort(); - }, timeoutMs); - return { - signal: controller.signal, - cancelTimeout: () => { - clearTimeout(timeout); - }, - }; - } else { - return { - signal: undefined, - cancelTimeout: () => { - /* No-op */ - }, - }; - } - } - - constructor() { - if (!DefaultNetworkTransport.fetch) { - throw new Error("DefaultNetworkTransport.fetch must be set before it's used"); - } - if (!DefaultNetworkTransport.AbortController) { - throw new Error("DefaultNetworkTransport.AbortController must be set before it's used"); - } - } - - /** @deprecated Not used by the `bindgen` SDK and can be deleted */ - public fetchWithCallbacks(request: Request, handler: ResponseHandler): void { - // tslint:disable-next-line: no-console - this.fetch(request) - .then(async (response) => { - const decodedBody = await response.text(); - // Pull out the headers of the response - const responseHeaders: Headers = {}; - response.headers.forEach((value, key) => { - responseHeaders[key] = value; - }); - return { - statusCode: response.status, - headers: responseHeaders, - body: decodedBody, - }; - }) - .then((r) => handler.onSuccess(r)) - .catch((e) => handler.onError(e)); - } - - public async fetch(request: Request): Promise { - const { timeoutMs, url, ...rest } = request; - const { signal, cancelTimeout } = DefaultNetworkTransport.createTimeoutSignal(timeoutMs); - try { - // Awaiting the response to cancel timeout on errors - const response = await DefaultNetworkTransport.fetch(url, { - ...DefaultNetworkTransport.extraFetchOptions, - signal, // Used to signal timeouts - ...rest, - }); - // A bug in the React Native fetch polyfill leaves the statusText empty - if (response.statusText === "") { - const statusText = deriveStatusText(response.status); - // @ts-expect-error Assigning to a read-only property - response.statusText = statusText; - } - // Wraps the body of the request in an iterable interface - return DefaultNetworkTransport.transformResponse(response); - } finally { - // Whatever happens, cancel any timeout - cancelTimeout(); - } - } -} diff --git a/packages/realm-network-transport/src/dom/index.ts b/packages/realm-network-transport/src/dom/index.ts deleted file mode 100644 index 7aed69cfdbd..00000000000 --- a/packages/realm-network-transport/src/dom/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -import { safeGlobalThis } from "@realm/common"; - -export * from "../index"; - -import { DefaultNetworkTransport } from "../DefaultNetworkTransport"; -import { AbortController, Fetch } from "../types"; - -DefaultNetworkTransport.fetch = safeGlobalThis.fetch.bind(safeGlobalThis) as Fetch; -DefaultNetworkTransport.AbortController = safeGlobalThis.AbortController.bind(safeGlobalThis) as AbortController; diff --git a/packages/realm-network-transport/src/dom/tsconfig.json b/packages/realm-network-transport/src/dom/tsconfig.json deleted file mode 100644 index 93d27a90138..00000000000 --- a/packages/realm-network-transport/src/dom/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "lib": [ - "ES2018", - "ES2018.AsyncIterable", - "DOM" - ] - }, - "exclude": [ - "../node/*", - "../react-native/*", - "../**/*.test.ts" - ] -} \ No newline at end of file diff --git a/packages/realm-network-transport/src/index.ts b/packages/realm-network-transport/src/index.ts deleted file mode 100644 index 6a993c144c6..00000000000 --- a/packages/realm-network-transport/src/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -export * from "./types"; -export { DefaultNetworkTransport } from "./DefaultNetworkTransport"; diff --git a/packages/realm-network-transport/src/node/tsconfig.json b/packages/realm-network-transport/src/node/tsconfig.json deleted file mode 100644 index 6b8f5ab935f..00000000000 --- a/packages/realm-network-transport/src/node/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "noResolve": false, - "types": [ - "node", - "node-fetch", - "abort-controller" - ] - }, - "exclude": [ - "../dom/*", - "../react-native/*", - "../**/*.test.ts" - ] -} \ No newline at end of file diff --git a/packages/realm-network-transport/src/react-native/index.ts b/packages/realm-network-transport/src/react-native/index.ts deleted file mode 100644 index 121d9ca3621..00000000000 --- a/packages/realm-network-transport/src/react-native/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -import { safeGlobalThis } from "@realm/common"; -import { makeRequestBodyIterable } from "../IterableReadableStream"; - -export * from "../index"; - -import { DefaultNetworkTransport } from "../DefaultNetworkTransport"; -import { AbortController, Fetch } from "../types"; - -DefaultNetworkTransport.fetch = safeGlobalThis.fetch.bind(safeGlobalThis) as Fetch; -DefaultNetworkTransport.AbortController = safeGlobalThis.AbortController.bind(safeGlobalThis) as AbortController; -DefaultNetworkTransport.transformResponse = makeRequestBodyIterable; - -// Setting this non-standard option to enable text streaming -// See https://github.com/react-native-community/fetch#enable-text-streaming -DefaultNetworkTransport.extraFetchOptions = { - reactNative: { textStreaming: true }, -}; diff --git a/packages/realm-network-transport/src/react-native/tsconfig.json b/packages/realm-network-transport/src/react-native/tsconfig.json deleted file mode 100644 index 55542456272..00000000000 --- a/packages/realm-network-transport/src/react-native/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "lib": [ - "ES2018", - "ES2018.AsyncIterable", - "DOM" - ] - }, - "exclude": [ - "../node/*", - "../dom/*", - "../**/*.test.ts" - ] -} \ No newline at end of file diff --git a/packages/realm-network-transport/src/tests/DefaultNetworkTransport.test.ts b/packages/realm-network-transport/src/tests/DefaultNetworkTransport.test.ts deleted file mode 100644 index 2690bb890dd..00000000000 --- a/packages/realm-network-transport/src/tests/DefaultNetworkTransport.test.ts +++ /dev/null @@ -1,184 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -import { expect } from "chai"; -import { createServer, RequestListener, Server } from "http"; - -import { DefaultNetworkTransport } from "../DefaultNetworkTransport"; -import { NetworkTransport, Request, CallbackResponse } from "../types"; - -function getServerUrl(server: Server) { - const address = server.address(); - if (address && typeof address === "object") { - return `http://localhost:${address.port}`; - } else { - throw new Error("Unable to determine server URL"); - } -} - -async function createTestServer(listener: RequestListener): Promise { - const server = createServer(listener); - await new Promise((resolve) => server.listen(0, () => resolve(server))); - return server; -} - -describe("DefaultNetworkTransport", () => { - // Create some tests! - it("constructs", () => { - const transport = new DefaultNetworkTransport(); - expect(transport).is.instanceOf(DefaultNetworkTransport); - }); - - describe("requesting with fetchAndParse", () => { - it("sends and receives GET requests", async () => { - const transport = new DefaultNetworkTransport(); - const server = await createTestServer((req, res) => { - expect(req.headers.accept).equals("application/json"); - expect(req.headers["content-type"]).equals(undefined); - res.setHeader("content-type", "application/json"); - res.end(JSON.stringify({ pong: "Hi GET request" })); - }); - - try { - const url = getServerUrl(server); - const response = await transport.fetch({ - method: "GET", - url, - headers: { - accept: "application/json", - }, - }); - const responseBody = await response.json(); - expect(responseBody).deep.equals({ pong: "Hi GET request" }); - } finally { - server.close(); - } - }); - - it("sends, receives and parses POST requests", async () => { - const transport = new DefaultNetworkTransport(); - const server = await createTestServer((req, res) => { - expect(req.headers.accept).equals("application/json"); - expect(req.headers["content-type"]).equals("application/json"); - res.setHeader("content-type", "application/json"); - req.once("data", (chunk: Buffer) => { - const body = chunk.toString("utf8"); - const parsedBody = JSON.parse(body); - const encodedResponseBody = JSON.stringify({ - pong: parsedBody.ping + " World", - }); - res.end(encodedResponseBody); - }); - }); - - try { - const url = getServerUrl(server); - const response = await transport.fetch({ - method: "POST", - url, - body: JSON.stringify({ ping: "Hello" }), - headers: { - accept: "application/json", - "content-type": "application/json", - }, - }); - const responseBody = await response.json(); - expect(responseBody).deep.equals({ pong: "Hello World" }); - } finally { - server.close(); - } - }); - }); - - describe("requesting with fetchWithCallbacks", () => { - function fetchWithCallbacksPromised(transport: NetworkTransport, request: Request) { - return new Promise((resolve, reject) => { - transport.fetchWithCallbacks(request, { - onSuccess: resolve, - onError: reject, - }); - }); - } - - it("sends and receives GET requests", async () => { - const transport = new DefaultNetworkTransport(); - const server = await createTestServer((req, res) => { - expect(req.headers.accept).equals("application/json"); - expect(req.headers["content-type"]).equals("application/json"); - res.setHeader("content-type", "application/json"); - res.end(JSON.stringify({ pong: "Hi GET request" })); - }); - - try { - const url = getServerUrl(server); - const response = await fetchWithCallbacksPromised(transport, { - url, - method: "GET", - headers: { - accept: "application/json", - "content-type": "application/json", - }, - }); - expect(response.statusCode).equals(200); - const decodedBody = JSON.parse(response.body); - expect(decodedBody).deep.equals({ - pong: "Hi GET request", - }); - // Call the method; - } finally { - server.close(); - } - }); - - it("sends, receives and parses POST requests", async () => { - const transport = new DefaultNetworkTransport(); - const server = await createTestServer((req, res) => { - expect(req.headers.accept).equals("application/json"); - expect(req.headers["content-type"]).equals("application/json"); - res.setHeader("content-type", "application/json"); - req.once("data", (chunk: Buffer) => { - const body = chunk.toString("utf8"); - const parsedBody = JSON.parse(body); - const encodedResponseBody = JSON.stringify({ - pong: parsedBody.ping + " World", - }); - res.end(encodedResponseBody); - }); - }); - - try { - const url = getServerUrl(server); - const response = await fetchWithCallbacksPromised(transport, { - method: "POST", - url, - body: JSON.stringify({ ping: "Hello" }), - headers: { - accept: "application/json", - "content-type": "application/json", - }, - }); - const decodedBody = JSON.parse(response.body); - expect(decodedBody).deep.equals({ pong: "Hello World" }); - } finally { - server.close(); - } - }); - }); - - // TODO: Test timeouts -}); diff --git a/packages/realm-network-transport/src/tests/tsconfig.json b/packages/realm-network-transport/src/tests/tsconfig.json deleted file mode 100644 index fdcf92daec0..00000000000 --- a/packages/realm-network-transport/src/tests/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "noResolve": false, - "types": [ - "mocha", - "chai", - "node" - ] - } -} \ No newline at end of file diff --git a/packages/realm-network-transport/src/types.ts b/packages/realm-network-transport/src/types.ts deleted file mode 100644 index 4f4d42f0d76..00000000000 --- a/packages/realm-network-transport/src/types.ts +++ /dev/null @@ -1,217 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -export type Method = "GET" | "POST" | "DELETE" | "PUT" | "PATCH"; - -export type Headers = { [name: string]: string }; - -export interface Request extends FetchRequestInit { - method: Method; - url: string; - timeoutMs?: number; -} - -export interface CallbackResponse { - statusCode: number; - headers: Headers; - body: string; -} - -export type SuccessCallback = (response: CallbackResponse) => void; - -export type ErrorCallback = (err: Error) => void; - -export interface ResponseHandler { - onSuccess: SuccessCallback; - onError: ErrorCallback; -} - -export interface NetworkTransport { - fetch(request: Request): Promise; - fetchWithCallbacks(request: Request, handler: ResponseHandler): void; -} - -// AbortController - -type AbortSignal = unknown; - -/** A controller object that allows you to abort one or more DOM requests as and when desired. */ - -export type AbortController = { - /** - * The prototype of an instance is the class. - */ - prototype: AbortController; - - /** - * Constructs an AbortController. - */ - new (): AbortController; - - /** - * Returns the AbortSignal object associated with this object. - */ - readonly signal: AbortSignal; - /** - * Invoking this method will set this object's AbortSignal's aborted flag and signal to any observers that the associated activity is to be aborted. - */ - abort(): void; -}; - -/** - * Minimal types for a ReadableStream. - * @todo Missing declarations for `constructor`, `pipeThrough`, `pipeTo` and `tee`. - */ - -export type ReadableStream = { - /** - * Creates a reader and locks the stream to it. - * While the stream is locked, no other reader can be acquired until this one is released. - * @see https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/getReader - */ - getReader(): StreamReader; - - /** - * Cancel is used when you've completely finished with the stream and don't need any more data from it, even if there are chunks enqueued waiting to be read. - * @return a Promise that resolves when the stream is canceled. - * @see https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/cancel - */ - cancel: (reason?: string) => Promise; -} & AsyncIterable; - -export type StreamReader = { - /** - * a Promise that fulfills when the stream closes, or rejects if the stream throws an error or the reader's lock is released. - */ - closed: Promise; - /** - * Cancel is used when you've completely finished with the stream and don't need any more data from it, even if there are chunks enqueued waiting to be read. - * @param reason A human-readable reason for the cancellation. - * @returns a Promise that resolves when the stream is canceled. Calling this method signals a loss of interest in the stream by a consumer. - * @see https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/cancel - */ - cancel(reason?: string): Promise; - /** - * @returns a Promise providing access to the next chunk in the stream's internal queue. - * @see https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/read - */ - read(): Promise<{ value: T; done: false } | { value: undefined; done: true }>; - /** - * Releases the reader's lock on the stream. - * @see https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/releaseLock - */ - releaseLock(): void; -} & AsyncIterable; - -// Environment independent Fetch API - -type FetchRequestInfo = FetchRequest | string; -type FetchHeadersInit = FetchHeaders | string[][] | Record; -type FetchRequestCredentials = "include" | "omit" | "same-origin"; -type FetchRequestMode = "cors" | "navigate" | "no-cors" | "same-origin"; - -interface FetchBody { - readonly body: ReadableStream | null; - readonly bodyUsed: boolean; - arrayBuffer(): Promise; - blob(): Promise; - json(): Promise; - text(): Promise; -} - -export interface FetchHeaders { - append(name: string, value: string): void; - delete(name: string): void; - get(name: string): string | null; - has(name: string): boolean; - set(name: string, value: string): void; - forEach(callback: (value: string, name: string) => void): void; -} - -/** This Fetch API interface represents a resource request. */ -export interface FetchRequest extends FetchBody { - /** - * Returns a Headers object consisting of the headers associated with request. Note that headers added in the network layer by the user agent will not be accounted for in this object, e.g., the "Host" header. - */ - readonly headers: FetchHeaders; - /** - * Returns a boolean indicating whether or not request can outlive the global in which it was created. - */ - readonly keepalive: boolean; - /** - * Returns request's HTTP method, which is "GET" by default. - */ - readonly method: string; - /** - * Returns the signal associated with request, which is an AbortSignal object indicating whether or not request has been aborted, and its abort event handler. - */ - readonly signal: AbortSignal; - /** - * Returns the URL of request as a string. - */ - readonly url: string; - clone(): FetchRequest; -} - -export interface FetchResponse extends FetchBody { - readonly headers: FetchHeaders; - readonly ok: boolean; - readonly redirected: boolean; - readonly status: number; - readonly statusText: string; - readonly type: unknown; - readonly url: string; - clone(): FetchResponse; -} - -export interface FetchRequestInit { - /** - * A BodyInit object or null to set request's body. - */ - body?: RequestBody; - /** - * A string indicating whether credentials will be sent with the request always, never, or only when sent to a same-origin URL. Sets request's credentials. - */ - credentials?: FetchRequestCredentials; - /** - * A Headers object, an object literal, or an array of two-item arrays to set request's headers. - */ - headers?: FetchHeadersInit; - /** - * A cryptographic hash of the resource to be fetched by request. Sets request's integrity. - */ - integrity?: string; - /** - * A boolean to set request's keepalive. - */ - keepalive?: boolean; - /** - * A string to set request's method. - */ - method?: string; - /** - * A string to indicate whether the request will use CORS, or will be restricted to same-origin URLs. Sets request's mode. - */ - mode?: FetchRequestMode; - /** - * An AbortSignal to set request's signal. - */ - signal?: AbortSignal | null; -} - -export type Fetch = (input: FetchRequestInfo, init?: FetchRequestInit) => Promise; diff --git a/packages/realm-network-transport/test/mocha.opts b/packages/realm-network-transport/test/mocha.opts deleted file mode 100644 index 422b5e4e57c..00000000000 --- a/packages/realm-network-transport/test/mocha.opts +++ /dev/null @@ -1,3 +0,0 @@ ---watch-extensions ts ---require ts-node/register/transpile-only ---file src/node/index.ts diff --git a/packages/realm-network-transport/tsconfig.cjs.json b/packages/realm-network-transport/tsconfig.cjs.json deleted file mode 100644 index c5271d797b5..00000000000 --- a/packages/realm-network-transport/tsconfig.cjs.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "CommonJS", - "esModuleInterop": true - } -} \ No newline at end of file diff --git a/packages/realm-network-transport/tsconfig.json b/packages/realm-network-transport/tsconfig.json deleted file mode 100644 index c11e1982063..00000000000 --- a/packages/realm-network-transport/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "target": "es2018", - "forceConsistentCasingInFileNames": true, - "esModuleInterop": true, - "noResolve": true, - "typeRoots": [ - "./types", - "./node_modules/@types" - ], - "types": [ - "@realm/common" - ], - "lib": [ - "ES2018", - "ES2018.AsyncIterable" - ] - }, - "include": [ - "src/**/*.ts", - "types/index.d.ts", - "types/generated/app.d.ts" - ], - "exclude": [] -} \ No newline at end of file diff --git a/packages/realm-network-transport/tsconfig.types.json b/packages/realm-network-transport/tsconfig.types.json deleted file mode 100644 index cffbae3ddee..00000000000 --- a/packages/realm-network-transport/tsconfig.types.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "types": [], - "emitDeclarationOnly": true - }, - "exclude": [ - "src/node/index.ts", - "src/dom/index.ts", - "src/react-native/index.ts", - "src/**/*.test.ts" - ] -} \ No newline at end of file diff --git a/packages/realm-network-transport/types/abort-controller/index.d.ts b/packages/realm-network-transport/types/abort-controller/index.d.ts deleted file mode 100644 index 4b65e86f2da..00000000000 --- a/packages/realm-network-transport/types/abort-controller/index.d.ts +++ /dev/null @@ -1,27 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -// Unfortunately no @types/abort-controller package has been published. - -declare module "abort-controller" { - class AbortController { - readonly signal: AbortSignal; - abort(): void; - } - export default AbortController; -} diff --git a/packages/realm-network-transport/types/index.d.ts b/packages/realm-network-transport/types/index.d.ts deleted file mode 100644 index 0eb020a2867..00000000000 --- a/packages/realm-network-transport/types/index.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Timer related stuff -type TimerHandler = string | Function; -declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; -declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; -declare function clearInterval(handle?: number): void; -declare function clearTimeout(handle?: number): void; diff --git a/packages/realm-network-transport/types/node/index.d.ts b/packages/realm-network-transport/types/node/index.d.ts deleted file mode 100644 index aa8c7842342..00000000000 --- a/packages/realm-network-transport/types/node/index.d.ts +++ /dev/null @@ -1,30 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -declare module "node" { - namespace NodeJS { - // Workaround for missing types. - // @see https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/64921 - interface ReadableStream { - // https://nodejs.org/api/webstreams.html#readablestreamcancelreason - cancel(reason?: any): Promise; - // https://nodejs.org/api/webstreams.html#readablestreamgetreaderoptions - getReader(options?: { mode?: string }): unknown; - } - } -} diff --git a/packages/realm-react/package.json b/packages/realm-react/package.json index c5c6680cddb..a2deb1f4ede 100644 --- a/packages/realm-react/package.json +++ b/packages/realm-react/package.json @@ -35,7 +35,8 @@ "dependencies": [ "../realm:bundle", "../realm:build:node", - "../realm-common:bundle" + "../realm-common:bundle", + "../realm-app-importer:type-check" ] }, "docs": { diff --git a/packages/realm-tools/package.json b/packages/realm-tools/package.json index 6280646caa8..77f173f2c1c 100644 --- a/packages/realm-tools/package.json +++ b/packages/realm-tools/package.json @@ -54,8 +54,6 @@ "ts-command-line-args": "^2.2.1" }, "devDependencies": { - "@types/command-line-args": "^5.2.0", - "@types/node": "^18.15.10", - "typescript": "^4.6.4" + "@types/command-line-args": "^5.2.0" } } diff --git a/packages/realm-web-integration-tests/package.json b/packages/realm-web-integration-tests/package.json index 1416ad8c4c7..40c5351abfd 100644 --- a/packages/realm-web-integration-tests/package.json +++ b/packages/realm-web-integration-tests/package.json @@ -48,14 +48,12 @@ "@types/chai": "^4.3.1", "@types/fs-extra": "^9.0.13", "@types/mocha": "^8", - "@types/node-fetch": "^2.6.2", "@types/puppeteer": "^5.4.6", "@types/webpack": "^5.28.0", "@types/webpack-env": "^1.17.0", "fs-extra": "^10.1.0", "html-webpack-plugin": "^5.5.0", "mongodb-realm-cli": "^1.3.2", - "node-fetch": "^3.2.5", "source-map-loader": "^4.0.2", "ts-loader": "^9.3.0", "webpack-cli": "^4.9.2" diff --git a/packages/realm-web-integration-tests/tsconfig.json b/packages/realm-web-integration-tests/tsconfig.json index fbf7aadbb0e..c402f161d8d 100644 --- a/packages/realm-web-integration-tests/tsconfig.json +++ b/packages/realm-web-integration-tests/tsconfig.json @@ -12,7 +12,6 @@ "mocha", "puppeteer", "realm-web", - "node-fetch", "fs-extra", "mocha-remote-client", "jwt-encode" diff --git a/packages/realm-web/README.md b/packages/realm-web/README.md index 3b3154c132f..6fada8729d9 100644 --- a/packages/realm-web/README.md +++ b/packages/realm-web/README.md @@ -28,10 +28,12 @@ As a script-tag in the head of a browser: - A limited selection of [services](https://docs.mongodb.com/stitch/services/) are implemented at the moment: - MongoDB: Read, write and watch MongoDB documents. + \ No newline at end of file diff --git a/packages/realm-web/package.json b/packages/realm-web/package.json index b1f76206838..62e9a0bae10 100644 --- a/packages/realm-web/package.json +++ b/packages/realm-web/package.json @@ -23,7 +23,8 @@ "bundle": { "command": "rollup --config", "dependencies": [ - "generate-types" + "generate-types", + "../fetch:build" ], "files": [ "rollup.config.mjs", @@ -39,8 +40,7 @@ "generate-types": { "command": "tsc --project src/dom/tsconfig.json --declaration --emitDeclarationOnly --declarationDir types/generated", "dependencies": [ - "../realm-common:bundle", - "../realm-network-transport:bundle" + "../realm-common:bundle" ], "files": [ "src/**/*.ts", @@ -88,21 +88,17 @@ "detect-browser": "^5.2.1", "js-base64": "^3.7.2" }, - "optionalDependencies": { - "abort-controller": "^3", - "node-fetch": "^3" - }, "devDependencies": { + "@realm/fetch": "^0.1.0", "@types/chai": "^4.2.9", "@types/fs-extra": "^8.1.0", "@types/js-base64": "^3.3.1", "@types/mocha": "^10.0.6", - "@types/node": "^18.15.10", - "abort-controller": "^3.0.0", "chai": "4.3.6", "fs-extra": "^10.0.0", - "mocha": "^10.2.0", - "node-fetch": "^3.3.2", - "@realm/network-transport": "^0.7.2" + "mocha": "^10.2.0" + }, + "engines": { + "node": ">=18" } } diff --git a/packages/realm-web/rollup.config.mjs b/packages/realm-web/rollup.config.mjs index 67969c70d28..4d7ac465424 100644 --- a/packages/realm-web/rollup.config.mjs +++ b/packages/realm-web/rollup.config.mjs @@ -49,10 +49,14 @@ export default [ typescript({ tsconfig: "src/node/tsconfig.json", }), - nodeResolve(), + nodeResolve({ + mainFields: ["module", "main"], + exportConditions: ["node", "module", "main"], + modulesOnly: true, + }), replacer, ], - external: ["bson", "node-fetch", "abort-controller"], + external: ["bson"], }, { input: "src/dom/index.ts", @@ -71,12 +75,14 @@ export default [ ], plugins: [ commonjs(), + nodeResolve({ + mainFields: ["browser", "module", "main"], + exportConditions: ["browser", "module", "main"], + modulesOnly: true, + }), typescript({ tsconfig: "src/dom/tsconfig.json", }), - nodeResolve({ - browser: true, - }), replacer, ], external: ["bson"], @@ -96,7 +102,9 @@ export default [ tsconfig: "src/dom/tsconfig.json", }), nodeResolve({ - browser: true, + mainFields: ["browser", "module", "main"], + exportConditions: ["browser", "module", "main"], + modulesOnly: true, preferBuiltins: false, }), replacer, @@ -111,10 +119,12 @@ export default [ }, plugins: [ dts({ - // Ensures that the @realm/network-transport types are included in the bundle + // Ensures that the @realm/fetch types are included in the bundle respectExternal: true, }), - nodeResolve(), + nodeResolve({ + modulesOnly: true, + }), ], external: ["bson"], }, diff --git a/packages/realm-web/src/App.ts b/packages/realm-web/src/App.ts index 40f36d96fe9..c9a84f8161a 100644 --- a/packages/realm-web/src/App.ts +++ b/packages/realm-web/src/App.ts @@ -16,7 +16,7 @@ // //////////////////////////////////////////////////////////////////////////// -import { NetworkTransport, DefaultNetworkTransport } from "@realm/network-transport"; +import { fetch } from "@realm/fetch"; import { ObjectId } from "bson"; import { User, UserState } from "./User"; @@ -26,7 +26,7 @@ import { Storage } from "./storage"; import { AppStorage } from "./AppStorage"; import { getEnvironment } from "./environment"; import { AuthResponse, Authenticator } from "./Authenticator"; -import { Fetcher, UserContext } from "./Fetcher"; +import { FetchFunction, Fetcher, UserContext } from "./Fetcher"; import routes from "./routes"; import { DeviceInformation, DEVICE_ID_STORAGE_KEY } from "./DeviceInformation"; @@ -44,7 +44,7 @@ export interface AppConfiguration extends Realm.AppConfiguration { /** * Transport to use when fetching resources. */ - transport?: NetworkTransport; + fetch?: FetchFunction; /** * Used when persisting app state, such as tokens of authenticated users. */ @@ -152,18 +152,17 @@ export class App< this._locationUrl = Promise.resolve(this.baseUrl); } this.localApp = configuration.app; - const { storage, transport = new DefaultNetworkTransport() } = configuration; // Construct a fetcher wrapping the network transport this.fetcher = new Fetcher({ appId: this.id, userContext: this as UserContext, locationUrlContext: this, - transport, + fetch: configuration.fetch ?? fetch, }); // Construct the auth providers this.emailPasswordAuth = new EmailPasswordAuth(this.fetcher); // Construct the storage - const baseStorage = storage || getEnvironment().defaultStorage; + const baseStorage = configuration.storage || getEnvironment().defaultStorage; this.storage = new AppStorage(baseStorage, this.id); this.authenticator = new Authenticator(this.fetcher, baseStorage, () => this.deviceInformation); // Hydrate the app state from storage diff --git a/packages/realm-web/src/Fetcher.ts b/packages/realm-web/src/Fetcher.ts index adf6b20c1ae..57bc30140bd 100644 --- a/packages/realm-web/src/Fetcher.ts +++ b/packages/realm-web/src/Fetcher.ts @@ -16,7 +16,7 @@ // //////////////////////////////////////////////////////////////////////////// -import { NetworkTransport, Request, FetchResponse, Headers } from "@realm/network-transport"; +import { fetch, RequestInit, Response } from "@realm/fetch"; import { MongoDBRealmError } from "./MongoDBRealmError"; @@ -24,33 +24,38 @@ import { User } from "./User"; import routes from "./routes"; import { deserialize, serialize } from "./utils/ejson"; -type SimpleObject = Record; +/** + * Some fetch function + */ +export type FetchFunction = typeof fetch; -type StreamReader = { - closed: boolean; - cancel(reason?: string): Promise; - read(): Promise<{ value: T | undefined; done: boolean }>; - releaseLock(): void; -}; -type ReadableStream = { getReader(): StreamReader; cancel: () => void }; +type SimpleObject = Record; /** - * @param body A possible resonse body. + * @param response A possible response. + * @param response.body A possible response body. * @returns An async iterator. */ -function asyncIteratorFromResponseBody(body: unknown): AsyncIterable { +function asyncIteratorFromResponseBody({ body }: Response): AsyncIterable { if (typeof body !== "object" || body === null) { throw new Error("Expected a non-null object"); } else if (Symbol.asyncIterator in body) { return body as AsyncIterable; } else if ("getReader" in body) { - const stream = body as ReadableStream; return { [Symbol.asyncIterator]() { - const reader = stream.getReader(); + const reader = body.getReader(); return { - next() { - return reader.read(); + async next() { + const { done, value } = await reader.read(); + if (done) { + // TODO: Simply return the result once https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1676 is merged and released + return { done, value: undefined }; + } else if (value instanceof Uint8Array) { + return { done, value }; + } else { + throw new Error("Expected value to be Uint8Array"); + } }, async return() { await reader.cancel(); @@ -84,11 +89,12 @@ export type LocationUrlContext = { type TokenType = "access" | "refresh" | "none"; -interface RequestWithUrl extends Request { +interface RequestWithUrl extends RequestInit { path?: never; + url: string; } -interface RequestWithPath extends Omit, "url"> { +interface RequestWithPath extends Omit, "url"> { /** Construct a URL from the location URL prepended is path */ path: string; url?: never; @@ -118,9 +124,9 @@ export type FetcherConfig = { */ appId: string; /** - * The underlying network transport. + * The underlying fetch function. */ - transport: NetworkTransport; + fetch: FetchFunction; /** * An object which can be used to determine the currently active user. */ @@ -132,7 +138,7 @@ export type FetcherConfig = { }; /** - * Wraps a NetworkTransport from the "@realm/network-transport" package. + * Wraps the fetch from the "@realm/fetch" package. * Extracts error messages and throws `MongoDBRealmError` objects upon failures. * Injects access or refresh tokens for a current or specific user. * Refreshes access tokens if requests fails due to a 401 error. @@ -145,7 +151,7 @@ export class Fetcher implements LocationUrlContext { * @param tokenType The type of token (access or refresh). * @returns An object containing the user's token as "Authorization" header or undefined if no user is given. */ - private static buildAuthorizationHeader(user: User | null, tokenType: TokenType): Headers { + private static buildAuthorizationHeader(user: User | null, tokenType: TokenType): Record { if (!user || tokenType === "none") { return {}; } else if (tokenType === "access") { @@ -178,7 +184,7 @@ export class Fetcher implements LocationUrlContext { * @param body The body string or object passed from a request. * @returns An object optionally specifying the "Content-Type" header. */ - private static buildJsonHeader(body: string | undefined): Headers { + private static buildJsonHeader(body: string | undefined): Record { if (body && body.length > 0) { return { "Content-Type": "application/json" }; } else { @@ -186,34 +192,22 @@ export class Fetcher implements LocationUrlContext { } } - /** - * The id of the app, which this Fetcher was created for. - */ - private readonly appId: string; - private readonly transport: NetworkTransport; - private readonly userContext: UserContext; - private readonly locationUrlContext: LocationUrlContext; + private readonly config: FetcherConfig; /** * @param config A configuration of the fetcher. * @param config.appId The application id. - * @param config.transport The transport used when fetching. + * @param config.fetch The fetch function used when fetching. * @param config.userContext An object used to determine the requesting user. * @param config.locationUrlContext An object used to determine the location / base URL. */ - constructor({ appId, transport, userContext, locationUrlContext }: FetcherConfig) { - this.appId = appId; - this.transport = transport; - this.userContext = userContext; - this.locationUrlContext = locationUrlContext; + constructor(config: FetcherConfig) { + this.config = config; } clone(config: Partial): Fetcher { return new Fetcher({ - appId: this.appId, - transport: this.transport, - userContext: this.userContext, - locationUrlContext: this.locationUrlContext, + ...this.config, ...config, }); } @@ -223,19 +217,18 @@ export class Fetcher implements LocationUrlContext { * @param request The request which should be sent to the server. * @returns The response from the server. */ - public async fetch(request: AuthenticatedRequest): Promise { - const { path, url, tokenType = "access", user = this.userContext.currentUser, ...restOfRequest } = request; + public async fetch(request: AuthenticatedRequest): Promise { + const { path, url, tokenType = "access", user = this.config.userContext.currentUser, ...restOfRequest } = request; if (typeof path === "string" && typeof url === "string") { throw new Error("Use of 'url' and 'path' mutually exclusive"); } else if (typeof path === "string") { // Derive the URL - const url = (await this.locationUrlContext.locationUrl) + path; + const url = (await this.config.locationUrlContext.locationUrl) + path; return this.fetch({ ...request, path: undefined, url }); } else if (typeof url === "string") { - const response = await this.transport.fetch({ + const response = await this.config.fetch(url, { ...restOfRequest, - url, headers: { ...Fetcher.buildAuthorizationHeader(user, tokenType), ...request.headers, @@ -257,7 +250,7 @@ export class Fetcher implements LocationUrlContext { user.refreshToken = null; } // Throw an error with a message extracted from the body - throw await MongoDBRealmError.fromRequestAndResponse(request as Request, response); + throw await MongoDBRealmError.fromRequestAndResponse(url, request, response); } } else { throw new Error("Expected either 'url' or 'path'"); @@ -303,27 +296,27 @@ export class Fetcher implements LocationUrlContext { public async fetchStream( request: AuthenticatedRequest, ): Promise> { - const { body } = await this.fetch({ + const response = await this.fetch({ ...request, headers: { Accept: "text/event-stream", ...request.headers, }, }); - return asyncIteratorFromResponseBody(body); + return asyncIteratorFromResponseBody(response); } /** * @returns The path of the app route. */ public get appRoute() { - return routes.api().app(this.appId); + return routes.api().app(this.config.appId); } /** * @returns A promise of the location URL of the app. */ public get locationUrl(): Promise { - return this.locationUrlContext.locationUrl; + return this.config.locationUrlContext.locationUrl; } } diff --git a/packages/realm-web/src/MongoDBRealmError.ts b/packages/realm-web/src/MongoDBRealmError.ts index 97a50f4c2e5..74a2202b1f2 100644 --- a/packages/realm-web/src/MongoDBRealmError.ts +++ b/packages/realm-web/src/MongoDBRealmError.ts @@ -16,7 +16,7 @@ // //////////////////////////////////////////////////////////////////////////// -import { Method, FetchResponse, Request } from "@realm/network-transport"; +import { RequestInit, Response } from "@realm/fetch"; // TODO: Determine if the shape of an error response is specific to each service or widely used. @@ -27,7 +27,7 @@ export class MongoDBRealmError extends Error { /** * The method used when requesting. */ - public readonly method: Method; + public readonly method: string; /** * The URL of the resource which got fetched. */ @@ -56,15 +56,17 @@ export class MongoDBRealmError extends Error { /** * Constructs and returns an error from a request and a response. * Note: The caller must throw this error themselves. + * @param url The url of the requested resource. * @param request The request sent to the server. * @param response A raw response, as returned from the server. * @returns An error from a request and a response. */ public static async fromRequestAndResponse( - request: Request, - response: FetchResponse, + url: string, + request: RequestInit, + response: Response, ): Promise { - const { url, method } = request; + const { method = "unknown" } = request; const { status, statusText } = response; if (response.headers.get("content-type")?.startsWith("application/json")) { const body = await response.json(); @@ -85,7 +87,7 @@ export class MongoDBRealmError extends Error { } constructor( - method: Method, + method: string, url: string, statusCode: number, statusText: string, diff --git a/packages/realm-web/src/dom/tsconfig.json b/packages/realm-web/src/dom/tsconfig.json index 134026e4315..8a36e6ef726 100644 --- a/packages/realm-web/src/dom/tsconfig.json +++ b/packages/realm-web/src/dom/tsconfig.json @@ -4,14 +4,14 @@ "noResolve": true, "types": [ "@realm/common", - "@realm/network-transport", + "@realm/fetch", "js-base64", "bson", "buffer", "detect-browser" ], "lib": [ - "es2019", + "es2022", "dom" ] }, diff --git a/packages/realm-web/src/node/tsconfig.json b/packages/realm-web/src/node/tsconfig.json index dcbc2bca976..e9a9d5f784a 100644 --- a/packages/realm-web/src/node/tsconfig.json +++ b/packages/realm-web/src/node/tsconfig.json @@ -4,14 +4,14 @@ "noResolve": false, "types": [ "@realm/common", - "@realm/network-transport", + "@realm/fetch", "js-base64", "bson", "buffer", "node" ], "lib": [ - "es2019" + "es2022" ] }, "exclude": [ diff --git a/packages/realm-web/src/tests/App.test.ts b/packages/realm-web/src/tests/App.test.ts index 1564337ee2f..88f798019bf 100644 --- a/packages/realm-web/src/tests/App.test.ts +++ b/packages/realm-web/src/tests/App.test.ts @@ -29,7 +29,7 @@ import { DEFAULT_AUTH_OPTIONS, INVALID_SESSION_ERROR, MockApp, - MockNetworkTransport, + createMockFetch, } from "./utils"; describe("App", () => { @@ -82,7 +82,7 @@ describe("App", () => { }); it("fetches the location first", async () => { - const transport = new MockNetworkTransport([ + const fetch = createMockFetch([ LOCATION_RESPONSE, { user_id: "totally-valid-user-id", @@ -94,13 +94,13 @@ describe("App", () => { const app = new App({ id: "my-mocked-app", storage: new MemoryStorage(), - transport, + fetch, baseUrl: "http://localhost:1234", }); const credentials = Credentials.anonymous(); await app.logIn(credentials, false); // Expect the request made it to the transport - expect(transport.requests).deep.equals([ + expect(fetch.requests).deep.equals([ LOCATION_REQUEST, { method: "POST", @@ -114,7 +114,7 @@ describe("App", () => { }); it("skips fetching the location if asked to", async () => { - const transport = new MockNetworkTransport([ + const fetch = createMockFetch([ { user_id: "totally-valid-user-id", access_token: "deadbeef", @@ -125,14 +125,14 @@ describe("App", () => { const app = new App({ id: "my-mocked-app", storage: new MemoryStorage(), - transport, + fetch, baseUrl: "http://localhost:1234", skipLocationRequest: true, }); const credentials = Credentials.anonymous(); await app.logIn(credentials, false); // Expect only a single request made via the transport - expect(transport.requests).deep.equals([ + expect(fetch.requests).deep.equals([ { method: "POST", url: `http://localhost:1234/api/client/v2.0/app/my-mocked-app/auth/providers/anon-user/login`, @@ -146,7 +146,7 @@ describe("App", () => { it("can log in a user", async () => { const storage = new MemoryStorage(); - const transport = new MockNetworkTransport([ + const fetch = createMockFetch([ { hostname: "http://localhost:1337" }, { user_id: "totally-valid-user-id", @@ -174,7 +174,7 @@ describe("App", () => { const app = new App({ id: "my-mocked-app", storage, - transport, + fetch, baseUrl: "http://localhost:1234", }); const credentials = Credentials.emailPassword("gilfoyle@testing.mongodb.com", "v3ry-s3cret"); @@ -190,7 +190,7 @@ describe("App", () => { expect(user.state).equals(UserState.Active); expect(user.isLoggedIn).equals(true); // Expect the request made it to the transport - expect(transport.requests).deep.equals([ + expect(fetch.requests).deep.equals([ LOCATION_REQUEST, { method: "POST", @@ -215,7 +215,7 @@ describe("App", () => { it("can log out a user", async () => { const storage = new MemoryStorage(); - const transport = new MockNetworkTransport([ + const fetch = createMockFetch([ { hostname: "http://localhost:1337" }, { user_id: "totally-valid-user-id", @@ -227,7 +227,7 @@ describe("App", () => { ]); const app = new App({ id: "my-mocked-app", - transport, + fetch, storage, baseUrl: "http://localhost:1234", }); @@ -246,7 +246,7 @@ describe("App", () => { expect(user.isLoggedIn).equals(false); expect(app.allUsers).deep.equals({ [user.id]: user }); // Assume the correct requests made it to the transport - expect(transport.requests).deep.equals([ + expect(fetch.requests).deep.equals([ LOCATION_REQUEST, { method: "POST", @@ -268,7 +268,7 @@ describe("App", () => { }); it("can log in a user, when another user is already logged in", async () => { - const transport = new MockNetworkTransport([ + const fetch = createMockFetch([ { hostname: "http://localhost:1337" }, { user_id: "totally-valid-user-id-1", @@ -299,7 +299,7 @@ describe("App", () => { ]); const app = new App({ id: "my-mocked-app", - transport, + fetch, baseUrl: "http://localhost:1234", }); // Log in two different users @@ -312,7 +312,7 @@ describe("App", () => { await app.logIn(credentials); } // Expect the request made it to the transport - expect(transport.requests).deep.equals([ + expect(fetch.requests).deep.equals([ LOCATION_REQUEST, { method: "POST", @@ -355,7 +355,7 @@ describe("App", () => { it("can delete a user", async () => { const storage = new MemoryStorage(); - const transport = new MockNetworkTransport([ + const fetch = createMockFetch([ { hostname: "http://localhost:1337" }, { user_id: "totally-valid-user-id", @@ -386,7 +386,7 @@ describe("App", () => { const app = new App({ id: "my-mocked-app", storage, - transport, + fetch, baseUrl: "http://localhost:1234", }); const credentials = Credentials.emailPassword("gilfoyle@testing.mongodb.com", "v3ry-s3cret"); @@ -403,7 +403,7 @@ describe("App", () => { it("can remove an active user", async () => { const storage = new MemoryStorage(); - const transport = new MockNetworkTransport([ + const fetch = createMockFetch([ { hostname: "http://localhost:1337" }, { user_id: "totally-valid-user-id", @@ -416,7 +416,7 @@ describe("App", () => { const app = new App({ id: "my-mocked-app", storage, - transport, + fetch, baseUrl: "http://localhost:1234", }); const credentials = Credentials.anonymous(); @@ -430,7 +430,7 @@ describe("App", () => { expect(user.state).equals("removed"); expect(app.allUsers).deep.equals({}); // Assume the correct requests made it to the transport - expect(transport.requests).deep.equals([ + expect(fetch.requests).deep.equals([ LOCATION_REQUEST, { method: "POST", @@ -453,7 +453,7 @@ describe("App", () => { it("throws if asked to switch to or remove an unknown user", async () => { const storage = new MemoryStorage(); - const transport = new MockNetworkTransport([ + const fetch = createMockFetch([ { hostname: "http://localhost:1337" }, { user_id: "totally-valid-user-id", @@ -465,7 +465,7 @@ describe("App", () => { const app = new App({ id: "my-mocked-app", storage, - transport, + fetch, baseUrl: "http://localhost:1234", }); const credentials = Credentials.anonymous(); @@ -501,7 +501,7 @@ describe("App", () => { expect(app.allUsers).deep.equals({ [user.id]: user }); expect(user.state).equals("active"); // Assume the correct requests made it to the transport - expect(transport.requests).deep.equals([ + expect(fetch.requests).deep.equals([ LOCATION_REQUEST, { method: "POST", @@ -644,7 +644,7 @@ describe("App", () => { it("expose a callable functions factory", async () => { const storage = new MemoryStorage(); - const transport = new MockNetworkTransport([ + const fetch = createMockFetch([ { hostname: "http://localhost:1337" }, { user_id: "totally-valid-user-id", @@ -657,7 +657,7 @@ describe("App", () => { const app = new App({ id: "my-mocked-app", storage, - transport, + fetch, baseUrl: "http://localhost:1234", }); const credentials = Credentials.anonymous(); @@ -665,7 +665,7 @@ describe("App", () => { // Call the function const response = await user.functions.hello(); expect(response).to.deep.equal({ msg: "hi there!" }); - expect(transport.requests).to.deep.equal([ + expect(fetch.requests).to.deep.equal([ LOCATION_REQUEST, { method: "POST", @@ -689,7 +689,7 @@ describe("App", () => { it("hydrates users from storage", () => { const storage = new MemoryStorage(); - const transport = new MockNetworkTransport([]); + const fetch = createMockFetch([]); // Fill data into the storage that can be hydrated const appStorage = storage.prefix("app(my-mocked-app)"); @@ -718,7 +718,7 @@ describe("App", () => { const app = new App({ id: "my-mocked-app", storage, - transport, + fetch, baseUrl: "http://localhost:1337", }); @@ -740,7 +740,7 @@ describe("App", () => { it("saves users to storage when logging in", async () => { const storage = new MemoryStorage(); - const transport = new MockNetworkTransport([ + const fetch = createMockFetch([ { hostname: "http://localhost:1337" }, { user_id: "totally-valid-user-id", @@ -752,7 +752,7 @@ describe("App", () => { const app = new App({ id: "my-mocked-app", storage, - transport, + fetch, baseUrl: "http://localhost:1234", }); @@ -773,7 +773,7 @@ describe("App", () => { const app1 = new App({ id: "my-mocked-app", storage, - transport: new MockNetworkTransport([ + fetch: createMockFetch([ LOCATION_RESPONSE, { user_id: "alices-id", @@ -802,7 +802,7 @@ describe("App", () => { const app2 = new App({ id: "my-mocked-app", storage, - transport: new MockNetworkTransport([ + fetch: createMockFetch([ LOCATION_RESPONSE, { user_id: "charlies-id", @@ -856,45 +856,46 @@ describe("App", () => { }); it("returns the same user when logged in twice", async () => { + const fetch = createMockFetch([ + LOCATION_RESPONSE, + { + user_id: "gilfoyles-id", + access_token: "gilfoyles-first-access-token", + refresh_token: "gilfoyles-first-refresh-token", + device_id: "000000000000000000000000", + }, + { + user_id: "dineshs-id", + access_token: "dineshs-first-access-token", + refresh_token: "dineshs-first-refresh-token", + device_id: "000000000000000000000000", + }, + { + user_id: "gilfoyles-id", + access_token: "gilfoyles-second-access-token", + refresh_token: "gilfoyles-second-refresh-token", + device_id: "000000000000000000000000", + }, + {}, + { + user_id: "gilfoyles-id", + access_token: "gilfoyles-third-access-token", + refresh_token: "gilfoyles-third-refresh-token", + device_id: "000000000000000000000000", + }, + {}, + { + user_id: "gilfoyles-id", + access_token: "gilfoyles-forth-access-token", + refresh_token: "gilfoyles-forth-refresh-token", + device_id: "000000000000000000000000", + }, + ]); const storage = new MemoryStorage(); const app = new App({ id: "my-mocked-app", storage, - transport: new MockNetworkTransport([ - LOCATION_RESPONSE, - { - user_id: "gilfoyles-id", - access_token: "gilfoyles-first-access-token", - refresh_token: "gilfoyles-first-refresh-token", - device_id: "000000000000000000000000", - }, - { - user_id: "dineshs-id", - access_token: "dineshs-first-access-token", - refresh_token: "dineshs-first-refresh-token", - device_id: "000000000000000000000000", - }, - { - user_id: "gilfoyles-id", - access_token: "gilfoyles-second-access-token", - refresh_token: "gilfoyles-second-refresh-token", - device_id: "000000000000000000000000", - }, - {}, - { - user_id: "gilfoyles-id", - access_token: "gilfoyles-third-access-token", - refresh_token: "gilfoyles-third-refresh-token", - device_id: "000000000000000000000000", - }, - {}, - { - user_id: "gilfoyles-id", - access_token: "gilfoyles-forth-access-token", - refresh_token: "gilfoyles-forth-refresh-token", - device_id: "000000000000000000000000", - }, - ]), + fetch, baseUrl: "http://localhost:1337", }); // Login twice with the same user diff --git a/packages/realm-web/src/tests/Fetcher.test.ts b/packages/realm-web/src/tests/Fetcher.test.ts index 6b4c3c25c97..2c333f4e8d7 100644 --- a/packages/realm-web/src/tests/Fetcher.test.ts +++ b/packages/realm-web/src/tests/Fetcher.test.ts @@ -21,13 +21,13 @@ import { expect } from "chai"; import { Fetcher } from "../Fetcher"; import { User } from "../User"; -import { MockNetworkTransport, SENDING_JSON_HEADERS, ACCEPT_JSON_HEADERS } from "./utils"; +import { SENDING_JSON_HEADERS, ACCEPT_JSON_HEADERS, createMockFetch } from "./utils"; describe("Fetcher", () => { it("constructs", () => { const fetcher = new Fetcher({ appId: "test-app-id", - transport: new MockNetworkTransport(), + fetch: createMockFetch([]), userContext: { currentUser: null }, locationUrlContext: { locationUrl: Promise.resolve("http://localhost:1337"), @@ -37,10 +37,10 @@ describe("Fetcher", () => { }); it("sends access token when requesting", async () => { - const transport = new MockNetworkTransport([{ foo: "bar" }]); + const fetch = createMockFetch([{ foo: "bar" }]); const fetcher = new Fetcher({ appId: "test-app-id", - transport, + fetch, userContext: { currentUser: { accessToken: "my-access-token" } as User, }, @@ -56,7 +56,7 @@ describe("Fetcher", () => { headers: { Cookie: "yes-please" }, }); // Expect something of the request and response - expect(transport.requests).deep.equals([ + expect(fetch.requests).deep.equals([ { method: "POST", url: "http://localhost:1337/w00t", @@ -72,10 +72,10 @@ describe("Fetcher", () => { }); it("allows overwriting headers", async () => { - const transport = new MockNetworkTransport([{}]); + const fetch = createMockFetch([{}]); const fetcher = new Fetcher({ appId: "test-app-id", - transport, + fetch, userContext: { currentUser: { accessToken: "my-access-token" } as User, }, @@ -92,7 +92,7 @@ describe("Fetcher", () => { }, }); // Expect something of the request - expect(transport.requests).deep.equals([ + expect(fetch.requests).deep.equals([ { method: "GET", url: "http://localhost:1337/w00t", diff --git a/packages/realm-web/src/tests/utils/MockApp.ts b/packages/realm-web/src/tests/utils/MockApp.ts index 7dd69923fc7..5f0f56b5122 100644 --- a/packages/realm-web/src/tests/utils/MockApp.ts +++ b/packages/realm-web/src/tests/utils/MockApp.ts @@ -19,7 +19,7 @@ import { App } from "../../App"; import { MemoryStorage } from "../../storage"; -import { MockNetworkTransport } from "./MockNetworkTransport"; +import { createMockFetch, MockFetch } from "./MockFetch"; /** * An App using the MockTransport @@ -28,7 +28,7 @@ export class MockApp extends App { /** * The mock network transport created when creating this mock app. */ - public readonly mockTransport: MockNetworkTransport; + public readonly mockFetch: MockFetch; /** * Create mocked App, useful when testing. @@ -36,19 +36,19 @@ export class MockApp extends App { * @param requests An array of requests returned by the underlying mocked network transport. */ constructor(id = "my-mocked-app", requests: unknown[] = []) { - const transport = new MockNetworkTransport(requests); + const mockFetch = createMockFetch(requests); const storage = new MemoryStorage(); super({ id, baseUrl: "http://localhost:1234", storage, - transport, + fetch: mockFetch, }); - this.mockTransport = transport; + this.mockFetch = mockFetch; } /** @returns All the requests issued via this mocked app. */ get requests() { - return this.mockTransport.requests; + return this.mockFetch.requests; } } diff --git a/packages/realm-web/src/tests/utils/MockNetworkTransport.ts b/packages/realm-web/src/tests/utils/MockFetch.ts similarity index 67% rename from packages/realm-web/src/tests/utils/MockNetworkTransport.ts rename to packages/realm-web/src/tests/utils/MockFetch.ts index 90a7d060a67..aebbb706aee 100644 --- a/packages/realm-web/src/tests/utils/MockNetworkTransport.ts +++ b/packages/realm-web/src/tests/utils/MockFetch.ts @@ -16,34 +16,26 @@ // //////////////////////////////////////////////////////////////////////////// -import { NetworkTransport, Request, FetchResponse, FetchHeaders } from "@realm/network-transport"; +import { fetch, RequestInit } from "@realm/fetch"; + +type RequestWithUrl = { + url: string; + body?: unknown; +} & Omit; import { MongoDBRealmError } from "../.."; +export type MockFetch = typeof fetch & { + requests: RequestWithUrl[]; +}; + /** * Perform mocked requests and get pre-recorded responses + * @param responses A list of pre-recorded responses + * @returns A mock for fetch */ -export class MockNetworkTransport implements NetworkTransport { - /** - * List of all requests captured. - */ - public readonly requests: Request[] = []; - - /** - * Responses sent back on each expected request. - */ - public readonly responses: unknown[]; - - /** - * Construct a mocked network transport which returns pre-recorded requests. - * @param responses An array of pre-recorded requests. - */ - constructor(responses: unknown[] = []) { - this.responses = responses; - } - - /** @inheritdoc */ - fetch(request: Request): Promise { +export function createMockFetch(responses: unknown[]): MockFetch { + const mock: MockFetch = (url: string, request: RequestInit = {}) => { if (!request.headers || Object.keys(request.headers).length === 0) { delete request.headers; } @@ -55,9 +47,9 @@ export class MockNetworkTransport implements NetworkTransport { if (request.body === undefined) { delete request.body; } - this.requests.push(request); - if (this.responses.length > 0) { - const [response] = this.responses.splice(0, 1); + mock.requests.push({ ...request, url }); + if (responses.length > 0) { + const [response] = responses.splice(0, 1); if (response instanceof MongoDBRealmError) { return Promise.resolve({ ok: false, @@ -75,8 +67,8 @@ export class MockNetworkTransport implements NetworkTransport { return "application/json"; } }, - } as FetchHeaders, - } as FetchResponse); + }, + } as Response); } else { return Promise.resolve({ ok: true, @@ -87,18 +79,16 @@ export class MockNetworkTransport implements NetworkTransport { return "application/json"; } }, - } as FetchHeaders, - } as FetchResponse); + }, + } as Response); } } else { throw new Error( - `Unexpected request (method = ${request.method}, url = ${request.url}, body = ${JSON.stringify(request.body)})`, + `Unexpected request (method = ${request.method}, url = ${url}, body = ${JSON.stringify(request.body)})`, ); } - } + }; + mock.requests = []; - /** @inheritdoc */ - fetchWithCallbacks(): void { - throw new Error("Not implemented"); - } + return mock; } diff --git a/packages/realm-web/src/tests/utils/MockFetcher.ts b/packages/realm-web/src/tests/utils/MockFetcher.ts index 045bf0e962f..9672b34906d 100644 --- a/packages/realm-web/src/tests/utils/MockFetcher.ts +++ b/packages/realm-web/src/tests/utils/MockFetcher.ts @@ -19,14 +19,14 @@ import { Fetcher, AuthenticatedRequest, UserContext } from "../../Fetcher"; import { User } from "../../User"; -import { MockNetworkTransport } from "./MockNetworkTransport"; +import { MockFetch, createMockFetch } from "./MockFetch"; /** * A mock of the fetcher useful when testing. */ export class MockFetcher extends Fetcher { private readonly mockUserContext: UserContext; - private readonly mockTransport: MockNetworkTransport; + private readonly mockFetch: MockFetch; /** * Construct a mocked network transport which returns pre-recorded requests. @@ -34,16 +34,16 @@ export class MockFetcher extends Fetcher { * @param userContext An object defining the current user. */ constructor(responses: unknown[] = [], userContext: UserContext = { currentUser: null }) { - const mockTransport = new MockNetworkTransport(responses); + const mockFetch = createMockFetch(responses); super({ appId: "mocked-app-id", userContext: userContext, locationUrlContext: { locationUrl: Promise.resolve("http://localhost:1337"), }, - transport: mockTransport, + fetch: mockFetch, }); - this.mockTransport = mockTransport; + this.mockFetch = mockFetch; this.mockUserContext = userContext; } @@ -55,6 +55,6 @@ export class MockFetcher extends Fetcher { * @returns List of all requests captured. */ public get requests(): AuthenticatedRequest[] { - return this.mockTransport.requests; + return this.mockFetch.requests; } } diff --git a/packages/realm-web/src/tests/utils/index.ts b/packages/realm-web/src/tests/utils/index.ts index 2eede3924fb..259b0bdd9e2 100644 --- a/packages/realm-web/src/tests/utils/index.ts +++ b/packages/realm-web/src/tests/utils/index.ts @@ -76,4 +76,4 @@ export const INVALID_SESSION_ERROR = new MongoDBRealmError( export * from "./MockApp"; export * from "./MockFetcher"; -export * from "./MockNetworkTransport"; +export * from "./MockFetch"; diff --git a/packages/realm-web/tsconfig.json b/packages/realm-web/tsconfig.json index 8852f35e109..c8a08d0234e 100644 --- a/packages/realm-web/tsconfig.json +++ b/packages/realm-web/tsconfig.json @@ -12,14 +12,14 @@ ], "types": [ "@realm/common", - "@realm/network-transport", + "@realm/fetch", "realm", "js-base64", "bson", "buffer" ], "lib": [ - "es2019" + "es2022" ] }, "include": [ diff --git a/packages/realm/package.json b/packages/realm/package.json index 4e9d73c62c6..8ee8ee413e0 100644 --- a/packages/realm/package.json +++ b/packages/realm/package.json @@ -97,7 +97,7 @@ "build:node", "bindgen:generate:typescript", "bindgen:generate:wrappers", - "../realm-network-transport:bundle" + "../fetch:build" ] }, "bundle": { @@ -108,9 +108,9 @@ } }, "dependencies": [ - "../realm-network-transport:bundle", "bindgen:generate:typescript", - "bindgen:generate:wrappers" + "bindgen:generate:wrappers", + "../fetch:build" ], "files": [ "rollup.config.mjs", @@ -278,7 +278,6 @@ "dependencies": { "bson": "^4.7.2", "debug": "^4.3.4", - "node-fetch": "^2.6.9", "node-machine-id": "^1.1.12", "prebuild-install": "^7.1.1" }, @@ -292,10 +291,10 @@ }, "devDependencies": { "@realm/bindgen": "^0.1.0", - "@realm/network-transport": "^0.7.2", + "@realm/fetch": "^0.1.0", "@types/chai": "^4.3.3", "@types/mocha": "^10.0.0", - "@types/node": "^18.15.10", + "@types/node": "^18.19.8", "@types/path-browserify": "^1.0.0", "command-line-args": "^5.2.1", "chai": "4.3.6", @@ -307,6 +306,9 @@ "react-native": "0.73.2", "typedoc-plugin-missing-exports": "^2.0.1" }, + "engines": { + "node": ">=18" + }, "repository": { "type": "git", "url": "https://github.com/realm/realm-js.git", diff --git a/packages/realm/rollup.config.mjs b/packages/realm/rollup.config.mjs index 53ddf0fbb80..8085daa3bfe 100644 --- a/packages/realm/rollup.config.mjs +++ b/packages/realm/rollup.config.mjs @@ -58,6 +58,7 @@ export default [ nodeResolve({ modulesOnly: true, preferBuiltins: true, + exportConditions: ["node", "module", "main"], }), replace({ preventAssignment: true, @@ -74,7 +75,7 @@ export default [ include: typescriptInclude, }), ], - external: ["bson", "debug", "node-fetch", "node:module", "node:fs", "node:path"], + external: ["bson", "debug", "node:module", "node:fs", "node:path"], }, { input: "src/platform/react-native/index.ts", @@ -87,7 +88,7 @@ export default [ nodeResolve({ mainFields: ["react-native", "browser", "module", "main"], exportConditions: ["react-native", "browser", "module", "main"], - resolveOnly: ["@realm/network-transport", "path-browserify"], + resolveOnly: ["@realm/fetch", "path-browserify"], }), // We need to use `commonjs` because of "path-browserify" commonjs(), diff --git a/packages/realm/src/app-services/App.ts b/packages/realm/src/app-services/App.ts index cb8684d16dd..7cb33f52eb8 100644 --- a/packages/realm/src/app-services/App.ts +++ b/packages/realm/src/app-services/App.ts @@ -16,6 +16,7 @@ // //////////////////////////////////////////////////////////////////////////// +import type { AnyFetch } from "@realm/fetch"; import { AnyUser, Credentials, @@ -140,6 +141,11 @@ export type AppConfiguration = { * @since 12.2.0 */ metadata?: Metadata; + + /** + * Overrides the fetch function used to perform network requests. + */ + fetch?: AnyFetch; }; /** @@ -261,7 +267,7 @@ export class App< constructor(configOrId: AppConfiguration | string) { const config: AppConfiguration = typeof configOrId === "string" ? { id: configOrId } : configOrId; assert.object(config, "config"); - const { id, baseUrl, app, timeout, multiplexSessions = true, baseFilePath, metadata } = config; + const { id, baseUrl, app, timeout, multiplexSessions = true, baseFilePath, metadata, fetch } = config; assert.string(id, "id"); if (timeout !== undefined) { assert.number(timeout, "timeout"); @@ -276,6 +282,9 @@ export class App< assert(metadata.encryptionKey, "encryptionKey is required"); } } + if (fetch !== undefined) { + assert.function(fetch, "fetch"); + } fs.ensureDirectoryForFile(fs.joinPaths(baseFilePath || fs.getDefaultDirectoryPath(), "mongodb-realm")); // TODO: This used getSharedApp in the legacy SDK, but it's failing AppTests @@ -283,7 +292,7 @@ export class App< { appId: id, deviceInfo: App.deviceInfo, - transport: createNetworkTransport(), + transport: createNetworkTransport(fetch), baseUrl, defaultRequestTimeoutMs: timeout ? binding.Int64.numToInt(timeout) : undefined, }, diff --git a/packages/realm/src/app-services/NetworkTransport.ts b/packages/realm/src/app-services/NetworkTransport.ts index f17d694c4e7..1cb553354b3 100644 --- a/packages/realm/src/app-services/NetworkTransport.ts +++ b/packages/realm/src/app-services/NetworkTransport.ts @@ -16,9 +16,13 @@ // //////////////////////////////////////////////////////////////////////////// -import { FetchHeaders, binding, network } from "../internal"; +import { toFetchArgs } from "src/binding"; +import { binding, extendDebug, network } from "../internal"; +import type { Headers } from "@realm/fetch"; -function flattenHeaders(headers: FetchHeaders) { +const debug = extendDebug("network"); + +function flattenHeaders(headers: Headers) { const result: Record = {}; headers.forEach((value: string, key: string) => { result[key] = value; @@ -27,10 +31,13 @@ function flattenHeaders(headers: FetchHeaders) { } /** @internal */ -export function createNetworkTransport() { +export function createNetworkTransport(fetch = network.fetch) { return binding.Helpers.makeNetworkTransport((request, callback) => { - network.fetch(request).then( + const [url, init] = toFetchArgs(request); + debug("requesting %s %O", url, init); + fetch(url, init).then( async (response) => { + debug("responded %O", response); const headers = flattenHeaders(response.headers); const contentType = headers["content-type"]; const body = contentType ? await response.text() : ""; diff --git a/packages/realm/src/app-services/User.ts b/packages/realm/src/app-services/User.ts index 85c5a34ea84..b83f99c6c80 100644 --- a/packages/realm/src/app-services/User.ts +++ b/packages/realm/src/app-services/User.ts @@ -31,6 +31,7 @@ import { ProviderType, PushClient, assert, + asyncIteratorFromResponse, binding, cleanArguments, createFactory, @@ -310,11 +311,11 @@ export class User< serviceName, ); - const response = await network.fetch(request); + const response = await network.fetch(...binding.toFetchArgs(request)); assert(response.ok, () => `Request failed: ${response.statusText} (${response.status})`); assert(response.body, "Expected a body in the response"); - return response.body; + return asyncIteratorFromResponse(response); } /** diff --git a/packages/realm/src/async-iterator-from-response.ts b/packages/realm/src/async-iterator-from-response.ts new file mode 100644 index 00000000000..a760fc0d152 --- /dev/null +++ b/packages/realm/src/async-iterator-from-response.ts @@ -0,0 +1,56 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2024 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +import { Response } from "@realm/fetch"; + +// Falling back on a known string used in code transpiled by Babel +const asyncIteratorSymbol: typeof Symbol.asyncIterator = Symbol.asyncIterator || "@@asyncIterator"; + +/** @internal */ +export function asyncIteratorFromResponse({ body }: Response): AsyncIterable { + if (typeof body !== "object" || body === null) { + throw new Error("Expected a non-null object"); + } else if (Symbol.asyncIterator in body) { + return body as AsyncIterable; + } else if ("getReader" in body) { + return { + [asyncIteratorSymbol]() { + const reader = body.getReader(); + return { + async next() { + const { done, value } = await reader.read(); + if (done) { + // TODO: Simply return the result once https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1676 is merged and released + return { done, value: undefined }; + } else if (value instanceof Uint8Array) { + return { done, value }; + } else { + throw new Error("Expected value to be Uint8Array"); + } + }, + async return() { + await reader.cancel(); + return { done: true, value: null }; + }, + }; + }, + }; + } else { + throw new Error("Expected an AsyncIterable or a ReadableStream"); + } +} diff --git a/packages/realm/src/binding.ts b/packages/realm/src/binding.ts index 147635c151a..cd8202b0b01 100644 --- a/packages/realm/src/binding.ts +++ b/packages/realm/src/binding.ts @@ -16,7 +16,18 @@ // //////////////////////////////////////////////////////////////////////////// -import { IndexSet, Int64, ObjKey, SyncSession, Timestamp, WeakSyncSession } from "realm/binding"; +import { + HttpMethod, + IndexSet, + Int64, + Int64Type, + ObjKey, + Request, + SyncSession, + Timestamp, + WeakSyncSession, +} from "realm/binding"; +import { AbortSignal, fetch } from "@realm/fetch"; /** @internal */ export * from "realm/binding"; @@ -105,3 +116,44 @@ export function isEmptyObjKey(objKey: ObjKey) { // This relies on the JS representation of an ObjKey being a bigint return Int64.equals(objKey as unknown as Int64, -1); } + +function fromBindingFetchBody(body: string) { + if (body.length === 0) { + return undefined; + } else { + return body; + } +} + +const HTTP_METHOD: Record = { + [HttpMethod.Get]: "GET", + [HttpMethod.Post]: "POST", + [HttpMethod.Put]: "PUT", + [HttpMethod.Patch]: "PATCH", + [HttpMethod.Del]: "DELETE", +}; + +function fromBindingFetchMethod(method: HttpMethod) { + if (method in HTTP_METHOD) { + return HTTP_METHOD[method]; + } else { + throw new Error(`Unexpected method ${method}`); + } +} + +function fromBindingTimeoutSignal(timeoutMs: Int64Type): AbortSignal | undefined { + const timeout = Number(timeoutMs); + return timeout > 0 ? AbortSignal.timeout(timeout) : undefined; +} + +export function toFetchArgs({ url, method, timeoutMs, body, headers }: Request): Parameters { + return [ + url, + { + body: fromBindingFetchBody(body), + method: fromBindingFetchMethod(method), + signal: fromBindingTimeoutSignal(timeoutMs), + headers, + }, + ]; +} diff --git a/packages/realm/src/internal.ts b/packages/realm/src/internal.ts index e6baee766fd..d0a565fe753 100644 --- a/packages/realm/src/internal.ts +++ b/packages/realm/src/internal.ts @@ -39,6 +39,8 @@ export * from "./assert"; /** @internal */ export * from "./ranges"; /** @internal */ +export * from "./async-iterator-from-response"; +/** @internal */ export * from "./Listeners"; /** @internal */ export * from "./JSONCacheMap"; diff --git a/packages/realm/src/platform.ts b/packages/realm/src/platform.ts index 49f19e1d9f5..9b48fb8f312 100644 --- a/packages/realm/src/platform.ts +++ b/packages/realm/src/platform.ts @@ -18,4 +18,4 @@ export { deviceInfo } from "./platform/device-info"; export { fs } from "./platform/file-system"; -export { network, FetchHeaders, Request } from "./platform/network"; +export { network } from "./platform/network"; diff --git a/packages/realm/src/platform/network.ts b/packages/realm/src/platform/network.ts index 1cf7969c814..95b9d6918f9 100644 --- a/packages/realm/src/platform/network.ts +++ b/packages/realm/src/platform/network.ts @@ -16,48 +16,11 @@ // //////////////////////////////////////////////////////////////////////////// -import { DefaultNetworkTransport, FetchHeaders, FetchResponse, Method, Request } from "@realm/network-transport"; +import { fetch } from "@realm/fetch"; -import { extendDebug } from "../debug"; -import * as binding from "../binding"; +type NetworkType = { fetch: typeof fetch }; -export type { FetchHeaders, Request }; - -const debug = extendDebug("network"); -const transport = new DefaultNetworkTransport(); - -type NetworkType = { - fetch(request: Request): Promise; - fetch(request: binding.Request): Promise; -}; - -const HTTP_METHOD: Record = { - [binding.HttpMethod.Get]: "GET", - [binding.HttpMethod.Post]: "POST", - [binding.HttpMethod.Put]: "PUT", - [binding.HttpMethod.Patch]: "PATCH", - [binding.HttpMethod.Del]: "DELETE", -}; - -function toFetchRequest({ method, timeoutMs, body, headers, url }: binding.Request_Relaxed) { - return { - url, - headers, - method: HTTP_METHOD[method], - timeoutMs: Number(timeoutMs), - body: body !== "" ? body : undefined, - }; -} - -export const network: NetworkType = { - async fetch(request) { - debug("Requesting %O", request); - const fetchRequest = typeof request.method === "string" ? request : toFetchRequest(request); - const response = await transport.fetch(fetchRequest); - debug("Responded %O", response); - return response; - }, -}; +export const network: NetworkType = { fetch }; export function inject(injected: NetworkType) { Object.freeze(Object.assign(network, injected)); diff --git a/packages/realm/src/platform/node/tsconfig.json b/packages/realm/src/platform/node/tsconfig.json index d2a32861e4e..79601781630 100644 --- a/packages/realm/src/platform/node/tsconfig.json +++ b/packages/realm/src/platform/node/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../../tsconfig.json", "compilerOptions": { - "types": ["node"], + "types": ["@realm/fetch", "node"], "noResolve": false, "incremental": true, "resolveJsonModule": true, diff --git a/packages/realm/tsconfig.json b/packages/realm/tsconfig.json index aa311436921..929d56f006a 100644 --- a/packages/realm/tsconfig.json +++ b/packages/realm/tsconfig.json @@ -11,7 +11,7 @@ "ES2022" ], "types": [ - "@realm/network-transport", + "@realm/fetch", "bson", "debug", "ms" // debug dependency