Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

config: make I/O operations synchronous #1110

Merged
merged 2 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions app/server/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import * as fse from "fs-extra";

// Export dependencies for stubbing in tests.
export const Deps = {
readFile: fse.readFile,
readFile: fse.readFileSync,
writeFile: fse.writeFile,
pathExists: fse.pathExists,
pathExists: fse.pathExistsSync,
};

/**
Expand Down Expand Up @@ -56,15 +56,15 @@ export class FileConfig<FileContents> {
* @param validator - Validates the contents are in the correct format, and converts to the correct type.
* Should throw an error or return null if not valid.
*/
public static async create<CreateConfigFileContents>(
public static create<CreateConfigFileContents>(
configPath: string,
validator: FileContentsValidator<CreateConfigFileContents>
): Promise<FileConfig<CreateConfigFileContents>> {
): FileConfig<CreateConfigFileContents> {
// Start with empty object, as it can be upgraded to a full config.
let rawFileContents: any = {};

if (await Deps.pathExists(configPath)) {
rawFileContents = JSON.parse(await Deps.readFile(configPath, 'utf8'));
if (Deps.pathExists(configPath)) {
rawFileContents = JSON.parse(Deps.readFile(configPath, 'utf8'));
}

let fileContents = null;
Expand Down Expand Up @@ -97,7 +97,7 @@ export class FileConfig<FileContents> {
await this.persistToDisk();
}

public async persistToDisk(): Promise<void> {
public async persistToDisk() {
await Deps.writeFile(this._filePath, JSON.stringify(this._rawConfig, null, 2));
}
}
Expand Down
4 changes: 2 additions & 2 deletions app/server/lib/configCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export interface IGristCoreConfig {
edition: IWritableConfigValue<Edition>;
}

export async function loadGristCoreConfigFile(configPath?: string): Promise<IGristCoreConfig> {
const fileConfig = configPath ? await FileConfig.create(configPath, convertToCoreFileContents) : undefined;
export function loadGristCoreConfigFile(configPath?: string): IGristCoreConfig {
const fileConfig = configPath ? FileConfig.create(configPath, convertToCoreFileContents) : undefined;
return loadGristCoreConfig(fileConfig);
}

Expand Down
2 changes: 1 addition & 1 deletion app/server/mergedServerMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export async function main(port: number, serverTypes: ServerType[],
const includeStatic = serverTypes.includes("static");
const includeApp = serverTypes.includes("app");

options.settings ??= await getGlobalConfig();
options.settings ??= getGlobalConfig();

const server = new FlexServer(port, `server(${serverTypes.join(",")})`, options);

Expand Down
4 changes: 2 additions & 2 deletions stubs/app/server/lib/globalConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ let cachedGlobalConfig: IGristCoreConfig | undefined = undefined;
/**
* Retrieves the cached grist config, or loads it from the default global path.
*/
export async function getGlobalConfig(): Promise<IGristCoreConfig> {
export function getGlobalConfig(): IGristCoreConfig {
if (!cachedGlobalConfig) {
log.info(`Loading config file from ${globalConfigPath}`);
cachedGlobalConfig = await loadGristCoreConfigFile(globalConfigPath);
cachedGlobalConfig = loadGristCoreConfigFile(globalConfigPath);
}

return cachedGlobalConfig;
Expand Down
12 changes: 6 additions & 6 deletions test/server/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('FileConfig', () => {
const useFakeConfigFile = (contents: string) => {
const fakeFile = { contents };
sinon.replace(Deps, 'pathExists', sinon.fake.resolves(true));
sinon.replace(Deps, 'readFile', sinon.fake((path, encoding: string) => Promise.resolve(fakeFile.contents)) as any);
sinon.replace(Deps, 'readFile', sinon.fake((path, encoding: string) => fakeFile.contents) as any);
sinon.replace(Deps, 'writeFile', sinon.fake((path, newContents) => {
fakeFile.contents = newContents;
return Promise.resolve();
Expand All @@ -31,17 +31,17 @@ describe('FileConfig', () => {
sinon.restore();
});

it('throws an error from create if the validator does not return a value', async () => {
it('throws an error from create if the validator does not return a value', () => {
useFakeConfigFile(testFileContentsJSON);
const validator = () => null;
await assert.isRejected(FileConfig.create<TestFileContents>("anypath.json", validator));
assert.throws(() => FileConfig.create<TestFileContents>("anypath.json", validator));
});

it('persists changes when values are assigned', async () => {
const fakeFile = useFakeConfigFile(testFileContentsJSON);
// Don't validate - this is guaranteed to be valid above.
const validator = (input: any) => input as TestFileContents;
const fileConfig = await FileConfig.create("anypath.json", validator);
const fileConfig = FileConfig.create("anypath.json", validator);
await fileConfig.set("myNum", 999);

assert.equal(fileConfig.get("myNum"), 999);
Expand All @@ -58,7 +58,7 @@ describe('FileConfig', () => {
const fakeFile = useFakeConfigFile(JSON.stringify(configWithExtraProperties));
// It's entirely possible the validator can damage the extra properties, but that's not in scope for this test.
const validator = (input: any) => input as TestFileContents;
const fileConfig = await FileConfig.create("anypath.json", validator);
const fileConfig = FileConfig.create("anypath.json", validator);
// Triggering a write to the file
await fileConfig.set("myNum", 999);
await fileConfig.set("myStr", "Something");
Expand Down Expand Up @@ -94,7 +94,7 @@ describe('createConfigValue', () => {
assert.equal(accessors.get(), 2);
});

it('initialises with the persistent value if available', async () => {
it('initialises with the persistent value if available', () => {
const accessors = makeInMemoryAccessors(22);
const configValue = createConfigValue(1, accessors);
assert.equal(configValue.get(), 22);
Expand Down
6 changes: 3 additions & 3 deletions test/server/lib/configCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ describe('loadGristCoreConfig', () => {
});

it('will function correctly when no config file is present', async () => {
sinon.replace(Deps, 'pathExists', sinon.fake.resolves(false));
sinon.replace(Deps, 'readFile', sinon.fake.resolves(""));
sinon.replace(Deps, 'pathExists', sinon.fake.returns(false));
sinon.replace(Deps, 'readFile', sinon.fake.returns("" as any));
const writeFileFake = sinon.fake.resolves(undefined);
sinon.replace(Deps, 'writeFile', writeFileFake);

const config = await loadGristCoreConfigFile("doesntmatter.json");
const config = loadGristCoreConfigFile("doesntmatter.json");
assert.exists(config.edition.get());

await config.edition.set("enterprise");
Expand Down
2 changes: 1 addition & 1 deletion test/server/lib/configCoreFileFormats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { assert } from 'chai';
import { convertToCoreFileContents, IGristCoreConfigFileLatest } from "app/server/lib/configCoreFileFormats";

describe('convertToCoreFileContents', () => {
it('fails with a malformed config', async () => {
it('fails with a malformed config', () => {
const badConfig = {
version: "This is a random version number that will never exist",
};
Expand Down
Loading