Skip to content

Commit 8b2e2cc

Browse files
author
Jeroen Peeters
committed
feat: make feature flags a bit more granular
1 parent 354d80a commit 8b2e2cc

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'
@@ -32,6 +32,14 @@ export interface Env {
3232

3333
ENABLE_ALLOWLIST?: boolean
3434
ENABLE_RLS?: boolean
35+
ENABLE_REST?: boolean
36+
ENABLE_WEBSOCKET?: boolean
37+
ENABLE_EXPORT?: boolean
38+
ENABLE_IMPORT?: boolean
39+
ENABLE_CRON?: boolean
40+
ENABLE_CDC?: boolean
41+
ENABLE_INTERFACE?: boolean
42+
ENABLE_STUDIO?: boolean
3543

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

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

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

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
@@ -51,6 +51,14 @@ REGION = "auto"
5151
# Toggle to enable default features
5252
ENABLE_ALLOWLIST = 0
5353
ENABLE_RLS = 0
54+
ENABLE_CRON = 1
55+
ENABLE_CDC = 1
56+
ENABLE_INTERFACE = 1
57+
ENABLE_STUDIO = 1
58+
ENABLE_REST = 1
59+
ENABLE_WEBSOCKET = 1
60+
ENABLE_EXPORT = 1
61+
ENABLE_IMPORT = 1
5462

5563
# External database source details
5664
# This enables Starbase to connect to an external data source

0 commit comments

Comments
 (0)