Files
MetonaSqlark/site/index.html
T
thzxx 7a53cfd530
CI / test (18.x) (push) Successful in 9m55s
CI / test (20.x) (push) Successful in 9m50s
CI / test (22.x) (push) Successful in 9m52s
CI / test (24.x) (push) Successful in 9m48s
feat: site全面更新 + CONTRIBUTING/CHANGELOG完善,准备v0.1.13迭代
2026-07-26 16:13:34 +08:00

442 lines
22 KiB
HTML
Raw 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.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MetonaSqlark — 前端 TypeScript 数据库</title>
<style>
:root {
--bg: #0a0a0f;
--surface: #14141f;
--surface2: #1a1a2e;
--border: #2a2a45;
--primary: #6366f1;
--primary-glow: #818cf8;
--accent: #06b6d4;
--accent2: #ec4899;
--text: #e2e8f0;
--text2: #94a3b8;
--gradient: linear-gradient(135deg, #6366f1 0%, #06b6d4 50%, #ec4899 100%);
--gradient2: linear-gradient(135deg, #1e1b4b 0%, #0f172a 50%, #1e1b4b 100%);
--radius: 16px;
--shadow: 0 0 60px rgba(99,102,241,0.15);
}
* { margin:0; padding:0; box-sizing:border-box; }
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.6;
overflow-x: hidden;
}
/* --- Particles background --- */
.bg-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:0; opacity:0.3; pointer-events:none; }
/* --- Layout --- */
.container { max-width:1200px; margin:0 auto; padding:0 24px; position:relative; z-index:1; }
/* --- Header --- */
header {
position:fixed; top:0; left:0; right:0; z-index:100;
backdrop-filter:blur(20px); -webkit-backdrop-filter:blur(20px);
background:rgba(10,10,15,0.85); border-bottom:1px solid var(--border);
padding:16px 0;
}
header .container { display:flex; align-items:center; justify-content:space-between; }
.logo { font-size:1.4rem; font-weight:800; letter-spacing:-0.5px; display:flex; align-items:center; gap:10px; }
.logo .icon {
width:36px; height:36px; border-radius:10px;
background:var(--gradient); display:flex; align-items:center; justify-content:center;
font-size:1.2rem; font-weight:900; color:#fff;
}
.logo span { background:var(--gradient); -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
nav { display:flex; gap:32px; }
nav a { color:var(--text2); text-decoration:none; font-weight:500; font-size:0.95rem; transition:.2s; position:relative; }
nav a:hover { color:var(--text); }
nav a::after { content:''; position:absolute; bottom:-4px; left:0; width:0; height:2px; background:var(--gradient); transition:.3s; border-radius:1px; }
nav a:hover::after { width:100%; }
.btn {
display:inline-flex; align-items:center; gap:8px; padding:10px 24px; border-radius:10px;
font-weight:600; font-size:0.95rem; cursor:pointer; border:none; transition:.3s; text-decoration:none;
}
.btn-primary { background:var(--gradient); color:#fff; box-shadow:0 4px 20px rgba(99,102,241,0.4); }
.btn-primary:hover { transform:translateY(-2px); box-shadow:0 8px 30px rgba(99,102,241,0.6); }
.btn-outline { background:transparent; border:2px solid var(--border); color:var(--text); }
.btn-outline:hover { border-color:var(--primary); background:rgba(99,102,241,0.08); }
/* --- Hero --- */
.hero {
padding:160px 0 80px; text-align:center; position:relative;
background:radial-gradient(ellipse 80% 60% at 50% -10%, rgba(99,102,241,0.15), transparent 60%),
radial-gradient(ellipse 60% 50% at 80% 80%, rgba(6,182,212,0.08), transparent 60%);
}
.hero h1 { font-size:clamp(2.4rem, 6vw, 4rem); font-weight:900; letter-spacing:-1.5px; line-height:1.1; margin-bottom:24px; }
.hero h1 .gradient-text { background:var(--gradient); -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
.hero p { font-size:1.2rem; color:var(--text2); max-width:620px; margin:0 auto 40px; }
.hero .actions { display:flex; gap:16px; justify-content:center; flex-wrap:wrap; }
.badge {
display:inline-flex; align-items:center; gap:6px; padding:6px 14px; border-radius:999px;
background:var(--surface2); border:1px solid var(--border); font-size:0.85rem; color:var(--text2);
}
.badge .dot { width:7px; height:7px; border-radius:50%; background:#22c55e; animation:pulse 2s infinite; }
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:.4} }
/* --- Section --- */
section { padding:100px 0; }
.section-title { text-align:center; margin-bottom:60px; }
.section-title h2 { font-size:2.2rem; font-weight:800; letter-spacing:-0.5px; margin-bottom:12px; }
.section-title h2 span { background:var(--gradient); -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
.section-title p { color:var(--text2); font-size:1.1rem; }
/* --- Feature Grid --- */
.feature-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(280px,1fr)); gap:24px; }
.feature-card {
background:var(--surface); border:1px solid var(--border); border-radius:var(--radius);
padding:32px; transition:.3s; position:relative; overflow:hidden;
}
.feature-card:hover { border-color:var(--primary); transform:translateY(-4px); box-shadow:var(--shadow); }
.feature-card::before {
content:''; position:absolute; top:0; left:0; right:0; height:3px;
background:var(--gradient); opacity:0; transition:.3s;
}
.feature-card:hover::before { opacity:1; }
.feature-card .icon { font-size:2rem; margin-bottom:16px; }
.feature-card h3 { font-size:1.2rem; margin-bottom:8px; }
.feature-card p { color:var(--text2); font-size:0.95rem; }
/* --- Code Block --- */
.code-block {
background:var(--surface2); border:1px solid var(--border); border-radius:var(--radius);
padding:28px; font-family:'JetBrains Mono','Fira Code',monospace; font-size:0.88rem;
overflow-x:auto; position:relative; line-height:1.7;
}
.code-block .keyword { color:#c084fc; }
.code-block .string { color:#34d399; }
.code-block .func { color:#60a5fa; }
.code-block .comment { color:#64748b; }
.code-block .number { color:#fbbf24; }
.code-block .type { color:#f472b6; }
/* --- Stats --- */
.stats { display:grid; grid-template-columns:repeat(auto-fit,minmax(200px,1fr)); gap:24px; text-align:center; }
.stat-card {
background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); padding:32px;
}
.stat-card .num { font-size:2.8rem; font-weight:900; background:var(--gradient); -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
.stat-card .label { color:var(--text2); font-size:0.95rem; margin-top:4px; }
/* --- Footer --- */
footer { border-top:1px solid var(--border); padding:40px 0; text-align:center; color:var(--text2); }
footer a { color:var(--primary); text-decoration:none; }
/* --- Responsive --- */
@media(max-width:768px){
nav { display:none; }
.hero { padding:120px 0 60px; }
section { padding:60px 0; }
}
</style>
</head>
<body>
<canvas class="bg-canvas" id="bg"></canvas>
<header>
<div class="container">
<div class="logo">
<div class="icon"></div>
<span>MetonaSqlark</span>
</div>
<nav>
<a href="index.html">首页</a>
<a href="docs.html">文档</a>
<a href="demo.html">演示</a>
<a href="https://git.metona.cn/MetonaTeam/MetonaSqlark" target="_blank">GitHub</a>
</nav>
<a href="demo.html" class="btn btn-primary">立即体验</a>
</div>
</header>
<!-- Hero -->
<section class="hero">
<div class="container">
<div class="badge" style="margin-bottom:24px;"><span class="dot"></span> v0.1.12 已发布</div>
<h1>前端的 <span class="gradient-text">SQL 数据库</span></h1>
<p>TypeScript 原生构建,内存与磁盘双模式,支持完整 SQL 查询。<br>零运行时依赖,开箱即用。</p>
<div class="actions">
<a href="demo.html" class="btn btn-primary" style="font-size:1.05rem;padding:14px 32px;">▶ 在线演示</a>
<a href="docs.html" class="btn btn-outline" style="font-size:1.05rem;padding:14px 32px;">📖 API 文档</a>
</div>
</div>
</section>
<!-- Install -->
<section style="padding:20px 0 60px;">
<div class="container">
<div class="section-title">
<h2>📦 <span>安装</span>引入</h2>
<p>多种方式,按需选择</p>
</div>
<div class="feature-grid">
<div class="feature-card">
<div class="icon">📦</div>
<h3>npm (Gitea Registry)</h3>
<div class="code-block" style="font-size:0.8rem;margin-top:12px;padding:14px;">
<span class="comment"># 配置 registry</span>
npm config set @metona-team:registry https://git.metona.cn/api/packages/MetonaTeam/npm/
<span class="comment"># 安装</span>
npm install @metona-team/metona-sqlark
</div>
</div>
<div class="feature-card">
<div class="icon">🌐</div>
<h3>CDN / Browser UMD</h3>
<div class="code-block" style="font-size:0.8rem;margin-top:12px;padding:14px;">
<span class="comment">&lt;!-- 直接引用 dist 产物 --&gt;</span>
&lt;<span class="keyword">script</span> src=<span class="string">"metona-sqlark.min.js"</span>&gt;&lt;/script&gt;
&lt;<span class="keyword">script</span>&gt;
<span class="keyword">const</span> db = <span class="keyword">await</span> window.<span class="func">MetonaSqlark</span>.create({...});
<span class="comment">// 或 window.MeSqlark(别名)</span>
&lt;/script&gt;
</div>
</div>
<div class="feature-card">
<div class="icon"></div>
<h3>ESM / TypeScript</h3>
<div class="code-block" style="font-size:0.8rem;margin-top:12px;padding:14px;">
<span class="keyword">import</span> { MetonaSqlark, MeSqlark }
<span class="keyword">from</span> <span class="string">'@metona-team/metona-sqlark'</span>;
<span class="keyword">const</span> db = <span class="keyword">await</span> <span class="func">MetonaSqlark</span>.create({
<span class="string">name</span>: <span class="string">'my-app'</span>,
<span class="string">mode</span>: <span class="string">'hybrid'</span>,
});
</div>
</div>
<div class="feature-card">
<div class="icon">🔧</div>
<h3>CommonJS</h3>
<div class="code-block" style="font-size:0.8rem;margin-top:12px;padding:14px;">
<span class="keyword">const</span> { MetonaSqlark } =
<span class="func">require</span>(<span class="string">'@metona-team/metona-sqlark'</span>);
(<span class="keyword">async</span> () => {
<span class="keyword">const</span> db = <span class="keyword">await</span> <span class="func">MetonaSqlark</span>.create({...});
})();
</div>
</div>
</div>
</div>
</section>
<!-- Features -->
<section>
<div class="container">
<div class="section-title">
<h2>为什么选择 <span>MetonaSqlark</span></h2>
<p>专为前端打造的数据库引擎</p>
</div>
<div class="feature-grid">
<div class="feature-card">
<div class="icon">🧠</div>
<h3>双模式存储</h3>
<p>Memory(内存)保证极致速度,DiskIndexedDB / OPFS)提供持久化。Hybrid 模式 write-through 策略,读写均在微秒级完成。</p>
</div>
<div class="feature-card">
<div class="icon"></div>
<h3>完整 SQL 解析器</h3>
<p>手写递归下降 SQL 解析器,无第三方依赖。支持 SELECT / INSERT / UPDATE / DELETE / CREATE TABLE / DROP TABLE / JOIN / GROUP BY / HAVING / DISTINCT。</p>
</div>
<div class="feature-card">
<div class="icon">🔗</div>
<h3>Query Builder</h3>
<p>链式 API + TypeScript 泛型支持。.select().where().innerJoin().orderBy().limit() — IDE 自动补全,类型安全。</p>
</div>
<div class="feature-card">
<div class="icon">🔒</div>
<h3>事务支持</h3>
<p>原子性操作,事务内任意步骤失败自动回滚。IndexedDB 引擎原生事务 + Memory 引擎模拟事务。</p>
</div>
<div class="feature-card">
<div class="icon">🧩</div>
<h3>插件系统</h3>
<p>14 种生命周期钩子:beforeCreateTable / afterInsert / beforeQuery / afterTransaction……按优先级注册,完整生命周期管理。</p>
</div>
<div class="feature-card">
<div class="icon">📦</div>
<h3>零运行时依赖</h3>
<p>纯 TypeScript 实现,不依赖任何第三方库。Tree-shakableUMD/ESM/CJS 多格式输出,最小体积 ~10KB gzip。</p>
</div>
<div class="feature-card">
<div class="icon">📊</div>
<h3>聚合 & 分组</h3>
<p>COUNT / SUM / AVG / MIN / MAX 五大聚合函数,GROUP BY + HAVING 子句,DISTINCT 去重,完整的数据分析能力。</p>
</div>
<div class="feature-card">
<div class="icon">🔄</div>
<h3>数据迁移</h3>
<p>内置版本迁移系统,addMigration + migrateTo API。导入导出支持单表/全库 JSON 序列化。</p>
</div>
<div class="feature-card">
<div class="icon">📡</div>
<h3>发布订阅</h3>
<p>subscribe() 订阅表变更事件,emit() 触发通知。React useQuery / Vue useSqlarkQuery 开箱即用。</p>
</div>
</div>
</div>
</section>
<!-- Architecture -->
<section>
<div class="container">
<div class="section-title">
<h2>🏗 <span>架构</span>设计</h2>
<p>清晰的分层结构,各模块职责单一</p>
</div>
<div class="code-block" style="text-align:center;font-size:0.82rem;line-height:2;background:transparent;border:none;">
<pre style="color:var(--text2);">
┌─────────────────────────────────────────────────────┐
<span style="color:#f472b6;">MetonaSqlark / MeSqlark</span>
│ create() · query() · table() · tx() │
├──────────┬──────────┬──────────┬──────────┬─────────┤
<span style="color:#c084fc;">SQL Parser</span><span style="color:#c084fc;">QueryBuilder</span><span style="color:#c084fc;">Transaction</span><span style="color:#c084fc;">Plugin</span><span style="color:#c084fc;">Migration</span>
│ Lexer → │ .select() │ Manager │ Manager │ System │
│ Parser │ .where() │ │ 14 hooks │ │
├──────────┴──────────┴──────────┴──────────┴─────────┤
<span style="color:#60a5fa;">QueryExecutor</span> (AST → Results) │
│ JOIN · GROUP BY · HAVING · DISTINCT │
├─────────────────────────────────────────────────────┤
<span style="color:#34d399;">IStorageEngine</span> Interface │
├──────────┬──────────┬──────────┬───────────────────┤
<span style="color:#fbbf24;">Memory </span><span style="color:#fbbf24;">IndexedDB </span><span style="color:#fbbf24;">OPFS </span><span style="color:#fbbf24;">Hybrid </span>
│ Engine │ Engine │ Engine │ Engine │
│ (Map) │ (IDB) │ (File) │ (Write-through) │
└──────────┴──────────┴──────────┴───────────────────┘
</pre>
</div>
</div>
</section>
<section style="background:var(--gradient2);">
<div class="container">
<div class="section-title">
<h2>5 行<span>代码</span>开始</h2>
<p>像写 SQL 一样操作前端数据 — 或使用 Query Builder 链式 API</p>
</div>
<div class="code-block">
<pre><span class="comment">// 创建数据库 — MeSqlark 是别名,完全等价</span>
<span class="keyword">const</span> db = <span class="keyword">await</span> <span class="func">MetonaSqlark.create</span>({ <span class="string">name</span>: <span class="string">'my-app'</span>, <span class="string">mode</span>: <span class="string">'hybrid'</span> });
<span class="comment">// 定义表结构</span>
<span class="keyword">await</span> db.<span class="func">defineTable</span>(<span class="string">'users'</span>, {
<span class="string">id</span>: { <span class="string">type</span>: <span class="string">'string'</span>, <span class="string">primaryKey</span>: <span class="keyword">true</span> },
<span class="string">name</span>: { <span class="string">type</span>: <span class="string">'string'</span>, <span class="string">required</span>: <span class="keyword">true</span> },
<span class="string">age</span>: { <span class="string">type</span>: <span class="string">'number'</span>, <span class="string">default</span>: <span class="number">0</span> },
<span class="string">email</span>: { <span class="string">type</span>: <span class="string">'string'</span>, <span class="string">unique</span>: <span class="keyword">true</span>, <span class="string">index</span>: <span class="keyword">true</span> },
});
<span class="comment">// SQL 查询</span>
<span class="keyword">await</span> db.<span class="func">query</span>(<span class="string">"INSERT INTO users VALUES ('1', 'Alice', 30, 'alice@demo.com')"</span>);
<span class="keyword">const</span> rows = <span class="keyword">await</span> db.<span class="func">query</span>(<span class="string">'SELECT * FROM users WHERE age > 18 ORDER BY name'</span>);
<span class="comment">// Query Builder — 类型安全,链式调用</span>
<span class="keyword">const</span> rows2 = <span class="keyword">await</span> db.<span class="func">table</span>(<span class="string">'users'</span>)
.<span class="func">select</span>([<span class="string">'name'</span>, <span class="string">'age'</span>]).<span class="func">where</span>({ <span class="string">age</span>: { <span class="string">$gt</span>: <span class="number">18</span> } })
.<span class="func">orderBy</span>(<span class="string">'name'</span>).<span class="func">limit</span>(<span class="number">10</span>).<span class="func">execute</span>();
<span class="comment">// JOIN 多表关联</span>
<span class="keyword">await</span> db.<span class="func">query</span>(<span class="string">`SELECT u.name, o.product, o.amount
FROM users u INNER JOIN orders o ON u.id = o.user_id
WHERE o.amount > 100`</span>);
<span class="comment">// GROUP BY 聚合分析</span>
<span class="keyword">await</span> db.<span class="func">query</span>(<span class="string">`SELECT dept, COUNT(*) as cnt, AVG(salary) as avg
FROM employees GROUP BY dept HAVING COUNT(*) > 1`</span>);
<span class="comment">// 事务 — 原子操作</span>
<span class="keyword">await</span> db.<span class="func">transaction</span>(<span class="keyword">async</span> (trx) => {
<span class="keyword">await</span> trx.<span class="func">table</span>(<span class="string">'users'</span>).<span class="func">insert</span>({ id: <span class="string">'3'</span>, name: <span class="string">'Charlie'</span> });
<span class="keyword">await</span> trx.<span class="func">table</span>(<span class="string">'orders'</span>).<span class="func">insert</span>({ id: <span class="string">'o1'</span>, userId: <span class="string">'3'</span>, amount: <span class="number">99</span> });
});</pre>
</div>
</div>
</section>
<!-- Stats -->
<section>
<div class="container">
<div class="section-title">
<h2><span>数字</span>说话</h2>
<p>MetonaSqlark 的核心指标</p>
</div>
<div class="stats">
<div class="stat-card"><div class="num">264+</div><div class="label">测试用例</div></div>
<div class="stat-card"><div class="num">93.5%</div><div class="label">代码覆盖率</div></div>
<div class="stat-card"><div class="num">~10KB</div><div class="label">gzip 体积</div></div>
<div class="stat-card"><div class="num">4</div><div class="label">存储引擎</div></div>
<div class="stat-card"><div class="num">29</div><div class="label">SQL 关键字</div></div>
<div class="stat-card"><div class="num">15</div><div class="label">测试套件</div></div>
</div>
</div>
</section>
<footer>
<div class="container">
<p>© 2026 <a href="https://git.metona.cn/MetonaTeam/MetonaSqlark">MetonaTeam</a>. MIT License. Built with TypeScript.</p>
</div>
</footer>
<script>
// Particle background
const canvas = document.getElementById('bg');
const ctx = canvas.getContext('2d');
let particles = [];
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resize(); window.addEventListener('resize', resize);
class Particle {
constructor() {
this.reset();
}
reset() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.vx = (Math.random() - 0.5) * 0.5;
this.vy = (Math.random() - 0.5) * 0.5;
this.size = Math.random() * 2 + 0.5;
this.opacity = Math.random() * 0.4 + 0.1;
}
update() {
this.x += this.vx; this.y += this.vy;
if (this.x < 0 || this.x > canvas.width) this.vx *= -1;
if (this.y < 0 || this.y > canvas.height) this.vy *= -1;
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = `rgba(99,102,241,${this.opacity})`;
ctx.fill();
}
}
for (let i = 0; i < 80; i++) particles.push(new Particle());
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
particles.forEach(p => { p.update(); p.draw(); });
// Draw connections
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; j < particles.length; j++) {
const dx = particles[i].x - particles[j].x;
const dy = particles[i].y - particles[j].y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 120) {
ctx.beginPath();
ctx.moveTo(particles[i].x, particles[i].y);
ctx.lineTo(particles[j].x, particles[j].y);
ctx.strokeStyle = `rgba(99,102,241,${0.06 * (1 - dist / 120)})`;
ctx.stroke();
}
}
}
requestAnimationFrame(animate);
}
animate();
</script>
</body>
</html>