Skip to content

Send remote evaluation context as canonical JSON - #712

Open
roncohen wants to merge 3 commits into
feat/array-valued-context-evaluationfrom
feat/context-json-sdk-transport
Open

Send remote evaluation context as canonical JSON#712
roncohen wants to merge 3 commits into
feat/array-valued-context-evaluationfrom
feat/context-json-sdk-transport

Conversation

@roncohen

@roncohen roncohen commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • send Node SDK remote evaluation context through canonical contextJson
  • send browser SDK evaluation context through the same canonical representation
  • widen browser context types to accept JSON-compatible arrays and objects
  • preserve array order while recursively sorting object keys for stable request/cache URLs

Rollout

This is stacked on #711 for review. Do not release these SDK changes until contextJson support from reflagcom/web#3600 has been deployed. The evaluator from #711 should be released first, then consumed and deployed by web before this PR is merged/released.

Verification

  • Node SDK: 156 focused tests pass; full run passed 204 tests but hit one unrelated Vitest worker resolveSnapshotPath timeout under concurrent load
  • Browser SDK: all 196 tests pass
  • Node SDK TypeScript build passes
  • Browser SDK TypeScript and Vite builds pass
  • lint and formatting pass

Copilot AI lite review requested due to automatic review settings September 3, 2026 19:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new Browser SDK ContextValue type permits undefined inside arrays (which serializes to null in JSON), creating a mismatch with “JSON-compatible” intent and potentially surprising contextJson semantics.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR updates both the Node and Browser SDKs to send remote evaluation context via a single canonical JSON query parameter (contextJson), enabling stable URLs/caching while supporting array/object-valued context attributes.

Changes:

  • Add canonical JSON serialization that recursively sorts object keys while preserving array order, and use it to send contextJson for remote flag evaluation requests.
  • Widen Browser SDK context types to accept JSON-compatible arrays/objects and update SSE/context serialization to use the canonical representation.
  • Update Node/Browser SDK tests and add a changeset documenting the minor release.
File summaries
File Description
packages/node-sdk/test/utils.test.ts Adds unit coverage for canonical JSON key sorting/array order behavior.
packages/node-sdk/test/client.test.ts Updates remote evaluation URL assertions to use contextJson (including array context).
packages/node-sdk/src/utils.ts Introduces canonicalJSONStringify() for stable canonical JSON serialization.
packages/node-sdk/src/client.ts Switches remote evaluation requests to send context via contextJson.
packages/browser-sdk/test/flags.test.ts Updates flag initialization tests to assert contextJson transport (including arrays).
packages/browser-sdk/test/client.test.ts Updates mocked request assertions to read context from contextJson.
packages/browser-sdk/src/sse.ts Replaces ad-hoc context serialization with canonicalContextJSONStringify().
packages/browser-sdk/src/index.ts Exports the new ContextValue type.
packages/browser-sdk/src/flag/flags.ts Sends canonical contextJson for evaluated flags requests.
packages/browser-sdk/src/context.ts Adds canonical context JSON serializer + widens context value typing for arrays/objects.
.changeset/context-json-sdk-transport.md Declares minor versions for both SDKs for the transport/type changes.
Review details
  • Files reviewed: 11/11 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/browser-sdk/src/context.ts Outdated
Comment thread packages/browser-sdk/src/flag/flags.ts
Copilot AI review requested due to automatic review settings September 4, 2026 06:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

canonical JSON serialization currently allows uncaught JSON.stringify exceptions (notably bigint/circular refs), creating regressions and inconsistent error surfacing versus the prior flattening transport.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 11/11 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment on lines +14 to +33
function canonicalJSONStringify(value: unknown): string {
const serialized = JSON.stringify(value, (_key, nestedValue: unknown) => {
if (
nestedValue === null ||
typeof nestedValue !== "object" ||
Array.isArray(nestedValue)
) {
return nestedValue;
}

return Object.fromEntries(
Object.entries(nestedValue).sort(([left], [right]) =>
left < right ? -1 : left > right ? 1 : 0,
),
);
});
if (serialized === undefined)
throw new Error("value must be JSON serializable");
return serialized;
}
Comment on lines +154 to +172
export function canonicalJSONStringify(value: unknown): string {
const serialized = JSON.stringify(value, (_key, nestedValue: unknown) => {
if (
nestedValue === null ||
typeof nestedValue !== "object" ||
Array.isArray(nestedValue)
) {
return nestedValue;
}

return Object.fromEntries(
Object.entries(nestedValue).sort(([left], [right]) =>
left < right ? -1 : left > right ? 1 : 0,
),
);
});
ok(serialized !== undefined, "value must be JSON serializable");
return serialized;
}
Copilot AI review requested due to automatic review settings September 4, 2026 06:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new context JSON transport can emit noisy/empty canonical payloads (e.g., contextJson={} or nested empty objects) and can silently coerce undefined array elements to null in the Node SDK, which can fragment cache keys and alter semantics.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (2)

Previously missed (2) — in code that hasn't changed since the last review.

packages/node-sdk/src/client.ts:1677

  • Object.keys(context).length can be non-zero even when canonicalJSONStringify(context) serializes to {} (e.g., context: { user: undefined } or user: { id: undefined }), which would send contextJson={} and create unnecessary request/cache-key variants. Consider only setting contextJson when the canonical JSON is not an empty object.
    packages/node-sdk/src/utils.ts:172
  • JSON.stringify converts undefined array elements to null. Since the Node SDK Context types allow any, callers can accidentally include undefined inside arrays and silently change semantics in contextJson. Consider failing fast by detecting undefined array elements during serialization.
  • Files reviewed: 11/11 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines +35 to +49
export function canonicalContextJSONStringify(
context: ReflagContext | undefined,
): string | undefined {
if (!context) return undefined;
const nonEmptyContext = Object.fromEntries(
Object.entries(context).filter(
([, attributes]) =>
attributes &&
Object.values(attributes).some((value) => value !== undefined),
),
);
return Object.keys(nonEmptyContext).length
? canonicalJSONStringify(nonEmptyContext)
: undefined;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants