@lunora/cli 1.0.0-alpha.20 → 1.0.0-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -385,6 +385,920 @@ const offerRegistryExtras = async (deps) => {
|
|
|
385
385
|
await deps.applyAll(plans);
|
|
386
386
|
};
|
|
387
387
|
|
|
388
|
+
const LOGO_PATH = "M 259.500 10.552 C 220.080 15.859, 182.424 32.566, 152.500 58.025 C 110.179 94.031, 85.380 137.183, 77.518 188.500 C 75.410 202.255, 74.569 225.677, 75.796 236.466 C 76.757 244.917, 76.683 245.692, 74.518 249.966 C 63.118 272.466, 53.141 303.876, 51.382 322.799 L 50.718 329.943 71.960 320.471 C 83.643 315.262, 93.326 311, 93.478 311 C 93.630 311, 96.547 316.063, 99.959 322.250 C 103.371 328.438, 107.249 334.850, 108.577 336.500 L 110.990 339.500 110.981 336 C 110.977 334.075, 111.499 324.991, 112.143 315.813 L 113.312 299.127 121.406 293.336 C 132.495 285.403, 149.593 271.554, 161 261.268 C 171.556 251.748, 189.116 235, 188.540 235 C 188.337 235, 183.069 238.648, 176.835 243.106 C 142.318 267.789, 68.537 314, 63.646 314 C 61.843 314, 72.791 281.179, 80.905 262.259 C 92.233 235.845, 107.473 212.389, 132.106 183.453 L 138.451 176 148.268 176 C 176.192 176, 197.512 187.154, 212.868 209.797 C 216.470 215.108, 217.035 216.595, 216.477 219.297 C 211.386 243.968, 202.359 274.496, 193.797 296 C 183.898 320.861, 167.147 352.101, 152.395 373.215 L 147.004 380.930 152.891 385.830 C 161.400 392.911, 165.563 396, 166.594 395.998 C 167.092 395.998, 168.772 391.641, 170.327 386.317 C 176.279 365.934, 188.422 338.749, 200.942 317.778 C 223.060 280.731, 256.432 244.369, 294.500 215.836 C 309.956 204.252, 313.937 201.603, 314.719 202.385 C 315.116 202.783, 315.449 213.096, 315.460 225.304 C 315.474 241.855, 315.021 250.405, 313.680 258.924 C 307.009 301.272, 291.175 336.677, 263.112 372 C 255.259 381.883, 227.182 410.673, 218.516 417.727 L 213.532 421.783 223.439 424.880 C 281.705 443.093, 349.165 436.018, 398.616 406.508 C 446.728 377.797, 483.322 331.466, 497.366 281.481 C 503.381 260.075, 504.480 250.741, 504.491 221 C 504.501 191.997, 503.598 184.047, 497.987 163.732 C 484.768 115.871, 452.505 72.708, 407.718 42.964 C 381.051 25.254, 352.818 14.828, 319.695 10.460 C 305.932 8.645, 273.298 8.695, 259.500 10.552";
|
|
389
|
+
const WELCOME_CSS = `/* The welcome page is a full-bleed starter — reset the browser's default
|
|
390
|
+
body margin/padding so it sits flush; everything else is scoped under
|
|
391
|
+
.lunora-welcome (collision-safe). */
|
|
392
|
+
body {
|
|
393
|
+
margin: 0;
|
|
394
|
+
padding: 0;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.lunora-welcome {
|
|
398
|
+
--cyan: hsl(186 84% 56%); --violet: hsl(256 72% 68%); --rose: hsl(330 80% 64%);
|
|
399
|
+
--ribbon: linear-gradient(115deg, var(--cyan), var(--violet) 52%, var(--rose));
|
|
400
|
+
--sans: "Geist Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
401
|
+
--mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
|
402
|
+
/* NIGHT (default) */
|
|
403
|
+
--bg: #0e0e11; --surface: hsl(240 12% 8% / 0.72); --surface-2: hsl(240 11% 11% / 0.82);
|
|
404
|
+
--line: hsl(0 0% 100% / 0.08); --line-2: hsl(0 0% 100% / 0.14);
|
|
405
|
+
--t-display: hsl(228 30% 97%); --t-primary: hsl(228 26% 90%); --t-secondary: hsl(228 12% 62%); --t-faint: hsl(228 10% 44%);
|
|
406
|
+
--logo: hsl(228 30% 97%); --accent: var(--violet); --shot-bg: hsl(240 12% 6%);
|
|
407
|
+
--glow-1: hsl(256 80% 52% / 0.30); --glow-2: hsl(196 84% 52% / 0.13); --arc: hsl(256 60% 70% / 0.11);
|
|
408
|
+
|
|
409
|
+
position: relative; min-height: 100vh; background: var(--bg); color: var(--t-primary);
|
|
410
|
+
font-family: var(--sans); line-height: 1.55; -webkit-font-smoothing: antialiased; overflow-x: hidden;
|
|
411
|
+
transition: background .3s, color .3s;
|
|
412
|
+
}
|
|
413
|
+
.lunora-welcome[data-theme="light"] {
|
|
414
|
+
--bg: hsl(228 32% 97%); --surface: hsl(0 0% 100% / 0.82); --surface-2: hsl(0 0% 100% / 0.95);
|
|
415
|
+
--line: hsl(228 16% 88%); --line-2: hsl(228 14% 80%);
|
|
416
|
+
--t-display: hsl(240 14% 10%); --t-primary: hsl(240 12% 18%); --t-secondary: hsl(235 9% 42%); --t-faint: hsl(235 8% 58%);
|
|
417
|
+
--logo: hsl(240 16% 9%); --accent: hsl(256 58% 56%); --shot-bg: hsl(228 26% 99%);
|
|
418
|
+
--glow-1: hsl(256 80% 60% / 0.14); --glow-2: hsl(196 84% 58% / 0.08); --arc: hsl(256 40% 55% / 0.12);
|
|
419
|
+
}
|
|
420
|
+
.lunora-welcome *, .lunora-welcome *::before, .lunora-welcome *::after { box-sizing: border-box; }
|
|
421
|
+
.lunora-welcome a { color: inherit; text-decoration: none; }
|
|
422
|
+
.lunora-welcome button { font-family: inherit; cursor: pointer; }
|
|
423
|
+
.lunora-welcome ::selection { background: hsl(256 72% 68% / 0.3); }
|
|
424
|
+
.lunora-welcome code { font-family: var(--mono); }
|
|
425
|
+
|
|
426
|
+
/* glow background */
|
|
427
|
+
.lunora-welcome .lw-bg { position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
|
|
428
|
+
.lunora-welcome .lw-bg .glow { position: absolute; left: 50%; top: -4%; width: 860px; height: 720px; transform: translateX(-50%);
|
|
429
|
+
border-radius: 50%; background: radial-gradient(circle at 50% 50%, var(--glow-1), var(--glow-2) 40%, transparent 66%); }
|
|
430
|
+
.lunora-welcome .lw-bg .arc { position: absolute; left: 50%; border-radius: 50%; border: 1px solid var(--arc); transform: translateX(-50%); }
|
|
431
|
+
.lunora-welcome .lw-bg .arc.a1 { top: -340px; width: 980px; height: 980px; }
|
|
432
|
+
.lunora-welcome .lw-bg .arc.a2 { top: -240px; width: 720px; height: 720px; opacity: .7; }
|
|
433
|
+
|
|
434
|
+
/* theme toggle */
|
|
435
|
+
.lunora-welcome .lw-toggle { position: fixed; z-index: 5; top: 20px; right: clamp(16px,4vw,36px); display: inline-flex; align-items: center;
|
|
436
|
+
gap: 7px; border: 1px solid var(--line-2); background: var(--surface); backdrop-filter: blur(12px); color: var(--t-secondary);
|
|
437
|
+
font-family: var(--mono); font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase; padding: 7px 11px; transition: .15s; }
|
|
438
|
+
.lunora-welcome .lw-toggle:hover { color: var(--t-display); border-color: var(--accent); }
|
|
439
|
+
.lunora-welcome .lw-toggle svg { width: 13px; height: 13px; }
|
|
440
|
+
|
|
441
|
+
.lunora-welcome .lw-wrap { position: relative; z-index: 2; width: 100%; max-width: 1080px; margin: 0 auto;
|
|
442
|
+
padding: clamp(44px,8vh,92px) clamp(20px,5vw,48px); display: flex; flex-direction: column; min-height: 100vh; }
|
|
443
|
+
.lunora-welcome .brand { display: flex; align-items: center; justify-content: center; gap: 15px; margin-bottom: clamp(38px,6vh,68px); color: var(--logo); }
|
|
444
|
+
.lunora-welcome .brand svg { width: 54px; height: auto; display: block; }
|
|
445
|
+
.lunora-welcome .brand .word { font-size: 34px; font-weight: 600; letter-spacing: -0.03em; color: var(--t-display); }
|
|
446
|
+
|
|
447
|
+
.lunora-welcome .grid { display: grid; grid-template-columns: 1.06fr 0.94fr; gap: 16px; align-items: stretch; flex: 1; max-height: 580px; }
|
|
448
|
+
@media (max-width: 820px) { .lunora-welcome .grid { grid-template-columns: 1fr; max-height: none; } }
|
|
449
|
+
|
|
450
|
+
.lunora-welcome .card { border: 1px solid var(--line); background: var(--surface); backdrop-filter: blur(10px); transition: border-color .2s, background .2s; }
|
|
451
|
+
.lunora-welcome .card:hover { border-color: var(--line-2); background: var(--surface-2); }
|
|
452
|
+
.lunora-welcome .card:hover .arrow { color: var(--accent); transform: translateX(3px); }
|
|
453
|
+
.lunora-welcome .arrow { color: var(--t-faint); transition: color .2s, transform .2s; }
|
|
454
|
+
.lunora-welcome .arrow svg { width: 20px; height: 20px; display: block; }
|
|
455
|
+
.lunora-welcome .ic { display: grid; place-items: center; color: var(--accent);
|
|
456
|
+
border: 1px solid color-mix(in oklab, var(--accent) 36%, transparent); background: color-mix(in oklab, var(--accent) 12%, transparent); }
|
|
457
|
+
.lunora-welcome .ic svg { display: block; }
|
|
458
|
+
|
|
459
|
+
/* left feature card — stretches to fill the box height */
|
|
460
|
+
.lunora-welcome .feature { padding: clamp(18px,2vw,24px); display: flex; flex-direction: column; }
|
|
461
|
+
.lunora-welcome .shot { border: 1px solid var(--line); overflow: hidden; background: var(--shot-bg);
|
|
462
|
+
-webkit-mask-image: linear-gradient(to bottom, #000 56%, transparent 100%); mask-image: linear-gradient(to bottom, #000 56%, transparent 100%); }
|
|
463
|
+
.lunora-welcome .shot .top { display: flex; align-items: center; gap: 12px; padding: 11px 14px; border-bottom: 1px solid var(--line); }
|
|
464
|
+
.lunora-welcome .shot .wm { display: flex; align-items: center; gap: 7px; font-size: 12px; font-weight: 600; color: var(--t-primary); }
|
|
465
|
+
.lunora-welcome .shot .wm i { width: 11px; height: 11px; background: var(--ribbon); -webkit-mask: radial-gradient(circle,#000 60%,transparent 62%); mask: radial-gradient(circle,#000 60%,transparent 62%); }
|
|
466
|
+
.lunora-welcome .shot .search { flex: 1; height: 22px; border: 1px solid var(--line); border-radius: 4px; }
|
|
467
|
+
.lunora-welcome .shot .ver { font-family: var(--mono); font-size: 8px; letter-spacing: .08em; color: var(--t-faint); }
|
|
468
|
+
.lunora-welcome .shot .body { display: grid; grid-template-columns: 92px 1fr; min-height: 224px; }
|
|
469
|
+
.lunora-welcome .shot .nav { border-right: 1px solid var(--line); padding: 14px 12px; display: flex; flex-direction: column; gap: 11px; }
|
|
470
|
+
.lunora-welcome .shot .nav i, .lunora-welcome .shot .doc i { height: 5px; border-radius: 2px; background: var(--line); }
|
|
471
|
+
.lunora-welcome .shot .doc { padding: 16px 18px; display: flex; flex-direction: column; gap: 9px; }
|
|
472
|
+
.lunora-welcome .shot .doc .h { height: 8px; width: 46%; border-radius: 2px; background: var(--line-2); margin-bottom: 5px; }
|
|
473
|
+
.lunora-welcome .shot .doc .accent { height: 5px; width: 26%; border-radius: 2px; background: var(--accent); }
|
|
474
|
+
.lunora-welcome .feature .info { margin-top: auto; padding-top: clamp(18px,2.4vh,28px); }
|
|
475
|
+
.lunora-welcome .feature .ic { width: 40px; height: 40px; margin-bottom: 15px; }
|
|
476
|
+
.lunora-welcome .feature .ic svg { width: 19px; height: 19px; }
|
|
477
|
+
.lunora-welcome .feature h2 { margin: 0 0 10px; font-size: 19px; font-weight: 600; letter-spacing: -0.015em; color: var(--t-display); }
|
|
478
|
+
.lunora-welcome .feature .row { display: flex; align-items: flex-end; gap: 16px; }
|
|
479
|
+
.lunora-welcome .feature p { margin: 0; color: var(--t-secondary); font-size: 14px; max-width: 50ch; }
|
|
480
|
+
.lunora-welcome .feature .row .arrow { margin-left: auto; }
|
|
481
|
+
|
|
482
|
+
/* right stack — smaller cards, spread to align bottoms with the feature */
|
|
483
|
+
.lunora-welcome .stack { display: flex; flex-direction: column; gap: 16px; height: 100%; }
|
|
484
|
+
.lunora-welcome .mini { flex: 1; padding: 15px 17px; display: flex; align-items: center; gap: 16px; min-height: 0; }
|
|
485
|
+
.lunora-welcome .mini .mc { flex: 1; }
|
|
486
|
+
.lunora-welcome .mini .ic { width: 32px; height: 32px; margin-bottom: 10px; }
|
|
487
|
+
.lunora-welcome .mini .ic svg { width: 16px; height: 16px; }
|
|
488
|
+
.lunora-welcome .mini h3 { margin: 0 0 5px; font-size: 16px; font-weight: 600; letter-spacing: -0.01em; color: var(--t-display); }
|
|
489
|
+
.lunora-welcome .mini p { margin: 0; color: var(--t-secondary); font-size: 12.5px; line-height: 1.45; }
|
|
490
|
+
|
|
491
|
+
.lunora-welcome .lw-foot { text-align: center; padding-top: 26px; font-family: var(--mono); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--t-faint); }
|
|
492
|
+
|
|
493
|
+
@media (prefers-reduced-motion: reduce) { .lunora-welcome * { transition: none !important; } }
|
|
494
|
+
`;
|
|
495
|
+
const REACT_APP = `import { useEffect, useState } from "react";
|
|
496
|
+
|
|
497
|
+
type Theme = "dark" | "light";
|
|
498
|
+
|
|
499
|
+
export default function App() {
|
|
500
|
+
// Default to "dark" for a stable first paint, then reconcile to the OS
|
|
501
|
+
// preference; the toggle takes over after that.
|
|
502
|
+
const [theme, setTheme] = useState<Theme>("dark");
|
|
503
|
+
|
|
504
|
+
useEffect(() => {
|
|
505
|
+
if (typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: light)").matches) {
|
|
506
|
+
setTheme("light");
|
|
507
|
+
}
|
|
508
|
+
}, []);
|
|
509
|
+
|
|
510
|
+
const isLight = theme === "light";
|
|
511
|
+
|
|
512
|
+
return (
|
|
513
|
+
<div className="lunora-welcome" data-theme={theme}>
|
|
514
|
+
<div className="lw-bg">
|
|
515
|
+
<div className="arc a1" />
|
|
516
|
+
<div className="arc a2" />
|
|
517
|
+
<div className="glow" />
|
|
518
|
+
</div>
|
|
519
|
+
|
|
520
|
+
<button className="lw-toggle" type="button" aria-label="Toggle color theme" onClick={() => setTheme(isLight ? "dark" : "light")}>
|
|
521
|
+
{isLight ? (
|
|
522
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
|
|
523
|
+
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" />
|
|
524
|
+
</svg>
|
|
525
|
+
) : (
|
|
526
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
|
|
527
|
+
<circle cx="12" cy="12" r="4" />
|
|
528
|
+
<path d="M12 2v2M12 20v2M4 12H2M22 12h-2M5 5l1.5 1.5M17.5 17.5 19 19M19 5l-1.5 1.5M6.5 17.5 5 19" />
|
|
529
|
+
</svg>
|
|
530
|
+
)}
|
|
531
|
+
<span>{isLight ? "Ivory" : "Night"}</span>
|
|
532
|
+
</button>
|
|
533
|
+
|
|
534
|
+
<div className="lw-wrap">
|
|
535
|
+
<div className="brand">
|
|
536
|
+
<svg viewBox="0 0 543 446" role="img" aria-label="Lunora">
|
|
537
|
+
<path d="${LOGO_PATH}" fill="currentColor" fillRule="evenodd" />
|
|
538
|
+
</svg>
|
|
539
|
+
<span className="word">Lunora</span>
|
|
540
|
+
</div>
|
|
541
|
+
|
|
542
|
+
<div className="grid">
|
|
543
|
+
<a className="card feature" href="https://lunora.sh/docs">
|
|
544
|
+
<div className="shot" aria-hidden="true">
|
|
545
|
+
<div className="top">
|
|
546
|
+
<span className="wm">
|
|
547
|
+
<i /> Lunora
|
|
548
|
+
</span>
|
|
549
|
+
<span className="search" />
|
|
550
|
+
<span className="ver">v0.1</span>
|
|
551
|
+
</div>
|
|
552
|
+
<div className="body">
|
|
553
|
+
<div className="nav">
|
|
554
|
+
<i style={{ width: "80%" }} />
|
|
555
|
+
<i style={{ width: "60%" }} />
|
|
556
|
+
<i style={{ width: "72%" }} />
|
|
557
|
+
<i style={{ width: "50%" }} />
|
|
558
|
+
<i style={{ width: "66%" }} />
|
|
559
|
+
<i style={{ width: "44%" }} />
|
|
560
|
+
<i style={{ width: "58%" }} />
|
|
561
|
+
</div>
|
|
562
|
+
<div className="doc">
|
|
563
|
+
<span className="h" />
|
|
564
|
+
<i style={{ width: "92%" }} />
|
|
565
|
+
<i style={{ width: "88%" }} />
|
|
566
|
+
<span className="accent" />
|
|
567
|
+
<i style={{ width: "80%" }} />
|
|
568
|
+
<i style={{ width: "90%" }} />
|
|
569
|
+
<i style={{ width: "72%" }} />
|
|
570
|
+
<i style={{ width: "84%" }} />
|
|
571
|
+
<i style={{ width: "78%" }} />
|
|
572
|
+
</div>
|
|
573
|
+
</div>
|
|
574
|
+
</div>
|
|
575
|
+
<div className="info">
|
|
576
|
+
<span className="ic">
|
|
577
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7">
|
|
578
|
+
<path d="M4 5a2 2 0 0 1 2-2h9l5 5v11a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2z" />
|
|
579
|
+
<path d="M14 3v5h5" />
|
|
580
|
+
</svg>
|
|
581
|
+
</span>
|
|
582
|
+
<h2>Documentation</h2>
|
|
583
|
+
<div className="row">
|
|
584
|
+
<p>
|
|
585
|
+
Schemas, queries, live subscriptions, sharding, and edge deploy — start to finish. New here or coming from Convex or tRPC,
|
|
586
|
+
you'll have a live app fast.
|
|
587
|
+
</p>
|
|
588
|
+
<span className="arrow">
|
|
589
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
590
|
+
<path d="M5 12h14M13 6l6 6-6 6" />
|
|
591
|
+
</svg>
|
|
592
|
+
</span>
|
|
593
|
+
</div>
|
|
594
|
+
</div>
|
|
595
|
+
</a>
|
|
596
|
+
|
|
597
|
+
<div className="stack">
|
|
598
|
+
<a className="card mini" href="https://lunora.sh/blog">
|
|
599
|
+
<div className="mc">
|
|
600
|
+
<span className="ic">
|
|
601
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7">
|
|
602
|
+
<path d="M5 4h11a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H6a2 2 0 0 1-2-2V5a1 1 0 0 1 1-1zM8 8h7M8 12h7M8 16h4" />
|
|
603
|
+
</svg>
|
|
604
|
+
</span>
|
|
605
|
+
<h3>Blog</h3>
|
|
606
|
+
<p>Product updates, deep dives, and what's new in Lunora.</p>
|
|
607
|
+
</div>
|
|
608
|
+
<span className="arrow">
|
|
609
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
610
|
+
<path d="M5 12h14M13 6l6 6-6 6" />
|
|
611
|
+
</svg>
|
|
612
|
+
</span>
|
|
613
|
+
</a>
|
|
614
|
+
<a className="card mini" href="/_lunora">
|
|
615
|
+
<div className="mc">
|
|
616
|
+
<span className="ic">
|
|
617
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7">
|
|
618
|
+
<rect x="3" y="3" width="18" height="18" rx="1" />
|
|
619
|
+
<path d="M3 9h18M9 21V9" />
|
|
620
|
+
</svg>
|
|
621
|
+
</span>
|
|
622
|
+
<h3>Lunora Studio</h3>
|
|
623
|
+
<p>Local admin for schema, data, logs, and advisors.</p>
|
|
624
|
+
</div>
|
|
625
|
+
<span className="arrow">
|
|
626
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
627
|
+
<path d="M5 12h14M13 6l6 6-6 6" />
|
|
628
|
+
</svg>
|
|
629
|
+
</span>
|
|
630
|
+
</a>
|
|
631
|
+
<a className="card mini" href="https://lunora.sh/packages">
|
|
632
|
+
<div className="mc">
|
|
633
|
+
<span className="ic">
|
|
634
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7">
|
|
635
|
+
<path d="M12 2 3 7v10l9 5 9-5V7z" />
|
|
636
|
+
<path d="M3 7l9 5 9-5M12 12v10" />
|
|
637
|
+
</svg>
|
|
638
|
+
</span>
|
|
639
|
+
<h3>Cloudflare ecosystem</h3>
|
|
640
|
+
<p>Auth, mail, storage, AI, payments — one deploy.</p>
|
|
641
|
+
</div>
|
|
642
|
+
<span className="arrow">
|
|
643
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
644
|
+
<path d="M5 12h14M13 6l6 6-6 6" />
|
|
645
|
+
</svg>
|
|
646
|
+
</span>
|
|
647
|
+
</a>
|
|
648
|
+
</div>
|
|
649
|
+
</div>
|
|
650
|
+
|
|
651
|
+
<div className="lw-foot">Running on Lunora · Vite + React</div>
|
|
652
|
+
</div>
|
|
653
|
+
</div>
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
`;
|
|
657
|
+
const VUE_APP = `<script setup lang="ts">
|
|
658
|
+
import { onMounted, ref } from "vue";
|
|
659
|
+
|
|
660
|
+
// Default to "dark" for a stable first paint, then reconcile to the OS
|
|
661
|
+
// preference; the toggle takes over after that.
|
|
662
|
+
const theme = ref<"dark" | "light">("dark");
|
|
663
|
+
|
|
664
|
+
onMounted(() => {
|
|
665
|
+
if (window.matchMedia("(prefers-color-scheme: light)").matches) {
|
|
666
|
+
theme.value = "light";
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
const toggle = (): void => {
|
|
671
|
+
theme.value = theme.value === "light" ? "dark" : "light";
|
|
672
|
+
};
|
|
673
|
+
<\/script>
|
|
674
|
+
|
|
675
|
+
<template>
|
|
676
|
+
<div class="lunora-welcome" :data-theme="theme">
|
|
677
|
+
<div class="lw-bg">
|
|
678
|
+
<div class="arc a1" />
|
|
679
|
+
<div class="arc a2" />
|
|
680
|
+
<div class="glow" />
|
|
681
|
+
</div>
|
|
682
|
+
|
|
683
|
+
<button class="lw-toggle" type="button" aria-label="Toggle color theme" @click="toggle">
|
|
684
|
+
<svg v-if="theme === 'light'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
|
685
|
+
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" />
|
|
686
|
+
</svg>
|
|
687
|
+
<svg v-else viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
|
688
|
+
<circle cx="12" cy="12" r="4" />
|
|
689
|
+
<path d="M12 2v2M12 20v2M4 12H2M22 12h-2M5 5l1.5 1.5M17.5 17.5 19 19M19 5l-1.5 1.5M6.5 17.5 5 19" />
|
|
690
|
+
</svg>
|
|
691
|
+
<span>{{ theme === "light" ? "Ivory" : "Night" }}</span>
|
|
692
|
+
</button>
|
|
693
|
+
|
|
694
|
+
<div class="lw-wrap">
|
|
695
|
+
<div class="brand">
|
|
696
|
+
<svg viewBox="0 0 543 446" role="img" aria-label="Lunora">
|
|
697
|
+
<path d="${LOGO_PATH}" fill="currentColor" fill-rule="evenodd" />
|
|
698
|
+
</svg>
|
|
699
|
+
<span class="word">Lunora</span>
|
|
700
|
+
</div>
|
|
701
|
+
|
|
702
|
+
<div class="grid">
|
|
703
|
+
<a class="card feature" href="https://lunora.sh/docs">
|
|
704
|
+
<div class="shot" aria-hidden="true">
|
|
705
|
+
<div class="top">
|
|
706
|
+
<span class="wm"><i /> Lunora</span>
|
|
707
|
+
<span class="search" />
|
|
708
|
+
<span class="ver">v0.1</span>
|
|
709
|
+
</div>
|
|
710
|
+
<div class="body">
|
|
711
|
+
<div class="nav">
|
|
712
|
+
<i style="width: 80%" />
|
|
713
|
+
<i style="width: 60%" />
|
|
714
|
+
<i style="width: 72%" />
|
|
715
|
+
<i style="width: 50%" />
|
|
716
|
+
<i style="width: 66%" />
|
|
717
|
+
<i style="width: 44%" />
|
|
718
|
+
<i style="width: 58%" />
|
|
719
|
+
</div>
|
|
720
|
+
<div class="doc">
|
|
721
|
+
<span class="h" />
|
|
722
|
+
<i style="width: 92%" />
|
|
723
|
+
<i style="width: 88%" />
|
|
724
|
+
<span class="accent" />
|
|
725
|
+
<i style="width: 80%" />
|
|
726
|
+
<i style="width: 90%" />
|
|
727
|
+
<i style="width: 72%" />
|
|
728
|
+
<i style="width: 84%" />
|
|
729
|
+
<i style="width: 78%" />
|
|
730
|
+
</div>
|
|
731
|
+
</div>
|
|
732
|
+
</div>
|
|
733
|
+
<div class="info">
|
|
734
|
+
<span class="ic">
|
|
735
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7">
|
|
736
|
+
<path d="M4 5a2 2 0 0 1 2-2h9l5 5v11a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2z" />
|
|
737
|
+
<path d="M14 3v5h5" />
|
|
738
|
+
</svg>
|
|
739
|
+
</span>
|
|
740
|
+
<h2>Documentation</h2>
|
|
741
|
+
<div class="row">
|
|
742
|
+
<p>
|
|
743
|
+
Schemas, queries, live subscriptions, sharding, and edge deploy — start to finish. New here or coming from Convex or tRPC,
|
|
744
|
+
you'll have a live app fast.
|
|
745
|
+
</p>
|
|
746
|
+
<span class="arrow">
|
|
747
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
748
|
+
<path d="M5 12h14M13 6l6 6-6 6" />
|
|
749
|
+
</svg>
|
|
750
|
+
</span>
|
|
751
|
+
</div>
|
|
752
|
+
</div>
|
|
753
|
+
</a>
|
|
754
|
+
|
|
755
|
+
<div class="stack">
|
|
756
|
+
<a class="card mini" href="https://lunora.sh/blog">
|
|
757
|
+
<div class="mc">
|
|
758
|
+
<span class="ic">
|
|
759
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7">
|
|
760
|
+
<path d="M5 4h11a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H6a2 2 0 0 1-2-2V5a1 1 0 0 1 1-1zM8 8h7M8 12h7M8 16h4" />
|
|
761
|
+
</svg>
|
|
762
|
+
</span>
|
|
763
|
+
<h3>Blog</h3>
|
|
764
|
+
<p>Product updates, deep dives, and what's new in Lunora.</p>
|
|
765
|
+
</div>
|
|
766
|
+
<span class="arrow">
|
|
767
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
768
|
+
<path d="M5 12h14M13 6l6 6-6 6" />
|
|
769
|
+
</svg>
|
|
770
|
+
</span>
|
|
771
|
+
</a>
|
|
772
|
+
<a class="card mini" href="/_lunora">
|
|
773
|
+
<div class="mc">
|
|
774
|
+
<span class="ic">
|
|
775
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7">
|
|
776
|
+
<rect x="3" y="3" width="18" height="18" rx="1" />
|
|
777
|
+
<path d="M3 9h18M9 21V9" />
|
|
778
|
+
</svg>
|
|
779
|
+
</span>
|
|
780
|
+
<h3>Lunora Studio</h3>
|
|
781
|
+
<p>Local admin for schema, data, logs, and advisors.</p>
|
|
782
|
+
</div>
|
|
783
|
+
<span class="arrow">
|
|
784
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
785
|
+
<path d="M5 12h14M13 6l6 6-6 6" />
|
|
786
|
+
</svg>
|
|
787
|
+
</span>
|
|
788
|
+
</a>
|
|
789
|
+
<a class="card mini" href="https://lunora.sh/packages">
|
|
790
|
+
<div class="mc">
|
|
791
|
+
<span class="ic">
|
|
792
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7">
|
|
793
|
+
<path d="M12 2 3 7v10l9 5 9-5V7z" />
|
|
794
|
+
<path d="M3 7l9 5 9-5M12 12v10" />
|
|
795
|
+
</svg>
|
|
796
|
+
</span>
|
|
797
|
+
<h3>Cloudflare ecosystem</h3>
|
|
798
|
+
<p>Auth, mail, storage, AI, payments — one deploy.</p>
|
|
799
|
+
</div>
|
|
800
|
+
<span class="arrow">
|
|
801
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
802
|
+
<path d="M5 12h14M13 6l6 6-6 6" />
|
|
803
|
+
</svg>
|
|
804
|
+
</span>
|
|
805
|
+
</a>
|
|
806
|
+
</div>
|
|
807
|
+
</div>
|
|
808
|
+
|
|
809
|
+
<div class="lw-foot">Running on Lunora · Vite + Vue</div>
|
|
810
|
+
</div>
|
|
811
|
+
</div>
|
|
812
|
+
</template>
|
|
813
|
+
`;
|
|
814
|
+
const SOLID_APP = `import { createSignal, onMount } from "solid-js";
|
|
815
|
+
|
|
816
|
+
export default function App() {
|
|
817
|
+
// Default to "dark" for a stable first paint, then reconcile to the OS
|
|
818
|
+
// preference; the toggle takes over after that.
|
|
819
|
+
const [theme, setTheme] = createSignal<"dark" | "light">("dark");
|
|
820
|
+
|
|
821
|
+
onMount(() => {
|
|
822
|
+
if (window.matchMedia("(prefers-color-scheme: light)").matches) {
|
|
823
|
+
setTheme("light");
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
const isLight = () => theme() === "light";
|
|
828
|
+
|
|
829
|
+
return (
|
|
830
|
+
<div class="lunora-welcome" data-theme={theme()}>
|
|
831
|
+
<div class="lw-bg">
|
|
832
|
+
<div class="arc a1" />
|
|
833
|
+
<div class="arc a2" />
|
|
834
|
+
<div class="glow" />
|
|
835
|
+
</div>
|
|
836
|
+
|
|
837
|
+
<button class="lw-toggle" type="button" aria-label="Toggle color theme" onClick={() => setTheme(isLight() ? "dark" : "light")}>
|
|
838
|
+
{isLight() ? (
|
|
839
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
|
840
|
+
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" />
|
|
841
|
+
</svg>
|
|
842
|
+
) : (
|
|
843
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
|
844
|
+
<circle cx="12" cy="12" r="4" />
|
|
845
|
+
<path d="M12 2v2M12 20v2M4 12H2M22 12h-2M5 5l1.5 1.5M17.5 17.5 19 19M19 5l-1.5 1.5M6.5 17.5 5 19" />
|
|
846
|
+
</svg>
|
|
847
|
+
)}
|
|
848
|
+
<span>{isLight() ? "Ivory" : "Night"}</span>
|
|
849
|
+
</button>
|
|
850
|
+
|
|
851
|
+
<div class="lw-wrap">
|
|
852
|
+
<div class="brand">
|
|
853
|
+
<svg viewBox="0 0 543 446" role="img" aria-label="Lunora">
|
|
854
|
+
<path d="${LOGO_PATH}" fill="currentColor" fill-rule="evenodd" />
|
|
855
|
+
</svg>
|
|
856
|
+
<span class="word">Lunora</span>
|
|
857
|
+
</div>
|
|
858
|
+
|
|
859
|
+
<div class="grid">
|
|
860
|
+
<a class="card feature" href="https://lunora.sh/docs">
|
|
861
|
+
<div class="shot" aria-hidden="true">
|
|
862
|
+
<div class="top">
|
|
863
|
+
<span class="wm">
|
|
864
|
+
<i /> Lunora
|
|
865
|
+
</span>
|
|
866
|
+
<span class="search" />
|
|
867
|
+
<span class="ver">v0.1</span>
|
|
868
|
+
</div>
|
|
869
|
+
<div class="body">
|
|
870
|
+
<div class="nav">
|
|
871
|
+
<i style={{ width: "80%" }} />
|
|
872
|
+
<i style={{ width: "60%" }} />
|
|
873
|
+
<i style={{ width: "72%" }} />
|
|
874
|
+
<i style={{ width: "50%" }} />
|
|
875
|
+
<i style={{ width: "66%" }} />
|
|
876
|
+
<i style={{ width: "44%" }} />
|
|
877
|
+
<i style={{ width: "58%" }} />
|
|
878
|
+
</div>
|
|
879
|
+
<div class="doc">
|
|
880
|
+
<span class="h" />
|
|
881
|
+
<i style={{ width: "92%" }} />
|
|
882
|
+
<i style={{ width: "88%" }} />
|
|
883
|
+
<span class="accent" />
|
|
884
|
+
<i style={{ width: "80%" }} />
|
|
885
|
+
<i style={{ width: "90%" }} />
|
|
886
|
+
<i style={{ width: "72%" }} />
|
|
887
|
+
<i style={{ width: "84%" }} />
|
|
888
|
+
<i style={{ width: "78%" }} />
|
|
889
|
+
</div>
|
|
890
|
+
</div>
|
|
891
|
+
</div>
|
|
892
|
+
<div class="info">
|
|
893
|
+
<span class="ic">
|
|
894
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7">
|
|
895
|
+
<path d="M4 5a2 2 0 0 1 2-2h9l5 5v11a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2z" />
|
|
896
|
+
<path d="M14 3v5h5" />
|
|
897
|
+
</svg>
|
|
898
|
+
</span>
|
|
899
|
+
<h2>Documentation</h2>
|
|
900
|
+
<div class="row">
|
|
901
|
+
<p>
|
|
902
|
+
Schemas, queries, live subscriptions, sharding, and edge deploy — start to finish. New here or coming from Convex or tRPC,
|
|
903
|
+
you'll have a live app fast.
|
|
904
|
+
</p>
|
|
905
|
+
<span class="arrow">
|
|
906
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
907
|
+
<path d="M5 12h14M13 6l6 6-6 6" />
|
|
908
|
+
</svg>
|
|
909
|
+
</span>
|
|
910
|
+
</div>
|
|
911
|
+
</div>
|
|
912
|
+
</a>
|
|
913
|
+
|
|
914
|
+
<div class="stack">
|
|
915
|
+
<a class="card mini" href="https://lunora.sh/blog">
|
|
916
|
+
<div class="mc">
|
|
917
|
+
<span class="ic">
|
|
918
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7">
|
|
919
|
+
<path d="M5 4h11a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H6a2 2 0 0 1-2-2V5a1 1 0 0 1 1-1zM8 8h7M8 12h7M8 16h4" />
|
|
920
|
+
</svg>
|
|
921
|
+
</span>
|
|
922
|
+
<h3>Blog</h3>
|
|
923
|
+
<p>Product updates, deep dives, and what's new in Lunora.</p>
|
|
924
|
+
</div>
|
|
925
|
+
<span class="arrow">
|
|
926
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
927
|
+
<path d="M5 12h14M13 6l6 6-6 6" />
|
|
928
|
+
</svg>
|
|
929
|
+
</span>
|
|
930
|
+
</a>
|
|
931
|
+
<a class="card mini" href="/_lunora">
|
|
932
|
+
<div class="mc">
|
|
933
|
+
<span class="ic">
|
|
934
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7">
|
|
935
|
+
<rect x="3" y="3" width="18" height="18" rx="1" />
|
|
936
|
+
<path d="M3 9h18M9 21V9" />
|
|
937
|
+
</svg>
|
|
938
|
+
</span>
|
|
939
|
+
<h3>Lunora Studio</h3>
|
|
940
|
+
<p>Local admin for schema, data, logs, and advisors.</p>
|
|
941
|
+
</div>
|
|
942
|
+
<span class="arrow">
|
|
943
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
944
|
+
<path d="M5 12h14M13 6l6 6-6 6" />
|
|
945
|
+
</svg>
|
|
946
|
+
</span>
|
|
947
|
+
</a>
|
|
948
|
+
<a class="card mini" href="https://lunora.sh/packages">
|
|
949
|
+
<div class="mc">
|
|
950
|
+
<span class="ic">
|
|
951
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7">
|
|
952
|
+
<path d="M12 2 3 7v10l9 5 9-5V7z" />
|
|
953
|
+
<path d="M3 7l9 5 9-5M12 12v10" />
|
|
954
|
+
</svg>
|
|
955
|
+
</span>
|
|
956
|
+
<h3>Cloudflare ecosystem</h3>
|
|
957
|
+
<p>Auth, mail, storage, AI, payments — one deploy.</p>
|
|
958
|
+
</div>
|
|
959
|
+
<span class="arrow">
|
|
960
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
961
|
+
<path d="M5 12h14M13 6l6 6-6 6" />
|
|
962
|
+
</svg>
|
|
963
|
+
</span>
|
|
964
|
+
</a>
|
|
965
|
+
</div>
|
|
966
|
+
</div>
|
|
967
|
+
|
|
968
|
+
<div class="lw-foot">Running on Lunora · Vite + Solid</div>
|
|
969
|
+
</div>
|
|
970
|
+
</div>
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
`;
|
|
974
|
+
const SVELTE_APP = `<script lang="ts">
|
|
975
|
+
import { onMount } from "svelte";
|
|
976
|
+
|
|
977
|
+
// Default to "dark" for a stable first paint, then reconcile to the OS
|
|
978
|
+
// preference; the toggle takes over after that.
|
|
979
|
+
let theme = $state<"dark" | "light">("dark");
|
|
980
|
+
const isLight = $derived(theme === "light");
|
|
981
|
+
|
|
982
|
+
onMount(() => {
|
|
983
|
+
if (window.matchMedia("(prefers-color-scheme: light)").matches) {
|
|
984
|
+
theme = "light";
|
|
985
|
+
}
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
const toggle = (): void => {
|
|
989
|
+
theme = theme === "light" ? "dark" : "light";
|
|
990
|
+
};
|
|
991
|
+
<\/script>
|
|
992
|
+
|
|
993
|
+
<div class="lunora-welcome" data-theme={theme}>
|
|
994
|
+
<div class="lw-bg">
|
|
995
|
+
<div class="arc a1"></div>
|
|
996
|
+
<div class="arc a2"></div>
|
|
997
|
+
<div class="glow"></div>
|
|
998
|
+
</div>
|
|
999
|
+
|
|
1000
|
+
<button class="lw-toggle" type="button" aria-label="Toggle color theme" onclick={toggle}>
|
|
1001
|
+
{#if isLight}
|
|
1002
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
|
1003
|
+
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" />
|
|
1004
|
+
</svg>
|
|
1005
|
+
{:else}
|
|
1006
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
|
1007
|
+
<circle cx="12" cy="12" r="4" />
|
|
1008
|
+
<path d="M12 2v2M12 20v2M4 12H2M22 12h-2M5 5l1.5 1.5M17.5 17.5 19 19M19 5l-1.5 1.5M6.5 17.5 5 19" />
|
|
1009
|
+
</svg>
|
|
1010
|
+
{/if}
|
|
1011
|
+
<span>{isLight ? "Ivory" : "Night"}</span>
|
|
1012
|
+
</button>
|
|
1013
|
+
|
|
1014
|
+
<div class="lw-wrap">
|
|
1015
|
+
<div class="brand">
|
|
1016
|
+
<svg viewBox="0 0 543 446" role="img" aria-label="Lunora">
|
|
1017
|
+
<path d="${LOGO_PATH}" fill="currentColor" fill-rule="evenodd" />
|
|
1018
|
+
</svg>
|
|
1019
|
+
<span class="word">Lunora</span>
|
|
1020
|
+
</div>
|
|
1021
|
+
|
|
1022
|
+
<div class="grid">
|
|
1023
|
+
<a class="card feature" href="https://lunora.sh/docs">
|
|
1024
|
+
<div class="shot" aria-hidden="true">
|
|
1025
|
+
<div class="top">
|
|
1026
|
+
<span class="wm"><i></i> Lunora</span>
|
|
1027
|
+
<span class="search"></span>
|
|
1028
|
+
<span class="ver">v0.1</span>
|
|
1029
|
+
</div>
|
|
1030
|
+
<div class="body">
|
|
1031
|
+
<div class="nav">
|
|
1032
|
+
<i style="width: 80%"></i>
|
|
1033
|
+
<i style="width: 60%"></i>
|
|
1034
|
+
<i style="width: 72%"></i>
|
|
1035
|
+
<i style="width: 50%"></i>
|
|
1036
|
+
<i style="width: 66%"></i>
|
|
1037
|
+
<i style="width: 44%"></i>
|
|
1038
|
+
<i style="width: 58%"></i>
|
|
1039
|
+
</div>
|
|
1040
|
+
<div class="doc">
|
|
1041
|
+
<span class="h"></span>
|
|
1042
|
+
<i style="width: 92%"></i>
|
|
1043
|
+
<i style="width: 88%"></i>
|
|
1044
|
+
<span class="accent"></span>
|
|
1045
|
+
<i style="width: 80%"></i>
|
|
1046
|
+
<i style="width: 90%"></i>
|
|
1047
|
+
<i style="width: 72%"></i>
|
|
1048
|
+
<i style="width: 84%"></i>
|
|
1049
|
+
<i style="width: 78%"></i>
|
|
1050
|
+
</div>
|
|
1051
|
+
</div>
|
|
1052
|
+
</div>
|
|
1053
|
+
<div class="info">
|
|
1054
|
+
<span class="ic">
|
|
1055
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7">
|
|
1056
|
+
<path d="M4 5a2 2 0 0 1 2-2h9l5 5v11a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2z" />
|
|
1057
|
+
<path d="M14 3v5h5" />
|
|
1058
|
+
</svg>
|
|
1059
|
+
</span>
|
|
1060
|
+
<h2>Documentation</h2>
|
|
1061
|
+
<div class="row">
|
|
1062
|
+
<p>
|
|
1063
|
+
Schemas, queries, live subscriptions, sharding, and edge deploy — start to finish. New here or coming from Convex or tRPC,
|
|
1064
|
+
you'll have a live app fast.
|
|
1065
|
+
</p>
|
|
1066
|
+
<span class="arrow">
|
|
1067
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1068
|
+
<path d="M5 12h14M13 6l6 6-6 6" />
|
|
1069
|
+
</svg>
|
|
1070
|
+
</span>
|
|
1071
|
+
</div>
|
|
1072
|
+
</div>
|
|
1073
|
+
</a>
|
|
1074
|
+
|
|
1075
|
+
<div class="stack">
|
|
1076
|
+
<a class="card mini" href="https://lunora.sh/blog">
|
|
1077
|
+
<div class="mc">
|
|
1078
|
+
<span class="ic">
|
|
1079
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7">
|
|
1080
|
+
<path d="M5 4h11a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H6a2 2 0 0 1-2-2V5a1 1 0 0 1 1-1zM8 8h7M8 12h7M8 16h4" />
|
|
1081
|
+
</svg>
|
|
1082
|
+
</span>
|
|
1083
|
+
<h3>Blog</h3>
|
|
1084
|
+
<p>Product updates, deep dives, and what's new in Lunora.</p>
|
|
1085
|
+
</div>
|
|
1086
|
+
<span class="arrow">
|
|
1087
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1088
|
+
<path d="M5 12h14M13 6l6 6-6 6" />
|
|
1089
|
+
</svg>
|
|
1090
|
+
</span>
|
|
1091
|
+
</a>
|
|
1092
|
+
<a class="card mini" href="/_lunora">
|
|
1093
|
+
<div class="mc">
|
|
1094
|
+
<span class="ic">
|
|
1095
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7">
|
|
1096
|
+
<rect x="3" y="3" width="18" height="18" rx="1" />
|
|
1097
|
+
<path d="M3 9h18M9 21V9" />
|
|
1098
|
+
</svg>
|
|
1099
|
+
</span>
|
|
1100
|
+
<h3>Lunora Studio</h3>
|
|
1101
|
+
<p>Local admin for schema, data, logs, and advisors.</p>
|
|
1102
|
+
</div>
|
|
1103
|
+
<span class="arrow">
|
|
1104
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1105
|
+
<path d="M5 12h14M13 6l6 6-6 6" />
|
|
1106
|
+
</svg>
|
|
1107
|
+
</span>
|
|
1108
|
+
</a>
|
|
1109
|
+
<a class="card mini" href="https://lunora.sh/packages">
|
|
1110
|
+
<div class="mc">
|
|
1111
|
+
<span class="ic">
|
|
1112
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7">
|
|
1113
|
+
<path d="M12 2 3 7v10l9 5 9-5V7z" />
|
|
1114
|
+
<path d="M3 7l9 5 9-5M12 12v10" />
|
|
1115
|
+
</svg>
|
|
1116
|
+
</span>
|
|
1117
|
+
<h3>Cloudflare ecosystem</h3>
|
|
1118
|
+
<p>Auth, mail, storage, AI, payments — one deploy.</p>
|
|
1119
|
+
</div>
|
|
1120
|
+
<span class="arrow">
|
|
1121
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1122
|
+
<path d="M5 12h14M13 6l6 6-6 6" />
|
|
1123
|
+
</svg>
|
|
1124
|
+
</span>
|
|
1125
|
+
</a>
|
|
1126
|
+
</div>
|
|
1127
|
+
</div>
|
|
1128
|
+
|
|
1129
|
+
<div class="lw-foot">Running on Lunora · Vite + Svelte</div>
|
|
1130
|
+
</div>
|
|
1131
|
+
</div>
|
|
1132
|
+
`;
|
|
1133
|
+
const VANILLA_MAIN = `import "./style.css";
|
|
1134
|
+
|
|
1135
|
+
import { LunoraClient } from "lunorash/client";
|
|
1136
|
+
|
|
1137
|
+
import { api } from "#lunora/_generated/api.js";
|
|
1138
|
+
|
|
1139
|
+
// \`@lunora/vite\` runs the Worker on the same origin as Vite, so default to
|
|
1140
|
+
// \`location.origin\`. Point \`VITE_LUNORA_URL\` at a deployed Worker to develop
|
|
1141
|
+
// the client against production data.
|
|
1142
|
+
const url = (import.meta.env.VITE_LUNORA_URL as string | undefined) ?? globalThis.location.origin;
|
|
1143
|
+
const client = new LunoraClient({ url });
|
|
1144
|
+
|
|
1145
|
+
const MOON_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" /></svg>';
|
|
1146
|
+
const SUN_ICON =
|
|
1147
|
+
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="4" /><path d="M12 2v2M12 20v2M4 12H2M22 12h-2M5 5l1.5 1.5M17.5 17.5 19 19M19 5l-1.5 1.5M6.5 17.5 5 19" /></svg>';
|
|
1148
|
+
|
|
1149
|
+
const welcomeHtml = \`
|
|
1150
|
+
<div class="lw-bg">
|
|
1151
|
+
<div class="arc a1"></div>
|
|
1152
|
+
<div class="arc a2"></div>
|
|
1153
|
+
<div class="glow"></div>
|
|
1154
|
+
</div>
|
|
1155
|
+
|
|
1156
|
+
<button class="lw-toggle" type="button" aria-label="Toggle color theme"></button>
|
|
1157
|
+
|
|
1158
|
+
<div class="lw-wrap">
|
|
1159
|
+
<div class="brand">
|
|
1160
|
+
<svg viewBox="0 0 543 446" role="img" aria-label="Lunora">
|
|
1161
|
+
<path d="${LOGO_PATH}" fill="currentColor" fill-rule="evenodd" />
|
|
1162
|
+
</svg>
|
|
1163
|
+
<span class="word">Lunora</span>
|
|
1164
|
+
</div>
|
|
1165
|
+
|
|
1166
|
+
<div class="grid">
|
|
1167
|
+
<a class="card feature" href="https://lunora.sh/docs">
|
|
1168
|
+
<div class="shot" aria-hidden="true">
|
|
1169
|
+
<div class="top">
|
|
1170
|
+
<span class="wm"><i></i> Lunora</span>
|
|
1171
|
+
<span class="search"></span>
|
|
1172
|
+
<span class="ver">v0.1</span>
|
|
1173
|
+
</div>
|
|
1174
|
+
<div class="body">
|
|
1175
|
+
<div class="nav">
|
|
1176
|
+
<i style="width: 80%"></i>
|
|
1177
|
+
<i style="width: 60%"></i>
|
|
1178
|
+
<i style="width: 72%"></i>
|
|
1179
|
+
<i style="width: 50%"></i>
|
|
1180
|
+
<i style="width: 66%"></i>
|
|
1181
|
+
<i style="width: 44%"></i>
|
|
1182
|
+
<i style="width: 58%"></i>
|
|
1183
|
+
</div>
|
|
1184
|
+
<div class="doc">
|
|
1185
|
+
<span class="h"></span>
|
|
1186
|
+
<i style="width: 92%"></i>
|
|
1187
|
+
<i style="width: 88%"></i>
|
|
1188
|
+
<span class="accent"></span>
|
|
1189
|
+
<i style="width: 80%"></i>
|
|
1190
|
+
<i style="width: 90%"></i>
|
|
1191
|
+
<i style="width: 72%"></i>
|
|
1192
|
+
<i style="width: 84%"></i>
|
|
1193
|
+
<i style="width: 78%"></i>
|
|
1194
|
+
</div>
|
|
1195
|
+
</div>
|
|
1196
|
+
</div>
|
|
1197
|
+
<div class="info">
|
|
1198
|
+
<span class="ic">
|
|
1199
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7">
|
|
1200
|
+
<path d="M4 5a2 2 0 0 1 2-2h9l5 5v11a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2z" />
|
|
1201
|
+
<path d="M14 3v5h5" />
|
|
1202
|
+
</svg>
|
|
1203
|
+
</span>
|
|
1204
|
+
<h2>Documentation</h2>
|
|
1205
|
+
<div class="row">
|
|
1206
|
+
<p>
|
|
1207
|
+
Schemas, queries, live subscriptions, sharding, and edge deploy — start to finish. New here or coming from Convex or tRPC,
|
|
1208
|
+
you'll have a live app fast.
|
|
1209
|
+
</p>
|
|
1210
|
+
<span class="arrow">
|
|
1211
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1212
|
+
<path d="M5 12h14M13 6l6 6-6 6" />
|
|
1213
|
+
</svg>
|
|
1214
|
+
</span>
|
|
1215
|
+
</div>
|
|
1216
|
+
</div>
|
|
1217
|
+
</a>
|
|
1218
|
+
|
|
1219
|
+
<div class="stack">
|
|
1220
|
+
<a class="card mini" href="https://lunora.sh/blog">
|
|
1221
|
+
<div class="mc">
|
|
1222
|
+
<span class="ic">
|
|
1223
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7">
|
|
1224
|
+
<path d="M5 4h11a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H6a2 2 0 0 1-2-2V5a1 1 0 0 1 1-1zM8 8h7M8 12h7M8 16h4" />
|
|
1225
|
+
</svg>
|
|
1226
|
+
</span>
|
|
1227
|
+
<h3>Blog</h3>
|
|
1228
|
+
<p>Product updates, deep dives, and what's new in Lunora.</p>
|
|
1229
|
+
</div>
|
|
1230
|
+
<span class="arrow">
|
|
1231
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1232
|
+
<path d="M5 12h14M13 6l6 6-6 6" />
|
|
1233
|
+
</svg>
|
|
1234
|
+
</span>
|
|
1235
|
+
</a>
|
|
1236
|
+
<a class="card mini" href="/_lunora">
|
|
1237
|
+
<div class="mc">
|
|
1238
|
+
<span class="ic">
|
|
1239
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7">
|
|
1240
|
+
<rect x="3" y="3" width="18" height="18" rx="1" />
|
|
1241
|
+
<path d="M3 9h18M9 21V9" />
|
|
1242
|
+
</svg>
|
|
1243
|
+
</span>
|
|
1244
|
+
<h3>Lunora Studio</h3>
|
|
1245
|
+
<p>Local admin for schema, data, logs, and advisors.</p>
|
|
1246
|
+
</div>
|
|
1247
|
+
<span class="arrow">
|
|
1248
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1249
|
+
<path d="M5 12h14M13 6l6 6-6 6" />
|
|
1250
|
+
</svg>
|
|
1251
|
+
</span>
|
|
1252
|
+
</a>
|
|
1253
|
+
<a class="card mini" href="https://lunora.sh/packages">
|
|
1254
|
+
<div class="mc">
|
|
1255
|
+
<span class="ic">
|
|
1256
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7">
|
|
1257
|
+
<path d="M12 2 3 7v10l9 5 9-5V7z" />
|
|
1258
|
+
<path d="M3 7l9 5 9-5M12 12v10" />
|
|
1259
|
+
</svg>
|
|
1260
|
+
</span>
|
|
1261
|
+
<h3>Cloudflare ecosystem</h3>
|
|
1262
|
+
<p>Auth, mail, storage, AI, payments — one deploy.</p>
|
|
1263
|
+
</div>
|
|
1264
|
+
<span class="arrow">
|
|
1265
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1266
|
+
<path d="M5 12h14M13 6l6 6-6 6" />
|
|
1267
|
+
</svg>
|
|
1268
|
+
</span>
|
|
1269
|
+
</a>
|
|
1270
|
+
</div>
|
|
1271
|
+
</div>
|
|
1272
|
+
|
|
1273
|
+
<div class="lw-foot">Running on Lunora · Vite + Vanilla · <span id="lw-count">0</span> messages</div>
|
|
1274
|
+
</div>
|
|
1275
|
+
\`;
|
|
1276
|
+
|
|
1277
|
+
const root = document.querySelector<HTMLDivElement>("#app")!;
|
|
1278
|
+
root.classList.add("lunora-welcome");
|
|
1279
|
+
root.innerHTML = welcomeHtml;
|
|
1280
|
+
|
|
1281
|
+
// Theme toggle: flip the root's data-theme + swap the button's icon/label.
|
|
1282
|
+
const toggleButton = root.querySelector<HTMLButtonElement>(".lw-toggle")!;
|
|
1283
|
+
|
|
1284
|
+
const paintToggle = (theme: "dark" | "light"): void => {
|
|
1285
|
+
root.dataset.theme = theme;
|
|
1286
|
+
toggleButton.innerHTML = \`\${theme === "light" ? MOON_ICON : SUN_ICON}<span>\${theme === "light" ? "Ivory" : "Night"}</span>\`;
|
|
1287
|
+
};
|
|
1288
|
+
|
|
1289
|
+
paintToggle(window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark");
|
|
1290
|
+
toggleButton.addEventListener("click", () => {
|
|
1291
|
+
paintToggle(root.dataset.theme === "light" ? "dark" : "light");
|
|
1292
|
+
});
|
|
1293
|
+
|
|
1294
|
+
// Live demo: the message count of a demo channel re-renders on every delta.
|
|
1295
|
+
const count = document.querySelector<HTMLSpanElement>("#lw-count")!;
|
|
1296
|
+
|
|
1297
|
+
client.onUpdate(api.messages.list, { channelId: "channel:demo" }, (result) => {
|
|
1298
|
+
count.textContent = String(result.messages.length);
|
|
1299
|
+
});
|
|
1300
|
+
`;
|
|
1301
|
+
|
|
388
1302
|
const READ_URL = `const url = (import.meta.env.VITE_LUNORA_URL as string | undefined) ?? globalThis.location.origin;`;
|
|
389
1303
|
const REACT_MAIN = `import "./index.css";
|
|
390
1304
|
|
|
@@ -471,47 +1385,57 @@ const app = mount(Root, { target: document.getElementById("app")! });
|
|
|
471
1385
|
|
|
472
1386
|
export default app;
|
|
473
1387
|
`;
|
|
474
|
-
const VANILLA_MAIN = `import "./style.css";
|
|
475
|
-
|
|
476
|
-
import { LunoraClient } from "lunorash/client";
|
|
477
|
-
|
|
478
|
-
import { api } from "../lunora/_generated/api";
|
|
479
|
-
|
|
480
|
-
// Vanilla starter: no framework provider — talk to Lunora through the client
|
|
481
|
-
// directly. \`@lunora/vite\` runs the Worker on the same origin as Vite.
|
|
482
|
-
${READ_URL}
|
|
483
|
-
const client = new LunoraClient({ url });
|
|
484
|
-
|
|
485
|
-
const root = document.querySelector<HTMLDivElement>("#app")!;
|
|
486
|
-
|
|
487
|
-
const heading = document.createElement("h1");
|
|
488
|
-
heading.textContent = "Vite + Lunora";
|
|
489
|
-
|
|
490
|
-
const output = document.createElement("pre");
|
|
491
|
-
root.replaceChildren(heading, output);
|
|
492
|
-
|
|
493
|
-
const render = (messages: unknown): void => {
|
|
494
|
-
// textContent (not innerHTML) — never inject server data as markup.
|
|
495
|
-
output.textContent = JSON.stringify(messages, null, 2);
|
|
496
|
-
};
|
|
497
|
-
|
|
498
|
-
// Live subscription: the list re-renders on every server delta.
|
|
499
|
-
client.onUpdate(api.messages.list, { channelId: "channel:demo" }, render);
|
|
500
|
-
`;
|
|
501
1388
|
const ADAPTERS = {
|
|
502
|
-
react: {
|
|
503
|
-
|
|
1389
|
+
react: {
|
|
1390
|
+
adapter: "@lunora/react",
|
|
1391
|
+
createViteTemplate: "react-ts",
|
|
1392
|
+
files: [
|
|
1393
|
+
{ contents: REACT_MAIN, path: "src/main.tsx" },
|
|
1394
|
+
{ contents: REACT_APP, path: "src/App.tsx" },
|
|
1395
|
+
{ contents: WELCOME_CSS, path: "src/index.css" }
|
|
1396
|
+
],
|
|
1397
|
+
label: "React"
|
|
1398
|
+
},
|
|
1399
|
+
solid: {
|
|
1400
|
+
adapter: "@lunora/solid",
|
|
1401
|
+
createViteTemplate: "solid",
|
|
1402
|
+
files: [
|
|
1403
|
+
{ contents: SOLID_INDEX, path: "src/index.tsx" },
|
|
1404
|
+
{ contents: SOLID_APP, path: "src/App.tsx" },
|
|
1405
|
+
{ contents: WELCOME_CSS, path: "src/index.css" }
|
|
1406
|
+
],
|
|
1407
|
+
label: "Solid"
|
|
1408
|
+
},
|
|
504
1409
|
svelte: {
|
|
505
1410
|
adapter: "@lunora/svelte",
|
|
506
1411
|
createViteTemplate: "svelte-ts",
|
|
507
1412
|
files: [
|
|
508
1413
|
{ contents: SVELTE_ROOT, path: "src/Root.svelte" },
|
|
509
|
-
{ contents: SVELTE_MAIN, path: "src/main.ts" }
|
|
1414
|
+
{ contents: SVELTE_MAIN, path: "src/main.ts" },
|
|
1415
|
+
{ contents: SVELTE_APP, path: "src/App.svelte" },
|
|
1416
|
+
{ contents: WELCOME_CSS, path: "src/app.css" }
|
|
510
1417
|
],
|
|
511
1418
|
label: "Svelte"
|
|
512
1419
|
},
|
|
513
|
-
vanilla: {
|
|
514
|
-
|
|
1420
|
+
vanilla: {
|
|
1421
|
+
adapter: "lunorash/client",
|
|
1422
|
+
createViteTemplate: "vanilla-ts",
|
|
1423
|
+
files: [
|
|
1424
|
+
{ contents: VANILLA_MAIN, path: "src/main.ts" },
|
|
1425
|
+
{ contents: WELCOME_CSS, path: "src/style.css" }
|
|
1426
|
+
],
|
|
1427
|
+
label: "Vanilla"
|
|
1428
|
+
},
|
|
1429
|
+
vue: {
|
|
1430
|
+
adapter: "@lunora/vue",
|
|
1431
|
+
createViteTemplate: "vue-ts",
|
|
1432
|
+
files: [
|
|
1433
|
+
{ contents: VUE_MAIN, path: "src/main.ts" },
|
|
1434
|
+
{ contents: VUE_APP, path: "src/App.vue" },
|
|
1435
|
+
{ contents: WELCOME_CSS, path: "src/style.css" }
|
|
1436
|
+
],
|
|
1437
|
+
label: "Vue"
|
|
1438
|
+
}
|
|
515
1439
|
};
|
|
516
1440
|
const isOverlayFramework = (value) => Object.hasOwn(ADAPTERS, value);
|
|
517
1441
|
|
|
@@ -526,15 +1450,39 @@ export default defineSchema({
|
|
|
526
1450
|
.index("by_channel", ["channelId"]),
|
|
527
1451
|
});
|
|
528
1452
|
`;
|
|
529
|
-
const LUNORA_MESSAGES = `import {
|
|
1453
|
+
const LUNORA_MESSAGES = `import { RateLimiter, rateLimit } from "@lunora/ratelimit";
|
|
1454
|
+
|
|
1455
|
+
import { mutation, query, v } from "#lunora/_generated/server.js";
|
|
530
1456
|
|
|
531
|
-
|
|
532
|
-
|
|
1457
|
+
/**
|
|
1458
|
+
* One in-memory limiter so the public \`send\` mutation isn't an open flood target
|
|
1459
|
+
* out of the box. The default store is in-memory (per-isolate, resets on
|
|
1460
|
+
* eviction) — fine for a starter; run \`lunora add ratelimit\` for the durable,
|
|
1461
|
+
* \`ctx.db\`-backed store when you ship to production.
|
|
1462
|
+
*/
|
|
1463
|
+
const limiter = new RateLimiter({
|
|
1464
|
+
config: {
|
|
1465
|
+
send: { kind: "token bucket", period: 60_000, rate: 30 },
|
|
1466
|
+
},
|
|
533
1467
|
});
|
|
534
1468
|
|
|
535
|
-
export const
|
|
536
|
-
|
|
1469
|
+
export const list = query.input({ channelId: v.string().meta({ schema: { maxLength: 256 } }), limit: v.optional(v.number()) }).query(async ({ args, ctx }) => {
|
|
1470
|
+
const messages = await ctx.db
|
|
1471
|
+
.query("messages")
|
|
1472
|
+
.withIndex("by_channel", (q) => q.eq("channelId", args.channelId))
|
|
1473
|
+
.take(args.limit ?? 50);
|
|
1474
|
+
|
|
1475
|
+
return { channelId: args.channelId, messages };
|
|
537
1476
|
});
|
|
1477
|
+
|
|
1478
|
+
export const send = mutation
|
|
1479
|
+
.input({ channelId: v.string().meta({ schema: { maxLength: 256 } }), text: v.string().meta({ schema: { maxLength: 4096 } }) })
|
|
1480
|
+
.use(rateLimit(limiter, "send", { key: (ctx) => ctx.auth.userId ?? "anon" }))
|
|
1481
|
+
.mutation(async ({ args, ctx }) => {
|
|
1482
|
+
const id = await ctx.db.insert("messages", { channelId: args.channelId, text: args.text });
|
|
1483
|
+
|
|
1484
|
+
return { channelId: args.channelId, id, text: args.text };
|
|
1485
|
+
});
|
|
538
1486
|
`;
|
|
539
1487
|
const SERVER_ENTRY = `import type { ShardNamespaceLike } from "lunorash/runtime";
|
|
540
1488
|
|
|
@@ -605,6 +1553,7 @@ const patchPackageJson = async (target, name, adapter, distTag) => {
|
|
|
605
1553
|
const path = join$1(target, "package.json");
|
|
606
1554
|
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
607
1555
|
let dependencies = withDependency(parsed.dependencies ?? {}, "lunorash", distTag, distTag);
|
|
1556
|
+
dependencies = withDependency(dependencies, "@lunora/ratelimit", distTag, distTag);
|
|
608
1557
|
if (adapter.adapter.startsWith("@lunora/")) {
|
|
609
1558
|
dependencies = withDependency(dependencies, adapter.adapter, distTag, distTag);
|
|
610
1559
|
}
|