Upgrade Harper to v5 - #4
Merged
Merged
Conversation
Member
Author
|
Errors: {"type":"error:ResourceLoadError","code":"ResourceLoadError","title":"Could not load component 'jsResource' for application 'react-ssr-example' due to: Failed to load resource module /Users/<user>/harperdb_repos/react-ssr-example/resources.js: The \"path\" argument must be of type string. Received undefined","status":500,"instance":"/"}Console also prints [http/1] [error]: ResourceLoadError: Failed to load resource module /Users/<user>/harperdb_repos/react-ssr-example/resources.js: The "path" argument must be of type string. Received undefined
at handleResourceEntry (/Users/<user>/.nvm/versions/node/v24.10.0/lib/node_modules/harper/resources/jsResource.ts:68:10)
at async Promise.all (index 0)
at async <anonymous> (/Users/<user>/.nvm/versions/node/v24.10.0/lib/node_modules/harper/components/Scope.ts:244:6) {
filePath: '/Users/<user>/harperdb_repos/react-ssr-example/resources.js',
cause: TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received undefined
at Object.join (node:path:1339:7)
at file:///Users/<user>/harperdb_repos/react-ssr-example/resources.js:16:39 {
code: 'ERR_INVALID_ARG_TYPE'
}
}Would love some input on moving forward, fixing this issue 🙂 |
- Bump harper ^5.0.10 -> ^5.0.28 - Restore rest.lastModified: true (still honored by v5 REST; required for the Last-Modified/If-Modified-Since caching the example demonstrates) - Remove unused logger import from resources.js - Add @harperfast/integration-testing + typescript dev deps, tsconfig.json - Add integrationTests/app.test.ts covering REST, SSR render path, and multi-tier caching (ETag/Last-Modified/304 + cache invalidation) - Add pretest:integration build step and test:integration script - Add pinned-hash integration-tests CI workflow (Node 22/24/26) - HarperDB -> Harper branding in README prose Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A clean install regenerates the full optional-dependency tree (@emnapi/core, @emnapi/runtime, bufferutil, utf-8-validate) that an incremental macOS install had pruned, fixing `npm ci` on Linux CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BlogCache (sourced from PageBuilder) is populated asynchronously, so the ETag/Last-Modified can change across the first reads after a source change. Poll until the cache ETag is stable before asserting conditional-request (304/200) behavior, mirroring real cache usage. This fixes two CI-only flaky failures (full-document body and stale 304 assertions). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CachedBlog.get returned { contentType, data }, a v4-era convention that
v5's REST layer does not honor without a `headers` property — it was
JSON-serialized instead of served as raw HTML (CI showed the body was
{"contentType":"text/html"}). Return { status, headers, body } like
UncachedBlog so the HTML is served with Content-Type: text/html.
Also require a full HTML document (not just a stable ETag) before the
cache is considered settled, and drop the diagnostic logging.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reverting to { contentType, data } keeps CachedBlog in Harper's caching
response branch, which is what emits the ETag/Last-Modified the example
relies on. v5's serialize() honors { contentType, data } only when `data`
is non-null; the BlogCache entry is populated asynchronously, so until
`cached.content` exists the endpoint returns JSON with no ETag. The test
now polls until CachedBlog serves a stable text/html document before
asserting caching behavior, and reports body/status on timeout.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The prior commits mechanically converted PageBuilder/CachedBlog/UncachedBlog to static get(target). For a caching source that broke sourcedFrom: v5 resolves a cache source per-id through an instance get() (the cache instantiates the source resource for the requested id), so the static PageBuilder.get was never invoked and BlogCache stored raw Post records (cached.content was always undefined -> /CachedBlog served JSON, no HTML, no 304). Reverting to instance get() using super.get() matches Harper's own v5 caching reference (unitTests/testApp SimpleCacheSource) and restores the cached HTML body + ETag/Last-Modified/304 behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
After the cache settles, it can briefly re-revalidate, so a conditional request with a just-captured ETag may momentarily return 200. Poll the conditional request until it returns 304 (eventual consistency), and check the stale-headers cache miss before re-settling. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BboyAkers
marked this pull request as ready for review
June 5, 2026 19:55
Apply shell-injection fix to integration-tests.yml: move the github.event.inputs.node-version expression into an env var (NODE_VER) so it is never interpolated directly into the shell script body. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
BboyAkers
commented
Jun 11, 2026
BboyAkers
commented
Jun 11, 2026
BboyAkers
commented
Jun 11, 2026
…ertion
- resources.js line 26: escape < in JSON.stringify output to prevent XSS via
malicious post title/body/comment in inline script tag
- resources.js line 60: guard cached?.content with optional chaining to avoid
TypeError when cache entry is not yet populated
- integrationTests/app.test.ts lines 57/153: use startsWith('text/html') so
the settlement loop and assertion are robust to '; charset=utf-8' suffixes
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Member
Author
|
Review follow-up (autonomous agent): Fixed all three blocking findings: escaped |
Bump the harper dependency to ^5.2.1 and regenerate the lockfile. Regenerated in full so the optional native deps (bufferutil, utf-8-validate, segfault-handler) stay in the tree for Linux CI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous lockfile was generated with npm 11, which does not auto-install the peer dependencies of an optional dependency. harper 5.2.1 pulls alasql, which optionally depends on react-native-fs, whose peers (react-native, react) npm 12 installs and npm 11 does not. CI runs npm 12 on Node 24/26, so npm ci failed there with those packages "missing from lock file" while Node 22 (npm 11) passed. Regenerated with npm 12 so the lockfile carries the full tree. lockfileVersion stays 3; npm ci verified under both npm 11 and npm 12. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses the three remaining review threads on #4. Two were already fixed on-branch in 0a44062 and are only tidied here: the null dereference (`data: cached?.content`) and the `</script>` injection in the data island. The escape was an inline IIFE; it is now a named `safeJson` helper with a comment explaining why `<` is escaped, which is the shape the review suggested. The Content-Type thread was only partly fixed. The settlement guard and the CachedBlog assertion had been switched to `startsWith`, but the UncachedBlog test at line 139 still did `strictEqual(res.headers.get('Content-Type'), 'text/html')` — the same fragility, missed because the review named only lines 57 and 153. All three now go through a `mediaType()` helper that strips parameters, so a `; charset=utf-8` suffix cannot break them. Comparing the parsed media type is also tighter than `startsWith`, which would accept `text/html-x`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dawsontoth
approved these changes
Aug 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Completes the Harper v4 → v5 ("Lincoln") upgrade for this React SSR + multi-tier caching example, adds integration tests covering the SSR render path and Harper caching behavior, and wires up CI. This PR resumes the existing
v5-upgradebranch (PR #4) — no new/duplicate PR.Harper dependency
harperbumped^5.0.10→^5.0.28(latest).package-lock.jsonregenerated cleanly sonpm ciresolves the full platform-optional dependency tree (@emnapi/*,bufferutil,utf-8-validate) on Linux CI — an incremental macOS install had pruned them, which brokenpm ci.Migration items applied
harperdb→harper):resources.jsimports fromharper.async get()tostatic async get(target). ForUncachedBlog(a plain exported endpoint) that is fine, but for the caching sourcePageBuilder(BlogCache.sourcedFrom(PageBuilder)) it broke caching: in v5 a cache source is resolved per-id through an instanceget()(the cache instantiates the source resource for the requested id and callsget()). With a static method,PageBuilder.getwas never invoked andBlogCachestored rawPostrecords, socached.contentwas alwaysundefinedand/CachedBlog/0returned JSON ({"contentType":"text/html"}) with no HTML body and noETag/304. Reverted all three handlers to instanceget(query)usingawait super.get(query), matching Harper's own v5 caching reference (unitTests/testAppSimpleCacheSource). This restored the cached HTML body and the fullETag/Last-Modified/304behavior. (Found via the integration tests; not visible from static review.)rest.lastModifiedconfig restored: an earlier change torest: truedroppedLast-Modifiedheader emission. v5's REST layer still honorsrest.lastModified(verified inharper/dist/server/REST.js), and this example's caching demo depends onLast-Modified/If-Modified-Since. Restoredrest:\n lastModified: true.N/A migration items (and why)
Table.get()plain/frozen records — handlers read fields off the returned record and never mutate it in place; no{ ...record }copy needed.wasLoadedFromSource()removal — not used.allowedSpawnCommands+name) — the app spawns no processes.blob.save()removal — no blob storage.moduleLoader/allowedBuiltinModules— no install scripts or restricted built-ins; defaults work (CI green).Tests
Added
integrationTests/app.test.ts(@harperfast/integration-testing0.4.0,node:test, ESM + TS) running against a real ephemeral Harper instance. Coverage:Posttable: seeded recordGET /Post/0, listGET /Post/,PATCH /Post/0persistence.GET /UncachedBlog/0andGET /CachedBlog/0return a full SSR'd HTML document (<!doctype html>, placeholders replaced), the hydration data (window.__INITIAL_POST_DATA__), the correct__CACHED__flag, and the post title.CachedBlogemitsETag+Last-Modified; a conditional re-request returns304; updating the sourcePostinvalidates the cache (stale conditional headers →200fresh render) and, once re-cached, a conditional request with refreshed headers returns304again. Mirrorscaching-test.jsas an automated test. BecauseBlogCacheis populated/revalidated asynchronously, the tests poll until the cache settles on a fulltext/htmldocument with a stableETagbefore asserting conditional-request behavior (deterministic; mirrors real cache use).A
pretest:integrationstep runsnpm run build(Vite) sodist/client/index.htmlanddist/server/entry-server.jsexist beforeresources.jsloads them. The mandatoryharperBinPathharness fix is applied (resolve the CLI fromharper's exported main entry, since itsexportsmap only exposes.).Local run: blocked by the macOS loopback limitation only — the harness binds Harper to
127.0.0.2+and this machine has no loopback aliases (LoopbackAddressValidationError/EADDRNOTAVAIL). Environmental, not a code/test issue.CI (ubuntu-latest): ✅ all green on Node 22 / 24 / 26 — https://github.com/HarperFast/react-ssr-example/actions/runs/27036806463
CI
Added
.github/workflows/integration-tests.ymlat the repo root — Node matrix[22, 24, 26], actions pinned to commit hashes (checkout v6.0.3, setup-node v6.4.0, upload-artifact v7.0.1).npm run test:integrationtriggers the Vite build viapretest:integration, so no extra build step is needed. Uploads Harper logs on failure.Branding
HarperDB→HarperinREADME.mdprose (livedocs.harperdb.ioURLs left intact).Flagged for a human
@harperdb/code-guidelines(devDep +prettierconfig) is on the legacy@harperdbscope. Per policy, npm-scope moves are manual — flagged, not changed here.🤖 Generated with Claude Code