refactor: generic store and sources in core

This commit is contained in:
admin committed 2026-06-16 20:03:40 +05:00
1 parent 32b64460bf
commit f4249b58cf
7 files changed
+451 -114

No files matched your search

+68
View File
@@ -0,0 +1,68 @@
import { existsSync, readFileSync } from 'node:fs'
import type * as z from 'zod'
import { parseConnections, type StoreOptions } from './store.js'
export const readEnvConnections = <S extends z.ZodTypeAny>(
schema: S,
env: NodeJS.ProcessEnv,
envVar: string,
opts: StoreOptions
): z.infer<S>[] => {
const raw = env[envVar]
if (!raw) {
return []
}
let parsed: unknown
try {
parsed = JSON.parse(raw)
} catch {
console.error(opts.logLabel + ': ' + envVar + ' is not valid JSON, ignoring')
return []
}
if (!Array.isArray(parsed)) {
console.error(opts.logLabel + ': ' + envVar + ' must be a JSON array, ignoring')
return []
}
return parseConnections(schema, parsed, 'env ' + envVar, opts)
}
export const readConfigFile = <S extends z.ZodTypeAny>(
schema: S,
path: string | undefined,
opts: StoreOptions
): z.infer<S>[] => {
if (!path) {
return []
}
if (!existsSync(path)) {
console.error(opts.logLabel + ': config file ' + path + ' not found, ignoring')
return []
}
let parsed: unknown
try {
parsed = JSON.parse(readFileSync(path, 'utf8'))
} catch {
console.error(opts.logLabel + ': config file ' + path + ' is not valid JSON, ignoring')
return []
}
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
console.error(
opts.logLabel +
': config file ' +
path +
' must contain { "connections": [...] }, ignoring'
)
return []
}
const connections = (parsed as { connections?: unknown }).connections
if (!Array.isArray(connections)) {
console.error(
opts.logLabel +
': config file ' +
path +
' must contain { "connections": [...] }, ignoring'
)
return []
}
return parseConnections(schema, connections, 'config ' + path, opts)
}
+84
View File
@@ -0,0 +1,84 @@
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'
import { dirname } from 'node:path'
import type * as z from 'zod'
export type StoreOptions = { logLabel: string }
export const parseConnections = <S extends z.ZodTypeAny>(
schema: S,
items: unknown[],
origin: string,
opts: StoreOptions
): z.infer<S>[] => {
const result: z.infer<S>[] = []
for (const item of items) {
const check = schema.safeParse(item)
if (check.success) {
result.push(check.data)
} else {
const name = (item as { name?: unknown })?.name ?? '<unnamed>'
console.error(
opts.logLabel +
": skipping invalid connection '" +
String(name) +
"' from " +
origin
)
}
}
return result
}
export const readStore = <S extends z.ZodTypeAny>(
schema: S,
path: string,
opts: StoreOptions
): z.infer<S>[] => {
mkdirSync(dirname(path), { recursive: true, mode: 0o700 })
if (!existsSync(path)) {
return []
}
let parsed: unknown
try {
parsed = JSON.parse(readFileSync(path, 'utf8'))
} catch {
console.error(
opts.logLabel + ': store file ' + path + ' is not valid JSON, treating as empty'
)
return []
}
if (!Array.isArray(parsed)) {
console.error(
opts.logLabel + ': store file ' + path + ' must contain a JSON array, treating as empty'
)
return []
}
return parseConnections(schema, parsed, 'store ' + path, opts)
}
let writeSeq = 0
export const writeStore = (path: string, connections: unknown[]): void => {
mkdirSync(dirname(path), { recursive: true, mode: 0o700 })
const items = connections.map((c) => JSON.stringify(c, null, 4))
const content = items.length === 0 ? '[]\n' : '[\n' + items.join(',\n') + '\n]\n'
let tmp: string
for (;;) {
writeSeq += 1
tmp = path + '.' + process.pid + '.' + writeSeq + '.tmp'
try {
writeFileSync(tmp, content, { mode: 0o600, flag: 'wx' })
break
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') {
throw error
}
}
}
try {
renameSync(tmp, path)
} catch (error) {
rmSync(tmp, { force: true })
throw error
}
}
+3
View File
@@ -1,5 +1,8 @@
export type { ConnectionSource, ResolvedConnection, SshConfig } from './config/baseSchema.js'
export { baseConnectionShape, sshConfigSchema } from './config/baseSchema.js'
export { readConfigFile, readEnvConnections } from './config/sources.js'
export type { StoreOptions } from './config/store.js'
export { parseConnections, readStore, writeStore } from './config/store.js'
export {
clampLimit,
DEFAULT_ROW_LIMIT,