What tcm does away with — tcm-ts

2. What tcm does away with

Storing each definition by the hash of its code, with names as labels on hashes, takes away a lot of what development usually involves. Each claim below says where it stops, and names the test that proves it: tcm's own end-to-end tests, run on every change to tcm (the links are by hash, so a test renamed keeps its link, and one removed can't be without this page saying so).

2.1 Code

  • No dead code to eliminate. Code runs, and apps are bundled, from what they reach: definitions nothing reaches are never written out or bundled, so there's nothing to shake out. Where it stops: npm packages are still tree-shaken by the bundler, and a class is one definition, shipped whole. Proved by testBundlesHoldOnlyWhatTheAppReaches.
  • No circular imports. Definitions that refer to each other are hashed and stored together, as one module, so the modules code runs from never import each other round a loop: there's no import order to get wrong. Where it stops: mutual recursion is fine; top-level constants that need each other to exist first fail as they would in plain TypeScript. Proved by testNoCircularImports.
  • Renames are free. A rename moves a label: no code is edited, nothing breaks, every use shows the new name. Where it stops: a function's .name at runtime is part of its hash, so it keeps the name it was written with (hashing). Proved by testInitAddMvUpdateTodoEditRun and testClassesKeepIdentityThroughRenames.
  • No half-finished changes. An update carries through to everything that still typechecks, and puts the rest on a todo list, keeping its old version. Proved by testInitAddMvUpdateTodoEditRun.
  • No formatting churn. Formatting never changes a hash, so stored code is always formatted, and a restyle is never a change to diff, merge or review. Proved by testFmtRestylesWithNoHashChanging.
  • Docs that don't go stale. Links and examples in doc comments are stored by hash: they follow renames and updates, and examples are run. Where it stops: links between pages are kept as written (limitations). Proved by testDocsFollowRenamesAndUpdates.

2.2 Packages and repositories

  • No packages to share code. Code is shared by name, across namespaces, not by packaging it; tcm pack is only for publishing to npm. Where it stops: inside one codebase. Another codebase's code can't yet be used as a library without pulling its branch (limitations).
  • No monorepo. One codebase holds any number of apps and npm packages, as namespaces, even on different majors of a dependency; packages depend on each other's releases with no workspace tooling. Where it stops: a package can't be packed while it uses another's unreleased or internal code. Proved by testGeometryExample and testPackagesDependThroughReleases.
  • No package.json to maintain. Import a package; its version comes from the code around it, and tcm installs it and its types. Where it stops: tcm init writes a package.json for Node's import map (#codebase, #tcm/*), generated, with no dependencies in it; tcm pack writes one for npm. Proved by testPackagesPinnedByRange.
  • Two versions of a dependency, side by side. A definition records the range it was written against, so code on one major and code on the next run together. Where it stops: one app can't mix ranges of a package others are built against; transitive dependencies aren't pinned. Proved by testPackTwoRangesSideBySide and testPackagesOneRangePerApp.
  • Release versions and changelogs nobody writes. The version comes from semver checks of what changed in the package's API, and the notes list those changes. Where it stops: types, not behaviour, are checked. Proved by testSemverChecks.

2.3 Building, testing and linting

  • Nothing to build while you work, and builds made once. Node runs the stored code directly; a production build is named by the app's hash and reused, and builds in well under a second (the largest example's, userinput's, in about 0.7). Where it stops: JSX, and some syntax of imported projects, is compiled as it loads; a new version's production build bundles the whole app again (its unchanged chunks keep their URLs). Proved by testBuildStartDeployRollBack.
  • Each definition tested and linted once. A test's result and a definition's lint findings are cached by hash: only what a change reaches runs again, and end-to-end suites by the code they actually ran. Where it stops: the unit is the definition, not the file; results that depend on the clock, the network, files or the environment can go stale (limitations). Proved by testTestsCachedByHash, testSuitesRunAgainOnlyWhenCodeTheyRanChanged and testLintWithNothingToSetUp.
  • Shared outputs with no setup. Test results and lint findings travel with tcm push and tcm pull, so a clone, CI or another machine doesn't run or lint again what passed elsewhere: no remote cache to configure. Where it stops: production builds, watch values and kept constants are each machine's own (a kept constant's value is code, which isn't taken on trust); a pulled result is trusted as the remote's code is. Proved by testResultsTravel and testResultsTravelThroughAServer.

2.4 Apps

  • No deciding where code runs, or organizing it by side. Where each definition runs, server or browser, is inferred from what it uses, and browser code calls server functions by importing them: no routes, no fetch calls, no "use client" or "use server". Where it stops: an npm package's export that can't be followed (a computed import()) falls back to a guess about the whole package, a call across must be to an async function, and only plain data crosses. Proved by testDevPlacementAndCallsByHash and testReactPlacementStreamingHydration.
  • Skew protection with no setup. Calls go by hash and earlier builds stay served, so an open tab keeps calling the code it loaded; a job runs the code that queued it. Where it stops: a tab on a pruned build reloads (410), as does one reading rows newer code wrote; builds are kept by one server, so several behind a load balancer each keep their own. Proved by testBuildStartDeployRollBack, testRuntimeReloadsRetiredBuild and testBackgroundJobs.
  • Instant rollbacks. Rolling back is moving a name back: the earlier build is still there. Where it stops: a build pruned away is built again first (in under a second). Proved by testBuildStartDeployRollBack.
  • Deploys that re-download only what changed. Browser code ships in chunks named by their content and kept across deploys, and a changed chunk arrives as the difference from the one the browser has. Where it stops: browsers without compression dictionaries get the whole chunk. Proved by testAssetsKeepTheirUrlsAcrossDeploys and testDeployedChunksArriveAsDifferences.
  • Easy migrations. A table's row type is its schema; changing it adds a typed step, applied as rows are read, and tcm update says up front what the stored rows need. A new hash format moves a codebase with tcm migrate, history and all. Where it stops: keep a step while an older build still writes the old shape. Proved by testMigrationsUniqueIndexesUpdates and testMigrateKeepsEverything.

built from c482a14e