Files
xuyueandClaude Opus 5.5 de277d3e9d
CI/CD / test (push) Canceled after 0s
CI/CD / deploy (push) Canceled after 0s
用 bun fmt 格式化全部代码和文档
- bunfig.toml 的 [fmt]:不写分号、单引号、一行最长 160,和原来的写法一致
- .prettierignore:docs/教材摘要/ 是书上的摘录,不格式化
- 模板里一行写两条语句的事件处理(@keydown.delete、@dblclick)改成调用方法:去掉分号后拆成两行,Vue 编译不过

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 14:02:33 +08:00

113 lines
3.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Subprocess } from 'bun'
import { config } from './config'
import { readLines } from './runner-process'
/** 查询窗口的一个结果集 */
export interface SqlResultSet {
columns: { name: string; type: string | null }[]
rows: unknown[][]
/** 实际行数(只返回前 1000 行) */
total: number
}
export type SqlReply =
| { type: 'result'; database: string; results: SqlResultSet[]; affected: number; ms: number }
| { type: 'done' }
| { type: 'explorer'; databases: { name: string; tables: { name: string; columns: SqlColumnInfo[] }[] }[] }
| { type: 'error'; number: number; message: string; line: number }
export interface SqlColumnInfo {
name: string
type: string
nullable: boolean
identity: boolean
primaryKey: boolean
}
export type SqlRequest =
{ type: 'exec'; dir: string; database: string; sql: string } | { type: 'script'; dir: string; sql: string } | { type: 'explorer'; dir: string }
/** SQL 报错(编号和信息与 SQL Server 一致) */
export class SqlError extends Error {
constructor(
readonly number: number,
message: string,
readonly line: number,
) {
super(message)
}
}
/**
* 常驻 SQL 服务(C# 进程,T-SQL 转写成 SQLite 执行):数据库页面的查询和对象资源管理器、评分前准备实验数据库。
* 进程意外退出时挂起的请求全部失败,下一次请求自动重启。
*/
class SqlService {
private proc?: Subprocess<'pipe', 'pipe', 'inherit'>
private ready?: Promise<void>
private nextId = 1
private readonly pending = new Map<number, { resolve: (r: SqlReply) => void; reject: (e: Error) => void }>()
/** 发出请求;SQL 报错时抛出 SqlError */
async request(request: SqlRequest): Promise<Exclude<SqlReply, { type: 'error' }>> {
await this.start()
const id = this.nextId++
const reply = await new Promise<SqlReply>((resolve, reject) => {
this.pending.set(id, { resolve, reject })
this.proc!.stdin.write(`${JSON.stringify({ id, ...request })}\n`)
this.proc!.stdin.flush()
})
if (reply.type === 'error') throw new SqlError(reply.number, reply.message, reply.line)
return reply
}
stop() {
const proc = this.proc
this.proc = undefined
this.ready = undefined
for (const waiter of this.pending.values()) waiter.reject(new Error('SQL 服务已停止'))
this.pending.clear()
proc?.kill()
}
start() {
if (this.ready) return this.ready
const proc = Bun.spawn(['dotnet', config.runnerDll, 'sql-server'], {
stdin: 'pipe',
stdout: 'pipe',
stderr: 'inherit',
env: {
PATH: process.env.PATH ?? '/usr/bin:/bin',
HOME: process.env.HOME ?? '/tmp',
DOTNET_CLI_TELEMETRY_OPTOUT: '1',
DOTNET_NOLOGO: '1',
TZ: config.timeZone,
},
})
this.proc = proc
this.ready = new Promise<void>((resolveReady) => {
void readLines(proc.stdout, (line) => {
const message = JSON.parse(line) as ({ type: 'ready' } | SqlReply) & { id?: number }
if (message.type === 'ready') {
resolveReady()
return
}
const waiter = this.pending.get(message.id!)
if (!waiter) return
this.pending.delete(message.id!)
waiter.resolve(message)
})
})
proc.exited.then((code) => {
if (this.proc !== proc) return
this.proc = undefined
this.ready = undefined
for (const waiter of this.pending.values()) waiter.reject(new Error(`SQL 服务意外退出(${code})`))
this.pending.clear()
})
return this.ready
}
}
export const sqlService = new SqlService()