[verified] refactor: establish service and frontend boundaries
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import { ConfigValidationError } from './errors.js'
|
||||
|
||||
export function normalizeBaseUrl(rawUrl) {
|
||||
let url
|
||||
try { url = new URL(rawUrl) } catch { throw new ConfigValidationError('instance URL is invalid') }
|
||||
if (!['http:', 'https:'].includes(url.protocol)) throw new ConfigValidationError('instance URL must use http or https')
|
||||
if (url.username || url.password) throw new ConfigValidationError('instance URL must not contain credentials')
|
||||
url.hash = ''
|
||||
url.search = ''
|
||||
return url.toString().replace(/\/$/, '')
|
||||
}
|
||||
|
||||
export function normalizeInstance(instance, index = 0) {
|
||||
if (!instance || typeof instance !== 'object') throw new ConfigValidationError(`instances[${index}] must be an object`)
|
||||
const id = String(instance.id || '').trim()
|
||||
const name = String(instance.name || id || `SimAdmin ${index + 1}`).trim()
|
||||
const rawUrl = String(instance.url || '').trim()
|
||||
if (!id || !/^[a-zA-Z0-9_.-]+$/.test(id)) throw new ConfigValidationError(`instances[${index}].id is required and may only contain letters, numbers, _, -, .`)
|
||||
if (!rawUrl) throw new ConfigValidationError(`instances[${index}].url is required`)
|
||||
const password = instance.auth?.password ?? instance.password ?? ''
|
||||
const mode = password ? 'password' : String(instance.auth?.mode || 'none')
|
||||
if (!['none', 'password'].includes(mode)) throw new ConfigValidationError(`instances[${index}].auth.mode must be none or password`)
|
||||
return {
|
||||
id, name, url: normalizeBaseUrl(rawUrl),
|
||||
description: String(instance.description || '').trim(),
|
||||
tags: Array.isArray(instance.tags) ? instance.tags.map(String) : [],
|
||||
auth: { mode, password: password ? String(password) : '' },
|
||||
capabilities: Array.isArray(instance.capabilities) ? instance.capabilities.map(String) : [],
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeConfig(raw = {}, env = process.env) {
|
||||
const instances = Array.isArray(raw.instances) ? raw.instances.map(normalizeInstance) : []
|
||||
const ids = new Set()
|
||||
for (const item of instances) {
|
||||
if (ids.has(item.id)) throw new ConfigValidationError(`duplicate instance id: ${item.id}`)
|
||||
ids.add(item.id)
|
||||
}
|
||||
const port = Number(env.PORT ?? raw.server?.port ?? 8788)
|
||||
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new ConfigValidationError('server.port must be an integer from 1 to 65535')
|
||||
return {
|
||||
server: { host: raw.server?.host ?? env.HOST ?? '127.0.0.1', port },
|
||||
instances,
|
||||
raw: { ...raw, instances },
|
||||
}
|
||||
}
|
||||
|
||||
export function serializeConfig(snapshot) {
|
||||
return {
|
||||
...(snapshot.raw || {}),
|
||||
server: snapshot.raw?.server || snapshot.server,
|
||||
instances: snapshot.instances.map(instance => ({
|
||||
id: instance.id, name: instance.name, url: instance.url,
|
||||
description: instance.description || '', tags: instance.tags || [],
|
||||
auth: instance.auth?.password ? { mode: 'password', password: instance.auth.password } : { mode: 'none' },
|
||||
capabilities: instance.capabilities || [],
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
export function immutableSnapshot(value) {
|
||||
const clone = structuredClone(value)
|
||||
const freeze = object => {
|
||||
if (object && typeof object === 'object' && !Object.isFrozen(object)) {
|
||||
Object.freeze(object)
|
||||
for (const item of Object.values(object)) freeze(item)
|
||||
}
|
||||
return object
|
||||
}
|
||||
return freeze(clone)
|
||||
}
|
||||
Reference in New Issue
Block a user