Version 4.1.0 ยท All versions

All pages

Architecture

Solid Memo is structured as hexagonal (ports & adapters) layers, and each layer is its own npm workspace package in a Turborepo monorepo. A package's package.json names what it may import, and npm run check:boundaries holds every import to that (boundaries.md). The composition root is the one place where the layers meet.

Packages

PackageFolderResponsibility
@solid-memo/webapps/web/The site's one page: its entry point (src/main.tsx), which renders the app over the use cases composition wires, the page (src/App.tsx) that shows Solid Memo, or the Studio at its routes, loaded lazily, and the Vite build that also publishes ns/ (the vocabulary and shapes), decks/ (the deck library) and these docs, rendered by markdown's tooling, with the app (deployment.md).
@solid-memo/studioapps/studio/Solid Memo Studio (studio.md): its screens and router (src/ui/), and its entry point, StudioWorkspace, which the site's page loads as a chunk of its own at the hashes under #/studio.
@solid-memo/compositionpackages/composition/The composition root (createAppUseCases in src/appUseCases.ts): every adapter behind its port, the guest's pod, the write fence, the site's documents read from where it is served, and the use cases over them, a Studio trial's sandbox among them (createTrialUseCases in src/trialUseCases.ts, studio.md). An app passes what is its own: its name, where it is served, the build's values.
@solid-memo/uipackages/ui/The Preact UI: the components and the router (src/ui/), the messages in each language (src/i18n/), the theme and the styles (src/style.css). It receives UseCases and knows no adapter.
@solid-memo/applicationpackages/application/Use cases (what the app does) and ports (what the app needs).
@solid-memo/domainpackages/domain/Pure types and pure functions: the app's vocabulary, SRS, migrations.
@solid-memo/vocabpackages/vocab/The data contract: the TypeScript generated (src/*.generated.ts) from the RDF vocabulary and SHACL shapes in the repository's ns/ (vocab.md, shapes.md), with the generator (tooling/); vendored profiles (vendor/) and fixtures.
@solid-memo/solidpackages/solid/Adapters for Solid pods (Inrupt): repositories, the type index, the instance copier, the write fence, the pod-reading shape validator, the deck archive (decks as Turtle or JSON-LD files, data-model.md), the drafts of releases and the Turtle a release is written in (data-model.md), and the guest's pod kept in the browser (guest-mode.md).
@solid-memo/shaclpackages/shacl/The SHACL engine (rdf-validate-shacl, loaded lazily), profiles and shape loading (src/); node-side validation of Turtle files and the deck library's index and checks, its Markdown rules among them (node/, npm run library).
@solid-memo/browserpackages/browser/Adapters for the browser: the update journal, the language, the guest's pod's store (IndexedDB), and files saved as downloads and opened from the user's disk (FileExchange).
@solid-memo/turtlepackages/turtle/Node-only Turtle tooling (n3): parsing and the house-style formatter.
@solid-memo/markdownpackages/markdown/Markdown in data, read into a tree of its own, folded to what the app shows and bounded at every entry point, derived as plain text, and checked by the rules for a release; and the repository's own docs, read in the same dialect with their headings kept and rendered by its node tooling (tooling/) to static pages, their links checked and their Mermaid diagrams drawn to SVG in Chromium, with the Vite plugin that publishes them (markdown.md), their pages listed from their index, docs/docs.ttl. Imports nothing of the app's but turtle, from tooling/ only, to read that index.
@solid-memo/e2e-pode2e/pod/End-to-end tests of the app's use cases and Solid adapters against real Solid servers, started in Docker (testing.md).
@solid-memo/e2e-journeyse2e/journeys/The user journeys: whole flows a user takes through the built app, driven in Chromium, Firefox and WebKit with Playwright against a Community Solid Server in Docker (testing.md). It imports nothing of the app's but its messages, to find text on screen by.

Each package's folder has a README.md: what the package is, what it exports, what it may import and who may import it, and how to test it. Its package.json has a one-line description.

Dependency rule

A package may depend only on packages below it in this graph. Adapters implement application ports (dependency inversion): the application owns the interfaces, adapters conform to them.

Package dependencies
Which packages import which, with a dotted arrow where an import is allowed only from certain files.
The diagram's source
graph TD
    accTitle: Package dependencies
    accDescr: Which packages import which, with a dotted arrow where an import is allowed only from certain files.
    web["apps/web<br/>the page"] --> ui["ui<br/>the components"]
    web -. "main.tsx only, lazily" .-> studio["apps/studio<br/>its screens"]
    studio --> ui
    ui --> application
    web -. "main.tsx only" .-> composition["composition<br/>the composition root"]
    composition --> application & solid & browser
    ui -. src/ui/ only .-> markdown
    web -. "vite.config.ts and its test only" .-> markdown
    application --> domain --> vocab["vocab<br/>the data contract"]
    solid -. implements ports .-> application
    solid --> domain & vocab & shacl
    browser -. implements ports .-> application
    shacl --> domain & vocab
    shacl -. node/ only .-> turtle & markdown
    vocab -. tooling/ only .-> turtle
    markdown -. tooling/ only .-> turtle
    e2e["e2e/pod"] --> application & solid
    journeys["e2e/journeys"] -. "harness/strings.ts only" .-> ui

Packages are consumed as TypeScript source (exports maps @solid-memo/<package>/<module> to src/<module>.ts): there is no build step between packages, and Vite bundles the app from source. ui maps @solid-memo/ui/<module> to src/ui/<module>.tsx, lists its few .ts modules one by one, and exports style.css and the message files (i18n/<locale>.json).

Tasks

turbo.json runs each package's typecheck, test, test:roundtrip (solid's, every library release through a draft, testing.md), generate:check, format:turtle:check, library:check and build in dependency order, cached by input. A task's inputs are its package's files and those of the packages it depends on; the few that also read ns/ or decks/ add them in their package's own turbo.json (vocab, shacl, solid, composition, apps/web and apps/studio), so a deck edit reruns only the tasks that read the library. A test that reads any other file outside its package adds it the same way (e2e/pod's the workflows, vocab's the root's package.json, apps/web's the LICENSE its About page links to): a file Turbo does not know of changes no input, so CI, which starts from main's cache, would reuse main's result and never run the test on the change it checks. The one exception is a test that checks only that a file the docs link to is there (markdown's and apps/web's), as that could be any file: lychee checks the same links in CI's docs job, on every run (testing.md). The Studio has no build of its own: Solid Memo's bundles it (studio.md).

npm run check     # Biome's lint, markdownlint, cspell; then every package: typecheck, tests (100% coverage), the round trip, drift, Turtle formatting, the deck library; then boundaries
npm run build     # the site, the Studio and the docs included, into apps/web/dist/
npm run browsers  # once: Chromium, which npm run check and npm run build draw the docs' diagrams in (markdown.md)
npm run dev       # the site, from source; the Studio at #/studio
npm start         # the site as deployed: built, then served at http://localhost:4173
npm run test:unit # every package's tests in one run, without coverage (a file or two: `-- <path>`)
npm run test:watch # the same, in watch mode
npm run test:pod  # e2e/pod against the Solid servers it starts in Docker (testing.md)
npm run pod       # a Community Solid Server at http://127.0.0.1:3999/, to poke at by hand
npm run pod:clean # take down the servers an interrupted run left
npm run generate  # the vocab package's generated TypeScript, from ns/
npm run format:turtle  # every Turtle file in the house style
npm run library   # the deck library's index, decks/index.ttl
npm run crosscheck # the pySHACL cross-check CI runs (Python, scripts/requirements-ci.txt)

Key objects

Why