Replace bootstrap methods with async factories

Controller.bootstrap and Host.bootstrap returned constructor arguments
that the caller had to spread into new Controller/Host, and
DatastoreProvider.bootstrap did the same for datastores. They are
replaced by Controller.fromConfig, Host.fromConfig and
KeyValueDatastore/SubscribableDatastore.fromProvider, which load the
data and return the finished instance.

Closes #1126

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bbassieandClaude Opus 5.5 committed 2026-10-23 23:51:22 +00:00
1 parent c3abf27266
commit d7fc3aaec4
8 files changed
+133 -57

No files matched your search

+32
View File
@@ -57,6 +57,38 @@ describe("controller/src/Controller", function() {
assert(list.check(ip), `${ip} missing from list`);
}
}
describe(".fromConfig()", function() {
let dir;
beforeEach(async function() {
dir = await fs.mkdtemp(path.join(os.tmpdir(), "clusterio-controller-"));
});
afterEach(async function() {
await fs.rm(dir, { recursive: true, force: true });
});
it("should load datastores and mods from disk", async function() {
const databaseDirectory = path.join(dir, "database");
const modsDirectory = path.join(dir, "mods");
await fs.mkdir(databaseDirectory);
await fs.writeFile(path.join(databaseDirectory, "notes.json"), JSON.stringify([
{ resourceType: "instance", resourceId: "100", content: "hello" },
]));
const config = new ControllerConfig("controller", {
"controller.version": controllerVersion,
"controller.database_directory": databaseDirectory,
"controller.mods_directory": modsDirectory,
});
const loaded = await Controller.fromConfig({}, [], config, true, false);
assert(loaded instanceof Controller);
assert.equal(loaded.canRestart, true);
assert.equal(loaded.recoveryMode, false);
assert.equal(loaded.notes.get("instance/100").content, "hello");
assert.equal(loaded.modStore.modsDirectory, modsDirectory);
assert(loaded.roles.size > 0, "default roles were not added");
assert(loaded.modPacks.size > 0, "default mod packs were not added");
await fs.access(modsDirectory);
});
});
describe(".loadJsonObject()", function() {
let dir;
before(async function() {
+15
View File
@@ -62,6 +62,21 @@ describe("host/src/Host", function() {
});
});
describe("class Host", function() {
describe(".fromConfig()", function() {
const modsDirectory = path.join("temp", "test", "host_from_config", "mods");
beforeEach(async function() {
await fs.rm(path.dirname(modsDirectory), { recursive: true, force: true });
});
it("should create the mods directory and load the mod store", async function() {
const hostConfig = new lib.HostConfig("host", { "host.mods_directory": modsDirectory });
const host = await Host.fromConfig(new HostConnector(hostConfig, []), hostConfig, [], true, false);
assert(host instanceof Host);
assert.equal(host.canRestart, true);
assert.equal(host.recoveryMode, false);
assert.equal(host.modStore.modsDirectory, modsDirectory);
await fs.access(modsDirectory);
});
});
describe(".handleSyncUserListsEvent()", function() {
const hostAddress = lib.Address.fromShorthand({ hostId: 1 });
const instanceAddress = lib.Address.fromShorthand({ instanceId: 1 });
+29 -21
View File
@@ -50,13 +50,6 @@ describe("lib/datastore", function() {
it("can be constructed", function() {
assert.deepEqual(datastoreProvider.methodCalls[0], ["constructor"]);
});
it("can be bootstrapped", async function() {
const rtn = await datastoreProvider.bootstrap();
assert.equal(rtn[0], datastoreProvider);
assert.equal(rtn[1], datastoreProvider.value);
assert.deepEqual(datastoreProvider.methodCalls[1], ["load"]);
});
});
describe("class MemoryDatastoreProvider", function() {
@@ -70,13 +63,13 @@ describe("lib/datastore", function() {
describe("constructor", function() {
it("has a default value", async function() {
const [, value] = await new lib.MemoryDatastoreProvider().bootstrap();
const value = await new lib.MemoryDatastoreProvider().load();
assert.notEqual(value, undefined);
assert.notEqual(value, null);
});
it("can have a value given", async function() {
const expectedValue = new Map([["foo", "bar"]]);
const [, value] = await new lib.MemoryDatastoreProvider(expectedValue).bootstrap();
const value = await new lib.MemoryDatastoreProvider(expectedValue).load();
assert.deepEqual(value, expectedValue);
});
});
@@ -116,7 +109,7 @@ describe("lib/datastore", function() {
it("has default values", async function() {
datastoreProvider = new lib.JsonDatastoreProvider(dataPath);
const [, value] = await datastoreProvider.bootstrap();
const value = await datastoreProvider.load();
assert.deepEqual(value, new Map(initialData));
});
it("can accept a fromJson method", async function() {
@@ -125,7 +118,7 @@ describe("lib/datastore", function() {
json => [json]
);
const [, value] = await datastoreProvider.bootstrap();
const value = await datastoreProvider.load();
assert.deepEqual(value, new Map(initialData.map(([k, v]) => [k, [v]])));
});
it("can accept a migration method", async function() {
@@ -135,7 +128,7 @@ describe("lib/datastore", function() {
rawJson => ({ c: "foo", ...rawJson })
);
const [, value] = await datastoreProvider.bootstrap();
const value = await datastoreProvider.load();
assert.deepEqual(value, new Map([...initialData, ["c", "foo"]]));
});
it("can accept a finalise method", async function() {
@@ -146,7 +139,7 @@ describe("lib/datastore", function() {
obj => [obj]
);
const [, value] = await datastoreProvider.bootstrap();
const value = await datastoreProvider.load();
assert.deepEqual(value, new Map(initialData.map(([k, v]) => [k, [v]])));
});
});
@@ -210,7 +203,7 @@ describe("lib/datastore", function() {
it("has default values", async function() {
datastoreProvider = new lib.JsonIdDatastoreProvider(dataPath);
const [, value] = await datastoreProvider.bootstrap();
const value = await datastoreProvider.load();
assert.deepEqual(value, new Map(initialData));
});
it("can accept a fromJson method", async function() {
@@ -219,7 +212,7 @@ describe("lib/datastore", function() {
json => ({...json, foo: true})
);
const [, value] = await datastoreProvider.bootstrap();
const value = await datastoreProvider.load();
assert.deepEqual(value, new Map(initialData.map(v => ([v[0], {...v[1], foo: true}]))));
});
it("can accept a migration method", async function() {
@@ -229,7 +222,7 @@ describe("lib/datastore", function() {
rawJson => ([{ id: "c" }, ...rawJson])
);
const [, value] = await datastoreProvider.bootstrap();
const value = await datastoreProvider.load();
assert.deepEqual(value, new Map([...initialData, ["c", { id: "c" }]]));
});
it("can accept a finalise method", async function() {
@@ -240,7 +233,7 @@ describe("lib/datastore", function() {
obj => ({...obj, foo: true})
);
const [, value] = await datastoreProvider.bootstrap();
const value = await datastoreProvider.load();
assert.deepEqual(value, new Map(initialData.map(v => ([v[0], {...v[1], foo: true}]))));
});
});
@@ -306,10 +299,6 @@ describe("lib/datastore", function() {
datastore = new MockDatastore(datastoreProvider, datastoreProvider.value);
assert.deepEqual(datastore.getData(), datastoreProvider.value);
});
it("accepts the return of bootstrap", async function() {
datastore = new MockDatastore(...await datastoreProvider.bootstrap());
assert.deepEqual(datastore.getData(), datastoreProvider.value);
});
});
describe("getFilePath", function() {
@@ -478,6 +467,15 @@ describe("lib/datastore", function() {
datastore.on("update", updates => eventsRaised.push(updates));
});
describe("fromProvider", function() {
it("loads data from the provider", async function() {
datastore = await lib.KeyValueDatastore.fromProvider(datastoreProvider);
assert(datastore instanceof lib.KeyValueDatastore);
assert.equal(datastore.get("foo"), "fooValue");
assert.deepEqual(datastoreProvider.methodCalls[1], ["load"]);
});
});
describe("set", function() {
it("can set a value", function() {
datastore.set("test", "testValue");
@@ -571,6 +569,16 @@ describe("lib/datastore", function() {
datastore.on("update", updates => eventsRaised.push(updates));
});
describe("fromProvider", function() {
it("loads data from the provider", async function() {
datastore = await lib.SubscribableDatastore.fromProvider(datastoreProvider);
assert(datastore instanceof lib.SubscribableDatastore);
assert.equal(datastore.get("foo").id, "foo");
assert.equal(datastore.lastUpdatedAtMs, 10);
assert.deepEqual(datastoreProvider.methodCalls[1], ["load"]);
});
});
function forceFuture() {
const future = {id: "future"};
for (let i = 1; i < 100; i++) {