Skip to content

Commit

Permalink
fix: Cache API file reads across all requests.
Browse files Browse the repository at this point in the history
  • Loading branch information
SamTolmay committed Oct 14, 2022
1 parent 20977c0 commit 2b90efb
Show file tree
Hide file tree
Showing 18 changed files with 136 additions and 13 deletions.
3 changes: 2 additions & 1 deletion packages/api/src/context/createApiContext.js
Expand Up @@ -21,12 +21,13 @@ function createApiContext({
buildDirectory,
config,
connections,
fileCache,
logger,
operators,
secrets,
session,
}) {
const readConfigFile = createReadConfigFile({ buildDirectory });
const readConfigFile = createReadConfigFile({ buildDirectory, fileCache });
return {
authorize: createAuthorize({ session }),
config,
Expand Down
7 changes: 5 additions & 2 deletions packages/api/src/context/createReadConfigFile.js
Expand Up @@ -18,15 +18,18 @@ import path from 'path';
import { cachedPromises } from '@lowdefy/helpers';
import { getFileExtension, readFile } from '@lowdefy/node-utils';

function createReadConfigFile({ buildDirectory }) {
function createReadConfigFile({ buildDirectory, fileCache }) {
async function readConfigFile(filePath) {
const fileContent = await readFile(path.resolve(buildDirectory, filePath));
if (getFileExtension(filePath) === 'json') {
return JSON.parse(fileContent);
}
return fileContent;
}
return cachedPromises(readConfigFile);
return cachedPromises({
cache: fileCache,
getter: readConfigFile,
});
}

export default createReadConfigFile;
5 changes: 3 additions & 2 deletions packages/api/src/routes/page/getPageConfig.js
Expand Up @@ -17,8 +17,9 @@
async function getPageConfig({ authorize, readConfigFile }, { pageId }) {
const pageConfig = await readConfigFile(`pages/${pageId}/${pageId}.json`);
if (pageConfig && authorize(pageConfig)) {
delete pageConfig.auth;
return pageConfig;
// eslint-disable-next-line no-unused-vars
const { auth, ...rest } = pageConfig;
return { ...rest };
}
return null;
}
Expand Down
5 changes: 4 additions & 1 deletion packages/build/src/utils/readConfigFile.js
Expand Up @@ -22,7 +22,10 @@ function createReadConfigFile({ directories }) {
async function readConfigFile(filePath) {
return readFile(path.resolve(directories.config, filePath));
}
return cachedPromises(readConfigFile);
return cachedPromises({
cache: new Map(),
getter: readConfigFile,
});
}

export default createReadConfigFile;
20 changes: 20 additions & 0 deletions packages/server-dev/lib/fileCache.js
@@ -0,0 +1,20 @@
/*
Copyright 2020-2022 Lowdefy, 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 default {
get: () => undefined,
set: () => undefined,
};
2 changes: 2 additions & 0 deletions packages/server-dev/pages/api/auth/[...nextauth].js
Expand Up @@ -22,11 +22,13 @@ import config from '../../../build/config.json';
import adapters from '../../../build/plugins/auth/adapters.js';
import callbacks from '../../../build/plugins/auth/callbacks.js';
import events from '../../../build/plugins/auth/events.js';
import fileCache from '../../../lib/fileCache.js';
import providers from '../../../build/plugins/auth/providers.js';

export const authOptions = getNextAuthConfig(
createApiContext({
config,
fileCache,
logger: console,
}),
{ authJson, plugins: { adapters, callbacks, events, providers } }
Expand Down
2 changes: 2 additions & 0 deletions packages/server-dev/pages/api/page/[pageId].js
Expand Up @@ -17,13 +17,15 @@
import { createApiContext, getPageConfig } from '@lowdefy/api';

import config from '../../../build/config.json';
import fileCache from '../../../lib/fileCache.js';
import getServerSession from '../../../lib/auth/getServerSession.js';

export default async function handler(req, res) {
const session = await getServerSession({ req, res });
const apiContext = createApiContext({
buildDirectory: './build',
config,
fileCache,
logger: console,
session,
});
Expand Down
2 changes: 2 additions & 0 deletions packages/server-dev/pages/api/request/[pageId]/[requestId].js
Expand Up @@ -19,6 +19,7 @@ import { getSecretsFromEnv } from '@lowdefy/node-utils';

import config from '../../../../build/config.json';
import connections from '../../../../build/plugins/connections.js';
import fileCache from '../../../../lib/fileCache.js';
import getServerSession from '../../../../lib/auth/getServerSession.js';
import operators from '../../../../build/plugins/operators/server.js';

Expand All @@ -32,6 +33,7 @@ export default async function handler(req, res) {
buildDirectory: './build',
config,
connections,
fileCache,
logger: console,
operators,
secrets: getSecretsFromEnv(),
Expand Down
2 changes: 2 additions & 0 deletions packages/server-dev/pages/api/root.js
Expand Up @@ -17,13 +17,15 @@
import { createApiContext, getRootConfig } from '@lowdefy/api';

import config from '../../build/config.json';
import fileCache from '../../lib/fileCache.js';
import getServerSession from '../../lib/auth/getServerSession.js';

export default async function handler(req, res) {
const session = await getServerSession({ req, res });
const apiContext = createApiContext({
buildDirectory: './build',
config,
fileCache,
logger: console,
session,
});
Expand Down
21 changes: 21 additions & 0 deletions packages/server/lib/fileCache.js
@@ -0,0 +1,21 @@
/*
Copyright 2020-2022 Lowdefy, 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 { LRUCache } from '@lowdefy/helpers';

const fileCache = new LRUCache({ maxSize: 100 });

export default fileCache;
3 changes: 3 additions & 0 deletions packages/server/pages/404.js
Expand Up @@ -17,13 +17,16 @@ import path from 'path';
import { createApiContext, getPageConfig, getRootConfig } from '@lowdefy/api';

import config from '../build/config.json';
import fileCache from '../lib/fileCache.js';
import Page from '../lib/Page.js';

export async function getStaticProps() {
// Important to give absolute path so Next can trace build files
const apiContext = createApiContext({
buildDirectory: path.join(process.cwd(), 'build'),
config,
fileCache,
logger: console,
});

const [rootConfig, pageConfig] = await Promise.all([
Expand Down
2 changes: 2 additions & 0 deletions packages/server/pages/[pageId].js
Expand Up @@ -18,6 +18,7 @@ import path from 'path';
import { createApiContext, getPageConfig, getRootConfig } from '@lowdefy/api';

import config from '../build/config.json';
import fileCache from '../lib/fileCache.js';
import getServerSession from '../lib/auth/getServerSession.js';
import Page from '../lib/Page.js';

Expand All @@ -28,6 +29,7 @@ export async function getServerSideProps(context) {
const apiContext = createApiContext({
buildDirectory: path.join(process.cwd(), 'build'),
config,
fileCache,
logger: console,
session,
});
Expand Down
6 changes: 4 additions & 2 deletions packages/server/pages/api/auth/[...nextauth].js
Expand Up @@ -17,16 +17,18 @@
import NextAuth from 'next-auth';
import { createApiContext, getNextAuthConfig } from '@lowdefy/api';

import authJson from '../../../build/auth.json';
import config from '../../../build/config.json';
import adapters from '../../../build/plugins/auth/adapters.js';
import authJson from '../../../build/auth.json';
import callbacks from '../../../build/plugins/auth/callbacks.js';
import config from '../../../build/config.json';
import events from '../../../build/plugins/auth/events.js';
import fileCache from '../../../lib/fileCache.js';
import providers from '../../../build/plugins/auth/providers.js';

export const authOptions = getNextAuthConfig(
createApiContext({
config,
fileCache,
logger: console,
}),
{ authJson, plugins: { adapters, callbacks, events, providers } }
Expand Down
2 changes: 2 additions & 0 deletions packages/server/pages/api/request/[pageId]/[requestId].js
Expand Up @@ -22,6 +22,7 @@ import config from '../../../../build/config.json';
import connections from '../../../../build/plugins/connections.js';
import getServerSession from '../../../../lib/auth/getServerSession.js';
import operators from '../../../../build/plugins/operators/server.js';
import fileCache from '../../../../lib/fileCache.js';

export default async function handler(req, res) {
try {
Expand All @@ -34,6 +35,7 @@ export default async function handler(req, res) {
buildDirectory: path.join(process.cwd(), 'build'),
config,
connections,
fileCache,
// logger: console,
logger: { debug: () => {} },
operators,
Expand Down
2 changes: 2 additions & 0 deletions packages/server/pages/index.js
Expand Up @@ -18,6 +18,7 @@ import path from 'path';
import { createApiContext, getPageConfig, getRootConfig } from '@lowdefy/api';

import config from '../build/config.json';
import fileCache from '../lib/fileCache.js';
import getServerSession from '../lib/auth/getServerSession.js';
import Page from '../lib/Page.js';

Expand All @@ -28,6 +29,7 @@ export async function getServerSideProps(context) {
const apiContext = createApiContext({
buildDirectory: path.join(process.cwd(), 'build'),
config,
fileCache,
logger: console,
session,
});
Expand Down
52 changes: 52 additions & 0 deletions packages/utils/helpers/src/LRUCache.js
@@ -0,0 +1,52 @@
/*
Copyright 2020-2022 Lowdefy, 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.
*/

// https://stackoverflow.com/a/46432113/8295949
// Implementation of Least Recently Updated cache.
// It uses the fact that the implementation of Map keep track of item insertion order
class LRUCache {
constructor({ maxSize = 100 } = {}) {
this.maxSize = maxSize;
this.cache = new Map();
}

get(key) {
const item = this.cache.get(key);
if (item) {
// Refresh key.
this.cache.delete(key);
this.cache.set(key, item);
}
return item;
}

set(key, val) {
if (this.cache.has(key)) {
// Refresh key
this.cache.delete(key);
} else if (this.cache.size === this.maxSize) {
// Evict oldest
this.cache.delete(this.first());
}
this.cache.set(key, val);
}

first() {
return this.cache.keys().next().value;
}
}

export default LRUCache;
11 changes: 6 additions & 5 deletions packages/utils/helpers/src/cachedPromises.js
Expand Up @@ -14,13 +14,14 @@
limitations under the License.
*/

function createCachedPromises(getter) {
const cache = new Map();

function createCachedPromises({ getter, cache }) {
function cachedPromises(key) {
if (cache.has(key)) {
return Promise.resolve(cache.get(key));
const cached = cache.get(key);

if (cached) {
return Promise.resolve(cached);
}

const promise = getter(key);
cache.set(key, promise);
return Promise.resolve(promise);
Expand Down
2 changes: 2 additions & 0 deletions packages/utils/helpers/src/index.js
Expand Up @@ -17,6 +17,7 @@
import applyArrayIndices from './applyArrayIndices.js';
import cachedPromises from './cachedPromises.js';
import get from './get.js';
import LRUCache from './LRUCache.js';
import mergeObjects from './mergeObjects.js';
import omit from './omit.js';
import serializer from './serializer.js';
Expand All @@ -32,6 +33,7 @@ export {
applyArrayIndices,
cachedPromises,
get,
LRUCache,
mergeObjects,
omit,
serializer,
Expand Down

0 comments on commit 2b90efb

Please sign in to comment.