Skip to content

Commit 46b9050

Browse files
committed
fix(framework): use explicit server result envelopes
1 parent cbe486a commit 46b9050

25 files changed

Lines changed: 342 additions & 211 deletions

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Changelog
22

3+
## 0.11.7 - 2026-06-18
4+
5+
- Introduced an explicit `__async_server_result__: 1` marker for framework
6+
server-result envelopes so ordinary domain objects with fields such as
7+
`value`, `signals`, `cache`, `html`, `boundary`, `redirect`, or `error`
8+
remain application values.
9+
- Centralized server-result effect application and unwrapping across local
10+
registry, remote proxy, and handler command paths so `.run(...)` and
11+
namespaced calls return equivalent values without double-applying effects.
12+
- Kept cache-only and other effect-only server envelopes representable through
13+
the explicit marker and updated server-call, router, partial, and SSR
14+
examples to use the protocol.
15+
- Bundle size from bundled TypeScript source: `browser.ts` 183,176 B raw /
16+
34,360 B gzip -> `browser.min.js` 77,854 B raw / 23,100 B gzip
17+
(-105,322 B raw, -11,260 B gzip).
18+
319
## 0.11.6 - 2026-06-18
420

521
- Owned async-signal runs with private execution tokens so canceled, restored,

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,7 @@ import {
806806
const server = createServerRegistry({
807807
"cart.add"(productId, quantity) {
808808
return {
809+
__async_server_result__: 1,
809810
value: { ok: true },
810811
signals: {
811812
cartCount: 3

browser.js

Lines changed: 20 additions & 25 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

browser.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

browser.ts

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2379,9 +2379,10 @@ const __componentModule = (() => {
23792379
})();
23802380

23812381
const __serverModule = (() => {
2382-
const serverEnvelopeKeys = new Set(["value", "signals", "boundary", "html", "redirect", "error"]);
2382+
const serverEnvelopeKind = Symbol.for("@async/framework.serverResult");
2383+
const serverEnvelopeWireKey = "__async_server_result__";
2384+
const serverEnvelopeWireVersion = 1;
23832385
const appliedServerResult = Symbol.for("@async/framework.appliedServerResult");
2384-
const appliedServerValues = new WeakSet();
23852386

23862387
function createServerProxy({
23872388
endpoint = "/__async/server",
@@ -2424,9 +2425,7 @@ const __serverModule = (() => {
24242425
throw new Error(`Server function "${id}" failed with ${response.status}.`);
24252426
}
24262427

2427-
const result = await readServerResponse(id, response);
2428-
await applyServerResult(result, runContext);
2429-
return markAppliedServerValue(unwrapServerResult(result));
2428+
return consumeServerResult(await readServerResponse(id, response), runContext);
24302429
}
24312430

24322431
return createServerNamespace(run, {
@@ -2461,7 +2460,7 @@ const __serverModule = (() => {
24612460
if (!isServerEnvelope(result)) {
24622461
return result;
24632462
}
2464-
if (result[appliedServerResult] || appliedServerValues.has(result)) {
2463+
if (result[appliedServerResult]) {
24652464
return result;
24662465
}
24672466

@@ -2493,18 +2492,16 @@ const __serverModule = (() => {
24932492
return result;
24942493
}
24952494

2496-
function unwrapServerResult(result) {
2497-
if (isServerEnvelope(result) && Object.hasOwn(result, "value")) {
2498-
return result.value;
2499-
}
2500-
return result;
2495+
async function consumeServerResult(result, context = {}) {
2496+
await applyServerResult(result, context);
2497+
return unwrapServerResult(result);
25012498
}
25022499

2503-
function markAppliedServerValue(value) {
2504-
if (value && typeof value === "object") {
2505-
appliedServerValues.add(value);
2500+
function unwrapServerResult(result) {
2501+
if (isServerEnvelope(result)) {
2502+
return Object.hasOwn(result, "value") ? result.value : undefined;
25062503
}
2507-
return value;
2504+
return result;
25082505
}
25092506

25102507
function markAppliedServerResult(result) {
@@ -2548,9 +2545,7 @@ const __serverModule = (() => {
25482545
throw new Error("Server namespace is not directly callable.");
25492546
}
25502547
const context = contextProvider() ?? {};
2551-
const result = await run(parts.join("."), args, context);
2552-
await applyServerResult(result, context);
2553-
return unwrapServerResult(result);
2548+
return run(parts.join("."), args, context);
25542549
};
25552550

25562551
const proxy = new Proxy(callable, {
@@ -2607,7 +2602,7 @@ const __serverModule = (() => {
26072602

26082603
async function readServerResponse(id, response) {
26092604
if (response.status === 204) {
2610-
return { value: undefined };
2605+
return undefined;
26112606
}
26122607
const type = response.headers.get("content-type") ?? "";
26132608
if (type.includes("application/json")) {
@@ -2625,7 +2620,7 @@ const __serverModule = (() => {
26252620
if (typeof response.text !== "function") {
26262621
throw new Error(`Server function "${id}" transport returned an invalid response: missing text().`);
26272622
}
2628-
return { value: await response.text() };
2623+
return response.text();
26292624
}
26302625

26312626
function snapshotSignalPaths(paths = [], signals) {
@@ -2770,7 +2765,8 @@ const __serverModule = (() => {
27702765
if (!value || typeof value !== "object" || Array.isArray(value)) {
27712766
return false;
27722767
}
2773-
return Object.keys(value).some((key) => serverEnvelopeKeys.has(key));
2768+
return value[serverEnvelopeKind] === true
2769+
|| value[serverEnvelopeWireKey] === serverEnvelopeWireVersion;
27742770
}
27752771

27762772
function toError(value) {
@@ -2792,11 +2788,11 @@ const __serverModule = (() => {
27922788
throw new TypeError("Server function id must be a non-empty string.");
27932789
}
27942790
}
2795-
return { createServerProxy, resolveServerCommandArguments, applyServerResult, unwrapServerResult, defaultInput, createServerNamespace, createSignalReader, assertServerId };
2791+
return { createServerProxy, resolveServerCommandArguments, applyServerResult, consumeServerResult, unwrapServerResult, defaultInput, createServerNamespace, createSignalReader, assertServerId };
27962792
})();
27972793

27982794
const __handlersModule = (() => {
2799-
const { applyServerResult, defaultInput, resolveServerCommandArguments, unwrapServerResult } = __serverModule;
2795+
const { defaultInput, resolveServerCommandArguments } = __serverModule;
28002796
const { attachRegistryInspection, createRegistryStore } = __registryStoreModule;
28012797
const { createLazyRegistry, isLazyDescriptor } = __lazyRegistryModule;
28022798
const builtInTokens = new Set(["prevent", "preventDefault", "stopPropagation", "stopImmediatePropagation"]);
@@ -2894,8 +2890,7 @@ const __handlersModule = (() => {
28942890
signalPaths: resolved.signalPaths,
28952891
signalValues: resolved.signalValues
28962892
});
2897-
await applyServerResult(result, runContext);
2898-
results.push(unwrapServerResult(result));
2893+
results.push(result);
28992894
continue;
29002895
}
29012896

browser.umd.js

Lines changed: 20 additions & 25 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

browser.umd.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)