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
16 changes: 5 additions & 11 deletions docs/graalvm.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,9 @@ metadata directory, small enough to read in a minute:
- [Spring Boot example metadata](https://github.com/storm-orm/storm-example-kotlin-spring-boot-4-graalvm/blob/main/src/main/kotlin/st/orm/demo/imdb/ApplicationRuntimeHints.kt)
- [Ktor example metadata](https://github.com/storm-orm/storm-example-kotlin-ktor-graalvm/tree/main/src/main/resources/META-INF/native-image)

## Measured
## What you get

Both examples, on an Apple M-series MacBook against PostgreSQL 17, verified by their Playwright
interface suites running against the native binaries:

| | Startup | Dataset import (1.5M rows) | Binary |
|---|---|---|---|
| Spring Boot 4 | ~0.25 s | 87 s | 122 MB |
| Ktor 3 (CIO) | ~0.13 s | 83 s | 97 MB |

The import runs faster natively than on the JVM (117 s): a one-shot batch job never gets the JIT
warmup it would need to catch up.
Both examples compile to a self-contained native binary that starts in a fraction of a second,
with no JVM and no warmup, and both are verified by their Playwright interface suites running
against the native binaries. A batch import can even finish faster natively than on the JVM: a
one-shot job never runs long enough to earn back the JIT warmup it would need to catch up.
14 changes: 10 additions & 4 deletions website/docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ const config: Config = {
tagline: 'A modern, high-performance ORM for Kotlin 2.0+ and Java 21+',
favicon: 'img/storm-dark.png',

// Load the marketing type pair (Inter + JetBrains Mono) on the docs pages too,
// so the Docusaurus-chrome theme matches the custom pages (see custom.css).
stylesheets: [
'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap',
],

url: 'https://orm.st',
baseUrl: '/',

Expand Down Expand Up @@ -151,7 +157,7 @@ const config: Config = {
// TODO(campaign): replace with a purpose-built 1200x630 social card.
image: 'img/storm.png',
navbar: {
title: 'Storm',
title: 'ST/ORM',
logo: {
alt: 'Storm Logo',
src: 'img/storm-dark.png',
Expand Down Expand Up @@ -231,9 +237,9 @@ const config: Config = {
additionalLanguages: ['java', 'kotlin', 'groovy'],
},
colorMode: {
defaultMode: 'light',
disableSwitch: false,
respectPrefersColorScheme: true,
defaultMode: 'dark',
disableSwitch: true,
respectPrefersColorScheme: false,
},
} satisfies Preset.ThemeConfig,
};
Expand Down
2 changes: 1 addition & 1 deletion website/src/components/blog/blogTheme.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function BlogPage({title, description, slug, dateISO, body}) {
<>
<Head>
<html lang="en" />
<title>{`${title} · Storm Blog`}</title>
<title>{`${title} · ST/ORM Blog`}</title>
<meta name="description" content={description} />
<link rel="canonical" href={url} />
<meta property="og:type" content="article" />
Expand Down
10 changes: 6 additions & 4 deletions website/src/components/examples/ExampleReadmePage.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import React, {useEffect} from 'react';
import Head from '@docusaurus/Head';
import {TUT_CSS, navHtml, FOOT_HTML} from '../tutorial/tutorialTheme';
import {TUT_CSS, navHtml, FOOT_HTML, clonebar, wireSqlToggles} from '../tutorial/tutorialTheme';

// Route component for the /examples/<slug> pages added by the
// example-readmes plugin. Receives the example (title, description, chips,
Expand Down Expand Up @@ -45,7 +45,9 @@ export default function ExampleReadmePage({example}) {
const {slug, repo, title, description, chips, html} = example;
const url = `https://orm.st/examples/${slug}/`;
const githubUrl = `https://github.com/storm-orm/${repo}`;
const pageTitle = `${title} · Storm Example Projects`;
const pageTitle = `${title} · ST/ORM Example Projects`;

useEffect(() => wireSqlToggles(), []);

const body = `
${navHtml('examples')}
Expand All @@ -56,7 +58,7 @@ ${navHtml('examples')}
<p class="dek">${description}</p>
<div class="meta">${chips.map((chip) => `<span>${chip}</span>`).join('')}</div>
<div class="getit">
<div class="clonebar"><span class="dollar">$</span>git clone ${githubUrl}.git</div>
${clonebar(`git clone ${githubUrl}.git`)}
<a class="btn" href="${githubUrl}">View on GitHub →</a>
</div>
</div>
Expand Down
87 changes: 53 additions & 34 deletions website/src/components/tutorial/tutorialTheme.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,15 @@ export function editor({file, tag, code, sql, copy, variants}) {
</div>`;
}

// A one-line "$ <command>" shell bar with a Copy button, matching the editor
// copy control. `command` is copied verbatim (without the leading $). Wired by
// wireSqlToggles(), so pages that render a clonebar() must call it in useEffect.
export function clonebar(command) {
const data = esc(command).replace(/"/g, '&quot;');
return `<div class="clonebar"><span class="dollar">$</span><span class="clonecmd">${esc(command)}</span>` +
`<span class="copybtn iconly" data-copy="${data}" title="Copy" aria-label="Copy"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></span></div>`;
}

// A compact two-pane "at a glance" comparison for one-liner contrasts. Only
// use where both snippets are genuinely short; full examples stay sequential.
export function glance({left, right}) {
Expand All @@ -140,8 +149,43 @@ export function glance({left, right}) {
</div>`;
}

// Wires up every "Show SQL" and "Copy" button rendered by editor(). Returns a
// cleanup function for the calling useEffect.
// Wires one .copybtn: copies its data-copy to the clipboard and flips the label
// to "Copied" for a moment. Returns a cleanup function.
function attachCopy(copyBtn) {
const txt = copyBtn.querySelector('.copybtntext');
let timer = null;
const onCopy = () => {
const text = copyBtn.dataset.copy;
// The Clipboard API needs a secure context; fall back to a throwaway
// textarea so copy also works on plain-http previews (e.g. LAN).
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text);
} else {
const ta = document.createElement('textarea');
ta.value = text;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
ta.remove();
}
copyBtn.classList.add('on');
if (txt) txt.textContent = 'Copied';
clearTimeout(timer);
timer = setTimeout(() => {
copyBtn.classList.remove('on');
if (txt) txt.textContent = 'Copy';
}, 1600);
};
copyBtn.addEventListener('click', onCopy);
return () => {
copyBtn.removeEventListener('click', onCopy);
clearTimeout(timer);
};
}

// Wires up every "Show SQL" and "Copy" button rendered by editor(), plus the
// Copy button on any clonebar(). Returns a cleanup function for the calling
// useEffect.
export function wireSqlToggles() {
const cleanups = [];
document.querySelectorAll('.storm-tut .editor').forEach((ed) => {
Expand All @@ -157,37 +201,7 @@ export function wireSqlToggles() {
cleanups.push(() => btn.removeEventListener('click', onClick));
}
const copyBtn = ed.querySelector('.copybtn');
if (copyBtn) {
const txt = copyBtn.querySelector('.copybtntext');
let timer = null;
const onCopy = () => {
const text = copyBtn.dataset.copy;
// The Clipboard API needs a secure context; fall back to a throwaway
// textarea so copy also works on plain-http previews (e.g. LAN).
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text);
} else {
const ta = document.createElement('textarea');
ta.value = text;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
ta.remove();
}
copyBtn.classList.add('on');
if (txt) txt.textContent = 'Copied';
clearTimeout(timer);
timer = setTimeout(() => {
copyBtn.classList.remove('on');
if (txt) txt.textContent = 'Copy';
}, 1600);
};
copyBtn.addEventListener('click', onCopy);
cleanups.push(() => {
copyBtn.removeEventListener('click', onCopy);
clearTimeout(timer);
});
}
if (copyBtn) cleanups.push(attachCopy(copyBtn));
const sel = ed.querySelector('.varsel');
if (sel) {
const panes = ed.querySelectorAll('.varpane');
Expand All @@ -202,6 +216,9 @@ export function wireSqlToggles() {
cleanups.push(() => sel.removeEventListener('change', onChange));
}
});
document.querySelectorAll('.storm-tut .clonebar .copybtn').forEach((copyBtn) => {
cleanups.push(attachCopy(copyBtn));
});
return () => cleanups.forEach((fn) => fn());
}

Expand All @@ -215,7 +232,7 @@ export function TutorialPage({title, description, slug, body}) {
<>
<Head>
<html lang="en" />
<title>{`${title} · Storm Tutorials`}</title>
<title>{`${title} · ST/ORM Tutorials`}</title>
<meta name="description" content={description} />
<link rel="canonical" href={url} />
<meta property="og:type" content="article" />
Expand Down Expand Up @@ -342,6 +359,8 @@ export const TUT_CSS = `
.storm-tut .sqlbtn:hover,.storm-tut .copybtn:hover{background:rgba(129,140,248,.12);border-color:rgba(129,140,248,.5)}
.storm-tut .sqlbtn.on,.storm-tut .copybtn.on{background:rgba(129,140,248,.16);color:#aab2ff}
.storm-tut .sqlbtn .ico,.storm-tut .copybtn .ico{width:13px;height:13px;opacity:.9}
.storm-tut .clonebar .clonecmd{overflow-x:auto}
.storm-tut .clonebar .copybtn{margin-left:auto;flex:none;gap:0;padding:5px 8px}
/* variant selector (same chrome as the buttons; native select behind a
custom chevron because appearance:none drops the platform arrow) */
.storm-tut .varwrap{position:relative;margin-left:14px;display:inline-flex;align-items:center}
Expand Down
87 changes: 72 additions & 15 deletions website/src/css/custom.css
Original file line number Diff line number Diff line change
@@ -1,22 +1,79 @@
/* Docs (Infima) theme aligned with the marketing site's dark palette. The
custom landing/tutorial/blog pages carry their own dark shell; these variables
bring the Docusaurus-chrome pages (/docs) onto the same tokens: background
#070709, panels #0f0f14, text #eaeaf0, indigo accent #818cf8, borders #20202a,
and the Inter / JetBrains Mono type pair. Default mode is dark (see
docusaurus.config.ts colorMode); the light theme keeps a readable purple. */

:root {
--ifm-color-primary: #3949ab;
--ifm-color-primary-dark: #303f9f;
--ifm-color-primary-darker: #283593;
--ifm-color-primary-darkest: #1a237e;
--ifm-color-primary-light: #5c6bc0;
--ifm-color-primary-lighter: #7986cb;
--ifm-color-primary-lightest: #9fa8da;
--ifm-code-font-size: 95%;
/* Type pair shared with the marketing pages. */
--ifm-font-family-base: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
--ifm-font-family-monospace: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
--ifm-code-font-size: 92%;

/* Light theme uses a darker indigo so buttons/links keep contrast on white. */
--ifm-color-primary: #4f46e5;
--ifm-color-primary-dark: #4740ce;
--ifm-color-primary-darker: #433cc2;
--ifm-color-primary-darkest: #3731a0;
--ifm-color-primary-light: #6a62e9;
--ifm-color-primary-lighter: #7972eb;
--ifm-color-primary-lightest: #9d98f0;
}

[data-theme='dark'] {
--ifm-color-primary: #7986cb;
--ifm-color-primary-dark: #5c6bc0;
--ifm-color-primary-darker: #3949ab;
--ifm-color-primary-darkest: #303f9f;
--ifm-color-primary-light: #9fa8da;
--ifm-color-primary-lighter: #c5cae9;
--ifm-color-primary-lightest: #e8eaf6;
/* Accent: the marketing --accent (#818cf8), the lighter shade on dark. */
--ifm-color-primary: #818cf8;
--ifm-color-primary-dark: #6470f6;
--ifm-color-primary-darker: #5563f5;
--ifm-color-primary-darkest: #2b3cf2;
--ifm-color-primary-light: #9ea8fa;
--ifm-color-primary-lighter: #adb5fb;
--ifm-color-primary-lightest: #d8dcfe;

/* Surfaces and text mapped from the site tokens. */
--ifm-background-color: #070709;
--ifm-background-surface-color: #0f0f14;
--ifm-navbar-background-color: #0b0b0f;
--ifm-footer-background-color: #0b0b0f;
--ifm-font-color-base: #eaeaf0;
--ifm-heading-color: #eaeaf0;
--ifm-color-content: #b7b8c2;
--ifm-color-content-secondary: #8a8a96;

/* Borders / rules → --border #20202a, softened via the emphasis ramp. */
--ifm-toc-border-color: #20202a;
--ifm-table-border-color: #20202a;
--ifm-hr-border-color: #20202a;
--ifm-color-emphasis-100: #0f0f14;
--ifm-color-emphasis-200: #17171f;
--ifm-color-emphasis-300: #20202a;

/* Links, sidebar menu, breadcrumbs → accent. */
--ifm-link-color: #a5b4fc;
--ifm-menu-color: #b7b8c2;
--ifm-menu-color-active: #a5b4fc;
--ifm-menu-color-background-active: rgba(129, 140, 248, 0.08);
--ifm-menu-color-background-hover: rgba(129, 140, 248, 0.06);
--ifm-breadcrumb-color-active: #a5b4fc;
--ifm-breadcrumb-item-background-active: rgba(129, 140, 248, 0.1);

/* Code blocks → panel background (dracula tokens still read fine on it). */
--ifm-code-background: #0f0f14;
--prism-background-color: #0b0b0f;
--docusaurus-highlighted-code-line-bg: rgba(129, 140, 248, 0.14);
}

/* Navbar: subtle bottom rule like the marketing nav. */
[data-theme='dark'] .navbar {
border-bottom: 1px solid #17171f;
box-shadow: none;
}

/* Inline code chips: match the marketing --panel/--border-soft look. */
[data-theme='dark'] code {
background: #0f0f14;
border: 1px solid #17171f;
}

/* Cleaner tables in docs and blog. Infima draws a full cell grid (vertical +
Expand Down
6 changes: 3 additions & 3 deletions website/src/pages/benchmarks.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, {useEffect} from 'react';
import Head from '@docusaurus/Head';
import {
TUT_CSS, navHtml, FOOT_HTML, wireSqlToggles, editor,
TUT_CSS, navHtml, FOOT_HTML, wireSqlToggles, editor, clonebar,
K, T, S, C, F, N, A, P, QK, QQ,
} from '../components/tutorial/tutorialTheme';

const TITLE = 'Benchmarks · Storm ORM vs Hibernate, jOOQ, Exposed and Jimmer';
const TITLE = 'Benchmarks · ST/ORM vs Hibernate, jOOQ, Exposed and Jimmer';
const DESC = 'Reproducible JMH benchmarks of Storm against JDBC, Hibernate, jOOQ, Exposed and Jimmer on PostgreSQL 17, with the entity and query code behind every number.';

// Results from the reproducible suite: one tuned PostgreSQL 17 container over TCP, JMH, 2 forks,
Expand Down Expand Up @@ -486,7 +486,7 @@ ${charts}
<p>Versions: Storm 1.13.0, Hibernate 7.4.5, jOOQ 3.21.6, Exposed 1.3.1, Jimmer 0.11.0, PostgreSQL 17, JDK 21.</p>
<p>The repository contains the full methodology, the statement-log auditing tools used to verify round-trip counts, and every implementation in full; <code>scripts/run.sh</code> reproduces the numbers.</p>
<div class="getit">
<div class="clonebar"><span class="dollar">$</span>git clone https://github.com/storm-orm/storm-benchmarks.git</div>
${clonebar('git clone https://github.com/storm-orm/storm-benchmarks.git')}
<a class="btn" href="https://github.com/storm-orm/storm-benchmarks" target="_blank" rel="noopener">View on GitHub →</a>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion website/src/pages/blog/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {TUT_CSS, navHtml, FOOT_HTML} from '../../components/tutorial/tutorialThe
// matches the tutorials and examples. Each article lives next to this file as
// its own custom page; keep this list newest-first and in sync with them.

const TITLE = 'Storm Blog · Design decisions and deep dives';
const TITLE = 'ST/ORM Blog · Design decisions and deep dives';
const DESC =
'The design decisions behind ST/ORM, deep dives into how it works, and the ' +
'thinking that shapes the API. Plain values, explicit SQL, and no hidden ' +
Expand Down
2 changes: 1 addition & 1 deletion website/src/pages/comparison.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {TUT_CSS, navHtml, FOOT_HTML, wireSqlToggles} from '../components/tutoria
// only differences that are real and that the rival does not already cover
// are called out.

const TITLE = 'Comparison · Storm vs JPA, jOOQ, Exposed, Jimmer';
const TITLE = 'Comparison · ST/ORM vs JPA, jOOQ, Exposed, Jimmer';
const DESC =
'How Storm compares to Hibernate/JPA, Spring Data, jOOQ, Exposed, Ktorm, ' +
'MyBatis, and Jimmer. A decision matrix plus a short, fair take on each, with ' +
Expand Down
2 changes: 1 addition & 1 deletion website/src/pages/examples/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {TUT_CSS, navHtml, FOOT_HTML} from '../../components/tutorial/tutorialThe
// and Java. Each card links to its detail page (README rendered inline by
// the example-readmes plugin, with the clone command and GitHub link).

const TITLE = 'Storm Example Projects · Complete applications built with Storm';
const TITLE = 'ST/ORM Example Projects · Complete applications built with Storm';
const DESC =
'Complete example applications built with Storm ORM: a movie browser on ' +
'the public IMDB dataset, implemented on Spring Boot 4 and Ktor, in Kotlin ' +
Expand Down
6 changes: 3 additions & 3 deletions website/src/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,7 @@ export default function Home() {
<>
<Head>
<html lang="en" />
<title>Storm · The type-safe Kotlin ORM</title>
<title>ST/ORM · The type-safe Kotlin ORM</title>
<meta
name="description"
content="Storm is a type-safe, SQL-first Kotlin ORM. Immutable data-class entities, one-line queries checked at compile time, no proxies, no N+1. Try it in 5 minutes."
Expand All @@ -789,15 +789,15 @@ export default function Home() {
<meta property="og:type" content="website" />
<meta
property="og:title"
content={'Storm · The type-safe Kotlin ORM'}
content={'ST/ORM · The type-safe Kotlin ORM'}
/>
<meta
property="og:description"
content="Storm is a type-safe, SQL-first Kotlin ORM. Immutable data-class entities, one-line queries checked at compile time, no proxies, no N+1. Try it in 5 minutes."
/>
<meta
name="twitter:title"
content={'Storm · The type-safe Kotlin ORM'}
content={'ST/ORM · The type-safe Kotlin ORM'}
/>
<meta
name="twitter:description"
Expand Down
Loading
Loading