ReviewOS

stacks/stx

store-loader emits no runtime-globals preamble, so window.stx-only composables are a bare ReferenceError inside a store

#1838
Closed glennmichael123 opened this 11 days ago ·
11 days ago

packages/stx/src/store-loader.ts and packages/stx/src/composable-loader.ts load user code the same way, but only one of them wires the runtime globals in.

$ grep -c 'STX_RUNTIME_GLOBALS' packages/stx/src/store-loader.ts
0
$ grep -c 'STX_RUNTIME_GLOBALS' packages/stx/src/composable-loader.ts
2

composable-loader.ts imports it at :29, imports transformStoreImports at :30, calls that transform at :168, and builds runtimeBindings from STX_RUNTIME_GLOBALS at :216. store-loader.ts does none of it — note that the transform literally named for stores is applied by the composable loader and not by the store loader.

Why it bites

signals.js puts roughly a dozen names directly on window (state, effect, batch, navigate, defineStore, useLocalStorage, …). The other ~34 live only on window.stx — useCookie among them.

A <script client> block gets a generated var { useCookie, … } = window.stx preamble, so a bare useCookie(...) works there. A store file gets no such preamble, so the identical line is a ReferenceError.

Why it is hard to diagnose

Store files are stripped of export and concatenated into one shared IIFE. The ReferenceError is thrown inside that IIFE, so defineStore never completes — and the error the developer actually sees is Store not found from useStore('session'), raised somewhere else entirely, in a file that is fine.

Confirmed in a real app: resources/stores/session.ts in stacksjs/analyticshq carries a 24-line comment describing exactly this, plus a lazy globalThis.stx?.useCookie workaround, because the straightforward call is unusable.

No test covers it

$ grep -rn 'getStoreScript\|store-loader' packages/stx/test | wc -l
0

Fix

Lift the two blocks from composable-loader.ts — the transformStoreImports(code) call at :168 and the runtimeBindings construction at :216-224 — into store-loader.ts. Same mechanism, same globals list.

Failing that, the minimum is to make the failure legible: catch the throw inside the store IIFE and report which store file failed and which identifier was undefined, instead of surfacing it as Store not found on an unrelated line.

11 days ago

Fixed by 6c9a8cf and a96cbc3.

store-loader.ts now emits the same runtime-globals preamble composable-loader.ts does — only the names a store actually references, and skipping any the store declares itself so a local binding still shadows. It also applies transformStoreImports, which the loader named for stores never called.

Emitted shape:

;(function(){
var __stx = window.stx || {};
var defineStore = __stx["defineStore"];
var useCookie = __stx["useCookie"];
// Store: session

The follow-up matters as much as the fix: the declared-name scan had to cover destructuring, because const { useColorMode, state } = window.stx is precisely the workaround this removes, and so the shape most likely to sit in the same file. Missing it made things worse rather than unchanged — the generated var and the store's own const land in one shared IIFE, which is a SyntaxError, which takes every store down instead of one. Caught by running against a real app, not by reading.

Verified end to end on the app cited in the report: deleting its two const { … } = window.stx workarounds leaves both stores working — theme toggles and persists, the cookie-backed session token resolves, no console errors.

Tests: first coverage of store-loader at all (grep -rn 'getStoreScript|store-loader' packages/stx/test returned 0 before). Six cases covering the preamble, local-shadowing, destructured and renamed bindings, unreferenced globals, and the single-IIFE ordering.

Sign in to comment on this issue.