Skip to content

Commit

Permalink
Expose baseFilePath on App configuration (#5572)
Browse files Browse the repository at this point in the history
* Expose baseFilePath on App configuration
* PR feedback
  • Loading branch information
kneth authored Mar 23, 2023
1 parent 22977ea commit 430afad
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 16 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* None

### Enhancements
* None
* Added configuration option `App.baseFilePath` which controls where synced Realms and metadata is stored.

### Fixed
* Fix type error when using `realm.create` in combination with class base models. (since v11.0.0)
Expand Down
3 changes: 2 additions & 1 deletion docs/realm.js
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,8 @@ class Realm {
* Realm database should be stored. For synced Realms, a relative path is used together with app id and
* user id in order to avoid collisions with other apps or users. An absolute path is left untouched
* and on some platforms (iOS and Android) the app might not have permissions to create or open
* the file - permissions are not validated.
* the file - permissions are not validated. If a relative path is specified, it is relative to
* {@link Realm.App~AppConfiguration.baseFilePath}.
* @property {string} [fifoFilesFallbackPath] - Opening a Realm creates a number of FIFO special files in order to
* coordinate access to the Realm across threads and processes. If the Realm file is stored in a location
* that does not allow the creation of FIFO special files (e.g. FAT32 filesystems), then the Realm cannot be opened.
Expand Down
1 change: 1 addition & 0 deletions docs/sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
* @property {string} id - The id of the Atlas App Services application.
* @property {string} [baseUrl] - The base URL of the Atlas App Services server.
* @property {number} [timeout] - General timeout (in millisecs) for requests.
* @property {string} [baseFilePath] - Specify where synced Realms and metadata is stored. If not specified, the current work directory is used.
* @property {Realm.App~LocalAppConfiguration} [app] - local app configuration
*/

Expand Down
44 changes: 37 additions & 7 deletions integration-tests/tests/src/node/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@ import { expect } from "chai";
import Realm, { BSON } from "realm";
import path from "node:path";
import os from "node:os";
import { existsSync, rmSync } from "node:fs";

import { importAppBefore, authenticateUserBefore } from "../hooks";
import { importApp } from "../utils/import-app";

const getAbsolutePath = () => os.tmpdir() + path.sep + new BSON.UUID().toHexString();
const getRelativePath = () => "testFiles" + path.sep + new BSON.UUID().toHexString();
const getPartitionValue = () => new BSON.UUID().toHexString();

const schema = {
const Schema = {
name: "MixedClass",
primaryKey: "_id",
properties: {
Expand All @@ -36,24 +38,52 @@ const schema = {
},
};

const FlexibleSchema = { ...Schema, properties: { ...Schema.properties, nonQueryable: "string?" } };

describe("path configuration (local)", function () {
it("relative path", function () {
const filename = getRelativePath();
const realm = new Realm({ path: filename, schema: [schema] });
const realm = new Realm({ path: filename, schema: [Schema] });
expect(realm.path.endsWith(filename)).to.be.true;
realm.close();
Realm.deleteFile({ path: filename });
});

it("absolute path", function () {
const filename = getAbsolutePath();
const realm = new Realm({ path: filename, schema: [schema] });
const realm = new Realm({ path: filename, schema: [Schema] });
expect(realm.path).to.equal(filename);
realm.close();
Realm.deleteFile({ path: filename });
});
});

describe.skipIf(environment.missingServer, `app configuration of root directory (flexible sync)`, async function () {
const { appId, baseUrl } = await importApp("with-db-flx");

it("directory and file created where expected", async function () {
const tmpdir = getAbsolutePath();
expect(fs.exists(tmpdir)).to.be.false;

const app = new Realm.App({ id: appId, baseUrl, baseFilePath: tmpdir });
const user = await app.logIn(Realm.Credentials.anonymous());

const realm = await Realm.open({
schema: [FlexibleSchema],
sync: {
flexible: true,
user,
},
});

expect(existsSync(tmpdir)).to.be.true;
expect(realm.path.startsWith(tmpdir));

realm.close();
rmSync(tmpdir, { recursive: true });
});
});

describe.skipIf(environment.missingServer, "path configuration (partition based sync)", function () {
importAppBefore("with-db");
authenticateUserBefore();
Expand All @@ -62,7 +92,7 @@ describe.skipIf(environment.missingServer, "path configuration (partition based
const filename = getAbsolutePath();
const realm = await Realm.open({
path: filename,
schema: [schema],
schema: [Schema],
sync: {
partitionValue: getPartitionValue(),
user: this.user,
Expand All @@ -77,7 +107,7 @@ describe.skipIf(environment.missingServer, "path configuration (partition based
const filename = getRelativePath();
const realm = await Realm.open({
path: filename,
schema: [schema],
schema: [Schema],
sync: {
partitionValue: getPartitionValue(),
user: this.user,
Expand All @@ -98,7 +128,7 @@ describe.skipIf(environment.skipFlexibleSync, "path configuration (flexible sync
const filename = getAbsolutePath();
const realm = await Realm.open({
path: filename,
schema: [schema],
schema: [FlexibleSchema],
sync: {
flexible: true,
user: this.user,
Expand All @@ -114,7 +144,7 @@ describe.skipIf(environment.skipFlexibleSync, "path configuration (flexible sync
const filename = getRelativePath();
const realm = await Realm.open({
path: filename,
schema: [schema],
schema: [FlexibleSchema],
sync: {
flexible: true,
user: this.user,
Expand Down
21 changes: 14 additions & 7 deletions src/js_app.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "js_network_transport.hpp"
#include "js_email_password_auth.hpp"
#include "realm/object-store/sync/subscribable.hpp"
#include "realm/util/file.hpp"


using SharedApp = std::shared_ptr<realm::app::App>;
Expand Down Expand Up @@ -170,6 +171,7 @@ void AppClass<T>::constructor(ContextType ctx, ObjectType this_object, Arguments
static const String config_app = "app";
static const String config_app_name = "name";
static const String config_app_version = "version";
static const String config_base_file_path = "baseFilePath";

args.validate_count(1);

Expand All @@ -178,6 +180,11 @@ void AppClass<T>::constructor(ContextType ctx, ObjectType this_object, Arguments
std::string id;
realm::app::App::Config config;

SyncClientConfig client_config;
client_config.metadata_mode = SyncManager::MetadataMode::NoEncryption;
client_config.user_agent_binding_info = get_user_agent();
client_config.base_file_path = default_realm_file_directory(); // this may be changed

if (Value::is_object(ctx, args[0])) {
ObjectType config_object = Value::validated_to_object(ctx, args[0]);

Expand Down Expand Up @@ -217,6 +224,11 @@ void AppClass<T>::constructor(ContextType ctx, ObjectType this_object, Arguments
std::optional<std::string>(Value::validated_to_string(ctx, config_app_version_value, "version"));
}
}

ValueType base_file_path_value = Object::get_property(ctx, config_object, config_base_file_path);
if (!Value::is_undefined(ctx, base_file_path_value)) {
client_config.base_file_path = Value::validated_to_string(ctx, base_file_path_value);
}
}
else if (Value::is_string(ctx, args[0])) {
config.app_id = Value::validated_to_string(ctx, args[0]);
Expand All @@ -238,13 +250,8 @@ void AppClass<T>::constructor(ContextType ctx, ObjectType this_object, Arguments
config.device_info.framework_name = framework_name;
config.device_info.framework_version = framework_version;

auto realm_file_directory = default_realm_file_directory();
ensure_directory_exists_for_file(realm_file_directory);

SyncClientConfig client_config;
client_config.base_file_path = realm_file_directory;
client_config.metadata_mode = SyncManager::MetadataMode::NoEncryption;
client_config.user_agent_binding_info = get_user_agent();
util::try_make_dir(client_config.base_file_path);
set_default_realm_file_directory(client_config.base_file_path);

SharedApp app = app::App::get_shared_app(config, client_config);

Expand Down
12 changes: 12 additions & 0 deletions src/node/platform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

#include "../platform.hpp"

static std::string s_default_realm_directory;

namespace realm {

class UVException : public std::runtime_error {
Expand All @@ -44,9 +46,19 @@ struct FileSystemRequest : uv_fs_t {
}
};

void set_default_realm_file_directory(std::string dir)
{
s_default_realm_directory = dir;
}

// taken from Node.js: function Cwd in node.cc
std::string default_realm_file_directory()
{

if (!s_default_realm_directory.empty()) {
return s_default_realm_directory;
}

#ifdef _WIN32
/* MAX_PATH is in characters, not bytes. Make sure we have enough headroom. */
char buf[MAX_PATH * 4];
Expand Down
5 changes: 5 additions & 0 deletions types/app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,11 @@ declare namespace Realm {
*/
baseUrl?: string;

/**
* An optional path to a directory where synced Realms are stored.
*/
baseFilePath?: string;

/**
* This describes the local app, sent to the server when a user authenticates.
* Specifying this will enable the server to respond differently to specific versions of specific apps.
Expand Down

0 comments on commit 430afad

Please sign in to comment.