Skip to content

Commit

Permalink
Allow to use in-memory and file DB ion production build
Browse files Browse the repository at this point in the history
  • Loading branch information
ai committed Sep 3, 2024
1 parent e75f4e5 commit 2260716
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 82 deletions.
6 changes: 3 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 42 additions & 0 deletions server/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
type AssetsPaths =
| { assets: string; routes: string }
| { assets: undefined; routes: undefined }

export type Config = {
db: string
env: 'development' | 'production' | 'test'
} & AssetsPaths

function getDefaultDatabase(env: Config['env']): string {
if (env === 'production') {
throw new Error('Set DATABASE_URL with PostgreSQL credentials')
} else if (env === 'test') {
return 'memory://'
} else {
return 'file://./db/pgdata'
}
}

function getPaths(from: Record<string, string | undefined>): AssetsPaths {
if (from.ASSETS_DIR && from.ROUTES_FILE) {
return { assets: from.ASSETS_DIR, routes: from.ROUTES_FILE }
} else if (!from.ASSETS_DIR && !from.ROUTES_FILE) {
return { assets: undefined, routes: undefined }
} else {
throw new Error('ASSETS_DIR and ROUTES_FILE must be set together')
}
}

export function getConfig(from: Record<string, string | undefined>): Config {
let env = from.NODE_ENV ?? 'development'
if (env !== 'test' && env !== 'production' && env !== 'development') {
throw new Error('Unknown NODE_ENV')
}
return {
db: from.DATABASE_URL ?? getDefaultDatabase(env),
env,
...getPaths(from)
}
}

export const config = getConfig(process.env)
19 changes: 8 additions & 11 deletions server/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,25 @@ import { migrate as prodMigrate } from 'drizzle-orm/postgres-js/migrator'
import { join } from 'node:path'
import postgres from 'postgres'

import { config } from './config.ts'
import * as schema from './db/schema.ts'
import { env } from './env.ts'
export * from './db/schema.ts'

const MIGRATE_CONFIG: MigrationConfig = {
migrationsFolder: join(import.meta.dirname, 'db', 'migrations')
}

let drizzle: PgDatabase<PgQueryResultHKT, typeof schema>
if (env.DATABASE_URL) {
if (config.db.startsWith('memory:') || config.db.startsWith('file:')) {
let drizzlePglite = devDrizzle(new PGlite(config.db), { schema })
await devMigrate(drizzlePglite, MIGRATE_CONFIG)
drizzle = drizzlePglite
} else {
/* c8 ignore next 4 */
drizzle = prodDrizzle(postgres(env.DATABASE_URL), { schema })
let migrateConnection = postgres(env.DATABASE_URL, { max: 1 })
drizzle = prodDrizzle(postgres(config.db), { schema })
let migrateConnection = postgres(config.db, { max: 1 })
await prodMigrate(prodDrizzle(migrateConnection), MIGRATE_CONFIG)
await migrateConnection.end()
} else {
let drizzlePglite = devDrizzle(
new PGlite(process.env.NODE_ENV === 'test' ? 'memory://' : './db/pgdata'),
{ schema }
)
await devMigrate(drizzlePglite, MIGRATE_CONFIG)
drizzle = drizzlePglite
}

export const db = drizzle
38 changes: 0 additions & 38 deletions server/env.ts

This file was deleted.

20 changes: 11 additions & 9 deletions server/modules/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { readFile } from 'node:fs/promises'
import type { ServerResponse } from 'node:http'
import { extname, join, normalize } from 'node:path'

import { getEnv } from '../env.ts'
import { config } from '../config.ts'

interface Asset {
contentType: string
Expand Down Expand Up @@ -70,21 +70,23 @@ function send(res: ServerResponse, asset: Asset): void {
res.end(asset.data)
}

export default async (server: BaseServer): Promise<void> => {
let env = getEnv(process.env)
if (!env.ASSETS_DIR) return
export default async (
server: BaseServer,
{ assets, routes } = config
): Promise<void> => {
if (!assets) return

let CACHE: Record<string, Asset> = {}

let html = await readFile(join(env.ASSETS_DIR, 'index.html'))
let html = await readFile(join(assets, 'index.html'))
let appHtml: Asset = {
contentType: 'text/html',
data: html,
headers: getPageHeaders(html)
}

let routesData = await readFile(env.ROUTES_FILE)
let routes = new RegExp(routesData.toString())
let routesData = await readFile(routes)
let routesRegexp = new RegExp(routesData.toString())

server.http(async (req, res) => {
if (req.method !== 'GET') return false
Expand All @@ -93,9 +95,9 @@ export default async (server: BaseServer): Promise<void> => {
let pathname = url.pathname.replace(/\/$/, '')
let safe = normalize(url.pathname).replace(/^(\.\.[/\\])+/, '')
let cacheKey = safe
let path = join(env.ASSETS_DIR, safe)
let path = join(assets, safe)

if (routes.test(pathname)) {
if (routesRegexp.test(pathname)) {
send(res, appHtml)
return true
}
Expand Down
2 changes: 1 addition & 1 deletion server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"prepare:bcrypt": "cd node_modules/bcrypt && pnpm run install"
},
"dependencies": {
"@electric-sql/pglite": "^0.2.5",
"@logux/server": "github:logux/server#next",
"@slowreader/api": "link:../api",
"argon2": "^0.41.1",
Expand All @@ -24,7 +25,6 @@
"postgres": "^3.4.4"
},
"devDependencies": {
"@electric-sql/pglite": "^0.2.5",
"@types/cookie": "^0.6.0",
"better-node-test": "0.7.1",
"c8": "10.1.2",
Expand Down
11 changes: 7 additions & 4 deletions server/test/assets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, test } from 'node:test'

import { config } from '../config.ts'
import assetsModule from '../modules/assets.ts'

let toDelete: string[] = []
let server: TestServer | undefined
let originEnv = { ...process.env }

const IGNORE_HEADERS = new Set([
'keep-alive',
Expand All @@ -31,7 +31,6 @@ function checkHeaders(res: Response, expected: Record<string, string>): void {
}

afterEach(async () => {
process.env = originEnv
await server?.destroy()
server = undefined
for (let i of toDelete) {
Expand Down Expand Up @@ -66,7 +65,7 @@ test('serves static pages', async () => {
process.env.ROUTES_FILE = routes

server = new TestServer()
await assetsModule(server)
await assetsModule(server, { ...config, assets, routes })

let index1 = await server.fetch('/')
checkHeaders(index1, {
Expand Down Expand Up @@ -129,7 +128,11 @@ test('serves static pages', async () => {

test('ignores on missed environment variable', async () => {
server = new TestServer()
await assetsModule(server)
await assetsModule(server, {
...config,
assets: undefined,
routes: undefined
})

let index = await server.fetch('/')
match(await index.text(), /Logux/)
Expand Down
38 changes: 22 additions & 16 deletions server/test/env.test.ts → server/test/config.test.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,61 @@
import { deepStrictEqual, equal, throws } from 'node:assert'
import { test } from 'node:test'

import { getEnv } from '../env.ts'
import { config, getConfig } from '../config.ts'

const DATABASE_URL = 'postgresql://user:pass@localhost:5432/db'

test('throws on missed DATABASE_URL in production', () => {
throws(() => {
getEnv({ NODE_ENV: 'production' })
getConfig({ NODE_ENV: 'production' })
}, /Set DATABASE_URL with PostgreSQL credentials/)
equal(
getEnv({
getConfig({
DATABASE_URL,
NODE_ENV: 'production'
}).DATABASE_URL,
}).db,
DATABASE_URL
)
equal(getEnv({ NODE_ENV: 'test' }).DATABASE_URL, undefined)
equal(getEnv({ DATABASE_URL, NODE_ENV: 'test' }).DATABASE_URL, DATABASE_URL)
equal(getConfig({ NODE_ENV: 'test' }).db, 'memory://')
equal(getConfig({ DATABASE_URL, NODE_ENV: 'test' }).db, DATABASE_URL)
equal(getConfig({ NODE_ENV: 'development' }).db, 'file://./db/pgdata')
equal(getConfig({ DATABASE_URL, NODE_ENV: 'development' }).db, DATABASE_URL)
})

test('checks that assets is together with routes', () => {
throws(() => {
getEnv({ ASSETS_DIR: './dist/' })
getConfig({ ASSETS_DIR: './dist/' })
}, /ASSETS_DIR and ROUTES_FILE/)
throws(() => {
getEnv({ ROUTES_FILE: './routes.regexp' })
getConfig({ ROUTES_FILE: './routes.regexp' })
}, /ASSETS_DIR and ROUTES_FILE/)
})

test('checks NODE_ENV', () => {
equal(getEnv({}).NODE_ENV, 'development')
equal(getEnv({ NODE_ENV: 'test' }).NODE_ENV, 'test')
equal(getConfig({}).env, 'development')
equal(getConfig({ NODE_ENV: 'test' }).env, 'test')
throws(() => {
getEnv({ NODE_ENV: 'staging' })
getConfig({ NODE_ENV: 'staging' })
}, /NODE_ENV/)
})

test('passes keys', () => {
deepStrictEqual(
getEnv({
getConfig({
ASSETS_DIR: './dist/',
DATABASE_URL,
NODE_ENV: 'production',
ROUTES_FILE: './routes.regexp'
}),
{
ASSETS_DIR: './dist/',
DATABASE_URL,
NODE_ENV: 'production',
ROUTES_FILE: './routes.regexp'
assets: './dist/',
db: DATABASE_URL,
env: 'production',
routes: './routes.regexp'
}
)
})

test('has predefined config', () => {
equal(config.env, 'test')
})

0 comments on commit 2260716

Please sign in to comment.