Add public plugin settings helpers#148
Conversation
🦋 Changeset detectedLatest commit: 3d9fabc The changes in this PR will be included in the next version bump. This PR includes changesets to release 9 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/blocks
@emdash-cms/cloudflare
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
There was a problem hiding this comment.
Pull request overview
Adds a public runtime API for reading persisted plugin-scoped settings from EmDash core, enabling SSR/template code and plugin block rendering to access plugin settings without directly querying the options-table layout.
Changes:
- Added
getPluginSetting()/getPluginSettings()(and*WithDbvariants) to the core settings module. - Re-exported the new helpers from the
emdashpackage entrypoint.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| packages/core/src/settings/index.ts | Introduces new plugin settings read helpers (single key + all settings by plugin). |
| packages/core/src/index.ts | Re-exports the new helpers from the main public package entrypoint. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
packages/core/src/settings/index.ts
Outdated
|
|
||
| const settings: Record<string, unknown> = {}; | ||
| for (const [key, value] of allOptions) { | ||
| settings[key.replace(prefix, "")] = value; |
There was a problem hiding this comment.
getPluginSettingsWithDb() builds a SQL LIKE prefix using pluginId and passes it to OptionsRepository.getByPrefix(), which does where name like "${prefix}%" without escaping. If pluginId contains %, _, or \, the query can match settings for other plugins and also break the key.replace(prefix, "") stripping (keys may not actually start with the literal prefix). Please escape LIKE metacharacters (e.g., using an ESCAPE '\\' clause like MediaRepository does) or validate/restrict pluginId/prefix before querying.
| settings[key.replace(prefix, "")] = value; | |
| if (!key.startsWith(prefix)) { | |
| continue; | |
| } | |
| settings[key.slice(prefix.length)] = value; |
| export async function getPluginSetting<T = unknown>( | ||
| pluginId: string, | ||
| key: string, | ||
| ): Promise<T | undefined> { | ||
| const db = await getDb(); | ||
| return getPluginSettingWithDb<T>(pluginId, key, db); | ||
| } | ||
|
|
||
| /** | ||
| * Get a single plugin setting by key (with explicit db). | ||
| * | ||
| * @internal Use `getPluginSetting()` in templates and plugin rendering code. | ||
| */ | ||
| export async function getPluginSettingWithDb<T = unknown>( | ||
| pluginId: string, | ||
| key: string, | ||
| db: Kysely<Database>, | ||
| ): Promise<T | undefined> { | ||
| const options = new OptionsRepository(db); | ||
| const value = await options.get<T>(`plugin:${pluginId}:settings:${key}`); | ||
| return value ?? undefined; | ||
| } | ||
|
|
||
| /** | ||
| * Get all persisted plugin settings for a plugin. | ||
| * | ||
| * Defaults declared in `admin.settingsSchema` are not materialized | ||
| * automatically; callers should apply their own fallback defaults. | ||
| */ | ||
| export async function getPluginSettings(pluginId: string): Promise<Record<string, unknown>> { | ||
| const db = await getDb(); | ||
| return getPluginSettingsWithDb(pluginId, db); | ||
| } | ||
|
|
||
| /** | ||
| * Get all persisted plugin settings for a plugin (with explicit db). | ||
| * | ||
| * @internal Use `getPluginSettings()` in templates and plugin rendering code. | ||
| */ | ||
| export async function getPluginSettingsWithDb( | ||
| pluginId: string, | ||
| db: Kysely<Database>, | ||
| ): Promise<Record<string, unknown>> { | ||
| const prefix = `plugin:${pluginId}:settings:`; | ||
| const options = new OptionsRepository(db); | ||
| const allOptions = await options.getByPrefix(prefix); | ||
|
|
||
| const settings: Record<string, unknown> = {}; | ||
| for (const [key, value] of allOptions) { | ||
| settings[key.replace(prefix, "")] = value; | ||
| } | ||
|
|
||
| return settings; |
There was a problem hiding this comment.
New public helpers getPluginSetting* / getPluginSettings* are added in settings/index.ts, but the existing unit test suite in packages/core/tests/unit/settings/settings.test.ts only covers site settings. Please add tests that verify (1) a single plugin setting can be read by key and returns undefined when unset, and (2) getPluginSettingsWithDb() returns a de-prefixed object for multiple stored keys (and ideally covers edge cases like falsy values).
5124f37 to
904aaa5
Compare
|
All contributors have signed the CLA ✍️ ✅ |
What does this PR do?
Adds public
getPluginSetting()andgetPluginSettings()helpers to EmDash core so trusted/plugin SSR code can read persisted plugin settings without depending on the raw options-table key format.It also fixes plugin-prefix lookups so plugin IDs containing
%or_are treated literally, and adds targeted tests for unset settings, round-tripping, and wildcard-like IDs.Type of change
Checklist
pnpm typecheckpassespnpm --silent lint:json | jq '.diagnostics | length'returns 0pnpm testpasses (or targeted tests for my change)pnpm formathas been runAI-generated code disclosure
Screenshots / test output
pnpm --silent lint:quickpnpm --filter emdash exec vitest run tests/unit/settings/settings.test.tspnpm typecheckpnpm --silent lint:json | jq '.diagnostics | length'