Skip to content

Commit 66c80c7

Browse files
committed
feat: add FDv2 file data source initiator
this commit will introduce the implementation of FDv2 File initiator
1 parent fd134ea commit 66c80c7

File tree

8 files changed

+325
-12
lines changed

8 files changed

+325
-12
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { PutObject, DeleteObject, Event } from './proto';
2+
3+
// eventually this will be the same as the IntentCode type, but for now we'll use a simpler type
4+
type supportedIntentCodes = 'xfer-full';
5+
6+
/**
7+
* FDv2ChangeSetBuilder is a helper for constructing a change set for FDv2.
8+
* The main use case for this builder is to help construct a change set from
9+
* a FDv1 payload.
10+
*
11+
* @experimental
12+
* This type is not stable, and not subject to any backwards
13+
* compatibility guarantees or semantic versioning. It is not suitable for production usage.
14+
*/
15+
export default class FDv2ChangeSetBuilder {
16+
private intent?: supportedIntentCodes;
17+
private events: Event[] = [];
18+
19+
/**
20+
* Begins a new change set with a given intent.
21+
*/
22+
start(intent: supportedIntentCodes): this {
23+
this.intent = intent;
24+
this.events = [];
25+
26+
return this;
27+
}
28+
29+
/**
30+
* Returns the completed changeset.
31+
* NOTE: currently, this builder is not designed to continuously build changesets, rather
32+
* it is designed to construct a single changeset at a time. We can easily expand this by
33+
* resetting some values in the future.
34+
*/
35+
finish(): Array<Event> {
36+
if (this.intent === undefined) {
37+
throw new Error('changeset: cannot complete without a server-intent');
38+
}
39+
40+
// NOTE: currently the only use case for this builder is to
41+
// construct a change set for a file data intializer which only supports
42+
// FDv1 format. As such, we need to use dummy values to satisfy the FDv2
43+
// protocol.
44+
const events: Array<Event> = [
45+
{
46+
event: 'server-intent',
47+
data: {
48+
payloads: [{
49+
id: 'dummy-id',
50+
target: 1,
51+
intentCode: this.intent,
52+
reason: 'payload-missing',
53+
},
54+
]
55+
}
56+
},
57+
...this.events,
58+
{
59+
event: 'payload-transferred',
60+
data: {
61+
// IMPORTANT: the selector MUST be empty or "live" data synchronizers
62+
// will not work as it would try to resume from a bogus state.
63+
state: '',
64+
version: 1,
65+
id: 'dummy-id',
66+
}
67+
},
68+
];
69+
70+
return events;
71+
}
72+
73+
/**
74+
* Adds a new object to the changeset.
75+
*/
76+
putObject(obj: PutObject): this {
77+
this.events.push({
78+
event: 'put-object',
79+
data: obj,
80+
});
81+
82+
return this
83+
}
84+
85+
/**
86+
* Adds a deletion to the changeset.
87+
*/
88+
deleteObject(obj: DeleteObject): this {
89+
this.events.push({
90+
event: 'delete-object',
91+
data: obj
92+
});
93+
94+
return this
95+
}
96+
}

packages/shared/common/src/internal/fdv2/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
Update,
77
} from './payloadProcessor';
88
import { PayloadStreamReader } from './payloadStreamReader';
9+
import FDv2ChangeSetBuilder from './FDv2ChangeSetBuilder';
910

1011
export {
1112
FDv2EventsCollection,
@@ -14,4 +15,5 @@ export {
1415
PayloadProcessor,
1516
PayloadStreamReader,
1617
Update,
18+
FDv2ChangeSetBuilder,
1719
};

packages/shared/common/src/internal/fdv2/payloadProcessor.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,10 @@ export class PayloadProcessor {
221221
private _processPayloadTransferred = (data: PayloadTransferred) => {
222222
// if the following properties haven't been provided by now, we should reset
223223
if (
224-
!this._tempId || // server intent hasn't been received yet.
225-
!data.state ||
224+
// server intent hasn't been received yet.
225+
!this._tempId ||
226+
// selector can be an empty string if we are using a file data initilizer
227+
(data.state === null || data.state === undefined) ||
226228
!data.version
227229
) {
228230
this._resetAll(); // a reset is best defensive action since payload transferred terminates a payload
Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1+
export type EventType = 'server-intent' | 'put-object' | 'delete-object' | 'payload-transferred' | 'goodbye' | 'error'| 'heart-beat';
2+
export type IntentCode = 'xfer-full' | 'xfer-changes' | 'none';
3+
export type ObjectKind = 'flag' | 'segment';
4+
15
export interface Event {
2-
event: string;
3-
data: any;
6+
event: EventType;
7+
data: ServerIntentData | PutObject | DeleteObject | PayloadTransferred | GoodbyeObject | ErrorObject;
48
}
59

610
export interface ServerIntentData {
711
payloads: PayloadIntent[];
812
}
913

10-
export type IntentCode = 'xfer-full' | 'xfer-changes' | 'none';
11-
1214
export interface PayloadIntent {
1315
id: string;
1416
target: number;
@@ -17,19 +19,31 @@ export interface PayloadIntent {
1719
}
1820

1921
export interface PutObject {
20-
kind: string;
22+
kind: ObjectKind;
2123
key: string;
2224
version: number;
2325
object: any;
2426
}
2527

2628
export interface DeleteObject {
27-
kind: string;
29+
kind: ObjectKind;
2830
key: string;
2931
version: number;
3032
}
3133

34+
export interface GoodbyeObject {
35+
reason: string;
36+
silent: boolean;
37+
catastrophe: boolean;
38+
}
39+
40+
export interface ErrorObject {
41+
payload_id: string;
42+
reason: string;
43+
}
44+
3245
export interface PayloadTransferred {
3346
state: string;
3447
version: number;
48+
id?: string;
3549
}

packages/shared/sdk-server/src/LDClientImpl.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import BigSegmentStoreStatusProvider from './BigSegmentStatusProviderImpl';
4343
import { createPluginEnvironmentMetadata } from './createPluginEnvironmentMetadata';
4444
import { createPayloadListener } from './data_sources/createPayloadListenerFDv2';
4545
import { createStreamListeners } from './data_sources/createStreamListeners';
46+
import FileDataInitializerFDv2 from './data_sources/fileDataInitilizerFDv2';
4647
import DataSourceUpdates from './data_sources/DataSourceUpdates';
4748
import OneShotInitializerFDv2 from './data_sources/OneShotInitializerFDv2';
4849
import PollingProcessor from './data_sources/PollingProcessor';
@@ -325,14 +326,22 @@ function constructFDv2(
325326
const initializers: subsystem.LDDataSourceFactory[] = [];
326327

327328
// use one shot initializer for performance and cost if we can do a combination of polling and streaming
328-
if (isStandardOptions(dataSystem.dataSource)) {
329+
if (dataSystem.dataSource?.initializerOptions?.type === 'polling') {
329330
initializers.push(
330331
() =>
331332
new OneShotInitializerFDv2(
332333
new Requestor(config, platform.requests, baseHeaders, '/sdk/poll', config.logger),
333334
config.logger,
334335
),
335336
);
337+
} else if (dataSystem.dataSource?.initializerOptions?.type === 'file') {
338+
initializers.push(
339+
() =>
340+
new FileDataInitializerFDv2(
341+
config,
342+
platform,
343+
),
344+
);
336345
}
337346

338347
const synchronizers: subsystem.LDDataSourceFactory[] = [];

packages/shared/sdk-server/src/api/options/LDDataSystemOptions.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,33 @@ export type DataSourceOptions =
7575
| StreamingDataSourceOptions
7676
| PollingDataSourceOptions;
7777

78+
/**
79+
* Initializer option to read data from a file.
80+
*
81+
* NOTE: right now we only support data sources that are in FDv1 format.
82+
*/
83+
export interface FileDataInitializerOptions {
84+
type: 'file';
85+
paths: Array<string>;
86+
yamlParser?: (data: string) => any;
87+
};
88+
89+
/**
90+
* Initializer option to initilize the SDK from doing a one time full payload transfer.
91+
* This will be the default initializer used by the standard data source type.
92+
*/
93+
export interface PollingDataInitializerOptions {
94+
type: 'polling';
95+
};
96+
7897
/**
7998
* This standard data source is the recommended datasource for most customers. It will use
8099
* a combination of streaming and polling to initialize the SDK, provide real time updates,
81100
* and can switch between streaming and polling automatically to provide redundancy.
82101
*/
83102
export interface StandardDataSourceOptions {
84103
dataSourceOptionsType: 'standard';
85-
104+
initializerOptions?: FileDataInitializerOptions | PollingDataInitializerOptions;
86105
/**
87106
* Sets the initial reconnect delay for the streaming connection, in seconds. Default if omitted.
88107
*
@@ -106,7 +125,7 @@ export interface StandardDataSourceOptions {
106125
*/
107126
export interface StreamingDataSourceOptions {
108127
dataSourceOptionsType: 'streamingOnly';
109-
128+
initializerOptions?: FileDataInitializerOptions | PollingDataInitializerOptions;
110129
/**
111130
* Sets the initial reconnect delay for the streaming connection, in seconds. Default if omitted.
112131
*
@@ -124,7 +143,7 @@ export interface StreamingDataSourceOptions {
124143
*/
125144
export interface PollingDataSourceOptions {
126145
dataSourceOptionsType: 'pollingOnly';
127-
146+
initializerOptions?: FileDataInitializerOptions | PollingDataInitializerOptions;
128147
/**
129148
* The time between polling requests, in seconds. Default if omitted.
130149
*/

0 commit comments

Comments
 (0)