Skip to content

Commit 40a913d

Browse files
author
Jeroen Peeters
committed
feat: make feature flags a bit more granular
1 parent 303e04f commit 40a913d

File tree

7 files changed

+117
-42
lines changed

7 files changed

+117
-42
lines changed

src/handler.test.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,18 @@ vi.mock('./plugin', () => ({
4949
})),
5050
}))
5151

52-
vi.mock('./utils', () => ({
53-
createResponse: vi.fn((result, error, status) => ({
54-
result,
55-
error,
56-
status,
57-
})),
58-
}))
52+
vi.mock('./utils', async () => {
53+
const { getFeatureFromConfig } = await import('./utils')
54+
55+
return {
56+
createResponse: vi.fn((result, error, status) => ({
57+
result,
58+
error,
59+
status,
60+
})),
61+
getFeatureFromConfig,
62+
}
63+
})
5964

6065
let instance: StarbaseDB
6166
let mockDataSource: DataSource

src/handler.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ import { validator } from 'hono/validator'
55
import { DataSource } from './types'
66
import { LiteREST } from './literest'
77
import { executeQuery, executeTransaction } from './operation'
8-
import { createResponse, QueryRequest, QueryTransactionRequest } from './utils'
8+
import {
9+
createResponse,
10+
QueryRequest,
11+
QueryTransactionRequest,
12+
getFeatureFromConfig,
13+
} from './utils'
914
import { dumpDatabaseRoute } from './export/dump'
1015
import { exportTableToJsonRoute } from './export/json'
1116
import { exportTableToCsvRoute } from './export/csv'
@@ -26,6 +31,10 @@ export interface StarbaseDBConfiguration {
2631
websocket?: boolean
2732
export?: boolean
2833
import?: boolean
34+
studio?: boolean
35+
cron?: boolean
36+
cdc?: boolean
37+
interface?: boolean
2938
}
3039
}
3140

@@ -283,9 +292,9 @@ export class StarbaseDB {
283292
*/
284293
private getFeature(
285294
key: keyof NonNullable<StarbaseDBConfiguration['features']>,
286-
defaultValue = true
295+
defaultValue?: boolean
287296
): boolean {
288-
return this.config.features?.[key] ?? !!defaultValue
297+
return getFeatureFromConfig(this.config.features)(key, defaultValue)
289298
}
290299

291300
async queryRoute(request: Request, isRaw: boolean): Promise<Response> {

src/index.ts

Lines changed: 60 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createResponse } from './utils'
1+
import { createResponse, getFeatureFromConfig } from './utils'
22
import { StarbaseDB, StarbaseDBConfiguration } from './handler'
33
import { DataSource, RegionLocationHint } from './types'
44
import { createRemoteJWKSet, jwtVerify } from 'jose'
@@ -31,6 +31,14 @@ export interface Env {
3131

3232
ENABLE_ALLOWLIST?: boolean
3333
ENABLE_RLS?: boolean
34+
ENABLE_REST?: boolean
35+
ENABLE_WEBSOCKET?: boolean
36+
ENABLE_EXPORT?: boolean
37+
ENABLE_IMPORT?: boolean
38+
ENABLE_CRON?: boolean
39+
ENABLE_CDC?: boolean
40+
ENABLE_INTERFACE?: boolean
41+
ENABLE_STUDIO?: boolean
3442

3543
// External database source details
3644
OUTERBASE_API_KEY?: string
@@ -171,43 +179,63 @@ export default {
171179
features: {
172180
allowlist: env.ENABLE_ALLOWLIST,
173181
rls: env.ENABLE_RLS,
182+
rest: env.ENABLE_REST,
183+
websocket: env.ENABLE_WEBSOCKET,
184+
export: env.ENABLE_EXPORT,
185+
import: env.ENABLE_IMPORT,
186+
cron: env.ENABLE_CRON,
187+
cdc: env.ENABLE_CDC,
188+
interface: env.ENABLE_INTERFACE,
189+
studio: env.ENABLE_STUDIO,
174190
},
175191
}
176192

177-
const webSocketPlugin = new WebSocketPlugin()
178-
const cronPlugin = new CronPlugin()
179-
const cdcPlugin = new ChangeDataCapturePlugin({
193+
const getFeature = getFeatureFromConfig(config.features)
194+
195+
/**
196+
* Plugins
197+
*/
198+
const webSocketPlugin = getFeature('websocket') ? new WebSocketPlugin() : undefined
199+
const studioPlugin = getFeature('studio') ? new StudioPlugin({
200+
username: env.STUDIO_USER,
201+
password: env.STUDIO_PASS,
202+
apiKey: env.ADMIN_AUTHORIZATION_TOKEN,
203+
}) : undefined
204+
const sqlMacrosPlugin = new SqlMacrosPlugin({
205+
preventSelectStar: false,
206+
})
207+
const queryLogPlugin = new QueryLogPlugin({ ctx })
208+
const cdcPlugin = getFeature('cdc') ? new ChangeDataCapturePlugin({
180209
stub,
181210
broadcastAllEvents: false,
182211
events: [],
183-
})
184-
185-
cdcPlugin.onEvent(async ({ action, schema, table, data }) => {
186-
// Include change data capture code here
187-
}, ctx)
188-
189-
cronPlugin.onEvent(async ({ name, cron_tab, payload }) => {
190-
// Include cron event code here
191-
}, ctx)
192-
193-
const interfacePlugin = new InterfacePlugin()
194-
195-
const plugins = [
212+
}) : undefined
213+
const cronPlugin = getFeature('cron') ? new CronPlugin() : undefined
214+
const statsPlugin = new StatsPlugin()
215+
const interfacePlugin = getFeature('interface') ? new InterfacePlugin() : undefined
216+
217+
const plugins: StarbasePlugin[] = [
196218
webSocketPlugin,
197-
new StudioPlugin({
198-
username: env.STUDIO_USER,
199-
password: env.STUDIO_PASS,
200-
apiKey: env.ADMIN_AUTHORIZATION_TOKEN,
201-
}),
202-
new SqlMacrosPlugin({
203-
preventSelectStar: false,
204-
}),
205-
new QueryLogPlugin({ ctx }),
219+
studioPlugin,
220+
sqlMacrosPlugin,
221+
queryLogPlugin,
206222
cdcPlugin,
207223
cronPlugin,
208-
new StatsPlugin(),
224+
statsPlugin,
209225
interfacePlugin,
210-
] satisfies StarbasePlugin[]
226+
].filter(plugin => !!plugin)
227+
228+
if (getFeature('cdc')) {
229+
cdcPlugin?.onEvent(async ({ action, schema, table, data }) => {
230+
// Include change data capture code here
231+
}, ctx)
232+
}
233+
234+
if (getFeature('cron')) {
235+
cronPlugin?.onEvent(async ({ name, cron_tab, payload }) => {
236+
// Include cron event code here
237+
}, ctx)
238+
}
211239

212240
const starbase = new StarbaseDB({
213241
dataSource,
@@ -227,7 +255,10 @@ export default {
227255
// next authentication checks happen. If a page is meant to have any
228256
// sort of authentication, it can provide Basic Auth itself or expose
229257
// itself in another plugin.
230-
if (interfacePlugin.matchesRoute(url.pathname)) {
258+
if (
259+
getFeature('interface') &&
260+
interfacePlugin?.matchesRoute(url.pathname)
261+
) {
231262
return await starbase.handle(request, ctx)
232263
}
233264

src/operation.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { isQueryAllowed } from './allowlist'
2424
import { applyRLS } from './rls'
2525
import type { SqlConnection } from '@outerbase/sdk/dist/connections/sql-base'
2626
import { StarbasePlugin } from './plugin'
27-
27+
import { getFeatureFromConfig } from './utils'
2828
export type OperationQueueItem = {
2929
queries: { sql: string; params?: any[] }[]
3030
isTransaction: boolean
@@ -204,18 +204,20 @@ export async function executeQuery(opts: {
204204
return []
205205
}
206206

207+
const getFeature = getFeatureFromConfig(config.features)
208+
207209
// If the allowlist feature is enabled, we should verify the query is allowed before proceeding.
208210
await isQueryAllowed({
209211
sql: sql,
210-
isEnabled: config?.features?.allowlist ?? false,
212+
isEnabled: getFeature('allowlist', false),
211213
dataSource,
212214
config,
213215
})
214216

215217
// If the row level security feature is enabled, we should apply our policies to this SQL statement.
216218
sql = await applyRLS({
217219
sql,
218-
isEnabled: config?.features?.rls ?? true,
220+
isEnabled: getFeature('rls', true),
219221
dataSource,
220222
config,
221223
})

src/utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { corsHeaders } from './cors'
2+
import { StarbaseDBConfiguration } from './handler'
23

34
export type QueryTransactionRequest = {
45
transaction?: QueryRequest[]
@@ -22,3 +23,14 @@ export function createResponse(
2223
},
2324
})
2425
}
26+
27+
export function getFeatureFromConfig(
28+
features: StarbaseDBConfiguration['features']
29+
) {
30+
return function getFeature(
31+
key: keyof NonNullable<StarbaseDBConfiguration['features']>,
32+
defaultValue = true
33+
): boolean {
34+
return features?.[key] ?? !!defaultValue
35+
}
36+
}

worker-configuration.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ interface Env {
88
STUDIO_PASS: '123456'
99
ENABLE_ALLOWLIST: 0
1010
ENABLE_RLS: 0
11+
ENABLE_CRON: 0
12+
ENABLE_CDC: 0
13+
ENABLE_INTERFACE: 0
14+
ENABLE_STUDIO: 0
15+
ENABLE_REST: 1
16+
ENABLE_WEBSOCKET: 1
17+
ENABLE_EXPORT: 1
18+
ENABLE_IMPORT: 1
1119
AUTH_ALGORITHM: 'RS256'
1220
AUTH_JWKS_ENDPOINT: ''
1321
DATABASE_DURABLE_OBJECT: DurableObjectNamespace<

wrangler.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ REGION = "auto"
4747
# Toggle to enable default features
4848
ENABLE_ALLOWLIST = 0
4949
ENABLE_RLS = 0
50+
ENABLE_CRON = 1
51+
ENABLE_CDC = 1
52+
ENABLE_INTERFACE = 1
53+
ENABLE_STUDIO = 1
54+
ENABLE_REST = 1
55+
ENABLE_WEBSOCKET = 1
56+
ENABLE_EXPORT = 1
57+
ENABLE_IMPORT = 1
5058

5159
# External database source details
5260
# This enables Starbase to connect to an external data source

0 commit comments

Comments
 (0)