feat!: build deployments with web-standard FormData - #489
Conversation
deploy() now builds the multipart body with the web-standard FormData and Blob (global in browsers and Node >= 18) instead of the node `form-data` package, appending file contents as Blobs for both node and browser (the node/browser branch and the form-data dependency are gone). When the resulting form is used as a request body, native fetch (and node-fetch v3) set the multipart Content-Type with the boundary automatically, so deployments now work with a native-fetch fetcher without relying on node-fetch's form-data handling. BREAKING CHANGE: deploy() requires the injected fetcher to support web-standard FormData request bodies (native fetch, node-fetch v3, or a browser fetch). Fetchers built on node-fetch v2 no longer serialize the deployment body correctly.
Test this pull request
|
Follow-up to the web-standard FormData deploy change, completing the move off node-fetch in the client. - default fetcher now uses @dcl/fetch-component (native, with the same retry/timeout behaviour) instead of @well-known-components/fetch-component (which was node-fetch v2 via cross-fetch). Updated the build snapshot script and tests accordingly. - drop the now-unused direct dependencies @well-known-components/fetch-component and cross-fetch. - declare engines.node >= 18 (the client now relies on global fetch/FormData/Blob). - remove the dead, non-exported isNode() helper. - enable skipLibCheck (the @dcl/core-commons type declarations pulled in via @dcl/fetch-component reference jest types in their published mocks).
downloadContent built its result with Buffer (Node-only), which breaks the
client in the browser. Use the web-standard Uint8Array instead — hashV0/hashV1
already accept it, and Buffer was the only Node-only API left in src.
BREAKING CHANGE: downloadContent() now resolves to Uint8Array instead of
Buffer. Buffer is a Uint8Array subclass, so byte access is unaffected, but
consumers relying on Buffer-only methods (e.g. toString('hex')) must wrap the
result with Buffer.from().
decentraland-bot
left a comment
There was a problem hiding this comment.
Review Summary
Clean, well-motivated PR that makes @dcl/catalyst-client isomorphic by replacing Node-specific APIs with web standards. Net result: −46 lines, 3 fewer dependencies, unified code path for Node ≥ 18 and browsers.
What changed
deploy()— Replaced theform-datanpm package with web-standardFormData+Blob. The oldisNode()branch (Node →Buffer, browser →Blob) is gone; a singlenew Blob([file])path works everywhere.downloadContent()— ReturnsUint8Arrayinstead ofBuffer. The internal hash check already acceptedUint8Array.- Default fetcher — Swapped
@well-known-components/fetch-component(node-fetch v2 via cross-fetch) for@dcl/fetch-component(native fetch). - Dropped deps —
form-data,@types/form-data,cross-fetch,@well-known-components/fetch-component. - Removed dead
isNode()helper andarrayBufferFrom()shim. engines.node≥ 18 — Correctly documents the runtime requirement.
Findings
[P2 — Minor] skipLibCheck: true masks type errors
tsconfig.json now enables skipLibCheck because @dcl/core-commons (transitive via @dcl/fetch-component) publishes declaration files that reference jest types. This is a pragmatic workaround but it disables type-checking of all .d.ts files, which can hide real type mismatches in third-party packages. Consider filing an issue on @dcl/core-commons to fix the published declarations so skipLibCheck can be reverted.
[P2 — Minor] Consumer migration path
With ~30 repos across the org depending on this package, the two breaking changes are significant:
deploy()now requires a fetcher that supports web-standardFormDatabodies (node-fetch v2 via@well-known-components/fetch-componentwill no longer serialize correctly).downloadContent()returnsUint8Array— consumers callingBuffer-only methods (e.g..toString('hex')) needBuffer.from(result).
Both are clearly documented in the PR description. Since versioning is handled by oddish-action and feat!: triggers a major bump, consumers won't be broken silently. Consider adding a short migration note in the README or CHANGELOG to help downstream maintainers.
Security
No security issues found. No hardcoded secrets, no injection vectors, auth chain handling unchanged, @dcl/fetch-component is an internal DCL package with minimal supply-chain risk.
Git conventions (ADR-6)
- PR title —
feat!: build deployments with web-standard FormData✅ - Branch —
feat/web-standard-formdata-deploy✅
CI
All checks passing ✅
Code quality
- The unification from two code paths (Node/browser) to one (
new Blob([file])) is a clear simplification. DeploymentPreparationData.filesalready typed asMap<string, Uint8Array>, so theUint8Arrayreturn fromdownloadContent()is consistent with the rest of the type surface.- Tests are well-updated: the
buildEntityFormDataForDeploymenttest now asserts via the webFormDataAPI (form.get(),Blob.text()), and the download tests compare againstnew Uint8Array(…). Notably, one test input still usesBuffer.from('asd')(line 17), which validates thatBuffer(as aUint8Arraysubclass) still works as deploy input — good backward-compat check. - The custom
IFetchComponent/FetchResponsetypes intypes.tsare well-defined as the structural intersection of the old WKC interface and the native fetch interface, making migration transparent for callers.
Verdict
Approved — No P0 or P1 issues. The two P2 items (skipLibCheck tech debt, consumer migration docs) are worth tracking but don't block merge.
Reviewed by Jarvis 🤖 · Requested by Lautaro Petaccio via Slack
What
Makes the client isomorphic on native fetch — it works in both Node (≥ 18) and the browser, with no node-fetch / Node-only APIs in the request/response paths.
Deployments use web-standard
FormDatadeploy()builds the multipart body with the web-standardFormData+Blob(global in browsers and Node ≥ 18) instead of the Nodeform-datapackage. Files are appended asBlobs for both node and browser, so the oldisNode()branch is gone.form-dataproduced a stream whosemultipart/...; boundary=Content-Type only got set when the request went through node-fetch — sodeploy()couldn't be used with a native-fetch fetcher (it senttext/plainand dropped the body). With webFormData, native fetch (and node-fetch v3 / browsers) set the multipart Content-Type automatically.Proof (web FormData → native
fetch()→ busboy parse):Downloads return
Uint8ArraydownloadContent()previously returned a NodeBuffer(Buffer.from(...)), which doesn't exist in the browser. It now returns a web-standardUint8Array— the last Node-only API insrc. The internal hash check (hashV0/hashV1) already acceptsUint8Array.Fully native + cleanup
@dcl/fetch-component(native, same retry/timeout behaviour) instead of@well-known-components/fetch-component(which was node-fetch v2 via cross-fetch).form-data,@types/form-data,@well-known-components/fetch-component,cross-fetch.engines.nodeset to>=18(the client now relies on globalfetch/FormData/Blob).isNode()helper.skipLibCheckenabled (the@dcl/core-commonsdeclarations pulled in via@dcl/fetch-componentreferencejesttypes in their published mocks).Breaking changes
deploy()requires the injected fetcher to support web-standardFormDatarequest bodies — native fetch, node-fetch v3, or a browserfetch. Fetchers built on node-fetch v2 no longer serialize the deployment body correctly.downloadContent()now resolves toUint8Arrayinstead ofBuffer.Bufferis aUint8Arraysubclass so byte access is unchanged, but consumers usingBuffer-only methods (e.g.toString('hex')) must wrap the result withBuffer.from(...).Tests
buildEntityFormDataForDeploymentunit test to assert via the webFormDataAPI (form.get(),File.name,Blob.text()) instead of node form-data's.getBuffer().downloadContentassertions to compareUint8Array.prebuildsnapshots + orval +tsc) ✓, suite 55 passed / 4 suites,tsc --noEmit✓, eslint ✓.🤖 Generated with Claude Code