Skip to content

Commit e95c2f2

Browse files
committed
chore: extending resolveFallback to take in incoming directives
This will allow us to resolve directives that should override the fallback behavior such as goodbye or error
1 parent 1622eff commit e95c2f2

2 files changed

Lines changed: 42 additions & 39 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
"packages/sdk/shopify-oxygen/example",
6666
"packages/sdk/browser/example",
6767
"packages/sdk/browser/example-fdv2",
68+
"packages/sdk/browser/example-fdv1-fallback",
6869
"packages/sdk/openfeature-node-server",
6970
"packages/sdk/openfeature-node-server/examples/getting-started",
7071
"packages/sdk/vue"

packages/shared/sdk-client/src/datasource/fdv2/StreamingFDv2Base.ts

Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -140,20 +140,24 @@ export function createStreamingBase(config: {
140140
}
141141

142142
/**
143-
* Promotes a directive deferred from `onopen` (`pendingFallback`) into the
144-
* committed `fdv1Fallback`/`fdv1FallbackTtlMs` state, clears the pending
145-
* pair, and returns the current committed fallback state.
146-
*
147-
* Called via putWithFallback() by every path that can queue a payload
148-
* result, a stream error, a network failure, or a ping-triggered poll
149-
* result (including one that succeeds without its own fallback signal),
150-
* so a directive deferred at onopen surfaces no matter which path fires
151-
* next. The one exception is the plain goodbye path, which only runs
152-
* when nothing is pending and reads the committed flag directly instead.
153-
* Safe to call when nothing is pending; it just returns the current state
154-
* unchanged.
143+
* Resolves the current fallback directive, optionally overridden by an
144+
* `incoming` directive from a different source (an in-band goodbye payload
145+
* or an error-response header). When `incoming` signals fallback, it wins
146+
* outright - it carries its own TTL, which takes precedence over anything
147+
* deferred at `onopen`, and the pending pair is cleared since it is now
148+
* superseded. Otherwise, a directive deferred at `onopen` (`pendingFallback`)
149+
* is promoted into the committed `fdv1Fallback`/`fdv1FallbackTtlMs` state and
150+
* the pending pair is cleared. Safe to call with neither; it just returns the
151+
* current committed state unchanged.
155152
*/
156-
function resolveFallback(): FallbackDirective {
153+
function resolveFallback(incoming?: FallbackDirective): FallbackDirective {
154+
if (incoming?.fdv1Fallback) {
155+
fdv1Fallback = true;
156+
fdv1FallbackTtlMs = incoming.fdv1FallbackTtlMs;
157+
pendingFallback = false;
158+
pendingFallbackTtlMs = undefined;
159+
return { fdv1Fallback, fdv1FallbackTtlMs };
160+
}
157161
if (pendingFallback) {
158162
fdv1Fallback = true;
159163
fdv1FallbackTtlMs = pendingFallbackTtlMs;
@@ -164,15 +168,18 @@ export function createStreamingBase(config: {
164168
}
165169

166170
/**
167-
* The single place a result derived from the committed/pending stream state
168-
* is enqueued. It resolves the current fallback directive once (promoting any
169-
* directive deferred at onopen) and hands it to `build`, which stamps it onto
170-
* the result. Call sites that carry their own directive source - an in-band
171-
* goodbye directive, an error-response header, or a plain no-fallback goodbye
172-
* or shutdown - enqueue directly instead.
171+
* The single place a result derived from the fallback directive state is
172+
* enqueued. It resolves the current directive once via `resolveFallback()`
173+
* - passing `incoming` through when the call site has its own directive
174+
* source - and hands the result to `build`, which stamps it onto the
175+
* result. `shutdown()` is the only path with no fallback state to resolve,
176+
* so it still enqueues directly.
173177
*/
174-
function putWithFallback(build: (fallback: FallbackDirective) => FDv2SourceResult): void {
175-
resultQueue.put(build(resolveFallback()));
178+
function putWithFallback(
179+
build: (fallback: FallbackDirective) => FDv2SourceResult,
180+
incoming?: FallbackDirective,
181+
): void {
182+
resultQueue.put(build(resolveFallback(incoming)));
176183
}
177184

178185
function handleAction(action: internal.ProtocolAction, rawData?: unknown): void {
@@ -183,22 +190,16 @@ export function createStreamingBase(config: {
183190
break;
184191

185192
case 'goodbye': {
193+
// An in-band fallback signal in the goodbye data (its own TTL) takes
194+
// precedence over a directive deferred at onopen; putWithFallback()
195+
// passes it through to resolveFallback() as the incoming override.
186196
const goodbyeDirective = readGoodbyeFallbackDirective(rawData);
187-
if (goodbyeDirective.fdv1Fallback) {
188-
// An in-band fallback signal in the goodbye data overrides any pending
189-
// or committed state and carries its own TTL, so it does not go through
190-
// putWithFallback(). Clear the pending pair since it is superseded.
191-
fdv1Fallback = true;
192-
pendingFallback = false;
193-
pendingFallbackTtlMs = undefined;
194-
resultQueue.put(terminalError(errorInfoFromUnknown(action.reason), goodbyeDirective));
195-
} else if (pendingFallback) {
196-
// No in-band signal, but a directive was deferred at onopen: let
197-
// putWithFallback() consume the pending fallback and clear the values.
198-
putWithFallback((fallback) => terminalError(errorInfoFromUnknown(action.reason), fallback));
199-
} else {
200-
resultQueue.put(goodbye(action.reason, { fdv1Fallback }));
201-
}
197+
putWithFallback(
198+
(fallback) => (fallback.fdv1Fallback
199+
? terminalError(errorInfoFromUnknown(action.reason), fallback)
200+
: goodbye(action.reason, fallback)),
201+
goodbyeDirective,
202+
);
202203
break;
203204
}
204205

@@ -230,10 +231,11 @@ export function createStreamingBase(config: {
230231
// A fallback directive overrides normal retry handling, even for an
231232
// otherwise-recoverable HTTP status: the server is telling us to stop
232233
// trying FDv2 now rather than keep retrying this connection.
233-
fdv1Fallback = true;
234-
fdv1FallbackTtlMs = directive.fdv1FallbackTtlMs;
235234
logConnectionResult(false);
236-
resultQueue.put(terminalError(errorInfoFromHttpError(err.status ?? 0), directive));
235+
putWithFallback(
236+
(fallback) => terminalError(errorInfoFromHttpError(err.status ?? 0), fallback),
237+
directive,
238+
);
237239
return false;
238240
}
239241

0 commit comments

Comments
 (0)