@fonderie/config 2.0.0 → 3.0.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/README.md +2 -2
- package/brain/signatures.md +3 -3
- package/dist/{index-v2hLmm2m.d.cts → index-B2jOiL09.d.cts} +3 -3
- package/dist/{index-v2hLmm2m.d.ts → index-B2jOiL09.d.ts} +3 -3
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/middlewares/index.cjs.map +1 -1
- package/dist/middlewares/index.d.cts +1 -1
- package/dist/middlewares/index.d.ts +1 -1
- package/dist/middlewares/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -13,10 +13,10 @@ npm install @fonderie/config
|
|
|
13
13
|
|
|
14
14
|
```ts
|
|
15
15
|
import { FonderieApp, defineConfig } from '@fonderie/core';
|
|
16
|
-
import {
|
|
16
|
+
import { ConfigModule } from '@fonderie/config';
|
|
17
17
|
|
|
18
18
|
const app = await new FonderieApp(defineConfig({}))
|
|
19
|
-
.register(new
|
|
19
|
+
.register(new ConfigModule())
|
|
20
20
|
.boot();
|
|
21
21
|
```
|
|
22
22
|
|
package/brain/signatures.md
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
Subpath exports: `@fonderie/config/types`, `@fonderie/config/middleware`, `@fonderie/config/migrations`
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
|
-
new
|
|
10
|
+
new ConfigModule(store: IStoreAdapter, options?: IConfigOptions): ConfigModule
|
|
11
11
|
.name: "@fonderie/config"
|
|
12
12
|
.manager: RemoteConfigManager
|
|
13
13
|
.install(app: IFonderieApp): Promise<void>
|
|
14
14
|
|
|
15
|
-
new RemoteConfigManager(store: IStoreAdapter, options?:
|
|
15
|
+
new RemoteConfigManager(store: IStoreAdapter, options?: IConfigOptions): RemoteConfigManager
|
|
16
16
|
.boot(): Promise<void>
|
|
17
17
|
.stop(): void
|
|
18
18
|
.get<T>(key: string, fallback: T): T
|
|
@@ -48,7 +48,7 @@ interface IConfigSnapshot {
|
|
|
48
48
|
fetchedAt: Date;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
interface
|
|
51
|
+
interface IConfigOptions {
|
|
52
52
|
ttl?: number;
|
|
53
53
|
environment?: string;
|
|
54
54
|
table?: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Middleware } from '@fonderie/core';
|
|
2
2
|
import { IStoreAdapter } from '@fonderie/store';
|
|
3
3
|
|
|
4
|
-
interface
|
|
4
|
+
interface IConfigOptions {
|
|
5
5
|
ttl?: number;
|
|
6
6
|
environment?: string;
|
|
7
7
|
table?: string;
|
|
@@ -14,7 +14,7 @@ declare class RemoteConfigManager {
|
|
|
14
14
|
private environment;
|
|
15
15
|
private ttl;
|
|
16
16
|
private table;
|
|
17
|
-
constructor(store: IStoreAdapter, options?:
|
|
17
|
+
constructor(store: IStoreAdapter, options?: IConfigOptions);
|
|
18
18
|
boot(): Promise<void>;
|
|
19
19
|
stop(): void;
|
|
20
20
|
get<T>(key: string, fallback: T): T;
|
|
@@ -29,4 +29,4 @@ declare function getConfig(ctx: {
|
|
|
29
29
|
meta: Record<string, unknown>;
|
|
30
30
|
}, key: string, fallback?: unknown): unknown;
|
|
31
31
|
|
|
32
|
-
export { CONFIG_MANAGER_KEY as C, type
|
|
32
|
+
export { CONFIG_MANAGER_KEY as C, type IConfigOptions as I, RemoteConfigManager as R, configContextMiddleware as c, getConfig as g };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Middleware } from '@fonderie/core';
|
|
2
2
|
import { IStoreAdapter } from '@fonderie/store';
|
|
3
3
|
|
|
4
|
-
interface
|
|
4
|
+
interface IConfigOptions {
|
|
5
5
|
ttl?: number;
|
|
6
6
|
environment?: string;
|
|
7
7
|
table?: string;
|
|
@@ -14,7 +14,7 @@ declare class RemoteConfigManager {
|
|
|
14
14
|
private environment;
|
|
15
15
|
private ttl;
|
|
16
16
|
private table;
|
|
17
|
-
constructor(store: IStoreAdapter, options?:
|
|
17
|
+
constructor(store: IStoreAdapter, options?: IConfigOptions);
|
|
18
18
|
boot(): Promise<void>;
|
|
19
19
|
stop(): void;
|
|
20
20
|
get<T>(key: string, fallback: T): T;
|
|
@@ -29,4 +29,4 @@ declare function getConfig(ctx: {
|
|
|
29
29
|
meta: Record<string, unknown>;
|
|
30
30
|
}, key: string, fallback?: unknown): unknown;
|
|
31
31
|
|
|
32
|
-
export { CONFIG_MANAGER_KEY as C, type
|
|
32
|
+
export { CONFIG_MANAGER_KEY as C, type IConfigOptions as I, RemoteConfigManager as R, configContextMiddleware as c, getConfig as g };
|
package/dist/index.cjs
CHANGED
|
@@ -21,8 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
CONFIG_MANAGER_KEY: () => CONFIG_MANAGER_KEY,
|
|
24
|
+
ConfigModule: () => ConfigModule,
|
|
24
25
|
RemoteConfigManager: () => RemoteConfigManager,
|
|
25
|
-
RemoteConfigModule: () => RemoteConfigModule,
|
|
26
26
|
configContextMiddleware: () => configContextMiddleware,
|
|
27
27
|
deleteConfigEntry: () => deleteConfigEntry,
|
|
28
28
|
getConfig: () => getConfig,
|
|
@@ -132,7 +132,7 @@ function getConfig(ctx, key, fallback = null) {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
// src/module.ts
|
|
135
|
-
var
|
|
135
|
+
var ConfigModule = class {
|
|
136
136
|
name = "@fonderie/config";
|
|
137
137
|
manager;
|
|
138
138
|
constructor(store, options = {}) {
|
|
@@ -197,8 +197,8 @@ async function deleteConfigEntry(key, environment, store) {
|
|
|
197
197
|
// Annotate the CommonJS export names for ESM import in node:
|
|
198
198
|
0 && (module.exports = {
|
|
199
199
|
CONFIG_MANAGER_KEY,
|
|
200
|
+
ConfigModule,
|
|
200
201
|
RemoteConfigManager,
|
|
201
|
-
RemoteConfigModule,
|
|
202
202
|
configContextMiddleware,
|
|
203
203
|
deleteConfigEntry,
|
|
204
204
|
getConfig,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/manager.ts","../src/middlewares/config-context.ts","../src/module.ts","../src/services/config.ts"],"sourcesContent":["// ── Public API ───────────────────────────────────────────────────\nexport { RemoteConfigModule } from './module';\nexport { RemoteConfigManager, CONFIG_MANAGER_KEY } from './manager';\nexport { configContextMiddleware, getConfig } from './middlewares/config-context';\nexport {\n\tlistConfigEntries,\n\tgetConfigEntry,\n\tsetConfigEntry,\n\tdeleteConfigEntry,\n} from './services/config';\n\nexport type { IConfigEntry, IConfigSnapshot } from './types';\nexport type { IRemoteConfigOptions } from './config';\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IRemoteConfigOptions } from './config';\nimport type { IConfigSnapshot } from './types';\n\nconst FONDERIE_CONFIG_KEY = 'fonderie.config.snapshot';\n\nexport class RemoteConfigManager {\n\tprivate snapshot: IConfigSnapshot | null = null;\n\tprivate interval: ReturnType<typeof setInterval> | null = null;\n\tprivate environment: string;\n\tprivate ttl: number;\n\tprivate table: string;\n\n\tconstructor(\n\t\tprivate store: IStoreAdapter,\n\t\toptions: IRemoteConfigOptions = {},\n\t) {\n\t\tthis.ttl = options.ttl ?? 30_000;\n\t\tthis.environment = options.environment ?? process.env['NODE_ENV'] ?? 'development';\n\t\tthis.table = options.table ?? 'fonderie_config';\n\t}\n\n\tasync boot(): Promise<void> {\n\t\tawait this.refresh();\n\t\tthis.interval = setInterval(() => {\n\t\t\tthis.refresh().catch((err) => console.error('[config] refresh error:', err));\n\t\t}, this.ttl);\n\t}\n\n\tstop(): void {\n\t\tif (this.interval) {\n\t\t\tclearInterval(this.interval);\n\t\t\tthis.interval = null;\n\t\t}\n\t}\n\n\t// Get a single value with a typed fallback\n\tget<T>(key: string, fallback: T): T {\n\t\tif (!this.snapshot) {\n\t\t\treturn fallback;\n\t\t}\n\n\t\tconst value = this.snapshot.entries[key];\n\t\treturn value !== undefined ? (value as T) : fallback;\n\t}\n\n\t// Get all entries as a flat record\n\tall(): Record<string, unknown> {\n\t\treturn this.snapshot?.entries ?? {};\n\t}\n\n\tasync refresh(): Promise<void> {\n\t\ttry {\n\t\t\tconst rows = await this.store.query<{\n\t\t\t\tkey: string;\n\t\t\t\tvalue: string;\n\t\t\t\tenvironment: string;\n\t\t\t}>(\n\t\t\t\t`SELECT key, value, environment\n\t\t\t\t FROM ${this.table}\n\t\t\t\t WHERE (environment = $1 OR environment = 'all')\n\t\t\t\t\tAND active = true`,\n\t\t\t\t[this.environment],\n\t\t\t);\n\n\t\t\tconst entries: Record<string, unknown> = {};\n\n\t\t\t// First pass — load 'all' entries as base\n\t\t\tfor (const row of rows) {\n\t\t\t\tif (row.environment === 'all') {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tentries[row.key] = JSON.parse(row.value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tentries[row.key] = row.value;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Second pass — environment-specific entries override 'all'\n\t\t\tfor (const row of rows) {\n\t\t\t\tif (row.environment !== 'all') {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tentries[row.key] = JSON.parse(row.value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tentries[row.key] = row.value;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.snapshot = {\n\t\t\t\tentries,\n\t\t\t\tfetchedAt: new Date(),\n\t\t\t};\n\t\t} catch (err) {\n\t\t\tconsole.error('[config] failed to refresh:', err);\n\t\t}\n\t}\n\n\tisStale(): boolean {\n\t\tif (!this.snapshot) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn Date.now() - this.snapshot.fetchedAt.getTime() > this.ttl;\n\t}\n}\n\n// Well-known config keys — extend as needed\nexport const CONFIG_MANAGER_KEY = FONDERIE_CONFIG_KEY;\n","import type { Middleware } from '@fonderie/core';\n\nimport type { RemoteConfigManager } from '../manager';\n\nimport { CONFIG_MANAGER_KEY } from '../manager';\n\n// Injects the config manager into ctx.meta\n// so handlers can read config values per-request\n\nexport function configContextMiddleware(manager: RemoteConfigManager): Middleware {\n\treturn async (ctx, next) => {\n\t\tctx.meta[CONFIG_MANAGER_KEY] = manager;\n\t\treturn next();\n\t};\n}\n\n// Helper to read config from ctx inside a handler\nexport function getConfig(\n\tctx: { meta: Record<string, unknown> },\n\tkey: string,\n\tfallback: unknown = null,\n): unknown {\n\tconst manager = ctx.meta[CONFIG_MANAGER_KEY] as RemoteConfigManager | undefined;\n\tif (!manager) {\n\t\treturn fallback;\n\t}\n\n\treturn manager.get(key, fallback);\n}\n","import type { IFonderieModule, IFonderieApp } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IRemoteConfigOptions } from './config';\n\nimport { RemoteConfigManager, CONFIG_MANAGER_KEY } from './manager';\nimport { configContextMiddleware } from './middlewares/config-context';\n\nexport class RemoteConfigModule implements IFonderieModule {\n\treadonly name = '@fonderie/config';\n\treadonly manager: RemoteConfigManager;\n\n\tconstructor(store: IStoreAdapter, options: IRemoteConfigOptions = {}) {\n\t\tthis.manager = new RemoteConfigManager(store, options);\n\t}\n\n\tasync install(app: IFonderieApp): Promise<void> {\n\t\tawait this.manager.boot();\n\t\tapp.use(configContextMiddleware(this.manager));\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IConfigEntry } from '../types';\n\nconst SELECT_ENTRY = `\n\tSELECT\n\t\tkey,\n\t\tvalue,\n\t\tenvironment,\n\t\tdescription,\n\t\tactive,\n\t\tupdated_at AS \"updatedAt\"\n\tFROM fonderie_config`;\n\nexport async function listConfigEntries(\n\tenvironment: string | null,\n\tstore: IStoreAdapter,\n): Promise<IConfigEntry[]> {\n\treturn environment\n\t\t? store.query<IConfigEntry>(\n\t\t\t\t`${SELECT_ENTRY} WHERE (environment = $1 OR environment = 'all') AND active = true ORDER BY key`,\n\t\t\t\t[environment],\n\t\t\t)\n\t\t: store.query<IConfigEntry>(`${SELECT_ENTRY} ORDER BY environment, key`);\n}\n\nexport async function getConfigEntry(\n\tkey: string,\n\tenvironment: string,\n\tstore: IStoreAdapter,\n): Promise<IConfigEntry | null> {\n\tconst [row] = await store.query<IConfigEntry>(\n\t\t`${SELECT_ENTRY} WHERE key = $1 AND environment = $2`,\n\t\t[key, environment],\n\t);\n\treturn row ?? null;\n}\n\nexport async function setConfigEntry(\n\topts: {\n\t\tkey: string;\n\t\tvalue: unknown;\n\t\tenvironment?: string;\n\t\tdescription?: string;\n\t\tactive?: boolean;\n\t},\n\tstore: IStoreAdapter,\n): Promise<IConfigEntry> {\n\tconst env = opts.environment ?? 'all';\n\tconst raw = typeof opts.value === 'string' ? opts.value : JSON.stringify(opts.value);\n\tconst active = opts.active ?? true;\n\n\tconst [row] = await store.query<IConfigEntry>(\n\t\t`INSERT INTO fonderie_config (key, value, environment, description, active)\n\t\tVALUES ($1, $2, $3, $4, $5)\n\t\tON CONFLICT (key, environment) DO UPDATE SET\n\t\t\tvalue = $2,\n\t\t\tdescription = COALESCE($4, fonderie_config.description),\n\t\t\tactive = $5,\n\t\t\tupdated_at = now()\n\t\tRETURNING\n\t\t\tkey, value, environment, description, active,\n\t\t\tupdated_at AS \"updatedAt\"`,\n\t\t[opts.key, raw, env, opts.description ?? null, active],\n\t);\n\tif (!row) throw new Error('Failed to upsert config entry');\n\treturn row;\n}\n\nexport async function deleteConfigEntry(\n\tkey: string,\n\tenvironment: string,\n\tstore: IStoreAdapter,\n): Promise<boolean> {\n\tconst rows = await store.query<{ key: string }>(\n\t\t`DELETE FROM fonderie_config WHERE key = $1 AND environment = $2 RETURNING key`,\n\t\t[key, environment],\n\t);\n\treturn rows.length > 0;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/manager.ts","../src/middlewares/config-context.ts","../src/module.ts","../src/services/config.ts"],"sourcesContent":["// ── Public API ───────────────────────────────────────────────────\nexport { ConfigModule } from './module';\nexport { RemoteConfigManager, CONFIG_MANAGER_KEY } from './manager';\nexport { configContextMiddleware, getConfig } from './middlewares/config-context';\nexport {\n\tlistConfigEntries,\n\tgetConfigEntry,\n\tsetConfigEntry,\n\tdeleteConfigEntry,\n} from './services/config';\n\nexport type { IConfigEntry, IConfigSnapshot } from './types';\nexport type { IConfigOptions } from './config';\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IConfigOptions } from './config';\nimport type { IConfigSnapshot } from './types';\n\nconst FONDERIE_CONFIG_KEY = 'fonderie.config.snapshot';\n\nexport class RemoteConfigManager {\n\tprivate snapshot: IConfigSnapshot | null = null;\n\tprivate interval: ReturnType<typeof setInterval> | null = null;\n\tprivate environment: string;\n\tprivate ttl: number;\n\tprivate table: string;\n\n\tconstructor(\n\t\tprivate store: IStoreAdapter,\n\t\toptions: IConfigOptions = {},\n\t) {\n\t\tthis.ttl = options.ttl ?? 30_000;\n\t\tthis.environment = options.environment ?? process.env['NODE_ENV'] ?? 'development';\n\t\tthis.table = options.table ?? 'fonderie_config';\n\t}\n\n\tasync boot(): Promise<void> {\n\t\tawait this.refresh();\n\t\tthis.interval = setInterval(() => {\n\t\t\tthis.refresh().catch((err) => console.error('[config] refresh error:', err));\n\t\t}, this.ttl);\n\t}\n\n\tstop(): void {\n\t\tif (this.interval) {\n\t\t\tclearInterval(this.interval);\n\t\t\tthis.interval = null;\n\t\t}\n\t}\n\n\t// Get a single value with a typed fallback\n\tget<T>(key: string, fallback: T): T {\n\t\tif (!this.snapshot) {\n\t\t\treturn fallback;\n\t\t}\n\n\t\tconst value = this.snapshot.entries[key];\n\t\treturn value !== undefined ? (value as T) : fallback;\n\t}\n\n\t// Get all entries as a flat record\n\tall(): Record<string, unknown> {\n\t\treturn this.snapshot?.entries ?? {};\n\t}\n\n\tasync refresh(): Promise<void> {\n\t\ttry {\n\t\t\tconst rows = await this.store.query<{\n\t\t\t\tkey: string;\n\t\t\t\tvalue: string;\n\t\t\t\tenvironment: string;\n\t\t\t}>(\n\t\t\t\t`SELECT key, value, environment\n\t\t\t\t FROM ${this.table}\n\t\t\t\t WHERE (environment = $1 OR environment = 'all')\n\t\t\t\t\tAND active = true`,\n\t\t\t\t[this.environment],\n\t\t\t);\n\n\t\t\tconst entries: Record<string, unknown> = {};\n\n\t\t\t// First pass — load 'all' entries as base\n\t\t\tfor (const row of rows) {\n\t\t\t\tif (row.environment === 'all') {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tentries[row.key] = JSON.parse(row.value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tentries[row.key] = row.value;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Second pass — environment-specific entries override 'all'\n\t\t\tfor (const row of rows) {\n\t\t\t\tif (row.environment !== 'all') {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tentries[row.key] = JSON.parse(row.value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tentries[row.key] = row.value;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.snapshot = {\n\t\t\t\tentries,\n\t\t\t\tfetchedAt: new Date(),\n\t\t\t};\n\t\t} catch (err) {\n\t\t\tconsole.error('[config] failed to refresh:', err);\n\t\t}\n\t}\n\n\tisStale(): boolean {\n\t\tif (!this.snapshot) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn Date.now() - this.snapshot.fetchedAt.getTime() > this.ttl;\n\t}\n}\n\n// Well-known config keys — extend as needed\nexport const CONFIG_MANAGER_KEY = FONDERIE_CONFIG_KEY;\n","import type { Middleware } from '@fonderie/core';\n\nimport type { RemoteConfigManager } from '../manager';\n\nimport { CONFIG_MANAGER_KEY } from '../manager';\n\n// Injects the config manager into ctx.meta\n// so handlers can read config values per-request\n\nexport function configContextMiddleware(manager: RemoteConfigManager): Middleware {\n\treturn async (ctx, next) => {\n\t\tctx.meta[CONFIG_MANAGER_KEY] = manager;\n\t\treturn next();\n\t};\n}\n\n// Helper to read config from ctx inside a handler\nexport function getConfig(\n\tctx: { meta: Record<string, unknown> },\n\tkey: string,\n\tfallback: unknown = null,\n): unknown {\n\tconst manager = ctx.meta[CONFIG_MANAGER_KEY] as RemoteConfigManager | undefined;\n\tif (!manager) {\n\t\treturn fallback;\n\t}\n\n\treturn manager.get(key, fallback);\n}\n","import type { IFonderieModule, IFonderieApp } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IConfigOptions } from './config';\n\nimport { RemoteConfigManager, CONFIG_MANAGER_KEY } from './manager';\nimport { configContextMiddleware } from './middlewares/config-context';\n\nexport class ConfigModule implements IFonderieModule {\n\treadonly name = '@fonderie/config';\n\treadonly manager: RemoteConfigManager;\n\n\tconstructor(store: IStoreAdapter, options: IConfigOptions = {}) {\n\t\tthis.manager = new RemoteConfigManager(store, options);\n\t}\n\n\tasync install(app: IFonderieApp): Promise<void> {\n\t\tawait this.manager.boot();\n\t\tapp.use(configContextMiddleware(this.manager));\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IConfigEntry } from '../types';\n\nconst SELECT_ENTRY = `\n\tSELECT\n\t\tkey,\n\t\tvalue,\n\t\tenvironment,\n\t\tdescription,\n\t\tactive,\n\t\tupdated_at AS \"updatedAt\"\n\tFROM fonderie_config`;\n\nexport async function listConfigEntries(\n\tenvironment: string | null,\n\tstore: IStoreAdapter,\n): Promise<IConfigEntry[]> {\n\treturn environment\n\t\t? store.query<IConfigEntry>(\n\t\t\t\t`${SELECT_ENTRY} WHERE (environment = $1 OR environment = 'all') AND active = true ORDER BY key`,\n\t\t\t\t[environment],\n\t\t\t)\n\t\t: store.query<IConfigEntry>(`${SELECT_ENTRY} ORDER BY environment, key`);\n}\n\nexport async function getConfigEntry(\n\tkey: string,\n\tenvironment: string,\n\tstore: IStoreAdapter,\n): Promise<IConfigEntry | null> {\n\tconst [row] = await store.query<IConfigEntry>(\n\t\t`${SELECT_ENTRY} WHERE key = $1 AND environment = $2`,\n\t\t[key, environment],\n\t);\n\treturn row ?? null;\n}\n\nexport async function setConfigEntry(\n\topts: {\n\t\tkey: string;\n\t\tvalue: unknown;\n\t\tenvironment?: string;\n\t\tdescription?: string;\n\t\tactive?: boolean;\n\t},\n\tstore: IStoreAdapter,\n): Promise<IConfigEntry> {\n\tconst env = opts.environment ?? 'all';\n\tconst raw = typeof opts.value === 'string' ? opts.value : JSON.stringify(opts.value);\n\tconst active = opts.active ?? true;\n\n\tconst [row] = await store.query<IConfigEntry>(\n\t\t`INSERT INTO fonderie_config (key, value, environment, description, active)\n\t\tVALUES ($1, $2, $3, $4, $5)\n\t\tON CONFLICT (key, environment) DO UPDATE SET\n\t\t\tvalue = $2,\n\t\t\tdescription = COALESCE($4, fonderie_config.description),\n\t\t\tactive = $5,\n\t\t\tupdated_at = now()\n\t\tRETURNING\n\t\t\tkey, value, environment, description, active,\n\t\t\tupdated_at AS \"updatedAt\"`,\n\t\t[opts.key, raw, env, opts.description ?? null, active],\n\t);\n\tif (!row) throw new Error('Failed to upsert config entry');\n\treturn row;\n}\n\nexport async function deleteConfigEntry(\n\tkey: string,\n\tenvironment: string,\n\tstore: IStoreAdapter,\n): Promise<boolean> {\n\tconst rows = await store.query<{ key: string }>(\n\t\t`DELETE FROM fonderie_config WHERE key = $1 AND environment = $2 RETURNING key`,\n\t\t[key, environment],\n\t);\n\treturn rows.length > 0;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { IFonderieModule, IFonderieApp } from '@fonderie/core';
|
|
2
2
|
import { IStoreAdapter } from '@fonderie/store';
|
|
3
|
-
import { R as RemoteConfigManager, I as
|
|
4
|
-
export { C as CONFIG_MANAGER_KEY, c as configContextMiddleware, g as getConfig } from './index-
|
|
3
|
+
import { R as RemoteConfigManager, I as IConfigOptions } from './index-B2jOiL09.cjs';
|
|
4
|
+
export { C as CONFIG_MANAGER_KEY, c as configContextMiddleware, g as getConfig } from './index-B2jOiL09.cjs';
|
|
5
5
|
import { IConfigEntry } from './types.cjs';
|
|
6
6
|
export { IConfigSnapshot } from './types.cjs';
|
|
7
7
|
|
|
8
|
-
declare class
|
|
8
|
+
declare class ConfigModule implements IFonderieModule {
|
|
9
9
|
readonly name = "@fonderie/config";
|
|
10
10
|
readonly manager: RemoteConfigManager;
|
|
11
|
-
constructor(store: IStoreAdapter, options?:
|
|
11
|
+
constructor(store: IStoreAdapter, options?: IConfigOptions);
|
|
12
12
|
install(app: IFonderieApp): Promise<void>;
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -23,4 +23,4 @@ declare function setConfigEntry(opts: {
|
|
|
23
23
|
}, store: IStoreAdapter): Promise<IConfigEntry>;
|
|
24
24
|
declare function deleteConfigEntry(key: string, environment: string, store: IStoreAdapter): Promise<boolean>;
|
|
25
25
|
|
|
26
|
-
export { IConfigEntry,
|
|
26
|
+
export { ConfigModule, IConfigEntry, IConfigOptions, RemoteConfigManager, deleteConfigEntry, getConfigEntry, listConfigEntries, setConfigEntry };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { IFonderieModule, IFonderieApp } from '@fonderie/core';
|
|
2
2
|
import { IStoreAdapter } from '@fonderie/store';
|
|
3
|
-
import { R as RemoteConfigManager, I as
|
|
4
|
-
export { C as CONFIG_MANAGER_KEY, c as configContextMiddleware, g as getConfig } from './index-
|
|
3
|
+
import { R as RemoteConfigManager, I as IConfigOptions } from './index-B2jOiL09.js';
|
|
4
|
+
export { C as CONFIG_MANAGER_KEY, c as configContextMiddleware, g as getConfig } from './index-B2jOiL09.js';
|
|
5
5
|
import { IConfigEntry } from './types.js';
|
|
6
6
|
export { IConfigSnapshot } from './types.js';
|
|
7
7
|
|
|
8
|
-
declare class
|
|
8
|
+
declare class ConfigModule implements IFonderieModule {
|
|
9
9
|
readonly name = "@fonderie/config";
|
|
10
10
|
readonly manager: RemoteConfigManager;
|
|
11
|
-
constructor(store: IStoreAdapter, options?:
|
|
11
|
+
constructor(store: IStoreAdapter, options?: IConfigOptions);
|
|
12
12
|
install(app: IFonderieApp): Promise<void>;
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -23,4 +23,4 @@ declare function setConfigEntry(opts: {
|
|
|
23
23
|
}, store: IStoreAdapter): Promise<IConfigEntry>;
|
|
24
24
|
declare function deleteConfigEntry(key: string, environment: string, store: IStoreAdapter): Promise<boolean>;
|
|
25
25
|
|
|
26
|
-
export { IConfigEntry,
|
|
26
|
+
export { ConfigModule, IConfigEntry, IConfigOptions, RemoteConfigManager, deleteConfigEntry, getConfigEntry, listConfigEntries, setConfigEntry };
|
package/dist/index.js
CHANGED
|
@@ -98,7 +98,7 @@ function getConfig(ctx, key, fallback = null) {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
// src/module.ts
|
|
101
|
-
var
|
|
101
|
+
var ConfigModule = class {
|
|
102
102
|
name = "@fonderie/config";
|
|
103
103
|
manager;
|
|
104
104
|
constructor(store, options = {}) {
|
|
@@ -162,8 +162,8 @@ async function deleteConfigEntry(key, environment, store) {
|
|
|
162
162
|
}
|
|
163
163
|
export {
|
|
164
164
|
CONFIG_MANAGER_KEY,
|
|
165
|
+
ConfigModule,
|
|
165
166
|
RemoteConfigManager,
|
|
166
|
-
RemoteConfigModule,
|
|
167
167
|
configContextMiddleware,
|
|
168
168
|
deleteConfigEntry,
|
|
169
169
|
getConfig,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/manager.ts","../src/middlewares/config-context.ts","../src/module.ts","../src/services/config.ts"],"sourcesContent":["import type { IStoreAdapter } from '@fonderie/store';\n\nimport type {
|
|
1
|
+
{"version":3,"sources":["../src/manager.ts","../src/middlewares/config-context.ts","../src/module.ts","../src/services/config.ts"],"sourcesContent":["import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IConfigOptions } from './config';\nimport type { IConfigSnapshot } from './types';\n\nconst FONDERIE_CONFIG_KEY = 'fonderie.config.snapshot';\n\nexport class RemoteConfigManager {\n\tprivate snapshot: IConfigSnapshot | null = null;\n\tprivate interval: ReturnType<typeof setInterval> | null = null;\n\tprivate environment: string;\n\tprivate ttl: number;\n\tprivate table: string;\n\n\tconstructor(\n\t\tprivate store: IStoreAdapter,\n\t\toptions: IConfigOptions = {},\n\t) {\n\t\tthis.ttl = options.ttl ?? 30_000;\n\t\tthis.environment = options.environment ?? process.env['NODE_ENV'] ?? 'development';\n\t\tthis.table = options.table ?? 'fonderie_config';\n\t}\n\n\tasync boot(): Promise<void> {\n\t\tawait this.refresh();\n\t\tthis.interval = setInterval(() => {\n\t\t\tthis.refresh().catch((err) => console.error('[config] refresh error:', err));\n\t\t}, this.ttl);\n\t}\n\n\tstop(): void {\n\t\tif (this.interval) {\n\t\t\tclearInterval(this.interval);\n\t\t\tthis.interval = null;\n\t\t}\n\t}\n\n\t// Get a single value with a typed fallback\n\tget<T>(key: string, fallback: T): T {\n\t\tif (!this.snapshot) {\n\t\t\treturn fallback;\n\t\t}\n\n\t\tconst value = this.snapshot.entries[key];\n\t\treturn value !== undefined ? (value as T) : fallback;\n\t}\n\n\t// Get all entries as a flat record\n\tall(): Record<string, unknown> {\n\t\treturn this.snapshot?.entries ?? {};\n\t}\n\n\tasync refresh(): Promise<void> {\n\t\ttry {\n\t\t\tconst rows = await this.store.query<{\n\t\t\t\tkey: string;\n\t\t\t\tvalue: string;\n\t\t\t\tenvironment: string;\n\t\t\t}>(\n\t\t\t\t`SELECT key, value, environment\n\t\t\t\t FROM ${this.table}\n\t\t\t\t WHERE (environment = $1 OR environment = 'all')\n\t\t\t\t\tAND active = true`,\n\t\t\t\t[this.environment],\n\t\t\t);\n\n\t\t\tconst entries: Record<string, unknown> = {};\n\n\t\t\t// First pass — load 'all' entries as base\n\t\t\tfor (const row of rows) {\n\t\t\t\tif (row.environment === 'all') {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tentries[row.key] = JSON.parse(row.value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tentries[row.key] = row.value;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Second pass — environment-specific entries override 'all'\n\t\t\tfor (const row of rows) {\n\t\t\t\tif (row.environment !== 'all') {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tentries[row.key] = JSON.parse(row.value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tentries[row.key] = row.value;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.snapshot = {\n\t\t\t\tentries,\n\t\t\t\tfetchedAt: new Date(),\n\t\t\t};\n\t\t} catch (err) {\n\t\t\tconsole.error('[config] failed to refresh:', err);\n\t\t}\n\t}\n\n\tisStale(): boolean {\n\t\tif (!this.snapshot) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn Date.now() - this.snapshot.fetchedAt.getTime() > this.ttl;\n\t}\n}\n\n// Well-known config keys — extend as needed\nexport const CONFIG_MANAGER_KEY = FONDERIE_CONFIG_KEY;\n","import type { Middleware } from '@fonderie/core';\n\nimport type { RemoteConfigManager } from '../manager';\n\nimport { CONFIG_MANAGER_KEY } from '../manager';\n\n// Injects the config manager into ctx.meta\n// so handlers can read config values per-request\n\nexport function configContextMiddleware(manager: RemoteConfigManager): Middleware {\n\treturn async (ctx, next) => {\n\t\tctx.meta[CONFIG_MANAGER_KEY] = manager;\n\t\treturn next();\n\t};\n}\n\n// Helper to read config from ctx inside a handler\nexport function getConfig(\n\tctx: { meta: Record<string, unknown> },\n\tkey: string,\n\tfallback: unknown = null,\n): unknown {\n\tconst manager = ctx.meta[CONFIG_MANAGER_KEY] as RemoteConfigManager | undefined;\n\tif (!manager) {\n\t\treturn fallback;\n\t}\n\n\treturn manager.get(key, fallback);\n}\n","import type { IFonderieModule, IFonderieApp } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IConfigOptions } from './config';\n\nimport { RemoteConfigManager, CONFIG_MANAGER_KEY } from './manager';\nimport { configContextMiddleware } from './middlewares/config-context';\n\nexport class ConfigModule implements IFonderieModule {\n\treadonly name = '@fonderie/config';\n\treadonly manager: RemoteConfigManager;\n\n\tconstructor(store: IStoreAdapter, options: IConfigOptions = {}) {\n\t\tthis.manager = new RemoteConfigManager(store, options);\n\t}\n\n\tasync install(app: IFonderieApp): Promise<void> {\n\t\tawait this.manager.boot();\n\t\tapp.use(configContextMiddleware(this.manager));\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IConfigEntry } from '../types';\n\nconst SELECT_ENTRY = `\n\tSELECT\n\t\tkey,\n\t\tvalue,\n\t\tenvironment,\n\t\tdescription,\n\t\tactive,\n\t\tupdated_at AS \"updatedAt\"\n\tFROM fonderie_config`;\n\nexport async function listConfigEntries(\n\tenvironment: string | null,\n\tstore: IStoreAdapter,\n): Promise<IConfigEntry[]> {\n\treturn environment\n\t\t? store.query<IConfigEntry>(\n\t\t\t\t`${SELECT_ENTRY} WHERE (environment = $1 OR environment = 'all') AND active = true ORDER BY key`,\n\t\t\t\t[environment],\n\t\t\t)\n\t\t: store.query<IConfigEntry>(`${SELECT_ENTRY} ORDER BY environment, key`);\n}\n\nexport async function getConfigEntry(\n\tkey: string,\n\tenvironment: string,\n\tstore: IStoreAdapter,\n): Promise<IConfigEntry | null> {\n\tconst [row] = await store.query<IConfigEntry>(\n\t\t`${SELECT_ENTRY} WHERE key = $1 AND environment = $2`,\n\t\t[key, environment],\n\t);\n\treturn row ?? null;\n}\n\nexport async function setConfigEntry(\n\topts: {\n\t\tkey: string;\n\t\tvalue: unknown;\n\t\tenvironment?: string;\n\t\tdescription?: string;\n\t\tactive?: boolean;\n\t},\n\tstore: IStoreAdapter,\n): Promise<IConfigEntry> {\n\tconst env = opts.environment ?? 'all';\n\tconst raw = typeof opts.value === 'string' ? opts.value : JSON.stringify(opts.value);\n\tconst active = opts.active ?? true;\n\n\tconst [row] = await store.query<IConfigEntry>(\n\t\t`INSERT INTO fonderie_config (key, value, environment, description, active)\n\t\tVALUES ($1, $2, $3, $4, $5)\n\t\tON CONFLICT (key, environment) DO UPDATE SET\n\t\t\tvalue = $2,\n\t\t\tdescription = COALESCE($4, fonderie_config.description),\n\t\t\tactive = $5,\n\t\t\tupdated_at = now()\n\t\tRETURNING\n\t\t\tkey, value, environment, description, active,\n\t\t\tupdated_at AS \"updatedAt\"`,\n\t\t[opts.key, raw, env, opts.description ?? null, active],\n\t);\n\tif (!row) throw new Error('Failed to upsert config entry');\n\treturn row;\n}\n\nexport async function deleteConfigEntry(\n\tkey: string,\n\tenvironment: string,\n\tstore: IStoreAdapter,\n): Promise<boolean> {\n\tconst rows = await store.query<{ key: string }>(\n\t\t`DELETE FROM fonderie_config WHERE key = $1 AND environment = $2 RETURNING key`,\n\t\t[key, environment],\n\t);\n\treturn rows.length > 0;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/middlewares/index.ts","../../src/manager.ts","../../src/middlewares/config-context.ts"],"sourcesContent":["export { configContextMiddleware, getConfig } from './config-context';\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type {
|
|
1
|
+
{"version":3,"sources":["../../src/middlewares/index.ts","../../src/manager.ts","../../src/middlewares/config-context.ts"],"sourcesContent":["export { configContextMiddleware, getConfig } from './config-context';\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IConfigOptions } from './config';\nimport type { IConfigSnapshot } from './types';\n\nconst FONDERIE_CONFIG_KEY = 'fonderie.config.snapshot';\n\nexport class RemoteConfigManager {\n\tprivate snapshot: IConfigSnapshot | null = null;\n\tprivate interval: ReturnType<typeof setInterval> | null = null;\n\tprivate environment: string;\n\tprivate ttl: number;\n\tprivate table: string;\n\n\tconstructor(\n\t\tprivate store: IStoreAdapter,\n\t\toptions: IConfigOptions = {},\n\t) {\n\t\tthis.ttl = options.ttl ?? 30_000;\n\t\tthis.environment = options.environment ?? process.env['NODE_ENV'] ?? 'development';\n\t\tthis.table = options.table ?? 'fonderie_config';\n\t}\n\n\tasync boot(): Promise<void> {\n\t\tawait this.refresh();\n\t\tthis.interval = setInterval(() => {\n\t\t\tthis.refresh().catch((err) => console.error('[config] refresh error:', err));\n\t\t}, this.ttl);\n\t}\n\n\tstop(): void {\n\t\tif (this.interval) {\n\t\t\tclearInterval(this.interval);\n\t\t\tthis.interval = null;\n\t\t}\n\t}\n\n\t// Get a single value with a typed fallback\n\tget<T>(key: string, fallback: T): T {\n\t\tif (!this.snapshot) {\n\t\t\treturn fallback;\n\t\t}\n\n\t\tconst value = this.snapshot.entries[key];\n\t\treturn value !== undefined ? (value as T) : fallback;\n\t}\n\n\t// Get all entries as a flat record\n\tall(): Record<string, unknown> {\n\t\treturn this.snapshot?.entries ?? {};\n\t}\n\n\tasync refresh(): Promise<void> {\n\t\ttry {\n\t\t\tconst rows = await this.store.query<{\n\t\t\t\tkey: string;\n\t\t\t\tvalue: string;\n\t\t\t\tenvironment: string;\n\t\t\t}>(\n\t\t\t\t`SELECT key, value, environment\n\t\t\t\t FROM ${this.table}\n\t\t\t\t WHERE (environment = $1 OR environment = 'all')\n\t\t\t\t\tAND active = true`,\n\t\t\t\t[this.environment],\n\t\t\t);\n\n\t\t\tconst entries: Record<string, unknown> = {};\n\n\t\t\t// First pass — load 'all' entries as base\n\t\t\tfor (const row of rows) {\n\t\t\t\tif (row.environment === 'all') {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tentries[row.key] = JSON.parse(row.value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tentries[row.key] = row.value;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Second pass — environment-specific entries override 'all'\n\t\t\tfor (const row of rows) {\n\t\t\t\tif (row.environment !== 'all') {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tentries[row.key] = JSON.parse(row.value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tentries[row.key] = row.value;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.snapshot = {\n\t\t\t\tentries,\n\t\t\t\tfetchedAt: new Date(),\n\t\t\t};\n\t\t} catch (err) {\n\t\t\tconsole.error('[config] failed to refresh:', err);\n\t\t}\n\t}\n\n\tisStale(): boolean {\n\t\tif (!this.snapshot) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn Date.now() - this.snapshot.fetchedAt.getTime() > this.ttl;\n\t}\n}\n\n// Well-known config keys — extend as needed\nexport const CONFIG_MANAGER_KEY = FONDERIE_CONFIG_KEY;\n","import type { Middleware } from '@fonderie/core';\n\nimport type { RemoteConfigManager } from '../manager';\n\nimport { CONFIG_MANAGER_KEY } from '../manager';\n\n// Injects the config manager into ctx.meta\n// so handlers can read config values per-request\n\nexport function configContextMiddleware(manager: RemoteConfigManager): Middleware {\n\treturn async (ctx, next) => {\n\t\tctx.meta[CONFIG_MANAGER_KEY] = manager;\n\t\treturn next();\n\t};\n}\n\n// Helper to read config from ctx inside a handler\nexport function getConfig(\n\tctx: { meta: Record<string, unknown> },\n\tkey: string,\n\tfallback: unknown = null,\n): unknown {\n\tconst manager = ctx.meta[CONFIG_MANAGER_KEY] as RemoteConfigManager | undefined;\n\tif (!manager) {\n\t\treturn fallback;\n\t}\n\n\treturn manager.get(key, fallback);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACKA,IAAM,sBAAsB;AAwGrB,IAAM,qBAAqB;;;ACpG3B,SAAS,wBAAwB,SAA0C;AACjF,SAAO,OAAO,KAAK,SAAS;AAC3B,QAAI,KAAK,kBAAkB,IAAI;AAC/B,WAAO,KAAK;AAAA,EACb;AACD;AAGO,SAAS,UACf,KACA,KACA,WAAoB,MACV;AACV,QAAM,UAAU,IAAI,KAAK,kBAAkB;AAC3C,MAAI,CAAC,SAAS;AACb,WAAO;AAAA,EACR;AAEA,SAAO,QAAQ,IAAI,KAAK,QAAQ;AACjC;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/manager.ts","../../src/middlewares/config-context.ts"],"sourcesContent":["import type { IStoreAdapter } from '@fonderie/store';\n\nimport type {
|
|
1
|
+
{"version":3,"sources":["../../src/manager.ts","../../src/middlewares/config-context.ts"],"sourcesContent":["import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IConfigOptions } from './config';\nimport type { IConfigSnapshot } from './types';\n\nconst FONDERIE_CONFIG_KEY = 'fonderie.config.snapshot';\n\nexport class RemoteConfigManager {\n\tprivate snapshot: IConfigSnapshot | null = null;\n\tprivate interval: ReturnType<typeof setInterval> | null = null;\n\tprivate environment: string;\n\tprivate ttl: number;\n\tprivate table: string;\n\n\tconstructor(\n\t\tprivate store: IStoreAdapter,\n\t\toptions: IConfigOptions = {},\n\t) {\n\t\tthis.ttl = options.ttl ?? 30_000;\n\t\tthis.environment = options.environment ?? process.env['NODE_ENV'] ?? 'development';\n\t\tthis.table = options.table ?? 'fonderie_config';\n\t}\n\n\tasync boot(): Promise<void> {\n\t\tawait this.refresh();\n\t\tthis.interval = setInterval(() => {\n\t\t\tthis.refresh().catch((err) => console.error('[config] refresh error:', err));\n\t\t}, this.ttl);\n\t}\n\n\tstop(): void {\n\t\tif (this.interval) {\n\t\t\tclearInterval(this.interval);\n\t\t\tthis.interval = null;\n\t\t}\n\t}\n\n\t// Get a single value with a typed fallback\n\tget<T>(key: string, fallback: T): T {\n\t\tif (!this.snapshot) {\n\t\t\treturn fallback;\n\t\t}\n\n\t\tconst value = this.snapshot.entries[key];\n\t\treturn value !== undefined ? (value as T) : fallback;\n\t}\n\n\t// Get all entries as a flat record\n\tall(): Record<string, unknown> {\n\t\treturn this.snapshot?.entries ?? {};\n\t}\n\n\tasync refresh(): Promise<void> {\n\t\ttry {\n\t\t\tconst rows = await this.store.query<{\n\t\t\t\tkey: string;\n\t\t\t\tvalue: string;\n\t\t\t\tenvironment: string;\n\t\t\t}>(\n\t\t\t\t`SELECT key, value, environment\n\t\t\t\t FROM ${this.table}\n\t\t\t\t WHERE (environment = $1 OR environment = 'all')\n\t\t\t\t\tAND active = true`,\n\t\t\t\t[this.environment],\n\t\t\t);\n\n\t\t\tconst entries: Record<string, unknown> = {};\n\n\t\t\t// First pass — load 'all' entries as base\n\t\t\tfor (const row of rows) {\n\t\t\t\tif (row.environment === 'all') {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tentries[row.key] = JSON.parse(row.value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tentries[row.key] = row.value;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Second pass — environment-specific entries override 'all'\n\t\t\tfor (const row of rows) {\n\t\t\t\tif (row.environment !== 'all') {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tentries[row.key] = JSON.parse(row.value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tentries[row.key] = row.value;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.snapshot = {\n\t\t\t\tentries,\n\t\t\t\tfetchedAt: new Date(),\n\t\t\t};\n\t\t} catch (err) {\n\t\t\tconsole.error('[config] failed to refresh:', err);\n\t\t}\n\t}\n\n\tisStale(): boolean {\n\t\tif (!this.snapshot) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn Date.now() - this.snapshot.fetchedAt.getTime() > this.ttl;\n\t}\n}\n\n// Well-known config keys — extend as needed\nexport const CONFIG_MANAGER_KEY = FONDERIE_CONFIG_KEY;\n","import type { Middleware } from '@fonderie/core';\n\nimport type { RemoteConfigManager } from '../manager';\n\nimport { CONFIG_MANAGER_KEY } from '../manager';\n\n// Injects the config manager into ctx.meta\n// so handlers can read config values per-request\n\nexport function configContextMiddleware(manager: RemoteConfigManager): Middleware {\n\treturn async (ctx, next) => {\n\t\tctx.meta[CONFIG_MANAGER_KEY] = manager;\n\t\treturn next();\n\t};\n}\n\n// Helper to read config from ctx inside a handler\nexport function getConfig(\n\tctx: { meta: Record<string, unknown> },\n\tkey: string,\n\tfallback: unknown = null,\n): unknown {\n\tconst manager = ctx.meta[CONFIG_MANAGER_KEY] as RemoteConfigManager | undefined;\n\tif (!manager) {\n\t\treturn fallback;\n\t}\n\n\treturn manager.get(key, fallback);\n}\n"],"mappings":";AAKA,IAAM,sBAAsB;AAwGrB,IAAM,qBAAqB;;;ACpG3B,SAAS,wBAAwB,SAA0C;AACjF,SAAO,OAAO,KAAK,SAAS;AAC3B,QAAI,KAAK,kBAAkB,IAAI;AAC/B,WAAO,KAAK;AAAA,EACb;AACD;AAGO,SAAS,UACf,KACA,KACA,WAAoB,MACV;AACV,QAAM,UAAU,IAAI,KAAK,kBAAkB;AAC3C,MAAI,CAAC,SAAS;AACb,WAAO;AAAA,EACR;AAEA,SAAO,QAAQ,IAAI,KAAK,QAAQ;AACjC;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fonderie/config",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "DB-backed feature flags and remote config — per-environment overrides, TTL-based hot reload, and a typed getConfig helper usable inside any middleware.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fonderie-js",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"check": "biome check --write src"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@fonderie/core": "^0.
|
|
52
|
+
"@fonderie/core": "^0.3.0",
|
|
53
53
|
"@fonderie/store": "^0.1.1"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|