Skip to content

Upgrade Harper to v5#2

Open
BboyAkers wants to merge 13 commits into
mainfrom
v5-upgrade
Open

Upgrade Harper to v5#2
BboyAkers wants to merge 13 commits into
mainfrom
v5-upgrade

Conversation

@BboyAkers
Copy link
Copy Markdown
Member

@BboyAkers BboyAkers commented May 11, 2026

Upgrade Harper to v5

Completes the v4 → v5 upgrade for this Solid SSR + Harper caching example. Resumes the existing branch (no clobber/duplicate); the prior commits had swapped the dependency and (incorrectly) converted resources to static methods, and this work finalizes the migration, fixes a caching-source correctness bug, adds integration tests + CI, and merges main.

⚠️ Caching-source correctness fix

A prior migration commit mechanically converted the resource handlers to static async get(target). In Harper v5, a sourcedFrom source is resolved per-id by instantiating the source resource and calling its INSTANCE get(query) — a static get() is never invoked (this matches harper's own SimpleCacheSource reference and the react-ssr-example pattern). As a result the PageBuilder source never ran: BlogCache stored the raw Post record instead of rendered HTML, CachedBlog's cached.content was undefined, and GET /CachedBlog/:id served an empty 200 body. The earlier test only checked the BlogCache table directly, so it stayed green.

Fix: convert PageBuilder, UncachedBlog, and CachedBlog to instance async get(query) calling await super.get(query) (preserving solid's async renderPost + generateHydrationScript and the { contentType, data } shape on CachedBlog). The integration test was strengthened to fail on the pre-fix bug: GET /CachedBlog/0 must now return 200 with a non-empty text/html body containing the rendered markers (__INITIAL_POST_DATA__ + the post title), plus a 304 conditional-hit and a source-update→invalidate→re-cache flow driven through /CachedBlog (asserting the re-render reflects the mutated Post). Verified the strengthened test fails on the pre-fix static-get code and passes after the fix; CI green on Node 22/24/26.

Dependency

  • harper ^5.0.11^5.0.28
  • Added dev deps: @harperfast/integration-testing@^0.4.0, typescript@^6.0.3
  • Merged main (picked up version field + postinstall: npm run build for npm-pack / deploy_component compatibility) and resolved the package.json conflict.

Migration items applied

  • Package/import rename harperdbharper (resources.js: import { tables } from 'harper'). ✅
  • Caching-source resolution — cache source (PageBuilder) and the SSR resources (UncachedBlog, CachedBlog) use instance async get(query) with await super.get(query), as v5 requires for sourcedFrom sources. ✅ (corrected from the prior static-method commit — see fix above)
  • Records returned as plain objects — no in-place record mutation; all updates send new objects. ✅

Migration items N/A

  • Frozen/immutable records — app never mutates a fetched record in place. N/A.
  • Transaction/context (getContext().transaction.commit) — no cross-call transaction polling. N/A.
  • Child-process spawning (allowedSpawnCommands) — app spawns nothing. N/A.
  • Blob storage (blob.savesaveBeforeCommit) — no blobs. N/A.
  • wasLoadedFromSourceloadedFromSource — not used. N/A.
  • Install-script blocking / VM module loader — no install scripts; default loader works (CI green). N/A.

Tests (integrationTests/solid-ssr.test.ts, node:test + ESM/TS)

Runs against a real ephemeral Harper instance. The suite builds the Vite SSR output and assembles a clean component fixture (config + resources + schema + dist/ + a minimal solid-js runtime dependency set, since resources.js imports solid-js/web at runtime), pre-installed via setupHarperWithFixture. Coverage:

  • SSR render pathPost seed + REST GET; UncachedBlog server-side-renders the Solid app to HTML (title, body, hydration data); CachedBlog renders SSR HTML into the cache and serves byte-identical cached bytes for an unchanged Post — asserting a non-empty text/html body with __INITIAL_POST_DATA__ + title (regression guard for the static-get bug).
  • Harper cachingBlogCache cache table (sourcedFrom PageBuilder, expiration: 3600) serves a conditional-request 304 cache hit; the same 304 hit is also asserted through /CachedBlog; updating the source Post (REST PATCH) invalidates the cached /CachedBlog render (200 with fresh HTML reflecting the new comment) and then re-caches (304 once settled).
  • RESTPost GET/PATCH.

Applied the mandatory harperBinPath harness fix (harper's exports only exposes ., so harper/dist/bin/harper.js is not resolvable) — resolve the CLI from the package main and pass { harperBinPath }.

Local: blocked by the macOS loopback bind (EADDRNOTAVAIL on 127.0.0.2+; environmental, not a code/v5 bug — even --isolation=none validates a pool address).
CI (ubuntu): ✅ all green on Node 22 / 24 / 26https://github.com/HarperFast/solid-ssr-example/actions/runs/27149436373

CI

Added .github/workflows/integration-tests.yml (repo root) — Node matrix [22, 24, 26], actions pinned to commit hashes; pretest:integration builds before tests; uploads Harper logs on failure.

Notes / observed v5 behavior

  • With the instance-get() fix, CachedBlog reads through BlogCache and does participate in Harper's conditional-request handling: /CachedBlog/0 returns a 304 cache hit on a matching If-None-Match/If-Modified-Since, and invalidates → re-caches when the source Post changes. (The earlier note claiming /CachedBlog always returned 200 reflected the broken static-get state and no longer applies.)
  • Lockfile: package-lock.json already includes harper's optional native deps (bufferutil, utf-8-validate, node-gyp-build), so npm ci succeeds across the full Node 22/24/26 matrix; no regeneration needed for this fix.

Manual / human items

  • npm scope: @harperdb/code-guidelines (devDep) is on the legacy @harperdb scope. Per policy, npm-scope moves are manual — flagged, not changed here.

🤖 Generated with Claude Code

kriszyp and others added 11 commits May 24, 2026 22:42
- UncachedBlog.get(), PageBuilder.get(), CachedBlog.get() → static
- this → target (record properties and renderPost argument)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
target is a RequestTarget (URLSearchParams), not the record.
Fetch post/cached via tables.Post.get(target) and tables.BlogCache.get(target)
before passing to renderPost or accessing .content.
Remove unused context parameter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Bump harper 5.0.11 -> 5.0.28; add @harperfast/integration-testing + typescript dev deps
- Add integration tests covering SSR render path (UncachedBlog), Harper
  multi-tier caching (CachedBlog 304 cache hits + invalidation on Post update),
  and Post REST GET/PATCH; assembles a clean dist/ component fixture
- Apply harperBinPath harness fix (harper exports only ".")
- Add pinned-hash Integration Tests CI workflow (Node 22/24/26)
- Add tsconfig.json
- Branding: HarperDB -> Harper in README; fix v4 CLI (harperdb run -> harper run)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The macOS-generated lockfile omitted bufferutil/utf-8-validate/node-gyp-build
entries, causing npm ci to fail on CI. Regenerated via clean npm install.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
resources.js imports solid-js/web at runtime; the fixture now ships solid-js
and its closed dependency set so Harper can load the component, matching a
real deployment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The custom CachedBlog.get override returns a hand-built response object, which
sits outside Harper's conditional-request (ETag/304) handling, so it always
returns 200. Validate Harper v5's native caching/304 + source invalidation
against the BlogCache cache table (the actual caching primitive), and assert
CachedBlog still SSR-renders and serves byte-identical cached HTML.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@BboyAkers BboyAkers changed the title Updated to Harper v5 Upgrade Harper to v5 Jun 5, 2026
In Harper v5 a `sourcedFrom` source is resolved per-id by instantiating
the source resource and calling its INSTANCE `get()` — a `static get()`
is never invoked. The previous migration mechanically converted
PageBuilder/UncachedBlog/CachedBlog to `static async get(target)`, so the
PageBuilder source was never run: the BlogCache stored the raw Post
record instead of rendered HTML, CachedBlog's `cached.content` was
undefined, and `/CachedBlog/:id` served an empty body.

Convert PageBuilder, UncachedBlog, and CachedBlog to instance
`async get(query)` calling `await super.get(query)`, matching harper's
own SimpleCacheSource reference and the react-ssr-example pattern.
Preserves solid's async renderPost + generateHydrationScript and the
{contentType, data} response shape on CachedBlog.

Strengthen the integration test so it fails on the pre-fix bug: assert
GET /CachedBlog/0 returns 200 with a non-empty text/html body containing
the rendered markers (__INITIAL_POST_DATA__ + post title), add a 304
conditional-hit assertion through /CachedBlog, and drive the
source-update invalidation -> re-cache flow through /CachedBlog,
asserting the re-rendered HTML reflects the mutated Post.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants