Skip to content

Commit fe450c8

Browse files
authored
feat: typed feature flags (#76
Feature flags are now typed, this has two benefits: - Easier for users to enable flags, as they'll see features in intellisense, - Users are reminded to remove flags that have been removed from the sdk,
1 parent 9f4be9a commit fe450c8

File tree

6 files changed

+56
-8
lines changed

6 files changed

+56
-8
lines changed

.changeset/few-dolphins-fry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'magicbell': patch
3+
---
4+
5+
Feature flags are now typed, making it easier to enable beta features, and harder to forget removing flags when beta features turned stable.

packages/magicbell/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,10 +316,10 @@ Below is a list of features that are currently behind feature flags.
316316

317317
| Feature Flag | Description |
318318
| --------------------------------- | -------------------------------------------------------------------------- |
319-
| `users-push-subscriptions-list` | Fetch user's push subscriptions ([docs](#users-push-subscriptions-list)) |
320-
| `users-push-subscriptions-delete` | Delete user's push subscription ([docs](#users-push-subscriptions-delete)) |
321319
| `imports-create` | Create a import ([docs](#imports-create)) |
322320
| `imports-get` | Get the status of an import ([docs](#imports-get)) |
321+
| `users-push-subscriptions-delete` | Delete user's push subscription ([docs](#users-push-subscriptions-delete)) |
322+
| `users-push-subscriptions-list` | Fetch user's push subscriptions ([docs](#users-push-subscriptions-list)) |
323323

324324
<!-- AUTO-GENERATED-CONTENT:END (FEATURE_FLAGS) -->
325325

packages/magicbell/scripts/generate-resources.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,37 @@ function createFeatureFlagTable(methods: Method[]) {
306306
return lines.join('\n');
307307
}
308308

309+
async function updateTypes(filePath: string, betaMethods: Method[]) {
310+
const source = await fs.readFile(filePath, 'utf-8');
311+
const ast = recast.parse(source);
312+
const program = ast.program;
313+
314+
const clientOptions = program.body.find(
315+
(x) => x.type === 'ExportNamedDeclaration' && x.declaration.id.name === 'ClientOptions',
316+
);
317+
318+
const features = clientOptions.declaration.typeAnnotation.members.find((x) => x.key.name === 'features');
319+
320+
features.typeAnnotation = builders.tsTypeAnnotation.from({
321+
typeAnnotation: builders.tsTypeLiteral.from({
322+
members: betaMethods.map((method) =>
323+
builders.tsPropertySignature.from({
324+
optional: true,
325+
key: builders.stringLiteral(method.operationId),
326+
typeAnnotation: builders.tsTypeAnnotation.from({
327+
typeAnnotation: builders.tsLiteralType.from({
328+
literal: builders.booleanLiteral(true),
329+
}),
330+
}),
331+
}),
332+
),
333+
}),
334+
});
335+
336+
const output = await recast.print(ast, false);
337+
if (!output) return;
338+
await fs.writeFile(filePath, output, 'utf-8');
339+
}
309340
async function updateClient(filePath: string, files: File[]) {
310341
const resources = files
311342
.filter((x) => x.type === 'resources' && x.name.endsWith('.ts'))
@@ -366,7 +397,10 @@ async function main() {
366397
const resources = await getResources(argv.spec || SPEC_URL);
367398

368399
const files: Array<File> = [];
369-
const betaMethods = resources.flatMap((x) => x.methods).filter((x) => x.beta);
400+
const betaMethods = resources
401+
.flatMap((x) => x.methods)
402+
.filter((x) => x.beta)
403+
.sort((a, b) => a.operationId.localeCompare(b.operationId));
370404

371405
// generate ast for new resource files
372406
for (const rootResource of resources) {
@@ -430,6 +464,7 @@ async function main() {
430464
await updateReadme(README_MD, 'FEATURE_FLAGS', createFeatureFlagTable(betaMethods));
431465
console.log(`updated README.md`);
432466

467+
await updateTypes(path.join(process.cwd(), 'src', 'types.ts'), betaMethods);
433468
await updateClient(path.join(process.cwd(), 'src', 'client.ts'), files);
434469
console.log(`updated ${path.relative(process.cwd(), path.join('src', 'client.ts'))}`);
435470
}

packages/magicbell/src/client.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { Notifications } from './resources/notifications';
1414
import { PushSubscriptions } from './resources/push-subscriptions';
1515
import { Subscriptions } from './resources/subscriptions';
1616
import { Users } from './resources/users';
17-
import { ClientOptions, RequestArgs, RequestMethod, RequestOptions } from './types';
17+
import { ClientOptions, FeatureFlag, RequestArgs, RequestMethod, RequestOptions } from './types';
1818

1919
// some environments, like vscode extensions, don't have the XMLHttpRequest object defined.
2020
if (typeof XMLHttpRequest !== 'function') {
@@ -36,7 +36,7 @@ export class Client {
3636
#clientUserAgent: string;
3737
#options: ClientOptions;
3838
#logger = new Logger();
39-
#features: Record<string, boolean> = {};
39+
#features: ClientOptions['features'] = {};
4040
#lastRequest: Telemetry[] = [];
4141
listen = createListener(this);
4242

@@ -66,7 +66,7 @@ export class Client {
6666
this.#features = options.features || {};
6767
}
6868

69-
hasFlag(flag: string) {
69+
hasFlag(flag: FeatureFlag) {
7070
return this.#features[flag] || false;
7171
}
7272

packages/magicbell/src/resource.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Client } from './client';
22
import { joinUrlSegments } from './lib/utils';
33
import { IterablePromise, normalizeArgs } from './method';
44
import { autoPaginate } from './paginate';
5+
import { FeatureFlag } from './types';
56

67
type ResourceRequestOptions = {
78
method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'PATCH';
@@ -66,7 +67,7 @@ export class Resource {
6667
return makeRequest({ data, params });
6768
}
6869

69-
protected assertFeatureFlag(flag: string) {
70+
protected assertFeatureFlag(flag: FeatureFlag) {
7071
if (!this.client.hasFlag(flag)) {
7172
throw new Error(`This is a beta feature, please enable it by providing the "${flag}" feature flag.`);
7273
}

packages/magicbell/src/types.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,17 @@ export type ClientOptions = {
1616
idempotencyKey?: string;
1717
telemetry?: boolean;
1818
debug?: boolean;
19-
features?: Record<string, boolean>;
19+
features?: {
20+
'imports-create'?: true;
21+
'imports-get'?: true;
22+
'users-push-subscriptions-delete'?: true;
23+
'users-push-subscriptions-list'?: true;
24+
};
2025
headers?: Record<string, string>;
2126
};
2227

28+
export type FeatureFlag = keyof ClientOptions['features'];
29+
2330
export type RequestOptions = {
2431
userEmail?: string;
2532
userExternalId?: string;

0 commit comments

Comments
 (0)