@murumets-ee/settings 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/admin.d.mts CHANGED
@@ -57,6 +57,18 @@ interface SettingsDefinition<S extends Record<string, SettingConfig> = Record<st
57
57
  schema: S;
58
58
  /** Human-readable label for admin UI */
59
59
  label?: string;
60
+ /**
61
+ * Lucide icon name (e.g. `'globe'`, `'ticket'`) for the sidebar nav
62
+ * entry this definition produces. Falls back to `'settings'` when
63
+ * unset.
64
+ */
65
+ iconName?: string;
66
+ /**
67
+ * Hide this namespace from the auto-generated sidebar nav. Still
68
+ * renders in the settings API. Useful for internal-only namespaces
69
+ * like the permissions store.
70
+ */
71
+ hideFromMenu?: boolean;
60
72
  }
61
73
  //#endregion
62
74
  //#region src/admin/resources.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"admin.d.mts","names":[],"sources":["../src/types.ts","../src/admin/resources.ts","../src/admin/routes.ts"],"mappings":";;;;UAgBiB,iBAAA;EAMH;EAJZ,KAAA;EAOe;EALf,WAAA;;EAEA,YAAA;AAAA;AAAA,UAGe,iBAAA,SAA0B,iBAAA;EACzC,IAAA;EACA,OAAA;EACA,SAAA;EACA,SAAA;EACA,OAAA,GAAU,MAAA;EAEV;EAAA,SAAA;AAAA;AAAA,UAGe,mBAAA,SAA4B,iBAAA;EAC3C,IAAA;EACA,OAAA;EACA,GAAA;EACA,GAAA;EACA,OAAA;AAAA;AAAA,UAGe,oBAAA,SAA6B,iBAAA;EAC5C,IAAA;EACA,OAAA;AAAA;AAAA,UAGe,mBAAA,0DACP,iBAAA;EACR,IAAA;EACA,OAAA,EAAS,CAAA;EACT,OAAA,GAAU,CAAA;AAAA;AAAA,UAGK,iBAAA,sBAAuC,iBAAA;EACtD,IAAA;EACA,OAAA,GAAU,CAAA;EAZH;EAcP,MAAA,GAAS,OAAA,CAAQ,CAAA;AAAA;AAAA,UAGF,kBAAA,SAA2B,iBAAA;EAC1C,IAAA;EACA,OAAA;EACA,MAAA;AAAA;AAAA,KAGU,aAAA,GACR,iBAAA,GACA,mBAAA,GACA,oBAAA,GACA,mBAAA,GACA,iBAAA,GACA,kBAAA;AAAA,KAgDQ,YAAA;AAAA,UAYK,kBAAA,WACL,MAAA,SAAe,aAAA,IAAiB,MAAA,SAAe,aAAA;EA/EzD;EAkFA,SAAA;EAjFU;EAmFV,KAAA,EAAO,YAAA;EAjFE;EAmFT,MAAA,EAAQ,CAAA;EAnFU;EAqFlB,KAAA;AAAA;;;iBC1Hc,iBAAA,CACd,WAAA,EAAa,kBAAA,KACZ,KAAA;EAAQ,QAAA;EAAkB,OAAA;AAAA;;;;;;;;;;iBCuEb,cAAA,CAAe,UAAA,EAAY,kBAAA,GAAqB,UAAA;AAAA,iBAChD,cAAA,CAAe,WAAA,EAAa,kBAAA,KAAuB,UAAA"}
1
+ {"version":3,"file":"admin.d.mts","names":[],"sources":["../src/types.ts","../src/admin/resources.ts","../src/admin/routes.ts"],"mappings":";;;;UAgBiB,iBAAA;EAMH;EAJZ,KAAA;EAOe;EALf,WAAA;;EAEA,YAAA;AAAA;AAAA,UAGe,iBAAA,SAA0B,iBAAA;EACzC,IAAA;EACA,OAAA;EACA,SAAA;EACA,SAAA;EACA,OAAA,GAAU,MAAA;EAEV;EAAA,SAAA;AAAA;AAAA,UAGe,mBAAA,SAA4B,iBAAA;EAC3C,IAAA;EACA,OAAA;EACA,GAAA;EACA,GAAA;EACA,OAAA;AAAA;AAAA,UAGe,oBAAA,SAA6B,iBAAA;EAC5C,IAAA;EACA,OAAA;AAAA;AAAA,UAGe,mBAAA,0DACP,iBAAA;EACR,IAAA;EACA,OAAA,EAAS,CAAA;EACT,OAAA,GAAU,CAAA;AAAA;AAAA,UAGK,iBAAA,sBAAuC,iBAAA;EACtD,IAAA;EACA,OAAA,GAAU,CAAA;EAZH;EAcP,MAAA,GAAS,OAAA,CAAQ,CAAA;AAAA;AAAA,UAGF,kBAAA,SAA2B,iBAAA;EAC1C,IAAA;EACA,OAAA;EACA,MAAA;AAAA;AAAA,KAGU,aAAA,GACR,iBAAA,GACA,mBAAA,GACA,oBAAA,GACA,mBAAA,GACA,iBAAA,GACA,kBAAA;AAAA,KAgDQ,YAAA;AAAA,UAYK,kBAAA,WACL,MAAA,SAAe,aAAA,IAAiB,MAAA,SAAe,aAAA;EA/EzD;EAkFA,SAAA;EAjFU;EAmFV,KAAA,EAAO,YAAA;EAjFE;EAmFT,MAAA,EAAQ,CAAA;EAnFU;EAqFlB,KAAA;EAlFe;;;;;EAwFf,QAAA;EAtFA;;;;AAIF;EAwFE,YAAA;AAAA;;;iBCtIc,iBAAA,CACd,WAAA,EAAa,kBAAA,KACZ,KAAA;EAAQ,QAAA;EAAkB,OAAA;AAAA;;;;;;;;;;iBCuEb,cAAA,CAAe,UAAA,EAAY,kBAAA,GAAqB,UAAA;AAAA,iBAChD,cAAA,CAAe,WAAA,EAAa,kBAAA,KAAuB,UAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"client-factory-DBlcuyG0.mjs","names":[],"sources":["../src/schema.ts","../src/types.ts","../src/validation.ts","../src/client.ts","../src/client-factory.ts"],"sourcesContent":["/**\n * Drizzle schema for settings tables.\n *\n * Two tables:\n * - toolkit_settings: typed key-value settings grouped by namespace and scope\n * - toolkit_view_state: schemaless user-scoped JSON blobs with optional TTL\n *\n * These tables are registered for migration discovery via the `settings()`\n * plugin's `tables` field. `lumi migrate` picks them up automatically.\n *\n * Built on `@murumets-ee/db`'s `defineTable` — the raw `pgTable` is still\n * re-exported (via `.table`) for backwards compatibility with existing\n * imports and tests, but all query construction in SettingsClient /\n * ViewStateClient goes through the typed TableClient.\n */\n\nimport { column, defineTable } from '@murumets-ee/db'\n\n/**\n * Typed settings table.\n *\n * Stores key-value pairs grouped by namespace and scoped\n * to global, team, or user contexts.\n *\n * scopeId uses '__global__' sentinel for global scope to avoid\n * PostgreSQL's NULL != NULL behavior in unique constraints.\n */\nexport const toolkitSettingsTable = defineTable({\n name: 'toolkit_settings',\n columns: {\n id: column.uuid({ primaryKey: true, defaultRandom: true }),\n namespace: column.varchar({ length: 100, notNull: true }),\n scope: column.varchar({ length: 20, notNull: true, default: 'global' }),\n scopeId: column.varchar({\n length: 100,\n notNull: true,\n default: '__global__',\n pgName: 'scope_id',\n }),\n key: column.varchar({ length: 255, notNull: true }),\n locale: column.varchar({ length: 10, notNull: true, default: '_default' }),\n value: column.jsonb(),\n updatedAt: column.timestamp({\n notNull: true,\n defaultNow: true,\n withTimezone: true,\n pgName: 'updated_at',\n }),\n updatedBy: column.uuid({ pgName: 'updated_by' }),\n },\n unique: [\n { on: ['namespace', 'scope', 'scopeId', 'key', 'locale'] },\n ],\n})\n\n/** Backwards-compatible export — existing imports of the raw pgTable still work. */\nexport const toolkitSettings = toolkitSettingsTable.table\n\n/**\n * View state table.\n *\n * Stores schemaless user-scoped JSON blobs for persisting\n * UI state (table filters, column order, etc.) with optional TTL.\n */\nexport const toolkitViewStateTable = defineTable({\n name: 'toolkit_view_state',\n columns: {\n id: column.uuid({ primaryKey: true, defaultRandom: true }),\n userId: column.uuid({ notNull: true, pgName: 'user_id' }),\n viewKey: column.varchar({ length: 255, notNull: true, pgName: 'view_key' }),\n state: column.jsonb({ notNull: true }),\n expiresAt: column.timestamp({ withTimezone: true, pgName: 'expires_at' }),\n updatedAt: column.timestamp({\n notNull: true,\n defaultNow: true,\n withTimezone: true,\n pgName: 'updated_at',\n }),\n },\n unique: [\n { on: ['userId', 'viewKey'] },\n ],\n})\n\n/** Backwards-compatible export — existing imports of the raw pgTable still work. */\nexport const toolkitViewState = toolkitViewStateTable.table\n","/**\n * Setting configuration types and compile-time type inference.\n *\n * Design mirrors the entity field system:\n * - Config interfaces define what each setting type accepts\n * - SettingToTS maps a single config to its TypeScript type\n * - InferSettingValue adds null awareness based on `default` presence\n * - InferSettingsMap maps an entire schema to a typed record\n */\n\nimport type { ZodType } from 'zod'\n\n// ---------------------------------------------------------------\n// 1. Setting config interfaces\n// ---------------------------------------------------------------\n\nexport interface BaseSettingConfig {\n /** Human-readable label for admin UI */\n label?: string\n /** Description / help text */\n description?: string\n /** If true, this setting can have per-locale values (mirrors entity translatable pattern) */\n translatable?: boolean\n}\n\nexport interface TextSettingConfig extends BaseSettingConfig {\n type: 'text'\n default?: string\n maxLength?: number\n minLength?: number\n pattern?: RegExp\n /** Render as textarea instead of single-line input. */\n multiline?: boolean\n}\n\nexport interface NumberSettingConfig extends BaseSettingConfig {\n type: 'number'\n default?: number\n min?: number\n max?: number\n integer?: boolean\n}\n\nexport interface BooleanSettingConfig extends BaseSettingConfig {\n type: 'boolean'\n default?: boolean\n}\n\nexport interface SelectSettingConfig<O extends readonly string[] = readonly string[]>\n extends BaseSettingConfig {\n type: 'select'\n options: O\n default?: O[number]\n}\n\nexport interface JsonSettingConfig<T = unknown> extends BaseSettingConfig {\n type: 'json'\n default?: T\n /** Optional Zod schema for validation. If provided, values are validated on set. */\n schema?: ZodType<T>\n}\n\nexport interface MediaSettingConfig extends BaseSettingConfig {\n type: 'media'\n default?: string\n accept?: string[]\n}\n\nexport type SettingConfig =\n | TextSettingConfig\n | NumberSettingConfig\n | BooleanSettingConfig\n | SelectSettingConfig\n | JsonSettingConfig\n | MediaSettingConfig\n\n// ---------------------------------------------------------------\n// 2. Setting-to-TypeScript mapping (single setting)\n// ---------------------------------------------------------------\n\n/**\n * Maps a single SettingConfig to its TypeScript output type.\n * Each branch is a shallow comparison — no recursion.\n */\nexport type SettingToTS<S extends SettingConfig> = S extends TextSettingConfig\n ? string\n : S extends NumberSettingConfig\n ? number\n : S extends BooleanSettingConfig\n ? boolean\n : S extends SelectSettingConfig<infer O>\n ? O[number]\n : S extends JsonSettingConfig<infer T>\n ? T\n : S extends MediaSettingConfig\n ? string\n : never\n\n// ---------------------------------------------------------------\n// 3. Null awareness: settings with defaults always return value\n// ---------------------------------------------------------------\n\n/**\n * If a setting has a `default`, get() never returns null.\n * Without a default, it returns T | null.\n */\nexport type InferSettingValue<S extends SettingConfig> = S extends { default: unknown }\n ? SettingToTS<S>\n : SettingToTS<S> | null\n\n// ---------------------------------------------------------------\n// 4. Full settings map type (what getAll() returns)\n// ---------------------------------------------------------------\n\nexport type InferSettingsMap<Schema extends Record<string, SettingConfig>> = {\n [K in keyof Schema]: InferSettingValue<Schema[K]>\n}\n\n// ---------------------------------------------------------------\n// 5. Scope types\n// ---------------------------------------------------------------\n\nexport type SettingScope = 'global' | 'team' | 'user'\n\n/** Sentinel value for global scope_id (avoids NULL uniqueness issues) */\nexport const GLOBAL_SCOPE_ID = '__global__'\n\n/** Sentinel value for locale column when no locale is specified (base/default value) */\nexport const DEFAULT_LOCALE = '_default'\n\n// ---------------------------------------------------------------\n// 6. Settings definition (returned by defineSettings)\n// ---------------------------------------------------------------\n\nexport interface SettingsDefinition<\n S extends Record<string, SettingConfig> = Record<string, SettingConfig>,\n> {\n /** Unique namespace for this settings group */\n namespace: string\n /** Default scope for these settings */\n scope: SettingScope\n /** Setting schema (the shape) */\n schema: S\n /** Human-readable label for admin UI */\n label?: string\n}\n","/**\n * Generate Zod validation schemas from setting config definitions.\n * Validates values before writing to the database.\n */\n\nimport { z } from 'zod'\nimport type { SettingConfig, SettingsDefinition } from './types.js'\n\n/**\n * Convert a single setting config to a Zod schema.\n */\nexport function settingToZod(config: SettingConfig): z.ZodType {\n switch (config.type) {\n case 'text': {\n let schema: z.ZodString = z.string()\n if (config.maxLength) schema = schema.max(config.maxLength)\n if (config.minLength) schema = schema.min(config.minLength)\n if (config.pattern) schema = schema.regex(config.pattern)\n return schema\n }\n\n case 'number': {\n let schema: z.ZodNumber = z.number()\n if (config.integer) schema = schema.int()\n if (config.min !== undefined) schema = schema.min(config.min)\n if (config.max !== undefined) schema = schema.max(config.max)\n return schema\n }\n\n case 'boolean':\n return z.boolean()\n\n case 'select':\n return z.enum(config.options as [string, ...string[]])\n\n case 'json':\n return config.schema ?? z.unknown()\n\n case 'media':\n return z.string().uuid()\n\n default:\n return z.unknown()\n }\n}\n\n/**\n * Generate a validation map for all settings in a definition.\n * Returns a Record<key, ZodType> for validating individual set() calls.\n *\n * Settings without a default are nullable (matching InferSettingValue),\n * so their validators accept null.\n */\nexport function generateSettingValidators(\n definition: SettingsDefinition,\n): Record<string, z.ZodType> {\n const validators: Record<string, z.ZodType> = {}\n for (const [key, config] of Object.entries(definition.schema)) {\n let schema = settingToZod(config)\n const hasDefault = 'default' in config && config.default !== undefined\n if (!hasDefault) {\n schema = schema.nullable()\n }\n validators[key] = schema\n }\n return validators\n}\n","/**\n * SettingsClient — typed CRUD for key-value settings.\n *\n * Server-only. Uses read-write DB connection.\n * Validates values against the setting schema on set().\n *\n * Supports per-locale values for settings marked `translatable: true`.\n * Non-translatable settings always use the `__default__` locale.\n *\n * All persistence goes through the `toolkit_settings` TableClient\n * (`defineTable`) — no direct Drizzle usage from this module.\n *\n * @example\n * ```typescript\n * import { createSettingsClient } from '@murumets-ee/settings'\n * import { siteSettings } from './settings/site'\n *\n * const settings = createSettingsClient(siteSettings)\n *\n * // Default locale\n * const name = await settings.get('siteName') // string (has default)\n *\n * // Locale-specific (only for translatable settings)\n * const nameEt = await settings.get('siteName', { locale: 'et' })\n *\n * await settings.set('siteName', 'Mänguväljak', { locale: 'et' })\n * ```\n */\n\nimport type { Logger } from '@murumets-ee/core'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport type { ZodType } from 'zod'\nimport { toolkitSettingsTable } from './schema.js'\nimport type {\n InferSettingsMap,\n InferSettingValue,\n SettingConfig,\n SettingScope,\n SettingsDefinition,\n} from './types.js'\nimport { DEFAULT_LOCALE, GLOBAL_SCOPE_ID } from './types.js'\nimport { generateSettingValidators } from './validation.js'\n\nexport interface SettingsClientConfig {\n /** Database client (read-write) */\n db: PostgresJsDatabase\n /** Logger instance */\n logger?: Logger\n /** Override scope (defaults to definition's scope) */\n scope?: SettingScope\n /** Scope ID (required for team/user scope, defaults to '__global__' for global) */\n scopeId?: string\n}\n\nexport interface LocaleOption {\n /** Locale code for translatable settings (e.g. 'et', 'ru'). Ignored for non-translatable settings. */\n locale?: string\n}\n\n/** Row shape returned by the settings table (for narrow typing of fetched rows). */\ninterface SettingsRow {\n key: string\n locale: string\n value: unknown\n}\n\nexport class SettingsClient<\n S extends Record<string, SettingConfig> = Record<string, SettingConfig>,\n> {\n private definition: SettingsDefinition<S>\n private table: ReturnType<typeof toolkitSettingsTable.makeClient>\n private logger?: Logger\n private scope: SettingScope\n private scopeId: string\n private validators: Record<string, ZodType>\n\n constructor(definition: SettingsDefinition<S>, config: SettingsClientConfig) {\n if (typeof window !== 'undefined') {\n throw new Error('SettingsClient cannot be used in browser code.')\n }\n\n this.definition = definition\n this.table = toolkitSettingsTable.makeClient(config.db)\n this.logger = config.logger\n this.scope = config.scope ?? definition.scope\n this.scopeId = config.scopeId ?? GLOBAL_SCOPE_ID\n\n if ((this.scope === 'team' || this.scope === 'user') && !config.scopeId) {\n throw new Error(\n `scopeId is required for ${this.scope}-scoped settings (namespace: ${definition.namespace})`,\n )\n }\n\n this.validators = generateSettingValidators(definition)\n }\n\n /**\n * Get a single setting value.\n *\n * For translatable settings with a locale, tries locale-specific value first,\n * then falls back to the default value, then the schema default, then null.\n */\n async get<K extends string & keyof S>(\n key: K,\n options?: LocaleOption,\n ): Promise<InferSettingValue<S[K]>> {\n const config = this.definition.schema[key]\n const locale = config?.translatable && options?.locale ? options.locale : null\n\n this.logger?.debug({ namespace: this.definition.namespace, key, locale }, 'Getting setting')\n\n if (locale) {\n // Fetch both locale-specific and default in one query\n const rows = (await this.table.findMany({\n where: {\n ...this.baseWhere(),\n key,\n $or: [{ locale }, { locale: DEFAULT_LOCALE }],\n },\n limit: 2,\n })) as unknown as SettingsRow[]\n\n // Prefer locale-specific, fall back to default\n const localeRow = rows.find((r) => r.locale === locale)\n const defaultRow = rows.find((r) => r.locale === DEFAULT_LOCALE)\n const row = localeRow ?? defaultRow\n\n if (row && row.value !== undefined && row.value !== null) {\n return row.value as InferSettingValue<S[K]>\n }\n } else {\n const rows = (await this.table.findMany({\n where: { ...this.baseWhere(), key, locale: DEFAULT_LOCALE },\n limit: 1,\n })) as unknown as SettingsRow[]\n\n if (rows.length > 0 && rows[0].value !== undefined && rows[0].value !== null) {\n return rows[0].value as InferSettingValue<S[K]>\n }\n }\n\n if (config && 'default' in config && config.default !== undefined) {\n return config.default as InferSettingValue<S[K]>\n }\n\n return null as InferSettingValue<S[K]>\n }\n\n /**\n * Get all settings for this namespace/scope as a typed object.\n * Missing values are filled from schema defaults.\n *\n * When locale is specified, translatable settings prefer the locale-specific\n * value over the default value.\n */\n async getAll(options?: LocaleOption): Promise<InferSettingsMap<S>> {\n const locale = options?.locale ?? null\n\n this.logger?.debug({ namespace: this.definition.namespace, locale }, 'Getting all settings')\n\n let rows: SettingsRow[]\n\n if (locale) {\n // Fetch both default and locale-specific rows\n rows = (await this.table.findMany({\n where: {\n ...this.baseWhere(),\n $or: [{ locale: DEFAULT_LOCALE }, { locale }],\n },\n limit: 1000,\n })) as unknown as SettingsRow[]\n } else {\n rows = (await this.table.findMany({\n where: { ...this.baseWhere(), locale: DEFAULT_LOCALE },\n limit: 1000,\n })) as unknown as SettingsRow[]\n }\n\n // Build lookup: key → { default: value, locale: value }\n const stored = new Map<string, { default?: unknown; locale?: unknown }>()\n for (const row of rows) {\n const entry = stored.get(row.key) ?? {}\n if (row.locale === DEFAULT_LOCALE) {\n entry.default = row.value\n } else {\n entry.locale = row.value\n }\n stored.set(row.key, entry)\n }\n\n const result: Record<string, unknown> = {}\n for (const [key, config] of Object.entries(this.definition.schema)) {\n const entry = stored.get(key)\n\n // For translatable settings with locale, prefer locale-specific value\n let value: unknown\n if (config.translatable && locale && entry?.locale !== undefined && entry?.locale !== null) {\n value = entry.locale\n } else if (entry?.default !== undefined && entry?.default !== null) {\n value = entry.default\n }\n\n if (value !== undefined) {\n result[key] = value\n } else if ('default' in config && config.default !== undefined) {\n result[key] = config.default\n } else {\n result[key] = null\n }\n }\n\n return result as InferSettingsMap<S>\n }\n\n /**\n * Set a single setting value.\n * Validates against the schema before writing.\n *\n * Pass `{ locale }` to write a locale-specific value (only for translatable settings).\n */\n async set<K extends string & keyof S>(\n key: K,\n value: InferSettingValue<S[K]>,\n options?: LocaleOption,\n ): Promise<void> {\n const locale = this.resolveLocale(key, options)\n\n this.logger?.info({ namespace: this.definition.namespace, key, locale }, 'Setting value')\n\n if (!(key in this.definition.schema)) {\n throw new Error(`Unknown setting key '${key}' in namespace '${this.definition.namespace}'`)\n }\n\n const validator = this.validators[key]\n if (validator) {\n validator.parse(value)\n }\n\n await this.upsertRow(key, value as unknown, locale, this.table)\n }\n\n /**\n * Set multiple settings at once (validated individually).\n * Writes in a transaction — all or nothing.\n *\n * Pass `{ locale }` to write locale-specific values for translatable settings.\n * Non-translatable settings in the values will always write to the default locale.\n */\n async setMany(values: Partial<InferSettingsMap<S>>, options?: LocaleOption): Promise<void> {\n this.logger?.info(\n { namespace: this.definition.namespace, keys: Object.keys(values), locale: options?.locale },\n 'Setting multiple values',\n )\n\n // Validate all first (fail fast)\n for (const [key, value] of Object.entries(values)) {\n if (!(key in this.definition.schema)) {\n throw new Error(`Unknown setting key '${key}' in namespace '${this.definition.namespace}'`)\n }\n const validator = this.validators[key]\n if (validator && value !== undefined) {\n validator.parse(value)\n }\n }\n\n await this.table.transaction(async (tx) => {\n for (const [key, value] of Object.entries(values)) {\n if (value === undefined) continue\n const locale = this.resolveLocale(key, options)\n await this.upsertRow(key, value as unknown, locale, tx)\n }\n })\n }\n\n /**\n * Delete a setting (resets to default on next get).\n * Pass `{ locale }` to delete only the locale-specific value.\n */\n async delete<K extends string & keyof S>(key: K, options?: LocaleOption): Promise<void> {\n const locale = this.resolveLocale(key, options)\n\n this.logger?.info({ namespace: this.definition.namespace, key, locale }, 'Deleting setting')\n\n await this.table.deleteMany({\n ...this.baseWhere(),\n key,\n locale,\n })\n }\n\n /**\n * Check if a setting has a stored value (not relying on default).\n */\n async has<K extends string & keyof S>(key: K, options?: LocaleOption): Promise<boolean> {\n const locale = this.resolveLocale(key, options)\n\n return await this.table.exists({\n ...this.baseWhere(),\n key,\n locale,\n })\n }\n\n // ---------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------\n\n /**\n * Resolve which locale to use for a given key.\n * Non-translatable settings always use DEFAULT_LOCALE.\n * Translatable settings use the requested locale or DEFAULT_LOCALE.\n */\n private resolveLocale(key: string, options?: LocaleOption): string {\n const config = this.definition.schema[key as keyof S] as SettingConfig | undefined\n if (config?.translatable && options?.locale) {\n return options.locale\n }\n return DEFAULT_LOCALE\n }\n\n /** Base where conditions: namespace + scope + scopeId. */\n private baseWhere() {\n return {\n namespace: this.definition.namespace,\n scope: this.scope,\n scopeId: this.scopeId,\n }\n }\n\n /**\n * Upsert a single setting row via the TableClient.\n * Accepts either the top-level table client or a transactional one —\n * same shape, used inside `setMany`'s transaction.\n */\n private async upsertRow(\n key: string,\n value: unknown,\n locale: string,\n tableClient: ReturnType<typeof toolkitSettingsTable.makeClient>,\n ) {\n await tableClient.upsert(\n {\n namespace: this.definition.namespace,\n scope: this.scope,\n scopeId: this.scopeId,\n key,\n locale,\n value,\n updatedAt: new Date(),\n },\n {\n target: ['namespace', 'scope', 'scopeId', 'key', 'locale'],\n set: {\n value,\n updatedAt: new Date(),\n },\n },\n )\n }\n}\n","/**\n * Factory function for creating typed SettingsClient instances.\n *\n * Follows the createAdminClient(entity, app?) pattern from @murumets-ee/core.\n */\n\nimport type { ToolkitApp } from '@murumets-ee/core'\nimport { getApp } from '@murumets-ee/core'\nimport { SettingsClient } from './client.js'\nimport type { SettingConfig, SettingScope, SettingsDefinition } from './types.js'\n\nexport interface CreateSettingsClientOptions {\n /** Override the toolkit app (defaults to getApp()) */\n app?: ToolkitApp\n /** Override scope from definition */\n scope?: SettingScope\n /** Scope ID for team/user scoped settings */\n scopeId?: string\n}\n\n/**\n * Create a typed SettingsClient.\n *\n * @example\n * ```typescript\n * import { createSettingsClient } from '@murumets-ee/settings'\n * import { siteSettings } from './settings/site'\n *\n * const settings = createSettingsClient(siteSettings)\n * const name = await settings.get('siteName') // string\n * ```\n */\nexport function createSettingsClient<S extends Record<string, SettingConfig>>(\n definition: SettingsDefinition<S>,\n options?: CreateSettingsClientOptions,\n): SettingsClient<S> {\n const app = options?.app ?? getApp()\n\n return new SettingsClient(definition, {\n db: app.db.readWrite,\n logger: app.logger.child({ settings: definition.namespace }),\n scope: options?.scope,\n scopeId: options?.scopeId,\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client-factory-DBlcuyG0.mjs","names":[],"sources":["../src/schema.ts","../src/types.ts","../src/validation.ts","../src/client.ts","../src/client-factory.ts"],"sourcesContent":["/**\n * Drizzle schema for settings tables.\n *\n * Two tables:\n * - toolkit_settings: typed key-value settings grouped by namespace and scope\n * - toolkit_view_state: schemaless user-scoped JSON blobs with optional TTL\n *\n * These tables are registered for migration discovery via the `settings()`\n * plugin's `tables` field. `lumi migrate` picks them up automatically.\n *\n * Built on `@murumets-ee/db`'s `defineTable` — the raw `pgTable` is still\n * re-exported (via `.table`) for backwards compatibility with existing\n * imports and tests, but all query construction in SettingsClient /\n * ViewStateClient goes through the typed TableClient.\n */\n\nimport { column, defineTable } from '@murumets-ee/db'\n\n/**\n * Typed settings table.\n *\n * Stores key-value pairs grouped by namespace and scoped\n * to global, team, or user contexts.\n *\n * scopeId uses '__global__' sentinel for global scope to avoid\n * PostgreSQL's NULL != NULL behavior in unique constraints.\n */\nexport const toolkitSettingsTable = defineTable({\n name: 'toolkit_settings',\n columns: {\n id: column.uuid({ primaryKey: true, defaultRandom: true }),\n namespace: column.varchar({ length: 100, notNull: true }),\n scope: column.varchar({ length: 20, notNull: true, default: 'global' }),\n scopeId: column.varchar({\n length: 100,\n notNull: true,\n default: '__global__',\n pgName: 'scope_id',\n }),\n key: column.varchar({ length: 255, notNull: true }),\n locale: column.varchar({ length: 10, notNull: true, default: '_default' }),\n value: column.jsonb(),\n updatedAt: column.timestamp({\n notNull: true,\n defaultNow: true,\n withTimezone: true,\n pgName: 'updated_at',\n }),\n updatedBy: column.uuid({ pgName: 'updated_by' }),\n },\n unique: [\n { on: ['namespace', 'scope', 'scopeId', 'key', 'locale'] },\n ],\n})\n\n/** Backwards-compatible export — existing imports of the raw pgTable still work. */\nexport const toolkitSettings = toolkitSettingsTable.table\n\n/**\n * View state table.\n *\n * Stores schemaless user-scoped JSON blobs for persisting\n * UI state (table filters, column order, etc.) with optional TTL.\n */\nexport const toolkitViewStateTable = defineTable({\n name: 'toolkit_view_state',\n columns: {\n id: column.uuid({ primaryKey: true, defaultRandom: true }),\n userId: column.uuid({ notNull: true, pgName: 'user_id' }),\n viewKey: column.varchar({ length: 255, notNull: true, pgName: 'view_key' }),\n state: column.jsonb({ notNull: true }),\n expiresAt: column.timestamp({ withTimezone: true, pgName: 'expires_at' }),\n updatedAt: column.timestamp({\n notNull: true,\n defaultNow: true,\n withTimezone: true,\n pgName: 'updated_at',\n }),\n },\n unique: [\n { on: ['userId', 'viewKey'] },\n ],\n})\n\n/** Backwards-compatible export — existing imports of the raw pgTable still work. */\nexport const toolkitViewState = toolkitViewStateTable.table\n","/**\n * Setting configuration types and compile-time type inference.\n *\n * Design mirrors the entity field system:\n * - Config interfaces define what each setting type accepts\n * - SettingToTS maps a single config to its TypeScript type\n * - InferSettingValue adds null awareness based on `default` presence\n * - InferSettingsMap maps an entire schema to a typed record\n */\n\nimport type { ZodType } from 'zod'\n\n// ---------------------------------------------------------------\n// 1. Setting config interfaces\n// ---------------------------------------------------------------\n\nexport interface BaseSettingConfig {\n /** Human-readable label for admin UI */\n label?: string\n /** Description / help text */\n description?: string\n /** If true, this setting can have per-locale values (mirrors entity translatable pattern) */\n translatable?: boolean\n}\n\nexport interface TextSettingConfig extends BaseSettingConfig {\n type: 'text'\n default?: string\n maxLength?: number\n minLength?: number\n pattern?: RegExp\n /** Render as textarea instead of single-line input. */\n multiline?: boolean\n}\n\nexport interface NumberSettingConfig extends BaseSettingConfig {\n type: 'number'\n default?: number\n min?: number\n max?: number\n integer?: boolean\n}\n\nexport interface BooleanSettingConfig extends BaseSettingConfig {\n type: 'boolean'\n default?: boolean\n}\n\nexport interface SelectSettingConfig<O extends readonly string[] = readonly string[]>\n extends BaseSettingConfig {\n type: 'select'\n options: O\n default?: O[number]\n}\n\nexport interface JsonSettingConfig<T = unknown> extends BaseSettingConfig {\n type: 'json'\n default?: T\n /** Optional Zod schema for validation. If provided, values are validated on set. */\n schema?: ZodType<T>\n}\n\nexport interface MediaSettingConfig extends BaseSettingConfig {\n type: 'media'\n default?: string\n accept?: string[]\n}\n\nexport type SettingConfig =\n | TextSettingConfig\n | NumberSettingConfig\n | BooleanSettingConfig\n | SelectSettingConfig\n | JsonSettingConfig\n | MediaSettingConfig\n\n// ---------------------------------------------------------------\n// 2. Setting-to-TypeScript mapping (single setting)\n// ---------------------------------------------------------------\n\n/**\n * Maps a single SettingConfig to its TypeScript output type.\n * Each branch is a shallow comparison — no recursion.\n */\nexport type SettingToTS<S extends SettingConfig> = S extends TextSettingConfig\n ? string\n : S extends NumberSettingConfig\n ? number\n : S extends BooleanSettingConfig\n ? boolean\n : S extends SelectSettingConfig<infer O>\n ? O[number]\n : S extends JsonSettingConfig<infer T>\n ? T\n : S extends MediaSettingConfig\n ? string\n : never\n\n// ---------------------------------------------------------------\n// 3. Null awareness: settings with defaults always return value\n// ---------------------------------------------------------------\n\n/**\n * If a setting has a `default`, get() never returns null.\n * Without a default, it returns T | null.\n */\nexport type InferSettingValue<S extends SettingConfig> = S extends { default: unknown }\n ? SettingToTS<S>\n : SettingToTS<S> | null\n\n// ---------------------------------------------------------------\n// 4. Full settings map type (what getAll() returns)\n// ---------------------------------------------------------------\n\nexport type InferSettingsMap<Schema extends Record<string, SettingConfig>> = {\n [K in keyof Schema]: InferSettingValue<Schema[K]>\n}\n\n// ---------------------------------------------------------------\n// 5. Scope types\n// ---------------------------------------------------------------\n\nexport type SettingScope = 'global' | 'team' | 'user'\n\n/** Sentinel value for global scope_id (avoids NULL uniqueness issues) */\nexport const GLOBAL_SCOPE_ID = '__global__'\n\n/** Sentinel value for locale column when no locale is specified (base/default value) */\nexport const DEFAULT_LOCALE = '_default'\n\n// ---------------------------------------------------------------\n// 6. Settings definition (returned by defineSettings)\n// ---------------------------------------------------------------\n\nexport interface SettingsDefinition<\n S extends Record<string, SettingConfig> = Record<string, SettingConfig>,\n> {\n /** Unique namespace for this settings group */\n namespace: string\n /** Default scope for these settings */\n scope: SettingScope\n /** Setting schema (the shape) */\n schema: S\n /** Human-readable label for admin UI */\n label?: string\n /**\n * Lucide icon name (e.g. `'globe'`, `'ticket'`) for the sidebar nav\n * entry this definition produces. Falls back to `'settings'` when\n * unset.\n */\n iconName?: string\n /**\n * Hide this namespace from the auto-generated sidebar nav. Still\n * renders in the settings API. Useful for internal-only namespaces\n * like the permissions store.\n */\n hideFromMenu?: boolean\n}\n","/**\n * Generate Zod validation schemas from setting config definitions.\n * Validates values before writing to the database.\n */\n\nimport { z } from 'zod'\nimport type { SettingConfig, SettingsDefinition } from './types.js'\n\n/**\n * Convert a single setting config to a Zod schema.\n */\nexport function settingToZod(config: SettingConfig): z.ZodType {\n switch (config.type) {\n case 'text': {\n let schema: z.ZodString = z.string()\n if (config.maxLength) schema = schema.max(config.maxLength)\n if (config.minLength) schema = schema.min(config.minLength)\n if (config.pattern) schema = schema.regex(config.pattern)\n return schema\n }\n\n case 'number': {\n let schema: z.ZodNumber = z.number()\n if (config.integer) schema = schema.int()\n if (config.min !== undefined) schema = schema.min(config.min)\n if (config.max !== undefined) schema = schema.max(config.max)\n return schema\n }\n\n case 'boolean':\n return z.boolean()\n\n case 'select':\n return z.enum(config.options as [string, ...string[]])\n\n case 'json':\n return config.schema ?? z.unknown()\n\n case 'media':\n return z.string().uuid()\n\n default:\n return z.unknown()\n }\n}\n\n/**\n * Generate a validation map for all settings in a definition.\n * Returns a Record<key, ZodType> for validating individual set() calls.\n *\n * Settings without a default are nullable (matching InferSettingValue),\n * so their validators accept null.\n */\nexport function generateSettingValidators(\n definition: SettingsDefinition,\n): Record<string, z.ZodType> {\n const validators: Record<string, z.ZodType> = {}\n for (const [key, config] of Object.entries(definition.schema)) {\n let schema = settingToZod(config)\n const hasDefault = 'default' in config && config.default !== undefined\n if (!hasDefault) {\n schema = schema.nullable()\n }\n validators[key] = schema\n }\n return validators\n}\n","/**\n * SettingsClient — typed CRUD for key-value settings.\n *\n * Server-only. Uses read-write DB connection.\n * Validates values against the setting schema on set().\n *\n * Supports per-locale values for settings marked `translatable: true`.\n * Non-translatable settings always use the `__default__` locale.\n *\n * All persistence goes through the `toolkit_settings` TableClient\n * (`defineTable`) — no direct Drizzle usage from this module.\n *\n * @example\n * ```typescript\n * import { createSettingsClient } from '@murumets-ee/settings'\n * import { siteSettings } from './settings/site'\n *\n * const settings = createSettingsClient(siteSettings)\n *\n * // Default locale\n * const name = await settings.get('siteName') // string (has default)\n *\n * // Locale-specific (only for translatable settings)\n * const nameEt = await settings.get('siteName', { locale: 'et' })\n *\n * await settings.set('siteName', 'Mänguväljak', { locale: 'et' })\n * ```\n */\n\nimport type { Logger } from '@murumets-ee/core'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport type { ZodType } from 'zod'\nimport { toolkitSettingsTable } from './schema.js'\nimport type {\n InferSettingsMap,\n InferSettingValue,\n SettingConfig,\n SettingScope,\n SettingsDefinition,\n} from './types.js'\nimport { DEFAULT_LOCALE, GLOBAL_SCOPE_ID } from './types.js'\nimport { generateSettingValidators } from './validation.js'\n\nexport interface SettingsClientConfig {\n /** Database client (read-write) */\n db: PostgresJsDatabase\n /** Logger instance */\n logger?: Logger\n /** Override scope (defaults to definition's scope) */\n scope?: SettingScope\n /** Scope ID (required for team/user scope, defaults to '__global__' for global) */\n scopeId?: string\n}\n\nexport interface LocaleOption {\n /** Locale code for translatable settings (e.g. 'et', 'ru'). Ignored for non-translatable settings. */\n locale?: string\n}\n\n/** Row shape returned by the settings table (for narrow typing of fetched rows). */\ninterface SettingsRow {\n key: string\n locale: string\n value: unknown\n}\n\nexport class SettingsClient<\n S extends Record<string, SettingConfig> = Record<string, SettingConfig>,\n> {\n private definition: SettingsDefinition<S>\n private table: ReturnType<typeof toolkitSettingsTable.makeClient>\n private logger?: Logger\n private scope: SettingScope\n private scopeId: string\n private validators: Record<string, ZodType>\n\n constructor(definition: SettingsDefinition<S>, config: SettingsClientConfig) {\n if (typeof window !== 'undefined') {\n throw new Error('SettingsClient cannot be used in browser code.')\n }\n\n this.definition = definition\n this.table = toolkitSettingsTable.makeClient(config.db)\n this.logger = config.logger\n this.scope = config.scope ?? definition.scope\n this.scopeId = config.scopeId ?? GLOBAL_SCOPE_ID\n\n if ((this.scope === 'team' || this.scope === 'user') && !config.scopeId) {\n throw new Error(\n `scopeId is required for ${this.scope}-scoped settings (namespace: ${definition.namespace})`,\n )\n }\n\n this.validators = generateSettingValidators(definition)\n }\n\n /**\n * Get a single setting value.\n *\n * For translatable settings with a locale, tries locale-specific value first,\n * then falls back to the default value, then the schema default, then null.\n */\n async get<K extends string & keyof S>(\n key: K,\n options?: LocaleOption,\n ): Promise<InferSettingValue<S[K]>> {\n const config = this.definition.schema[key]\n const locale = config?.translatable && options?.locale ? options.locale : null\n\n this.logger?.debug({ namespace: this.definition.namespace, key, locale }, 'Getting setting')\n\n if (locale) {\n // Fetch both locale-specific and default in one query\n const rows = (await this.table.findMany({\n where: {\n ...this.baseWhere(),\n key,\n $or: [{ locale }, { locale: DEFAULT_LOCALE }],\n },\n limit: 2,\n })) as unknown as SettingsRow[]\n\n // Prefer locale-specific, fall back to default\n const localeRow = rows.find((r) => r.locale === locale)\n const defaultRow = rows.find((r) => r.locale === DEFAULT_LOCALE)\n const row = localeRow ?? defaultRow\n\n if (row && row.value !== undefined && row.value !== null) {\n return row.value as InferSettingValue<S[K]>\n }\n } else {\n const rows = (await this.table.findMany({\n where: { ...this.baseWhere(), key, locale: DEFAULT_LOCALE },\n limit: 1,\n })) as unknown as SettingsRow[]\n\n if (rows.length > 0 && rows[0].value !== undefined && rows[0].value !== null) {\n return rows[0].value as InferSettingValue<S[K]>\n }\n }\n\n if (config && 'default' in config && config.default !== undefined) {\n return config.default as InferSettingValue<S[K]>\n }\n\n return null as InferSettingValue<S[K]>\n }\n\n /**\n * Get all settings for this namespace/scope as a typed object.\n * Missing values are filled from schema defaults.\n *\n * When locale is specified, translatable settings prefer the locale-specific\n * value over the default value.\n */\n async getAll(options?: LocaleOption): Promise<InferSettingsMap<S>> {\n const locale = options?.locale ?? null\n\n this.logger?.debug({ namespace: this.definition.namespace, locale }, 'Getting all settings')\n\n let rows: SettingsRow[]\n\n if (locale) {\n // Fetch both default and locale-specific rows\n rows = (await this.table.findMany({\n where: {\n ...this.baseWhere(),\n $or: [{ locale: DEFAULT_LOCALE }, { locale }],\n },\n limit: 1000,\n })) as unknown as SettingsRow[]\n } else {\n rows = (await this.table.findMany({\n where: { ...this.baseWhere(), locale: DEFAULT_LOCALE },\n limit: 1000,\n })) as unknown as SettingsRow[]\n }\n\n // Build lookup: key → { default: value, locale: value }\n const stored = new Map<string, { default?: unknown; locale?: unknown }>()\n for (const row of rows) {\n const entry = stored.get(row.key) ?? {}\n if (row.locale === DEFAULT_LOCALE) {\n entry.default = row.value\n } else {\n entry.locale = row.value\n }\n stored.set(row.key, entry)\n }\n\n const result: Record<string, unknown> = {}\n for (const [key, config] of Object.entries(this.definition.schema)) {\n const entry = stored.get(key)\n\n // For translatable settings with locale, prefer locale-specific value\n let value: unknown\n if (config.translatable && locale && entry?.locale !== undefined && entry?.locale !== null) {\n value = entry.locale\n } else if (entry?.default !== undefined && entry?.default !== null) {\n value = entry.default\n }\n\n if (value !== undefined) {\n result[key] = value\n } else if ('default' in config && config.default !== undefined) {\n result[key] = config.default\n } else {\n result[key] = null\n }\n }\n\n return result as InferSettingsMap<S>\n }\n\n /**\n * Set a single setting value.\n * Validates against the schema before writing.\n *\n * Pass `{ locale }` to write a locale-specific value (only for translatable settings).\n */\n async set<K extends string & keyof S>(\n key: K,\n value: InferSettingValue<S[K]>,\n options?: LocaleOption,\n ): Promise<void> {\n const locale = this.resolveLocale(key, options)\n\n this.logger?.info({ namespace: this.definition.namespace, key, locale }, 'Setting value')\n\n if (!(key in this.definition.schema)) {\n throw new Error(`Unknown setting key '${key}' in namespace '${this.definition.namespace}'`)\n }\n\n const validator = this.validators[key]\n if (validator) {\n validator.parse(value)\n }\n\n await this.upsertRow(key, value as unknown, locale, this.table)\n }\n\n /**\n * Set multiple settings at once (validated individually).\n * Writes in a transaction — all or nothing.\n *\n * Pass `{ locale }` to write locale-specific values for translatable settings.\n * Non-translatable settings in the values will always write to the default locale.\n */\n async setMany(values: Partial<InferSettingsMap<S>>, options?: LocaleOption): Promise<void> {\n this.logger?.info(\n { namespace: this.definition.namespace, keys: Object.keys(values), locale: options?.locale },\n 'Setting multiple values',\n )\n\n // Validate all first (fail fast)\n for (const [key, value] of Object.entries(values)) {\n if (!(key in this.definition.schema)) {\n throw new Error(`Unknown setting key '${key}' in namespace '${this.definition.namespace}'`)\n }\n const validator = this.validators[key]\n if (validator && value !== undefined) {\n validator.parse(value)\n }\n }\n\n await this.table.transaction(async (tx) => {\n for (const [key, value] of Object.entries(values)) {\n if (value === undefined) continue\n const locale = this.resolveLocale(key, options)\n await this.upsertRow(key, value as unknown, locale, tx)\n }\n })\n }\n\n /**\n * Delete a setting (resets to default on next get).\n * Pass `{ locale }` to delete only the locale-specific value.\n */\n async delete<K extends string & keyof S>(key: K, options?: LocaleOption): Promise<void> {\n const locale = this.resolveLocale(key, options)\n\n this.logger?.info({ namespace: this.definition.namespace, key, locale }, 'Deleting setting')\n\n await this.table.deleteMany({\n ...this.baseWhere(),\n key,\n locale,\n })\n }\n\n /**\n * Check if a setting has a stored value (not relying on default).\n */\n async has<K extends string & keyof S>(key: K, options?: LocaleOption): Promise<boolean> {\n const locale = this.resolveLocale(key, options)\n\n return await this.table.exists({\n ...this.baseWhere(),\n key,\n locale,\n })\n }\n\n // ---------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------\n\n /**\n * Resolve which locale to use for a given key.\n * Non-translatable settings always use DEFAULT_LOCALE.\n * Translatable settings use the requested locale or DEFAULT_LOCALE.\n */\n private resolveLocale(key: string, options?: LocaleOption): string {\n const config = this.definition.schema[key as keyof S] as SettingConfig | undefined\n if (config?.translatable && options?.locale) {\n return options.locale\n }\n return DEFAULT_LOCALE\n }\n\n /** Base where conditions: namespace + scope + scopeId. */\n private baseWhere() {\n return {\n namespace: this.definition.namespace,\n scope: this.scope,\n scopeId: this.scopeId,\n }\n }\n\n /**\n * Upsert a single setting row via the TableClient.\n * Accepts either the top-level table client or a transactional one —\n * same shape, used inside `setMany`'s transaction.\n */\n private async upsertRow(\n key: string,\n value: unknown,\n locale: string,\n tableClient: ReturnType<typeof toolkitSettingsTable.makeClient>,\n ) {\n await tableClient.upsert(\n {\n namespace: this.definition.namespace,\n scope: this.scope,\n scopeId: this.scopeId,\n key,\n locale,\n value,\n updatedAt: new Date(),\n },\n {\n target: ['namespace', 'scope', 'scopeId', 'key', 'locale'],\n set: {\n value,\n updatedAt: new Date(),\n },\n },\n )\n }\n}\n","/**\n * Factory function for creating typed SettingsClient instances.\n *\n * Follows the createAdminClient(entity, app?) pattern from @murumets-ee/core.\n */\n\nimport type { ToolkitApp } from '@murumets-ee/core'\nimport { getApp } from '@murumets-ee/core'\nimport { SettingsClient } from './client.js'\nimport type { SettingConfig, SettingScope, SettingsDefinition } from './types.js'\n\nexport interface CreateSettingsClientOptions {\n /** Override the toolkit app (defaults to getApp()) */\n app?: ToolkitApp\n /** Override scope from definition */\n scope?: SettingScope\n /** Scope ID for team/user scoped settings */\n scopeId?: string\n}\n\n/**\n * Create a typed SettingsClient.\n *\n * @example\n * ```typescript\n * import { createSettingsClient } from '@murumets-ee/settings'\n * import { siteSettings } from './settings/site'\n *\n * const settings = createSettingsClient(siteSettings)\n * const name = await settings.get('siteName') // string\n * ```\n */\nexport function createSettingsClient<S extends Record<string, SettingConfig>>(\n definition: SettingsDefinition<S>,\n options?: CreateSettingsClientOptions,\n): SettingsClient<S> {\n const app = options?.app ?? getApp()\n\n return new SettingsClient(definition, {\n db: app.db.readWrite,\n logger: app.logger.child({ settings: definition.namespace }),\n scope: options?.scope,\n scopeId: options?.scopeId,\n })\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"define-settings-Bi3STtAH.mjs","names":[],"sources":["../src/builders.ts","../src/types.ts","../src/define-settings.ts"],"sourcesContent":["/**\n * Fluent API for building setting definitions.\n *\n * Each builder uses a `const` generic parameter on the config to preserve\n * literal types (e.g., `default: 'My Site'` stays literal, not `string`).\n * This enables compile-time type inference in the settings system.\n *\n * Pattern matches packages/entity/src/fields/builders.ts exactly.\n */\n\nimport type {\n BooleanSettingConfig,\n JsonSettingConfig,\n MediaSettingConfig,\n NumberSettingConfig,\n SelectSettingConfig,\n TextSettingConfig,\n} from './types.js'\n\nexport const setting = {\n /**\n * Text setting (string value)\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n text: <const C extends Partial<Omit<TextSettingConfig, 'type'>> = {}>(\n config?: C,\n ): TextSettingConfig & C => ({ type: 'text', ...config }) as TextSettingConfig & C,\n\n /**\n * Number setting\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n number: <const C extends Partial<Omit<NumberSettingConfig, 'type'>> = {}>(\n config?: C,\n ): NumberSettingConfig & C => ({ type: 'number', ...config }) as NumberSettingConfig & C,\n\n /**\n * Boolean setting\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n boolean: <const C extends Partial<Omit<BooleanSettingConfig, 'type'>> = {}>(\n config?: C,\n ): BooleanSettingConfig & C => ({ type: 'boolean', ...config }) as BooleanSettingConfig & C,\n\n /**\n * Select setting (enum from options tuple)\n * Preserves literal option types for type inference.\n *\n * @example\n * setting.select({ options: ['light', 'dark', 'system'] as const, default: 'system' })\n * // inferred type: 'light' | 'dark' | 'system'\n */\n select: <\n const O extends readonly string[],\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n const C extends Partial<Omit<SelectSettingConfig, 'type' | 'options'>> = {},\n >(\n config: { options: O } & C,\n ): SelectSettingConfig<O> & C => ({ type: 'select', ...config }) as SelectSettingConfig<O> & C,\n\n /**\n * JSON setting (arbitrary typed JSON)\n *\n * @example\n * setting.json<{ twitter?: string; github?: string }>()\n * setting.json<string[]>({ default: [] })\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n json: <T = unknown, const C extends Partial<Omit<JsonSettingConfig<T>, 'type'>> = {}>(\n config?: C,\n ): JsonSettingConfig<T> & C => ({ type: 'json', ...config }) as JsonSettingConfig<T> & C,\n\n /**\n * Media setting (stores media ID as string UUID)\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n media: <const C extends Partial<Omit<MediaSettingConfig, 'type'>> = {}>(\n config?: C,\n ): MediaSettingConfig & C => ({ type: 'media', ...config }) as MediaSettingConfig & C,\n}\n","/**\n * Setting configuration types and compile-time type inference.\n *\n * Design mirrors the entity field system:\n * - Config interfaces define what each setting type accepts\n * - SettingToTS maps a single config to its TypeScript type\n * - InferSettingValue adds null awareness based on `default` presence\n * - InferSettingsMap maps an entire schema to a typed record\n */\n\nimport type { ZodType } from 'zod'\n\n// ---------------------------------------------------------------\n// 1. Setting config interfaces\n// ---------------------------------------------------------------\n\nexport interface BaseSettingConfig {\n /** Human-readable label for admin UI */\n label?: string\n /** Description / help text */\n description?: string\n /** If true, this setting can have per-locale values (mirrors entity translatable pattern) */\n translatable?: boolean\n}\n\nexport interface TextSettingConfig extends BaseSettingConfig {\n type: 'text'\n default?: string\n maxLength?: number\n minLength?: number\n pattern?: RegExp\n /** Render as textarea instead of single-line input. */\n multiline?: boolean\n}\n\nexport interface NumberSettingConfig extends BaseSettingConfig {\n type: 'number'\n default?: number\n min?: number\n max?: number\n integer?: boolean\n}\n\nexport interface BooleanSettingConfig extends BaseSettingConfig {\n type: 'boolean'\n default?: boolean\n}\n\nexport interface SelectSettingConfig<O extends readonly string[] = readonly string[]>\n extends BaseSettingConfig {\n type: 'select'\n options: O\n default?: O[number]\n}\n\nexport interface JsonSettingConfig<T = unknown> extends BaseSettingConfig {\n type: 'json'\n default?: T\n /** Optional Zod schema for validation. If provided, values are validated on set. */\n schema?: ZodType<T>\n}\n\nexport interface MediaSettingConfig extends BaseSettingConfig {\n type: 'media'\n default?: string\n accept?: string[]\n}\n\nexport type SettingConfig =\n | TextSettingConfig\n | NumberSettingConfig\n | BooleanSettingConfig\n | SelectSettingConfig\n | JsonSettingConfig\n | MediaSettingConfig\n\n// ---------------------------------------------------------------\n// 2. Setting-to-TypeScript mapping (single setting)\n// ---------------------------------------------------------------\n\n/**\n * Maps a single SettingConfig to its TypeScript output type.\n * Each branch is a shallow comparison — no recursion.\n */\nexport type SettingToTS<S extends SettingConfig> = S extends TextSettingConfig\n ? string\n : S extends NumberSettingConfig\n ? number\n : S extends BooleanSettingConfig\n ? boolean\n : S extends SelectSettingConfig<infer O>\n ? O[number]\n : S extends JsonSettingConfig<infer T>\n ? T\n : S extends MediaSettingConfig\n ? string\n : never\n\n// ---------------------------------------------------------------\n// 3. Null awareness: settings with defaults always return value\n// ---------------------------------------------------------------\n\n/**\n * If a setting has a `default`, get() never returns null.\n * Without a default, it returns T | null.\n */\nexport type InferSettingValue<S extends SettingConfig> = S extends { default: unknown }\n ? SettingToTS<S>\n : SettingToTS<S> | null\n\n// ---------------------------------------------------------------\n// 4. Full settings map type (what getAll() returns)\n// ---------------------------------------------------------------\n\nexport type InferSettingsMap<Schema extends Record<string, SettingConfig>> = {\n [K in keyof Schema]: InferSettingValue<Schema[K]>\n}\n\n// ---------------------------------------------------------------\n// 5. Scope types\n// ---------------------------------------------------------------\n\nexport type SettingScope = 'global' | 'team' | 'user'\n\n/** Sentinel value for global scope_id (avoids NULL uniqueness issues) */\nexport const GLOBAL_SCOPE_ID = '__global__'\n\n/** Sentinel value for locale column when no locale is specified (base/default value) */\nexport const DEFAULT_LOCALE = '_default'\n\n// ---------------------------------------------------------------\n// 6. Settings definition (returned by defineSettings)\n// ---------------------------------------------------------------\n\nexport interface SettingsDefinition<\n S extends Record<string, SettingConfig> = Record<string, SettingConfig>,\n> {\n /** Unique namespace for this settings group */\n namespace: string\n /** Default scope for these settings */\n scope: SettingScope\n /** Setting schema (the shape) */\n schema: S\n /** Human-readable label for admin UI */\n label?: string\n}\n","/**\n * Define a typed settings group.\n *\n * @example\n * ```typescript\n * import { defineSettings, setting } from '@murumets-ee/settings'\n *\n * export const siteSettings = defineSettings({\n * namespace: 'site',\n * scope: 'global',\n * schema: {\n * siteName: setting.text({ default: 'My Site' }),\n * logo: setting.media(),\n * maintenance: setting.boolean({ default: false }),\n * },\n * })\n * ```\n */\n\nimport type { SettingConfig, SettingScope, SettingsDefinition } from './types.js'\n\nexport function defineSettings<const S extends Record<string, SettingConfig>>(definition: {\n namespace: string\n scope: SettingScope\n schema: S\n label?: string\n}): SettingsDefinition<S> {\n if (!definition.namespace) {\n throw new Error('Settings namespace is required')\n }\n if (!definition.schema || Object.keys(definition.schema).length === 0) {\n throw new Error('Settings schema must have at least one setting')\n }\n return definition\n}\n"],"mappings":"AAmBA,MAAa,EAAU,CAKrB,KACE,IAC2B,CAAE,KAAM,OAAQ,GAAG,EAAQ,EAMxD,OACE,IAC6B,CAAE,KAAM,SAAU,GAAG,EAAQ,EAM5D,QACE,IAC8B,CAAE,KAAM,UAAW,GAAG,EAAQ,EAU9D,OAKE,IACgC,CAAE,KAAM,SAAU,GAAG,EAAQ,EAU/D,KACE,IAC8B,CAAE,KAAM,OAAQ,GAAG,EAAQ,EAM3D,MACE,IAC4B,CAAE,KAAM,QAAS,GAAG,EAAQ,EAC3D,CC8CY,EAAkB,aAGlB,EAAiB,WC3G9B,SAAgB,EAA8D,EAKpD,CACxB,GAAI,CAAC,EAAW,UACd,MAAU,MAAM,iCAAiC,CAEnD,GAAI,CAAC,EAAW,QAAU,OAAO,KAAK,EAAW,OAAO,CAAC,SAAW,EAClE,MAAU,MAAM,iDAAiD,CAEnE,OAAO"}
1
+ {"version":3,"file":"define-settings-Bi3STtAH.mjs","names":[],"sources":["../src/builders.ts","../src/types.ts","../src/define-settings.ts"],"sourcesContent":["/**\n * Fluent API for building setting definitions.\n *\n * Each builder uses a `const` generic parameter on the config to preserve\n * literal types (e.g., `default: 'My Site'` stays literal, not `string`).\n * This enables compile-time type inference in the settings system.\n *\n * Pattern matches packages/entity/src/fields/builders.ts exactly.\n */\n\nimport type {\n BooleanSettingConfig,\n JsonSettingConfig,\n MediaSettingConfig,\n NumberSettingConfig,\n SelectSettingConfig,\n TextSettingConfig,\n} from './types.js'\n\nexport const setting = {\n /**\n * Text setting (string value)\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n text: <const C extends Partial<Omit<TextSettingConfig, 'type'>> = {}>(\n config?: C,\n ): TextSettingConfig & C => ({ type: 'text', ...config }) as TextSettingConfig & C,\n\n /**\n * Number setting\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n number: <const C extends Partial<Omit<NumberSettingConfig, 'type'>> = {}>(\n config?: C,\n ): NumberSettingConfig & C => ({ type: 'number', ...config }) as NumberSettingConfig & C,\n\n /**\n * Boolean setting\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n boolean: <const C extends Partial<Omit<BooleanSettingConfig, 'type'>> = {}>(\n config?: C,\n ): BooleanSettingConfig & C => ({ type: 'boolean', ...config }) as BooleanSettingConfig & C,\n\n /**\n * Select setting (enum from options tuple)\n * Preserves literal option types for type inference.\n *\n * @example\n * setting.select({ options: ['light', 'dark', 'system'] as const, default: 'system' })\n * // inferred type: 'light' | 'dark' | 'system'\n */\n select: <\n const O extends readonly string[],\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n const C extends Partial<Omit<SelectSettingConfig, 'type' | 'options'>> = {},\n >(\n config: { options: O } & C,\n ): SelectSettingConfig<O> & C => ({ type: 'select', ...config }) as SelectSettingConfig<O> & C,\n\n /**\n * JSON setting (arbitrary typed JSON)\n *\n * @example\n * setting.json<{ twitter?: string; github?: string }>()\n * setting.json<string[]>({ default: [] })\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n json: <T = unknown, const C extends Partial<Omit<JsonSettingConfig<T>, 'type'>> = {}>(\n config?: C,\n ): JsonSettingConfig<T> & C => ({ type: 'json', ...config }) as JsonSettingConfig<T> & C,\n\n /**\n * Media setting (stores media ID as string UUID)\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n media: <const C extends Partial<Omit<MediaSettingConfig, 'type'>> = {}>(\n config?: C,\n ): MediaSettingConfig & C => ({ type: 'media', ...config }) as MediaSettingConfig & C,\n}\n","/**\n * Setting configuration types and compile-time type inference.\n *\n * Design mirrors the entity field system:\n * - Config interfaces define what each setting type accepts\n * - SettingToTS maps a single config to its TypeScript type\n * - InferSettingValue adds null awareness based on `default` presence\n * - InferSettingsMap maps an entire schema to a typed record\n */\n\nimport type { ZodType } from 'zod'\n\n// ---------------------------------------------------------------\n// 1. Setting config interfaces\n// ---------------------------------------------------------------\n\nexport interface BaseSettingConfig {\n /** Human-readable label for admin UI */\n label?: string\n /** Description / help text */\n description?: string\n /** If true, this setting can have per-locale values (mirrors entity translatable pattern) */\n translatable?: boolean\n}\n\nexport interface TextSettingConfig extends BaseSettingConfig {\n type: 'text'\n default?: string\n maxLength?: number\n minLength?: number\n pattern?: RegExp\n /** Render as textarea instead of single-line input. */\n multiline?: boolean\n}\n\nexport interface NumberSettingConfig extends BaseSettingConfig {\n type: 'number'\n default?: number\n min?: number\n max?: number\n integer?: boolean\n}\n\nexport interface BooleanSettingConfig extends BaseSettingConfig {\n type: 'boolean'\n default?: boolean\n}\n\nexport interface SelectSettingConfig<O extends readonly string[] = readonly string[]>\n extends BaseSettingConfig {\n type: 'select'\n options: O\n default?: O[number]\n}\n\nexport interface JsonSettingConfig<T = unknown> extends BaseSettingConfig {\n type: 'json'\n default?: T\n /** Optional Zod schema for validation. If provided, values are validated on set. */\n schema?: ZodType<T>\n}\n\nexport interface MediaSettingConfig extends BaseSettingConfig {\n type: 'media'\n default?: string\n accept?: string[]\n}\n\nexport type SettingConfig =\n | TextSettingConfig\n | NumberSettingConfig\n | BooleanSettingConfig\n | SelectSettingConfig\n | JsonSettingConfig\n | MediaSettingConfig\n\n// ---------------------------------------------------------------\n// 2. Setting-to-TypeScript mapping (single setting)\n// ---------------------------------------------------------------\n\n/**\n * Maps a single SettingConfig to its TypeScript output type.\n * Each branch is a shallow comparison — no recursion.\n */\nexport type SettingToTS<S extends SettingConfig> = S extends TextSettingConfig\n ? string\n : S extends NumberSettingConfig\n ? number\n : S extends BooleanSettingConfig\n ? boolean\n : S extends SelectSettingConfig<infer O>\n ? O[number]\n : S extends JsonSettingConfig<infer T>\n ? T\n : S extends MediaSettingConfig\n ? string\n : never\n\n// ---------------------------------------------------------------\n// 3. Null awareness: settings with defaults always return value\n// ---------------------------------------------------------------\n\n/**\n * If a setting has a `default`, get() never returns null.\n * Without a default, it returns T | null.\n */\nexport type InferSettingValue<S extends SettingConfig> = S extends { default: unknown }\n ? SettingToTS<S>\n : SettingToTS<S> | null\n\n// ---------------------------------------------------------------\n// 4. Full settings map type (what getAll() returns)\n// ---------------------------------------------------------------\n\nexport type InferSettingsMap<Schema extends Record<string, SettingConfig>> = {\n [K in keyof Schema]: InferSettingValue<Schema[K]>\n}\n\n// ---------------------------------------------------------------\n// 5. Scope types\n// ---------------------------------------------------------------\n\nexport type SettingScope = 'global' | 'team' | 'user'\n\n/** Sentinel value for global scope_id (avoids NULL uniqueness issues) */\nexport const GLOBAL_SCOPE_ID = '__global__'\n\n/** Sentinel value for locale column when no locale is specified (base/default value) */\nexport const DEFAULT_LOCALE = '_default'\n\n// ---------------------------------------------------------------\n// 6. Settings definition (returned by defineSettings)\n// ---------------------------------------------------------------\n\nexport interface SettingsDefinition<\n S extends Record<string, SettingConfig> = Record<string, SettingConfig>,\n> {\n /** Unique namespace for this settings group */\n namespace: string\n /** Default scope for these settings */\n scope: SettingScope\n /** Setting schema (the shape) */\n schema: S\n /** Human-readable label for admin UI */\n label?: string\n /**\n * Lucide icon name (e.g. `'globe'`, `'ticket'`) for the sidebar nav\n * entry this definition produces. Falls back to `'settings'` when\n * unset.\n */\n iconName?: string\n /**\n * Hide this namespace from the auto-generated sidebar nav. Still\n * renders in the settings API. Useful for internal-only namespaces\n * like the permissions store.\n */\n hideFromMenu?: boolean\n}\n","/**\n * Define a typed settings group.\n *\n * @example\n * ```typescript\n * import { defineSettings, setting } from '@murumets-ee/settings'\n *\n * export const siteSettings = defineSettings({\n * namespace: 'site',\n * scope: 'global',\n * schema: {\n * siteName: setting.text({ default: 'My Site' }),\n * logo: setting.media(),\n * maintenance: setting.boolean({ default: false }),\n * },\n * })\n * ```\n */\n\nimport type { SettingConfig, SettingScope, SettingsDefinition } from './types.js'\n\nexport function defineSettings<const S extends Record<string, SettingConfig>>(definition: {\n namespace: string\n scope: SettingScope\n schema: S\n label?: string\n}): SettingsDefinition<S> {\n if (!definition.namespace) {\n throw new Error('Settings namespace is required')\n }\n if (!definition.schema || Object.keys(definition.schema).length === 0) {\n throw new Error('Settings schema must have at least one setting')\n }\n return definition\n}\n"],"mappings":"AAmBA,MAAa,EAAU,CAKrB,KACE,IAC2B,CAAE,KAAM,OAAQ,GAAG,EAAQ,EAMxD,OACE,IAC6B,CAAE,KAAM,SAAU,GAAG,EAAQ,EAM5D,QACE,IAC8B,CAAE,KAAM,UAAW,GAAG,EAAQ,EAU9D,OAKE,IACgC,CAAE,KAAM,SAAU,GAAG,EAAQ,EAU/D,KACE,IAC8B,CAAE,KAAM,OAAQ,GAAG,EAAQ,EAM3D,MACE,IAC4B,CAAE,KAAM,QAAS,GAAG,EAAQ,EAC3D,CC8CY,EAAkB,aAGlB,EAAiB,WC3G9B,SAAgB,EAA8D,EAKpD,CACxB,GAAI,CAAC,EAAW,UACd,MAAU,MAAM,iCAAiC,CAEnD,GAAI,CAAC,EAAW,QAAU,OAAO,KAAK,EAAW,OAAO,CAAC,SAAW,EAClE,MAAU,MAAM,iDAAiD,CAEnE,OAAO"}
@@ -1,4 +1,4 @@
1
- import { c as MediaSettingConfig, d as SettingConfig, f as SettingScope, h as TextSettingConfig, l as NumberSettingConfig, m as SettingsDefinition, n as BooleanSettingConfig, s as JsonSettingConfig, u as SelectSettingConfig } from "./types-D1x10wsL.mjs";
1
+ import { c as MediaSettingConfig, d as SettingConfig, f as SettingScope, h as TextSettingConfig, l as NumberSettingConfig, m as SettingsDefinition, n as BooleanSettingConfig, s as JsonSettingConfig, u as SelectSettingConfig } from "./types-D7gEj6e_.mjs";
2
2
 
3
3
  //#region src/builders.d.ts
4
4
  declare const setting: {
@@ -48,4 +48,4 @@ declare function defineSettings<const S extends Record<string, SettingConfig>>(d
48
48
  }): SettingsDefinition<S>;
49
49
  //#endregion
50
50
  export { setting as n, defineSettings as t };
51
- //# sourceMappingURL=define-settings-vAIuyCdH.d.mts.map
51
+ //# sourceMappingURL=define-settings-BwkJo0rZ.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"define-settings-vAIuyCdH.d.mts","names":[],"sources":["../src/builders.ts","../src/define-settings.ts"],"mappings":";;;cAmBa,OAAA;EAOY;;;yBAFA,OAAA,CAAQ,IAAA,CAAK,iBAAA,iBAA2B,MAAA,GACpD,CAAA,KACR,iBAAA,GAAoB,CAAA;EAOZ;;;2BADc,OAAA,CAAQ,IAAA,CAAK,mBAAA,iBAA6B,MAAA,GACxD,CAAA,KACR,mBAAA,GAAsB,CAAA;EAMS;;;4BAAR,OAAA,CAAQ,IAAA,CAAK,oBAAA,iBAA8B,MAAA,GAC1D,CAAA,KACR,oBAAA,GAAuB,CAAA;EAAA;;;;;;;;8DAaR,OAAA,CAAQ,IAAA,CAAK,mBAAA,6BAAyC,MAAA;IAE5D,OAAA,EAAS,CAAA;EAAA,IAAM,CAAA,KACxB,mBAAA,CAAoB,CAAA,IAAK,CAAA;EAUgB;;;;;;;sCAAR,OAAA,CAAQ,IAAA,CAAK,iBAAA,CAAkB,CAAA,kBAAY,MAAA,GACpE,CAAA,KACR,iBAAA,CAAkB,CAAA,IAAK,CAAA;EAMF;;;0BAAA,OAAA,CAAQ,IAAA,CAAK,kBAAA,iBAA4B,MAAA,GACtD,CAAA,KACR,kBAAA,GAAqB,CAAA;AAAA;;;iBCzDV,cAAA,iBAA+B,MAAA,SAAe,aAAA,EAAA,CAAgB,UAAA;EAC5E,SAAA;EACA,KAAA,EAAO,YAAA;EACP,MAAA,EAAQ,CAAA;EACR,KAAA;AAAA,IACE,kBAAA,CAAmB,CAAA"}
1
+ {"version":3,"file":"define-settings-BwkJo0rZ.d.mts","names":[],"sources":["../src/builders.ts","../src/define-settings.ts"],"mappings":";;;cAmBa,OAAA;EAOY;;;yBAFA,OAAA,CAAQ,IAAA,CAAK,iBAAA,iBAA2B,MAAA,GACpD,CAAA,KACR,iBAAA,GAAoB,CAAA;EAOZ;;;2BADc,OAAA,CAAQ,IAAA,CAAK,mBAAA,iBAA6B,MAAA,GACxD,CAAA,KACR,mBAAA,GAAsB,CAAA;EAMS;;;4BAAR,OAAA,CAAQ,IAAA,CAAK,oBAAA,iBAA8B,MAAA,GAC1D,CAAA,KACR,oBAAA,GAAuB,CAAA;EAAA;;;;;;;;8DAaR,OAAA,CAAQ,IAAA,CAAK,mBAAA,6BAAyC,MAAA;IAE5D,OAAA,EAAS,CAAA;EAAA,IAAM,CAAA,KACxB,mBAAA,CAAoB,CAAA,IAAK,CAAA;EAUgB;;;;;;;sCAAR,OAAA,CAAQ,IAAA,CAAK,iBAAA,CAAkB,CAAA,kBAAY,MAAA,GACpE,CAAA,KACR,iBAAA,CAAkB,CAAA,IAAK,CAAA;EAMF;;;0BAAA,OAAA,CAAQ,IAAA,CAAK,kBAAA,iBAA4B,MAAA,GACtD,CAAA,KACR,kBAAA,GAAqB,CAAA;AAAA;;;iBCzDV,cAAA,iBAA+B,MAAA,SAAe,aAAA,EAAA,CAAgB,UAAA;EAC5E,SAAA;EACA,KAAA,EAAO,YAAA;EACP,MAAA,EAAQ,CAAA;EACR,KAAA;AAAA,IACE,kBAAA,CAAmB,CAAA"}
package/dist/define.d.mts CHANGED
@@ -1,3 +1,3 @@
1
- import { a as InferSettingValue, c as MediaSettingConfig, d as SettingConfig, f as SettingScope, h as TextSettingConfig, i as GLOBAL_SCOPE_ID, l as NumberSettingConfig, m as SettingsDefinition, n as BooleanSettingConfig, o as InferSettingsMap, p as SettingToTS, r as DEFAULT_LOCALE, s as JsonSettingConfig, t as BaseSettingConfig, u as SelectSettingConfig } from "./types-D1x10wsL.mjs";
2
- import { n as setting, t as defineSettings } from "./define-settings-vAIuyCdH.mjs";
1
+ import { a as InferSettingValue, c as MediaSettingConfig, d as SettingConfig, f as SettingScope, h as TextSettingConfig, i as GLOBAL_SCOPE_ID, l as NumberSettingConfig, m as SettingsDefinition, n as BooleanSettingConfig, o as InferSettingsMap, p as SettingToTS, r as DEFAULT_LOCALE, s as JsonSettingConfig, t as BaseSettingConfig, u as SelectSettingConfig } from "./types-D7gEj6e_.mjs";
2
+ import { n as setting, t as defineSettings } from "./define-settings-BwkJo0rZ.mjs";
3
3
  export { type BaseSettingConfig, type BooleanSettingConfig, DEFAULT_LOCALE, GLOBAL_SCOPE_ID, type InferSettingValue, type InferSettingsMap, type JsonSettingConfig, type MediaSettingConfig, type NumberSettingConfig, type SelectSettingConfig, type SettingConfig, type SettingScope, type SettingToTS, type SettingsDefinition, type TextSettingConfig, defineSettings, setting };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as InferSettingValue, c as MediaSettingConfig, d as SettingConfig, f as SettingScope, h as TextSettingConfig, i as GLOBAL_SCOPE_ID, l as NumberSettingConfig, m as SettingsDefinition, n as BooleanSettingConfig, o as InferSettingsMap, p as SettingToTS, r as DEFAULT_LOCALE, s as JsonSettingConfig, t as BaseSettingConfig, u as SelectSettingConfig } from "./types-D1x10wsL.mjs";
2
- import { n as setting, t as defineSettings } from "./define-settings-vAIuyCdH.mjs";
1
+ import { a as InferSettingValue, c as MediaSettingConfig, d as SettingConfig, f as SettingScope, h as TextSettingConfig, i as GLOBAL_SCOPE_ID, l as NumberSettingConfig, m as SettingsDefinition, n as BooleanSettingConfig, o as InferSettingsMap, p as SettingToTS, r as DEFAULT_LOCALE, s as JsonSettingConfig, t as BaseSettingConfig, u as SelectSettingConfig } from "./types-D7gEj6e_.mjs";
2
+ import { n as setting, t as defineSettings } from "./define-settings-BwkJo0rZ.mjs";
3
3
  import { Logger, ToolkitApp } from "@murumets-ee/core";
4
4
  import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
5
5
 
package/dist/plugin.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { m as SettingsDefinition } from "./types-D1x10wsL.mjs";
1
+ import { m as SettingsDefinition } from "./types-D7gEj6e_.mjs";
2
2
  import { Plugin } from "@murumets-ee/core";
3
3
 
4
4
  //#region src/plugin.d.ts
package/dist/plugin.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{toolkitSettings as e,toolkitViewState as t}from"./schema.mjs";import{settingsRoutes as n}from"@murumets-ee/settings/admin";function r(r){let{definitions:i}=r;return{name:`@murumets-ee/settings`,server:{tables:{toolkitSettings:e,toolkitViewState:t},routes:[n(i)],init:async n=>{let{schemaRegistry:r}=await import(`@murumets-ee/db`);r.has(`toolkit_settings`)||r.register(`toolkit_settings`,e),r.has(`toolkit_view_state`)||r.register(`toolkit_view_state`,t),n.logger.info(`Settings plugin initialized`)}},shared:{settings:i,pluginResources:i.map(e=>({name:`settings:${e.namespace}`,actions:[`view`,`update`]}))}}}export{r as settings};
1
+ import{toolkitSettings as e,toolkitViewState as t}from"./schema.mjs";import{settingsRoutes as n}from"@murumets-ee/settings/admin";function r(r){let{definitions:a}=r;return{name:`@murumets-ee/settings`,server:{tables:{toolkitSettings:e,toolkitViewState:t},routes:[n(a)],init:async n=>{let{schemaRegistry:r}=await import(`@murumets-ee/db`);r.has(`toolkit_settings`)||r.register(`toolkit_settings`,e),r.has(`toolkit_view_state`)||r.register(`toolkit_view_state`,t),n.logger.info(`Settings plugin initialized`)}},shared:{settings:a,pluginResources:a.map(e=>({name:`settings:${e.namespace}`,actions:[`view`,`update`]}))},adminUi:{sidebar:a.filter(e=>!e.hideFromMenu).map(e=>({id:`settings:${e.namespace}`,group:`Settings`,label:e.label??i(e.namespace),href:`/admin/settings/${e.namespace}`,iconName:e.iconName??`settings`}))}}}function i(e){return e.replace(/(^|[-_\s])(\w)/g,(e,t,n)=>(t?` `:``)+n.toUpperCase())}export{r as settings};
2
2
  //# sourceMappingURL=plugin.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.mjs","names":[],"sources":["../src/plugin.ts"],"sourcesContent":["/**\n * Settings plugin — registers the settings Drizzle tables, mounts the\n * settings admin API route, and contributes per-namespace permission\n * resources for the admin shell.\n *\n * @example\n * ```ts\n * import { defineLumiConfig } from '@murumets-ee/core'\n * import { settings } from '@murumets-ee/settings/plugin'\n * import { siteSettings } from './settings/site'\n *\n * export default defineLumiConfig({\n * plugins: [settings({ definitions: [siteSettings] })],\n * })\n * ```\n *\n * Cross-plugin aggregation note: `settingsRoutes` exposes ONE prefix\n * (`settings`) that multiplexes across every namespace. Callers must pass\n * the FULL list of namespaces they want exposed — other plugins should NOT\n * also contribute `shared.settings`, because the merge engine throws on\n * duplicate namespaces and settingsRoutes would not see the other plugin's\n * entries anyway.\n */\n\nimport type { Plugin } from '@murumets-ee/core'\n// Self-reference the `./admin` subpath (not the internal `./admin/routes.js`)\n// so the plugin bundle externalizes `settingsRoutes` instead of inlining\n// its full body. Both subpaths are published; consumers pay for one copy.\nimport { settingsRoutes } from '@murumets-ee/settings/admin'\nimport { toolkitSettings, toolkitViewState } from './schema.js'\nimport type { SettingsDefinition } from './types.js'\n\nexport interface SettingsPluginOptions {\n /**\n * Settings namespace definitions exposed through the admin settings API.\n *\n * Each definition produces:\n * - one permission resource `settings:<namespace>` (view + update)\n * - one dispatch branch in `settingsRoutes` for `GET|PATCH /api/admin/settings/<namespace>`\n * - one `shared.settings` entry (for namespace-collision detection and\n * downstream admin-page rendering)\n *\n * Must be the FULL list — other plugins cannot contribute settings\n * namespaces independently because `settingsRoutes` has a single\n * `settings` prefix that multiplexes across every namespace.\n */\n definitions: SettingsDefinition[]\n}\n\nexport function settings(options: SettingsPluginOptions): Plugin {\n const { definitions } = options\n return {\n name: '@murumets-ee/settings',\n server: {\n tables: { toolkitSettings, toolkitViewState },\n routes: [settingsRoutes(definitions)],\n init: async (app) => {\n const { schemaRegistry } = await import('@murumets-ee/db')\n\n if (!schemaRegistry.has('toolkit_settings')) {\n schemaRegistry.register('toolkit_settings', toolkitSettings)\n }\n if (!schemaRegistry.has('toolkit_view_state')) {\n schemaRegistry.register('toolkit_view_state', toolkitViewState)\n }\n\n app.logger.info('Settings plugin initialized')\n },\n },\n shared: {\n // Full SettingsDefinition objects are structurally assignable to the\n // narrower `PluginSettingsDefinition` (both expose `namespace: string`).\n // The extra fields (`schema`, `scope`, `label`) ride along at runtime\n // so `createAdminShell` can render admin pages from `resolved.settings`.\n settings: definitions,\n pluginResources: definitions.map((def) => ({\n name: `settings:${def.namespace}`,\n actions: ['view', 'update'] as const,\n })),\n },\n }\n}\n"],"mappings":"kIAiDA,SAAgB,EAAS,EAAwC,CAC/D,GAAM,CAAE,eAAgB,EACxB,MAAO,CACL,KAAM,wBACN,OAAQ,CACN,OAAQ,CAAE,kBAAiB,mBAAkB,CAC7C,OAAQ,CAAC,EAAe,EAAY,CAAC,CACrC,KAAM,KAAO,IAAQ,CACnB,GAAM,CAAE,kBAAmB,MAAM,OAAO,mBAEnC,EAAe,IAAI,mBAAmB,EACzC,EAAe,SAAS,mBAAoB,EAAgB,CAEzD,EAAe,IAAI,qBAAqB,EAC3C,EAAe,SAAS,qBAAsB,EAAiB,CAGjE,EAAI,OAAO,KAAK,8BAA8B,EAEjD,CACD,OAAQ,CAKN,SAAU,EACV,gBAAiB,EAAY,IAAK,IAAS,CACzC,KAAM,YAAY,EAAI,YACtB,QAAS,CAAC,OAAQ,SAAS,CAC5B,EAAE,CACJ,CACF"}
1
+ {"version":3,"file":"plugin.mjs","names":[],"sources":["../src/plugin.ts"],"sourcesContent":["/**\n * Settings plugin — registers the settings Drizzle tables, mounts the\n * settings admin API route, and contributes per-namespace permission\n * resources for the admin shell.\n *\n * @example\n * ```ts\n * import { defineLumiConfig } from '@murumets-ee/core'\n * import { settings } from '@murumets-ee/settings/plugin'\n * import { siteSettings } from './settings/site'\n *\n * export default defineLumiConfig({\n * plugins: [settings({ definitions: [siteSettings] })],\n * })\n * ```\n *\n * Cross-plugin aggregation note: `settingsRoutes` exposes ONE prefix\n * (`settings`) that multiplexes across every namespace. Callers must pass\n * the FULL list of namespaces they want exposed — other plugins should NOT\n * also contribute `shared.settings`, because the merge engine throws on\n * duplicate namespaces and settingsRoutes would not see the other plugin's\n * entries anyway.\n */\n\nimport type { Plugin } from '@murumets-ee/core'\n// Self-reference the `./admin` subpath (not the internal `./admin/routes.js`)\n// so the plugin bundle externalizes `settingsRoutes` instead of inlining\n// its full body. Both subpaths are published; consumers pay for one copy.\nimport { settingsRoutes } from '@murumets-ee/settings/admin'\nimport { toolkitSettings, toolkitViewState } from './schema.js'\nimport type { SettingsDefinition } from './types.js'\n\nexport interface SettingsPluginOptions {\n /**\n * Settings namespace definitions exposed through the admin settings API.\n *\n * Each definition produces:\n * - one permission resource `settings:<namespace>` (view + update)\n * - one dispatch branch in `settingsRoutes` for `GET|PATCH /api/admin/settings/<namespace>`\n * - one `shared.settings` entry (for namespace-collision detection and\n * downstream admin-page rendering)\n *\n * Must be the FULL list — other plugins cannot contribute settings\n * namespaces independently because `settingsRoutes` has a single\n * `settings` prefix that multiplexes across every namespace.\n */\n definitions: SettingsDefinition[]\n}\n\nexport function settings(options: SettingsPluginOptions): Plugin {\n const { definitions } = options\n return {\n name: '@murumets-ee/settings',\n server: {\n tables: { toolkitSettings, toolkitViewState },\n routes: [settingsRoutes(definitions)],\n init: async (app) => {\n const { schemaRegistry } = await import('@murumets-ee/db')\n\n if (!schemaRegistry.has('toolkit_settings')) {\n schemaRegistry.register('toolkit_settings', toolkitSettings)\n }\n if (!schemaRegistry.has('toolkit_view_state')) {\n schemaRegistry.register('toolkit_view_state', toolkitViewState)\n }\n\n app.logger.info('Settings plugin initialized')\n },\n },\n shared: {\n // Full SettingsDefinition objects are structurally assignable to the\n // narrower `PluginSettingsDefinition` (both expose `namespace: string`).\n // The extra fields (`schema`, `scope`, `label`) ride along at runtime\n // so `createAdminShell` can render admin pages from `resolved.settings`.\n settings: definitions,\n pluginResources: definitions.map((def) => ({\n name: `settings:${def.namespace}`,\n actions: ['view', 'update'] as const,\n })),\n },\n adminUi: {\n // One sidebar entry per settings namespace. `lumi add` recipes read\n // this flat array as metadata when they patch `lib/admin-pages.ts`\n // with explicit sidebar groups. Namespaces marked `hideFromMenu`\n // (e.g. the permissions store) are excluded.\n sidebar: definitions\n .filter((def) => !def.hideFromMenu)\n .map((def) => ({\n id: `settings:${def.namespace}`,\n group: 'Settings',\n label: def.label ?? toTitleCase(def.namespace),\n href: `/admin/settings/${def.namespace}`,\n iconName: def.iconName ?? 'settings',\n })),\n },\n }\n}\n\nfunction toTitleCase(s: string): string {\n return s.replace(/(^|[-_\\s])(\\w)/g, (_m, pre, ch) => (pre ? ' ' : '') + ch.toUpperCase())\n}\n"],"mappings":"kIAiDA,SAAgB,EAAS,EAAwC,CAC/D,GAAM,CAAE,eAAgB,EACxB,MAAO,CACL,KAAM,wBACN,OAAQ,CACN,OAAQ,CAAE,kBAAiB,mBAAkB,CAC7C,OAAQ,CAAC,EAAe,EAAY,CAAC,CACrC,KAAM,KAAO,IAAQ,CACnB,GAAM,CAAE,kBAAmB,MAAM,OAAO,mBAEnC,EAAe,IAAI,mBAAmB,EACzC,EAAe,SAAS,mBAAoB,EAAgB,CAEzD,EAAe,IAAI,qBAAqB,EAC3C,EAAe,SAAS,qBAAsB,EAAiB,CAGjE,EAAI,OAAO,KAAK,8BAA8B,EAEjD,CACD,OAAQ,CAKN,SAAU,EACV,gBAAiB,EAAY,IAAK,IAAS,CACzC,KAAM,YAAY,EAAI,YACtB,QAAS,CAAC,OAAQ,SAAS,CAC5B,EAAE,CACJ,CACD,QAAS,CAKP,QAAS,EACN,OAAQ,GAAQ,CAAC,EAAI,aAAa,CAClC,IAAK,IAAS,CACb,GAAI,YAAY,EAAI,YACpB,MAAO,WACP,MAAO,EAAI,OAAS,EAAY,EAAI,UAAU,CAC9C,KAAM,mBAAmB,EAAI,YAC7B,SAAU,EAAI,UAAY,WAC3B,EAAE,CACN,CACF,CAGH,SAAS,EAAY,EAAmB,CACtC,OAAO,EAAE,QAAQ,mBAAoB,EAAI,EAAK,KAAQ,EAAM,IAAM,IAAM,EAAG,aAAa,CAAC"}
@@ -73,7 +73,19 @@ interface SettingsDefinition<S extends Record<string, SettingConfig> = Record<st
73
73
  schema: S;
74
74
  /** Human-readable label for admin UI */
75
75
  label?: string;
76
+ /**
77
+ * Lucide icon name (e.g. `'globe'`, `'ticket'`) for the sidebar nav
78
+ * entry this definition produces. Falls back to `'settings'` when
79
+ * unset.
80
+ */
81
+ iconName?: string;
82
+ /**
83
+ * Hide this namespace from the auto-generated sidebar nav. Still
84
+ * renders in the settings API. Useful for internal-only namespaces
85
+ * like the permissions store.
86
+ */
87
+ hideFromMenu?: boolean;
76
88
  }
77
89
  //#endregion
78
90
  export { InferSettingValue as a, MediaSettingConfig as c, SettingConfig as d, SettingScope as f, TextSettingConfig as h, GLOBAL_SCOPE_ID as i, NumberSettingConfig as l, SettingsDefinition as m, BooleanSettingConfig as n, InferSettingsMap as o, SettingToTS as p, DEFAULT_LOCALE as r, JsonSettingConfig as s, BaseSettingConfig as t, SelectSettingConfig as u };
79
- //# sourceMappingURL=types-D1x10wsL.d.mts.map
91
+ //# sourceMappingURL=types-D7gEj6e_.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types-D1x10wsL.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;;UAgBiB,iBAAA;EAMH;EAJZ,KAAA;EAOiC;EALjC,WAAA;EAK0D;EAH1D,YAAA;AAAA;AAAA,UAGe,iBAAA,SAA0B,iBAAA;EACzC,IAAA;EACA,OAAA;EACA,SAAA;EACA,SAAA;EACA,OAAA,GAAU,MAAA;EAED;EAAT,SAAA;AAAA;AAAA,UAGe,mBAAA,SAA4B,iBAAA;EAC3C,IAAA;EACA,OAAA;EACA,GAAA;EACA,GAAA;EACA,OAAA;AAAA;AAAA,UAGe,oBAAA,SAA6B,iBAAA;EAC5C,IAAA;EACA,OAAA;AAAA;AAAA,UAGe,mBAAA,0DACP,iBAAA;EACR,IAAA;EACA,OAAA,EAAS,CAAA;EACT,OAAA,GAAU,CAAA;AAAA;AAAA,UAGK,iBAAA,sBAAuC,iBAAA;EACtD,IAAA;EACA,OAAA,GAAU,CAAA;EAZH;EAcP,MAAA,GAAS,OAAA,CAAQ,CAAA;AAAA;AAAA,UAGF,kBAAA,SAA2B,iBAAA;EAC1C,IAAA;EACA,OAAA;EACA,MAAA;AAAA;AAAA,KAGU,aAAA,GACR,iBAAA,GACA,mBAAA,GACA,oBAAA,GACA,mBAAA,GACA,iBAAA,GACA,kBAAA;;;;;KAUQ,WAAA,WAAsB,aAAA,IAAiB,CAAA,SAAU,iBAAA,YAEzD,CAAA,SAAU,mBAAA,YAER,CAAA,SAAU,oBAAA,aAER,CAAA,SAAU,mBAAA,YACR,CAAA,WACA,CAAA,SAAU,iBAAA,YACR,CAAA,GACA,CAAA,SAAU,kBAAA;;;;;KAYV,iBAAA,WAA4B,aAAA,IAAiB,CAAA;EAAY,OAAA;AAAA,IACjE,WAAA,CAAY,CAAA,IACZ,WAAA,CAAY,CAAA;AAAA,KAMJ,gBAAA,gBAAgC,MAAA,SAAe,aAAA,mBAC7C,MAAA,GAAS,iBAAA,CAAkB,MAAA,CAAO,CAAA;AAAA,KAOpC,YAAA;;cAGC,eAAA;;cAGA,cAAA;AAAA,UAMI,kBAAA,WACL,MAAA,SAAe,aAAA,IAAiB,MAAA,SAAe,aAAA;EA9EzD;EAiFA,SAAA;EA/EA;EAiFA,KAAA,EAAO,YAAA;EAjFU;EAmFjB,MAAA,EAAQ,CAAA;EAnFU;EAqFlB,KAAA;AAAA"}
1
+ {"version":3,"file":"types-D7gEj6e_.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;;UAgBiB,iBAAA;EAMH;EAJZ,KAAA;EAOiC;EALjC,WAAA;EAK0D;EAH1D,YAAA;AAAA;AAAA,UAGe,iBAAA,SAA0B,iBAAA;EACzC,IAAA;EACA,OAAA;EACA,SAAA;EACA,SAAA;EACA,OAAA,GAAU,MAAA;EAED;EAAT,SAAA;AAAA;AAAA,UAGe,mBAAA,SAA4B,iBAAA;EAC3C,IAAA;EACA,OAAA;EACA,GAAA;EACA,GAAA;EACA,OAAA;AAAA;AAAA,UAGe,oBAAA,SAA6B,iBAAA;EAC5C,IAAA;EACA,OAAA;AAAA;AAAA,UAGe,mBAAA,0DACP,iBAAA;EACR,IAAA;EACA,OAAA,EAAS,CAAA;EACT,OAAA,GAAU,CAAA;AAAA;AAAA,UAGK,iBAAA,sBAAuC,iBAAA;EACtD,IAAA;EACA,OAAA,GAAU,CAAA;EAZH;EAcP,MAAA,GAAS,OAAA,CAAQ,CAAA;AAAA;AAAA,UAGF,kBAAA,SAA2B,iBAAA;EAC1C,IAAA;EACA,OAAA;EACA,MAAA;AAAA;AAAA,KAGU,aAAA,GACR,iBAAA,GACA,mBAAA,GACA,oBAAA,GACA,mBAAA,GACA,iBAAA,GACA,kBAAA;;;;;KAUQ,WAAA,WAAsB,aAAA,IAAiB,CAAA,SAAU,iBAAA,YAEzD,CAAA,SAAU,mBAAA,YAER,CAAA,SAAU,oBAAA,aAER,CAAA,SAAU,mBAAA,YACR,CAAA,WACA,CAAA,SAAU,iBAAA,YACR,CAAA,GACA,CAAA,SAAU,kBAAA;;;;;KAYV,iBAAA,WAA4B,aAAA,IAAiB,CAAA;EAAY,OAAA;AAAA,IACjE,WAAA,CAAY,CAAA,IACZ,WAAA,CAAY,CAAA;AAAA,KAMJ,gBAAA,gBAAgC,MAAA,SAAe,aAAA,mBAC7C,MAAA,GAAS,iBAAA,CAAkB,MAAA,CAAO,CAAA;AAAA,KAOpC,YAAA;;cAGC,eAAA;;cAGA,cAAA;AAAA,UAMI,kBAAA,WACL,MAAA,SAAe,aAAA,IAAiB,MAAA,SAAe,aAAA;EA9EzD;EAiFA,SAAA;EA/EA;EAiFA,KAAA,EAAO,YAAA;EAjFU;EAmFjB,MAAA,EAAQ,CAAA;EAnFU;EAqFlB,KAAA;EAlFkC;;;;;EAwFlC,QAAA;EArFA;;;AAGF;;EAwFE,YAAA;AAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@murumets-ee/settings",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "license": "Elastic-2.0",
5
5
  "type": "module",
6
6
  "exports": {
@@ -36,9 +36,9 @@
36
36
  "drizzle-orm": "^0.45.2",
37
37
  "zod": "^3.24.1",
38
38
  "server-only": "^0.0.1",
39
- "@murumets-ee/core": "0.6.0",
40
- "@murumets-ee/db": "0.6.0",
41
- "@murumets-ee/logging": "0.6.0"
39
+ "@murumets-ee/core": "0.7.0",
40
+ "@murumets-ee/db": "0.7.0",
41
+ "@murumets-ee/logging": "0.7.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/node": "^22.10.5",