@fonderie/config 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fonderie, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # @fonderie/config
2
+
3
+ DB-backed feature flags and remote config: per-environment overrides and
4
+ TTL-cached snapshots, changeable at runtime without a deploy.
5
+
6
+ ## Install
7
+
8
+ ```sh
9
+ npm install @fonderie/config
10
+ ```
11
+
12
+ ## Use
13
+
14
+ ```ts
15
+ import { FonderieApp, defineConfig } from '@fonderie/core';
16
+ import { RemoteConfigModule } from '@fonderie/config';
17
+
18
+ const app = await new FonderieApp(defineConfig({}))
19
+ .register(new RemoteConfigModule())
20
+ .boot();
21
+ ```
22
+
23
+ ```ts
24
+ import { configContextMiddleware, getConfig } from '@fonderie/config';
25
+ ```
26
+
27
+ CRUD services (`listConfigEntries`, `setConfigEntry`, …) are exported for
28
+ building an admin surface on top.
29
+
30
+ ## Why this exists
31
+
32
+ You've shipped this plumbing before — auth, teams, billing, messaging —
33
+ and the next project will ask for it again. Fonderie packages it once:
34
+ plain TypeScript modules for
35
+ [`@fonderie/core`](https://github.com/fonderie-js/sdk/tree/main/packages/core),
36
+ PostgreSQL-backed, self-hosted, MIT. No external control plane, no
37
+ per-seat anything. Register the modules you need; skip the ones you don't.
38
+
39
+ **This package owns** how behavior changes without a deploy. Feature flags and remote
40
+ config read live by the other bricks.
41
+
42
+ Browse the whole set at
43
+ [fonderie-js/sdk](https://github.com/fonderie-js/sdk) · follow
44
+ [@fonderiejs](https://x.com/fonderiejs)
45
+
46
+ ## License
47
+
48
+ MIT © Fonderie, Inc.
@@ -0,0 +1,32 @@
1
+ import { Middleware } from '@fonderie/core';
2
+ import { IStoreAdapter } from '@fonderie/store';
3
+
4
+ interface IRemoteConfigOptions {
5
+ ttl?: number;
6
+ environment?: string;
7
+ table?: string;
8
+ }
9
+
10
+ declare class RemoteConfigManager {
11
+ private store;
12
+ private snapshot;
13
+ private interval;
14
+ private environment;
15
+ private ttl;
16
+ private table;
17
+ constructor(store: IStoreAdapter, options?: IRemoteConfigOptions);
18
+ boot(): Promise<void>;
19
+ stop(): void;
20
+ get<T>(key: string, fallback: T): T;
21
+ all(): Record<string, unknown>;
22
+ refresh(): Promise<void>;
23
+ isStale(): boolean;
24
+ }
25
+ declare const CONFIG_MANAGER_KEY = "fonderie.config.snapshot";
26
+
27
+ declare function configContextMiddleware(manager: RemoteConfigManager): Middleware;
28
+ declare function getConfig(ctx: {
29
+ meta: Record<string, unknown>;
30
+ }, key: string, fallback?: unknown): unknown;
31
+
32
+ export { CONFIG_MANAGER_KEY as C, type IRemoteConfigOptions as I, RemoteConfigManager as R, configContextMiddleware as c, getConfig as g };
@@ -0,0 +1,32 @@
1
+ import { Middleware } from '@fonderie/core';
2
+ import { IStoreAdapter } from '@fonderie/store';
3
+
4
+ interface IRemoteConfigOptions {
5
+ ttl?: number;
6
+ environment?: string;
7
+ table?: string;
8
+ }
9
+
10
+ declare class RemoteConfigManager {
11
+ private store;
12
+ private snapshot;
13
+ private interval;
14
+ private environment;
15
+ private ttl;
16
+ private table;
17
+ constructor(store: IStoreAdapter, options?: IRemoteConfigOptions);
18
+ boot(): Promise<void>;
19
+ stop(): void;
20
+ get<T>(key: string, fallback: T): T;
21
+ all(): Record<string, unknown>;
22
+ refresh(): Promise<void>;
23
+ isStale(): boolean;
24
+ }
25
+ declare const CONFIG_MANAGER_KEY = "fonderie.config.snapshot";
26
+
27
+ declare function configContextMiddleware(manager: RemoteConfigManager): Middleware;
28
+ declare function getConfig(ctx: {
29
+ meta: Record<string, unknown>;
30
+ }, key: string, fallback?: unknown): unknown;
31
+
32
+ export { CONFIG_MANAGER_KEY as C, type IRemoteConfigOptions as I, RemoteConfigManager as R, configContextMiddleware as c, getConfig as g };
package/dist/index.cjs ADDED
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ CONFIG_MANAGER_KEY: () => CONFIG_MANAGER_KEY,
24
+ RemoteConfigManager: () => RemoteConfigManager,
25
+ RemoteConfigModule: () => RemoteConfigModule,
26
+ configContextMiddleware: () => configContextMiddleware,
27
+ deleteConfigEntry: () => deleteConfigEntry,
28
+ getConfig: () => getConfig,
29
+ getConfigEntry: () => getConfigEntry,
30
+ listConfigEntries: () => listConfigEntries,
31
+ setConfigEntry: () => setConfigEntry
32
+ });
33
+ module.exports = __toCommonJS(index_exports);
34
+
35
+ // src/manager.ts
36
+ var FONDERIE_CONFIG_KEY = "fonderie.config.snapshot";
37
+ var RemoteConfigManager = class {
38
+ constructor(store, options = {}) {
39
+ this.store = store;
40
+ this.ttl = options.ttl ?? 3e4;
41
+ this.environment = options.environment ?? process.env["NODE_ENV"] ?? "development";
42
+ this.table = options.table ?? "fonderie_config";
43
+ }
44
+ store;
45
+ snapshot = null;
46
+ interval = null;
47
+ environment;
48
+ ttl;
49
+ table;
50
+ async boot() {
51
+ await this.refresh();
52
+ this.interval = setInterval(() => {
53
+ this.refresh().catch((err) => console.error("[config] refresh error:", err));
54
+ }, this.ttl);
55
+ }
56
+ stop() {
57
+ if (this.interval) {
58
+ clearInterval(this.interval);
59
+ this.interval = null;
60
+ }
61
+ }
62
+ // Get a single value with a typed fallback
63
+ get(key, fallback) {
64
+ if (!this.snapshot) {
65
+ return fallback;
66
+ }
67
+ const value = this.snapshot.entries[key];
68
+ return value !== void 0 ? value : fallback;
69
+ }
70
+ // Get all entries as a flat record
71
+ all() {
72
+ return this.snapshot?.entries ?? {};
73
+ }
74
+ async refresh() {
75
+ try {
76
+ const rows = await this.store.query(
77
+ `SELECT key, value, environment
78
+ FROM ${this.table}
79
+ WHERE (environment = $1 OR environment = 'all')
80
+ AND active = true`,
81
+ [this.environment]
82
+ );
83
+ const entries = {};
84
+ for (const row of rows) {
85
+ if (row.environment === "all") {
86
+ try {
87
+ entries[row.key] = JSON.parse(row.value);
88
+ } catch {
89
+ entries[row.key] = row.value;
90
+ }
91
+ }
92
+ }
93
+ for (const row of rows) {
94
+ if (row.environment !== "all") {
95
+ try {
96
+ entries[row.key] = JSON.parse(row.value);
97
+ } catch {
98
+ entries[row.key] = row.value;
99
+ }
100
+ }
101
+ }
102
+ this.snapshot = {
103
+ entries,
104
+ fetchedAt: /* @__PURE__ */ new Date()
105
+ };
106
+ } catch (err) {
107
+ console.error("[config] failed to refresh:", err);
108
+ }
109
+ }
110
+ isStale() {
111
+ if (!this.snapshot) {
112
+ return true;
113
+ }
114
+ return Date.now() - this.snapshot.fetchedAt.getTime() > this.ttl;
115
+ }
116
+ };
117
+ var CONFIG_MANAGER_KEY = FONDERIE_CONFIG_KEY;
118
+
119
+ // src/middlewares/config-context.ts
120
+ function configContextMiddleware(manager) {
121
+ return async (ctx, next) => {
122
+ ctx.meta[CONFIG_MANAGER_KEY] = manager;
123
+ return next();
124
+ };
125
+ }
126
+ function getConfig(ctx, key, fallback = null) {
127
+ const manager = ctx.meta[CONFIG_MANAGER_KEY];
128
+ if (!manager) {
129
+ return fallback;
130
+ }
131
+ return manager.get(key, fallback);
132
+ }
133
+
134
+ // src/module.ts
135
+ var RemoteConfigModule = class {
136
+ name = "@fonderie/config";
137
+ manager;
138
+ constructor(store, options = {}) {
139
+ this.manager = new RemoteConfigManager(store, options);
140
+ }
141
+ async install(app) {
142
+ await this.manager.boot();
143
+ app.use(configContextMiddleware(this.manager));
144
+ }
145
+ };
146
+
147
+ // src/services/config.ts
148
+ var SELECT_ENTRY = `
149
+ SELECT
150
+ key,
151
+ value,
152
+ environment,
153
+ description,
154
+ active,
155
+ updated_at AS "updatedAt"
156
+ FROM fonderie_config`;
157
+ async function listConfigEntries(environment, store) {
158
+ return environment ? store.query(
159
+ `${SELECT_ENTRY} WHERE (environment = $1 OR environment = 'all') AND active = true ORDER BY key`,
160
+ [environment]
161
+ ) : store.query(`${SELECT_ENTRY} ORDER BY environment, key`);
162
+ }
163
+ async function getConfigEntry(key, environment, store) {
164
+ const [row] = await store.query(
165
+ `${SELECT_ENTRY} WHERE key = $1 AND environment = $2`,
166
+ [key, environment]
167
+ );
168
+ return row ?? null;
169
+ }
170
+ async function setConfigEntry(opts, store) {
171
+ const env = opts.environment ?? "all";
172
+ const raw = typeof opts.value === "string" ? opts.value : JSON.stringify(opts.value);
173
+ const active = opts.active ?? true;
174
+ const [row] = await store.query(
175
+ `INSERT INTO fonderie_config (key, value, environment, description, active)
176
+ VALUES ($1, $2, $3, $4, $5)
177
+ ON CONFLICT (key, environment) DO UPDATE SET
178
+ value = $2,
179
+ description = COALESCE($4, fonderie_config.description),
180
+ active = $5,
181
+ updated_at = now()
182
+ RETURNING
183
+ key, value, environment, description, active,
184
+ updated_at AS "updatedAt"`,
185
+ [opts.key, raw, env, opts.description ?? null, active]
186
+ );
187
+ if (!row) throw new Error("Failed to upsert config entry");
188
+ return row;
189
+ }
190
+ async function deleteConfigEntry(key, environment, store) {
191
+ const rows = await store.query(
192
+ `DELETE FROM fonderie_config WHERE key = $1 AND environment = $2 RETURNING key`,
193
+ [key, environment]
194
+ );
195
+ return rows.length > 0;
196
+ }
197
+ // Annotate the CommonJS export names for ESM import in node:
198
+ 0 && (module.exports = {
199
+ CONFIG_MANAGER_KEY,
200
+ RemoteConfigManager,
201
+ RemoteConfigModule,
202
+ configContextMiddleware,
203
+ deleteConfigEntry,
204
+ getConfig,
205
+ getConfigEntry,
206
+ listConfigEntries,
207
+ setConfigEntry
208
+ });
209
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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":[]}
@@ -0,0 +1,26 @@
1
+ import { IFonderieModule, IFonderieApp } from '@fonderie/core';
2
+ import { IStoreAdapter } from '@fonderie/store';
3
+ import { R as RemoteConfigManager, I as IRemoteConfigOptions } from './index-v2hLmm2m.cjs';
4
+ export { C as CONFIG_MANAGER_KEY, c as configContextMiddleware, g as getConfig } from './index-v2hLmm2m.cjs';
5
+ import { IConfigEntry } from './types.cjs';
6
+ export { IConfigSnapshot } from './types.cjs';
7
+
8
+ declare class RemoteConfigModule implements IFonderieModule {
9
+ readonly name = "@fonderie/config";
10
+ readonly manager: RemoteConfigManager;
11
+ constructor(store: IStoreAdapter, options?: IRemoteConfigOptions);
12
+ install(app: IFonderieApp): Promise<void>;
13
+ }
14
+
15
+ declare function listConfigEntries(environment: string | null, store: IStoreAdapter): Promise<IConfigEntry[]>;
16
+ declare function getConfigEntry(key: string, environment: string, store: IStoreAdapter): Promise<IConfigEntry | null>;
17
+ declare function setConfigEntry(opts: {
18
+ key: string;
19
+ value: unknown;
20
+ environment?: string;
21
+ description?: string;
22
+ active?: boolean;
23
+ }, store: IStoreAdapter): Promise<IConfigEntry>;
24
+ declare function deleteConfigEntry(key: string, environment: string, store: IStoreAdapter): Promise<boolean>;
25
+
26
+ export { IConfigEntry, IRemoteConfigOptions, RemoteConfigManager, RemoteConfigModule, deleteConfigEntry, getConfigEntry, listConfigEntries, setConfigEntry };
@@ -0,0 +1,26 @@
1
+ import { IFonderieModule, IFonderieApp } from '@fonderie/core';
2
+ import { IStoreAdapter } from '@fonderie/store';
3
+ import { R as RemoteConfigManager, I as IRemoteConfigOptions } from './index-v2hLmm2m.js';
4
+ export { C as CONFIG_MANAGER_KEY, c as configContextMiddleware, g as getConfig } from './index-v2hLmm2m.js';
5
+ import { IConfigEntry } from './types.js';
6
+ export { IConfigSnapshot } from './types.js';
7
+
8
+ declare class RemoteConfigModule implements IFonderieModule {
9
+ readonly name = "@fonderie/config";
10
+ readonly manager: RemoteConfigManager;
11
+ constructor(store: IStoreAdapter, options?: IRemoteConfigOptions);
12
+ install(app: IFonderieApp): Promise<void>;
13
+ }
14
+
15
+ declare function listConfigEntries(environment: string | null, store: IStoreAdapter): Promise<IConfigEntry[]>;
16
+ declare function getConfigEntry(key: string, environment: string, store: IStoreAdapter): Promise<IConfigEntry | null>;
17
+ declare function setConfigEntry(opts: {
18
+ key: string;
19
+ value: unknown;
20
+ environment?: string;
21
+ description?: string;
22
+ active?: boolean;
23
+ }, store: IStoreAdapter): Promise<IConfigEntry>;
24
+ declare function deleteConfigEntry(key: string, environment: string, store: IStoreAdapter): Promise<boolean>;
25
+
26
+ export { IConfigEntry, IRemoteConfigOptions, RemoteConfigManager, RemoteConfigModule, deleteConfigEntry, getConfigEntry, listConfigEntries, setConfigEntry };
package/dist/index.js ADDED
@@ -0,0 +1,174 @@
1
+ // src/manager.ts
2
+ var FONDERIE_CONFIG_KEY = "fonderie.config.snapshot";
3
+ var RemoteConfigManager = class {
4
+ constructor(store, options = {}) {
5
+ this.store = store;
6
+ this.ttl = options.ttl ?? 3e4;
7
+ this.environment = options.environment ?? process.env["NODE_ENV"] ?? "development";
8
+ this.table = options.table ?? "fonderie_config";
9
+ }
10
+ store;
11
+ snapshot = null;
12
+ interval = null;
13
+ environment;
14
+ ttl;
15
+ table;
16
+ async boot() {
17
+ await this.refresh();
18
+ this.interval = setInterval(() => {
19
+ this.refresh().catch((err) => console.error("[config] refresh error:", err));
20
+ }, this.ttl);
21
+ }
22
+ stop() {
23
+ if (this.interval) {
24
+ clearInterval(this.interval);
25
+ this.interval = null;
26
+ }
27
+ }
28
+ // Get a single value with a typed fallback
29
+ get(key, fallback) {
30
+ if (!this.snapshot) {
31
+ return fallback;
32
+ }
33
+ const value = this.snapshot.entries[key];
34
+ return value !== void 0 ? value : fallback;
35
+ }
36
+ // Get all entries as a flat record
37
+ all() {
38
+ return this.snapshot?.entries ?? {};
39
+ }
40
+ async refresh() {
41
+ try {
42
+ const rows = await this.store.query(
43
+ `SELECT key, value, environment
44
+ FROM ${this.table}
45
+ WHERE (environment = $1 OR environment = 'all')
46
+ AND active = true`,
47
+ [this.environment]
48
+ );
49
+ const entries = {};
50
+ for (const row of rows) {
51
+ if (row.environment === "all") {
52
+ try {
53
+ entries[row.key] = JSON.parse(row.value);
54
+ } catch {
55
+ entries[row.key] = row.value;
56
+ }
57
+ }
58
+ }
59
+ for (const row of rows) {
60
+ if (row.environment !== "all") {
61
+ try {
62
+ entries[row.key] = JSON.parse(row.value);
63
+ } catch {
64
+ entries[row.key] = row.value;
65
+ }
66
+ }
67
+ }
68
+ this.snapshot = {
69
+ entries,
70
+ fetchedAt: /* @__PURE__ */ new Date()
71
+ };
72
+ } catch (err) {
73
+ console.error("[config] failed to refresh:", err);
74
+ }
75
+ }
76
+ isStale() {
77
+ if (!this.snapshot) {
78
+ return true;
79
+ }
80
+ return Date.now() - this.snapshot.fetchedAt.getTime() > this.ttl;
81
+ }
82
+ };
83
+ var CONFIG_MANAGER_KEY = FONDERIE_CONFIG_KEY;
84
+
85
+ // src/middlewares/config-context.ts
86
+ function configContextMiddleware(manager) {
87
+ return async (ctx, next) => {
88
+ ctx.meta[CONFIG_MANAGER_KEY] = manager;
89
+ return next();
90
+ };
91
+ }
92
+ function getConfig(ctx, key, fallback = null) {
93
+ const manager = ctx.meta[CONFIG_MANAGER_KEY];
94
+ if (!manager) {
95
+ return fallback;
96
+ }
97
+ return manager.get(key, fallback);
98
+ }
99
+
100
+ // src/module.ts
101
+ var RemoteConfigModule = class {
102
+ name = "@fonderie/config";
103
+ manager;
104
+ constructor(store, options = {}) {
105
+ this.manager = new RemoteConfigManager(store, options);
106
+ }
107
+ async install(app) {
108
+ await this.manager.boot();
109
+ app.use(configContextMiddleware(this.manager));
110
+ }
111
+ };
112
+
113
+ // src/services/config.ts
114
+ var SELECT_ENTRY = `
115
+ SELECT
116
+ key,
117
+ value,
118
+ environment,
119
+ description,
120
+ active,
121
+ updated_at AS "updatedAt"
122
+ FROM fonderie_config`;
123
+ async function listConfigEntries(environment, store) {
124
+ return environment ? store.query(
125
+ `${SELECT_ENTRY} WHERE (environment = $1 OR environment = 'all') AND active = true ORDER BY key`,
126
+ [environment]
127
+ ) : store.query(`${SELECT_ENTRY} ORDER BY environment, key`);
128
+ }
129
+ async function getConfigEntry(key, environment, store) {
130
+ const [row] = await store.query(
131
+ `${SELECT_ENTRY} WHERE key = $1 AND environment = $2`,
132
+ [key, environment]
133
+ );
134
+ return row ?? null;
135
+ }
136
+ async function setConfigEntry(opts, store) {
137
+ const env = opts.environment ?? "all";
138
+ const raw = typeof opts.value === "string" ? opts.value : JSON.stringify(opts.value);
139
+ const active = opts.active ?? true;
140
+ const [row] = await store.query(
141
+ `INSERT INTO fonderie_config (key, value, environment, description, active)
142
+ VALUES ($1, $2, $3, $4, $5)
143
+ ON CONFLICT (key, environment) DO UPDATE SET
144
+ value = $2,
145
+ description = COALESCE($4, fonderie_config.description),
146
+ active = $5,
147
+ updated_at = now()
148
+ RETURNING
149
+ key, value, environment, description, active,
150
+ updated_at AS "updatedAt"`,
151
+ [opts.key, raw, env, opts.description ?? null, active]
152
+ );
153
+ if (!row) throw new Error("Failed to upsert config entry");
154
+ return row;
155
+ }
156
+ async function deleteConfigEntry(key, environment, store) {
157
+ const rows = await store.query(
158
+ `DELETE FROM fonderie_config WHERE key = $1 AND environment = $2 RETURNING key`,
159
+ [key, environment]
160
+ );
161
+ return rows.length > 0;
162
+ }
163
+ export {
164
+ CONFIG_MANAGER_KEY,
165
+ RemoteConfigManager,
166
+ RemoteConfigModule,
167
+ configContextMiddleware,
168
+ deleteConfigEntry,
169
+ getConfig,
170
+ getConfigEntry,
171
+ listConfigEntries,
172
+ setConfigEntry
173
+ };
174
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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 { 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":[]}
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/middlewares/index.ts
21
+ var middlewares_exports = {};
22
+ __export(middlewares_exports, {
23
+ configContextMiddleware: () => configContextMiddleware,
24
+ getConfig: () => getConfig
25
+ });
26
+ module.exports = __toCommonJS(middlewares_exports);
27
+
28
+ // src/manager.ts
29
+ var FONDERIE_CONFIG_KEY = "fonderie.config.snapshot";
30
+ var CONFIG_MANAGER_KEY = FONDERIE_CONFIG_KEY;
31
+
32
+ // src/middlewares/config-context.ts
33
+ function configContextMiddleware(manager) {
34
+ return async (ctx, next) => {
35
+ ctx.meta[CONFIG_MANAGER_KEY] = manager;
36
+ return next();
37
+ };
38
+ }
39
+ function getConfig(ctx, key, fallback = null) {
40
+ const manager = ctx.meta[CONFIG_MANAGER_KEY];
41
+ if (!manager) {
42
+ return fallback;
43
+ }
44
+ return manager.get(key, fallback);
45
+ }
46
+ // Annotate the CommonJS export names for ESM import in node:
47
+ 0 && (module.exports = {
48
+ configContextMiddleware,
49
+ getConfig
50
+ });
51
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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 { 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"],"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":[]}
@@ -0,0 +1,3 @@
1
+ export { c as configContextMiddleware, g as getConfig } from '../index-v2hLmm2m.cjs';
2
+ import '@fonderie/core';
3
+ import '@fonderie/store';
@@ -0,0 +1,3 @@
1
+ export { c as configContextMiddleware, g as getConfig } from '../index-v2hLmm2m.js';
2
+ import '@fonderie/core';
3
+ import '@fonderie/store';
@@ -0,0 +1,23 @@
1
+ // src/manager.ts
2
+ var FONDERIE_CONFIG_KEY = "fonderie.config.snapshot";
3
+ var CONFIG_MANAGER_KEY = FONDERIE_CONFIG_KEY;
4
+
5
+ // src/middlewares/config-context.ts
6
+ function configContextMiddleware(manager) {
7
+ return async (ctx, next) => {
8
+ ctx.meta[CONFIG_MANAGER_KEY] = manager;
9
+ return next();
10
+ };
11
+ }
12
+ function getConfig(ctx, key, fallback = null) {
13
+ const manager = ctx.meta[CONFIG_MANAGER_KEY];
14
+ if (!manager) {
15
+ return fallback;
16
+ }
17
+ return manager.get(key, fallback);
18
+ }
19
+ export {
20
+ configContextMiddleware,
21
+ getConfig
22
+ };
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/manager.ts","../../src/middlewares/config-context.ts"],"sourcesContent":["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"],"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":[]}
@@ -0,0 +1,7 @@
1
+ // src/migrations/index.ts
2
+ import { createMigrationsPath } from "@fonderie/store";
3
+ var getMigrationsPath = () => createMigrationsPath(import.meta.url);
4
+ export {
5
+ getMigrationsPath
6
+ };
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/migrations/index.ts"],"sourcesContent":["import { createMigrationsPath } from '@fonderie/store';\n\nexport const getMigrationsPath = (): string => createMigrationsPath(import.meta.url);\n"],"mappings":";AAAA,SAAS,4BAA4B;AAE9B,IAAM,oBAAoB,MAAc,qBAAqB,YAAY,GAAG;","names":[]}
@@ -0,0 +1,13 @@
1
+ CREATE TABLE IF NOT EXISTS fonderie_config (
2
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
3
+ key TEXT NOT NULL,
4
+ value TEXT NOT NULL,
5
+ environment TEXT NOT NULL DEFAULT 'all',
6
+ description TEXT,
7
+ active BOOLEAN NOT NULL DEFAULT true,
8
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
9
+ UNIQUE (key, environment)
10
+ );
11
+
12
+ CREATE INDEX IF NOT EXISTS fonderie_config_env_active_idx
13
+ ON fonderie_config (environment, active);
package/dist/types.cjs ADDED
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
+
16
+ // src/types.ts
17
+ var types_exports = {};
18
+ module.exports = __toCommonJS(types_exports);
19
+ //# sourceMappingURL=types.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["export interface IConfigEntry {\n\tkey: string;\n\tvalue: unknown;\n\tenvironment: string;\n\tdescription: string | null;\n\tactive: boolean;\n\tupdatedAt: string;\n}\n\nexport interface IConfigSnapshot {\n\tentries: Record<string, unknown>;\n\tfetchedAt: Date;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
@@ -0,0 +1,14 @@
1
+ interface IConfigEntry {
2
+ key: string;
3
+ value: unknown;
4
+ environment: string;
5
+ description: string | null;
6
+ active: boolean;
7
+ updatedAt: string;
8
+ }
9
+ interface IConfigSnapshot {
10
+ entries: Record<string, unknown>;
11
+ fetchedAt: Date;
12
+ }
13
+
14
+ export type { IConfigEntry, IConfigSnapshot };
@@ -0,0 +1,14 @@
1
+ interface IConfigEntry {
2
+ key: string;
3
+ value: unknown;
4
+ environment: string;
5
+ description: string | null;
6
+ active: boolean;
7
+ updatedAt: string;
8
+ }
9
+ interface IConfigSnapshot {
10
+ entries: Record<string, unknown>;
11
+ fetchedAt: Date;
12
+ }
13
+
14
+ export type { IConfigEntry, IConfigSnapshot };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@fonderie/config",
3
+ "version": "1.0.0",
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
+ "keywords": [
6
+ "fonderie-js",
7
+ "feature-flags",
8
+ "remote-config",
9
+ "runtime-config",
10
+ "saas",
11
+ "typescript"
12
+ ],
13
+ "license": "MIT",
14
+ "type": "module",
15
+ "engines": {
16
+ "node": ">=20"
17
+ },
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js",
22
+ "require": "./dist/index.cjs"
23
+ },
24
+ "./types": {
25
+ "types": "./dist/types.d.ts",
26
+ "import": "./dist/types.js",
27
+ "require": "./dist/types.cjs"
28
+ },
29
+ "./middleware": {
30
+ "types": "./dist/middlewares/index.d.ts",
31
+ "import": "./dist/middlewares/index.js",
32
+ "require": "./dist/middlewares/index.cjs"
33
+ },
34
+ "./migrations": {
35
+ "types": "./dist/migrations/index.d.ts",
36
+ "import": "./dist/migrations/index.js"
37
+ }
38
+ },
39
+ "main": "./dist/index.cjs",
40
+ "module": "./dist/index.js",
41
+ "types": "./dist/index.d.ts",
42
+ "scripts": {
43
+ "build": "tsup && node -e \"require('node:fs').cpSync('src/migrations/sql', 'dist/migrations/sql', {recursive:true})\"",
44
+ "dev": "tsup --watch",
45
+ "typecheck": "tsc --noEmit",
46
+ "test": "tsx --test src/__tests__/*.test.ts",
47
+ "lint": "biome lint src",
48
+ "format": "biome format --write src",
49
+ "check": "biome check --write src"
50
+ },
51
+ "peerDependencies": {
52
+ "@fonderie/core": "^0.1.0",
53
+ "@fonderie/store": "^0.1.0"
54
+ },
55
+ "devDependencies": {
56
+ "@fonderie/core": "../core",
57
+ "@fonderie/store": "../store",
58
+ "@types/node": "^25.6.0",
59
+ "tsup": "^8.5.1",
60
+ "tsx": "^4.21.0",
61
+ "typescript": "^6.0.3"
62
+ },
63
+ "publishConfig": {
64
+ "access": "public"
65
+ },
66
+ "files": [
67
+ "dist",
68
+ "LICENSE",
69
+ "README.md"
70
+ ],
71
+ "repository": {
72
+ "type": "git",
73
+ "url": "git+https://github.com/fonderie-js/sdk.git",
74
+ "directory": "packages/config"
75
+ },
76
+ "homepage": "https://github.com/fonderie-js/sdk/tree/main/packages/config#readme",
77
+ "bugs": {
78
+ "url": "https://github.com/fonderie-js/sdk/issues"
79
+ }
80
+ }