mirror of
https://github.com/actualbudget/actual.git
synced 2026-07-11 20:30:48 -05:00
* [AI] api: add browser build
Continuation of #7545 (and #8143, which moved the browser Web Worker
bootstrap into loot-core). This adds the browser-compatible build of
@actual-app/api: the full loot-core stack runs inside a Web Worker and a
thin main-thread facade exposes the same public surface as the Node entry.
loot-core:
- Add platform/client/backend-worker.ts: createBackendWorker(worker)
centralizes absurd-sql's main-thread initBackend + __absurd:* message
filtering, and is exported for the api to reuse.
- Refactor browser-preload's start.ts and worker-bridge.ts to route through
it (drops the duplicated __absurd:* filtering; terminate() now also clears
listeners). Behavior is preserved.
api:
- browser-worker.ts: Web Worker entry owning real loot-core, speaking
loot-core's {id,name,args} backend protocol; wraps fetch to map the
.js -> .js.data migration names.
- index.browser.ts: main-thread facade that spawns the worker.
- browser/rpc.ts: main-thread <-> worker RPC bridge (uses createBackendWorker).
- browser/lib-stub.ts: stubs lib.send so methods.ts routes over postMessage.
- vite.browser.config.mts / vite.browser-worker.config.mts and asset bundling
in vite.config.mts (sql-wasm.wasm + data/ + data-file-index.txt); package.json
build chain, browser export, and absurd-sql/npm-run-all/vite-plugin-node-polyfills deps.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] Rename release note to match PR number 8166
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] api: move browser worker plumbing into loot-core
Address review feedback: the browser worker logic shouldn't live in the api
package since it's generic "loot-core as a browser npm package" plumbing, not
api-specific. Resolve it via loot-core's existing conditional-export system
instead of a vite alias.
- loot-core: add server/main.api-browser.ts (worker-routing `lib` that
delegates to the existing platform/client/connection), selected via a new
`api-browser` export condition on "./server/main". Move the worker entry to
server/api-browser-worker.ts.
- api: the facade (index.browser.ts) now reuses loot-core's client connection
via a minimal global.Actual.getServerSocket shim and spawns the worker built
from loot-core's entry. Deletes browser-worker.ts, browser/rpc.ts and
browser/lib-stub.ts (~270 lines) in favor of the shared loot-core code.
- The facade build selects the impl with resolve.conditions: ['api-browser']
instead of resolve.alias, removing the enforce-boundaries lint exception.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] api: simplify browser build cleanups
Post-review cleanups (no behavior change):
- backend-worker.ts: fix stale doc comment referencing the removed
packages/api/browser/rpc.ts (the consumer is now index.browser.ts).
- api-browser-worker.ts: drop the redundant `endsWith('/data-file-index.txt')`
disjunct (subsumed by `endsWith('data-file-index.txt')`) and the no-op
`input as RequestInfo | URL` re-casts on the fetch pass-throughs.
- index.browser.ts: replace the single-field `state` holder with a plain
module-level `let backend`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] api: drop the createBackendWorker wrapper, revert browser-preload churn
Now that the facade rides on loot-core's platform/client/connection (which
already ignores absurd-sql's `__`-prefixed internal messages), the only thing
the main thread still needs is absurd's one-line initBackend. So:
- Replace the 60-line createBackendWorker wrapper (onMessage/postMessage/
terminate/listener filtering) with a tiny initBrowserBackend(worker) helper.
- Revert browser-preload/start.ts and worker-bridge.ts to their merged state —
they keep importing absurd's initBackend directly, so this PR no longer
refactors just-merged files and the loot-core change is purely additive.
- Facade holds a plain Worker handle and calls worker.terminate() on shutdown.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] api: trim verbose comments in the browser build
Keep only the load-bearing "why" notes (the absurd-sql init, the fetch/.js.data
workaround, the global.Actual shim, the api-browser condition); drop protocol
diagrams, usage examples, and restated context. No code/behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] api: drop redundant absurd-sql dependency
No api source imports absurd-sql; it's reached only through @actual-app/core
(the facade's initBrowserBackend and the bundled worker), which declares it.
It's bundled into the build output, so it isn't a runtime dependency of the
published package either. Bundles are byte-for-byte unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] api: drop the api-browser condition, inject the worker into the connection
Review feedback: the api-browser export condition fought loot-core's idiom
(browser IS the default platform; node/"api" is the override) and hid a bug —
the condition-swapped lib only had `send`, so api.utils.amountToInteger was
silently undefined in the browser build.
Replace the condition with a plain transport seam:
- methods.ts no longer imports server/main; it sends through api/send.ts,
which the Node entry wires to in-process lib.send and the browser entry
wires to loot-core's client connection. utils.ts re-exports
amountToInteger/integerToAmount from shared/util directly (fixes the bug).
- loot-core's client connection init() now accepts an optional injected
socket; connectBackendWorker(worker) installs absurd-sql's bridge and
connects in one call. This removes the forged global.Actual shim and the
facade's `define: { global: 'globalThis' }` workaround. Desktop's argless
init() path is unchanged.
- Delete main.api-browser.ts, the "./server/main" conditional export, and
the facade's resolve.conditions.
The 4 loot-core snapshot failures (main.test.ts / transfer.test.ts) are
pre-existing on the branch without these changes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] api: route methods through the client connection, slim the facade
Review feedback: get rid of the _setSend seam and move the worker init logic
into loot-core.
- methods.ts now imports `send` from loot-core's client connection. The node
build resolves it via the existing `api` condition to a new in-process
implementation (platform/client/connection/index.api.ts — previously a dead
re-export of the electron client), matching the platform idiom: browser is
the default, node/"api" overrides. api/send.ts is deleted and the node
entry (index.ts) reverts to master byte-for-byte.
- loot-core's startBackendWorker(worker, config, assetsBaseUrl) now owns the
whole worker boot (absurd-sql bridge + connection.init + server init RPC),
so the browser entry just spawns the worker and calls it.
- Trims: single chained build script (drops the npm-run-all dep), copyFileSync
instead of the linkOrCopy helper, drop the worker entry's error listeners
(browsers already log worker uncaughts to DevTools).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] api: trim prose comments to the essential ones
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] fix client connection stalling when no sends are queued at connect
Found while smoke-testing the browser api build in a real consumer app: the
client connection only nulled messageQueue (switching from queueing to direct
postMessage) when the queue was non-empty at 'connect'. The desktop app always
has queued sends by then, so it never hits this; the api facade connects
before sending anything, so every later send queued forever and init() hung.
Drop the queue on connect unconditionally.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] fix client connection dropping api/* error replies
Found via the browser smoke test: opening an e2e-encrypted budget without the
key surfaced as "undefined is not an object" instead of an error. The server
connection forwards api/* handler failures as {type:'reply', id, error}, but
the web client connection only read `result`, silently resolving undefined for
every failed api/* call. Reject when the reply carries an error, matching the
server's envelope (and the old api rpc behavior). The electron client has the
same latent gap; left untouched here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] api: move the worker entry back into the api package
Review feedback: browser-worker.ts is the api package's build entry, not
generic loot-core code — move it back (it now imports loot-core through the
public @actual-app/core exports instead of #-subpaths).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [AI] api: drop the manifest rewrite, align shutdown, trim comments
The data-file-index.txt manifest now lists migrations under their real .js
names (the worker's fetch shim already redirects those to the .js.data files
on disk), so the manifest-rewriting half of the shim is gone. The worker init
payload carries config and assetsBaseUrl as separate fields instead of
smuggling a private key into the config object, browser shutdown() mirrors
the node facade, and the client connection's queue flush drops a redundant
branch.
* [AI] fix rejected handler promises poisoning close-budget
runHandler only removed a method's promise from runningMethods when it
fulfilled, so any handler rejection left a stale promise in the set. The next
close-budget then failed: flushRunningMethods' Promise.all rejected with that
old, unrelated error before closeBudget even ran. Remove settled promises on
rejection too, and flush with Promise.allSettled since rejected methods
already reported their error to their caller.
* [AI] api: add a browser e2e test
A Playwright test loads the built browser bundle in chromium from a static
COOP/COEP server and exercises the full stack: worker boot, the rejected-error
contract, budget import, transaction read-back, and IndexedDB persistence
across a page reload. A dedicated workflow runs it in CI on api/loot-core
changes.
* [AI] api: drop the worker fetch shim; strip .data names in loot-core fs
The .js.data scheme was spread across three coordinated pieces: the build
renamed migrations on disk, the manifest listed names that didn't exist, and
a global fetch monkey-patch in the worker un-lied at runtime. Now the
manifest lists the true on-disk names and populateDefaultFilesystem (the only
fetcher of these files) strips the .data suffix when naming the virtual file
— the fetch patch is gone and the suffix becomes a small documented fs
convention. Also: e2e static server uses a single stat and drops a dead MIME
entry.
* [AI] fix electron client connection dropping api/* error replies
Both server connection variants post api/* handler failures as
{type: 'reply', id, error}, but only the web client rejected on it
(778ed25cf); the electron renderer client still resolved undefined. Mirror
the check so the wire contract is implemented uniformly.
* [AI] api: exclude the playwright e2e from vitest; review polish
vitest's default include matched e2e/browser.test.ts and failed CI - a
playwright test file defines no vitest tests. Exclude e2e/ from the unit-test
run (it has its own yarn e2e script). Also from review: the browser exports
condition now carries its own declaration file (the browser init's return
type differs from the node facade), and a failed init terminates the worker
it just spawned before rethrowing instead of leaving it running.
* [AI] api: trim code comments to the essentials
* [AI] api: always terminate the worker when browser shutdown fails
Review feedback: a rejected close-budget left the worker running. Terminate
in a finally block so cleanup is guaranteed while the failure still reaches
the caller.
* [AI] docs: document experimental browser support in the api package
* [AI] docs: drop the CORS note from the api browser section
* [AI] docs: trim the api browser section to the basics
* [AI] api: drop the import comment in methods.ts
* [AI] Run API browser test on depot runner
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* [AI] Align api vite-plugin-node-polyfills to 0.28.0
The api workspace was pinned to ^0.27.0 (left over from an earlier merge
conflict resolution) while loot-core uses ^0.28.0. This both tripped the
workspace version-consistency constraint and broke the browser worker build:
under vite 8.0.16 the 0.27 process shim no longer round-trips runtime writes
to process.env.PUBLIC_URL, so sql-wasm.wasm was fetched from
/dist/undefinedsql-wasm.wasm (404) and api.init() failed in the browser e2e.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
48 lines
1.3 KiB
YAML
48 lines
1.3 KiB
YAML
name: API Browser Test
|
|
|
|
on:
|
|
pull_request:
|
|
paths:
|
|
- 'packages/api/**'
|
|
- 'packages/loot-core/**'
|
|
- 'packages/crdt/**'
|
|
- 'package.json'
|
|
- 'yarn.lock'
|
|
- '.github/workflows/api-browser-test.yml'
|
|
merge_group:
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
api-browser:
|
|
name: API browser e2e
|
|
runs-on: depot-ubuntu-latest-4
|
|
container:
|
|
image: mcr.microsoft.com/playwright:v1.59.1-jammy
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
persist-credentials: false
|
|
- name: Set up environment
|
|
uses: ./.github/actions/setup
|
|
with:
|
|
download-translations: 'false'
|
|
- name: Trust the repository directory
|
|
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
|
- name: Build API
|
|
run: yarn build:api
|
|
- name: Run browser e2e tests
|
|
run: yarn workspace @actual-app/api e2e
|
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
if: failure()
|
|
with:
|
|
name: api-browser-test-results
|
|
path: packages/api/test-results/
|
|
retention-days: 30
|
|
overwrite: true
|