Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 82 additions & 43 deletions docs/.vitepress/theme/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -161,66 +161,101 @@ body {
margin-left: 0.375em;
}

.triplex-home__code {
margin-top: 28px;
.triplex-home__section {
margin-top: 64px;
padding-top: 32px;
border-top: 1px solid var(--vp-c-divider);
}

.triplex-home__section-intro {
max-width: 760px;
margin-bottom: 28px;
}

.triplex-home .triplex-home__eyebrow {
margin: 0 0 10px;
color: var(--vp-c-brand-1);
font-family: var(--vp-font-family-mono);
font-size: 0.72rem;
letter-spacing: 0.06em;
text-transform: uppercase;
}

.triplex-home__flow {
.triplex-home__section h2 {
margin: 0 0 16px;
padding: 0;
border: 0;
font-size: clamp(1.5rem, 3vw, 1.9rem);
line-height: 1.25;
}

.triplex-home__section h3 {
margin: 0 0 10px;
font-size: 1.1rem;
line-height: 1.4;
}

.triplex-home__section p {
color: var(--vp-c-text-2);
line-height: 1.7;
}

.triplex-home__principles {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 24px;
margin-top: 28px;
}

.triplex-home__principles > div {
padding: 20px;
border: 1px solid var(--vp-c-divider);
border-bottom: 0;
border-radius: 7px 7px 0 0;
background: color-mix(in srgb, var(--vp-c-bg-elv) 72%, var(--vp-c-bg));
border-radius: 7px;
background: var(--vp-c-bg-elv);
}

.triplex-home__flow > div {
min-width: 0;
padding: 13px 16px 14px;
.triplex-home__principles p {
margin: 0;
font-size: 0.95rem;
}

.triplex-home__flow > div + div {
border-left: 1px solid var(--vp-c-divider);
.triplex-home .triplex-home__fit {
margin: 24px 0 0;
font-size: 0.95rem;
}

.triplex-home__flow span,
.triplex-home__flow strong {
display: block;
.triplex-home__example {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 2.2fr);
align-items: start;
gap: 28px;
margin-top: 32px;
}

.triplex-home__flow span {
color: var(--vp-c-brand-1);
font-family: var(--vp-font-family-mono);
font-size: 0.66rem;
letter-spacing: 0.075em;
text-transform: uppercase;
.triplex-home__example + .triplex-home__example {
padding-top: 32px;
border-top: 1px solid var(--vp-c-divider);
}

.triplex-home__flow strong {
margin-top: 5px;
overflow: hidden;
color: var(--vp-c-text-1);
font-family: var(--vp-font-family-mono);
font-size: 0.77rem;
font-weight: 400;
text-overflow: ellipsis;
white-space: nowrap;
.triplex-home__example-description p {
margin: 0 0 16px;
font-size: 0.95rem;
}

.triplex-home__code .vp-code-group {
margin: 0;
border: 1px solid var(--vp-c-divider);
border-radius: 0 0 7px 7px;
overflow: hidden;
.triplex-home__example-description a {
display: inline-flex;
align-items: center;
font-size: 0.95rem;
}

.triplex-home__code .tabs {
margin: 0;
border-radius: 0;
.triplex-home__snippet {
min-width: 0;
}

.triplex-home__code div[class*="language-"] {
.triplex-home__snippet div[class*="language-"] {
margin: 0;
border: 1px solid var(--vp-c-divider);
border-radius: 7px;
box-shadow: none;
}

Expand Down Expand Up @@ -264,12 +299,16 @@ body {
padding: 36px 0 44px;
}

.triplex-home__flow {
grid-template-columns: 1fr;
.triplex-home__section {
margin-top: 40px;
padding-top: 28px;
}
}

.triplex-home__flow > div + div {
border-top: 1px solid var(--vp-c-divider);
border-left: 0;
@media (max-width: 800px) {
.triplex-home__example,
.triplex-home__principles {
grid-template-columns: 1fr;
gap: 20px;
}
}
150 changes: 111 additions & 39 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,54 +16,126 @@ PostgreSQL is a production candidate, while Cloudflare and FoundationDB are expe
:::

<div class="triplex-home">
<header class="triplex-home__intro">
<h1>Triplex</h1>
<p class="triplex-home__tagline">A temporal fact database for TypeScript.</p>
<p class="triplex-home__detail">
<header class="triplex-home__intro">
<h1>Triplex</h1>
<p class="triplex-home__tagline">A temporal fact database for TypeScript.</p>
<p class="triplex-home__detail">
Store facts. Query relationships. Derive state. Keep the complete history.<br>
Bitemporal facts, Datalog, and typed, content-addressed configuration—built on Effect.
</p>
<div class="triplex-home__actions">
<a class="triplex-home__primary" href="/current-state">
</p>
<div class="triplex-home__actions">
<a class="triplex-home__primary" href="/current-state">
Get started<span class="vpi-arrow-right triplex-home__link-icon" aria-hidden="true"></span>
</a>
<a class="triplex-home__secondary" href="https://github.com/bjacobso/triplex">GitHub</a>
</div>
</header>

<section class="triplex-home__code" aria-label="Triplex code examples">

<div class="triplex-home__flow" aria-label="Facts flow through Datalog into derived state">
<div>
<span>01 · Facts</span>
<strong>student → submitted → quiz</strong>
</div>
<div>
<span>02 · Datalog</span>
<strong>submission without a grade</strong>
</div>
<div>
<span>03 · Current state</span>
<strong>grading task · open</strong>
</div>
</div>

::: code-group
</a>
<a class="triplex-home__secondary" href="https://github.com/bjacobso/triplex">GitHub</a>
</div>
</header>

<<< @/snippets/home/query.ts{ts twoslash}
<section class="triplex-home__section" aria-labelledby="why-triplex">
<div class="triplex-home__section-intro">
<p class="triplex-home__eyebrow">Why Triplex?</p>
<h2 id="why-triplex">Your application should be able to explain itself.</h2>
<p>Current state tells you what is true now. Explaining why often means piecing together database rows, audit logs, policy versions, and background jobs. Those systems rarely share the same view of time.</p>
<p>Triplex connects facts, history, configuration, and derived state through a shared identity model and provenance chain. You can trace an answer back to the data and the versioned rules behind it.</p>
</div>
<div class="triplex-home__principles">
<div>
<h3>What did we know?</h3>
<p>Read facts as they were recorded at a point in time, or as they were valid in the world. Corrections preserve the assertion history.</p>
</div>
<div>
<h3>Which rules applied?</h3>
<p>Pin a write to an immutable configuration release. Keep the exact schema and policy version available after the live configuration changes.</p>
</div>
<div>
<h3>What needs to happen?</h3>
<p>Query relationships and missing evidence to derive work. Your application decides how to turn those results into tasks and workflows.</p>
</div>
</div>
<p class="triplex-home__fit">Built for domains where history and rules matter: compliance, onboarding, eligibility, entitlements, and agent-driven workflows.</p>
</section>

<section class="triplex-home__section triplex-home__code" aria-labelledby="facts-and-queries">
<div class="triplex-home__section-intro">
<p class="triplex-home__eyebrow">Facts → queries → work</p>
<h2 id="facts-and-queries">From a submission to an open grading task.</h2>
<p>A student submits a quiz. Until a grade exists, that submission implies work. Here is how the facts and query fit together.</p>
</div>

<article class="triplex-home__example" aria-labelledby="write-facts">
<div class="triplex-home__example-description">
<p class="triplex-home__eyebrow">01 · Facts</p>
<h3 id="write-facts">Record what happened.</h3>
<p>Assert the submission’s relationships in one atomic transaction, with the actor, command, and governing configuration recorded alongside it.</p>
<a href="/operational-primitives">Explore transactions<span class="vpi-arrow-right triplex-home__link-icon" aria-hidden="true"></span></a>
</div>
<div class="triplex-home__snippet">

<<< @/snippets/home/facts.ts{ts twoslash}

</div>
</article>

<article class="triplex-home__example" aria-labelledby="query-work">
<div class="triplex-home__example-description">
<p class="triplex-home__eyebrow">02 · Datalog</p>
<h3 id="query-work">Ask what still needs doing.</h3>
<p>Join facts by shared variables and find submissions without a grade. Once a grade is present, the submission no longer matches.</p>
<p>A derivation can track changes to these results and retain their source provenance, so your application can reconcile its grading tasks.</p>
<a href="/derivations">Explore derived facts<span class="vpi-arrow-right triplex-home__link-icon" aria-hidden="true"></span></a>
</div>
<div class="triplex-home__snippet">

<<< @/snippets/home/query.ts{ts twoslash}

</div>
</article>

</section>

<section class="triplex-home__section triplex-home__code" aria-labelledby="config-and-versioning">
<div class="triplex-home__section-intro">
<p class="triplex-home__eyebrow">Configuration &amp; versioning</p>
<h2 id="config-and-versioning">Version the rules alongside the facts.</h2>
<p>Schemas, forms, policies, and routines change. Triplex stores their typed configuration graph as immutable, content-addressed releases, so a new deployment does not erase the rules that governed an earlier decision.</p>
</div>

<article class="triplex-home__example" aria-labelledby="typed-config">
<div class="triplex-home__example-description">
<p class="triplex-home__eyebrow">03 · Typed configuration</p>
<h3 id="typed-config">Define once. Publish a release.</h3>
<p>Attributes define identity and value type. Entity types define how those attributes are used, including requiredness and uniqueness.</p>
<p>Commit those definitions with your application’s other configuration into one release, and point <code>live</code> at its snapshot.</p>
<a href="/configuration">Explore configuration<span class="vpi-arrow-right triplex-home__link-icon" aria-hidden="true"></span></a>
</div>
<div class="triplex-home__snippet">

<<< @/snippets/home/ontology.ts{ts twoslash}

:::
</div>
</article>

<div class="triplex-home__principles">
<div>
<h3>Immutable releases</h3>
<p>Content determines identity. Equal nodes deduplicate, and unchanged revisions are shared between releases. Each snapshot preserves an exact configuration graph.</p>
</div>
<div>
<h3>Movable refs</h3>
<p>Names such as <code>test</code> and <code>live</code> point to snapshots. Promote a release or point back to an earlier one without copying configuration. Moving a ref does not undo operational writes.</p>
</div>
<div>
<h3>Pinned provenance</h3>
<p>Attach the release’s snapshot ID to a transaction with <code>configSnapshot</code>. Later, inspect the exact version that governed the write, even after <code>live</code> moves.</p>
</div>
</div>

</section>
</section>

<nav class="triplex-home__guides" aria-label="Guides">
<a href="/datalog">Datalog<span class="vpi-arrow-right triplex-home__link-icon" aria-hidden="true"></span></a>
<a href="/configuration">Typed configuration<span class="vpi-arrow-right triplex-home__link-icon" aria-hidden="true"></span></a>
<a href="/derivations">Derived facts<span class="vpi-arrow-right triplex-home__link-icon" aria-hidden="true"></span></a>
<a href="/operational-primitives">Operations<span class="vpi-arrow-right triplex-home__link-icon" aria-hidden="true"></span></a>
</nav>
<nav class="triplex-home__guides" aria-label="Guides">
<a href="/datalog">Datalog<span class="vpi-arrow-right triplex-home__link-icon" aria-hidden="true"></span></a>
<a href="/configuration">Typed configuration<span class="vpi-arrow-right triplex-home__link-icon" aria-hidden="true"></span></a>
<a href="/derivations">Derived facts<span class="vpi-arrow-right triplex-home__link-icon" aria-hidden="true"></span></a>
<a href="/operational-primitives">Operations<span class="vpi-arrow-right triplex-home__link-icon" aria-hidden="true"></span></a>
</nav>
</div>
24 changes: 23 additions & 1 deletion e2e/docs-home.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,33 @@ for (const colorScheme of ["light", "dark"] as const) {
await expect(root).not.toHaveClass(/dark/);
}
await expect(page.getByRole("heading", { level: 1, name: "Triplex" })).toBeVisible();
await expect(page.locator(".triplex-home__code")).toBeVisible();
const examples = page.locator('.triplex-home__snippet div[class*="language-"]');
await expect(examples).toHaveCount(3);
for (const example of await examples.all()) {
await expect(example).toBeVisible();
}
await expect(page.getByRole("tab")).toHaveCount(0);
await expect(
page.getByRole("heading", { name: "Your application should be able to explain itself." }),
).toBeVisible();
await expect(
page.getByRole("heading", { name: "Version the rules alongside the facts." }),
).toBeVisible();

await expect(page).toHaveScreenshot(`home-${colorScheme}.png`, {
animations: "disabled",
fullPage: true,
});
});
}

test("home examples remain visible on mobile without page overflow", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await page.goto("/");
const examples = page.locator('.triplex-home__snippet div[class*="language-"]');
await expect(examples).toHaveCount(3);
for (const example of await examples.all()) {
await expect(example).toBeVisible();
}
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390);
});
Binary file modified e2e/docs-home.spec.ts-snapshots/home-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified e2e/docs-home.spec.ts-snapshots/home-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading