enpilink 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/config/config.test.js +201 -8
- package/dist/server/config/config.test.js.map +1 -1
- package/dist/server/config/index.d.ts +3 -2
- package/dist/server/config/index.js +3 -2
- package/dist/server/config/index.js.map +1 -1
- package/dist/server/config/presets.d.ts +36 -0
- package/dist/server/config/presets.js +46 -0
- package/dist/server/config/presets.js.map +1 -0
- package/dist/server/config/resolve.d.ts +42 -3
- package/dist/server/config/resolve.js +88 -8
- package/dist/server/config/resolve.js.map +1 -1
- package/dist/server/config/router.d.ts +22 -14
- package/dist/server/config/router.js +153 -51
- package/dist/server/config/router.js.map +1 -1
- package/dist/server/config/schema.d.ts +39 -1
- package/dist/server/config/schema.js +121 -0
- package/dist/server/config/schema.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/storage/memory.d.ts +1 -0
- package/dist/server/storage/memory.js +14 -0
- package/dist/server/storage/memory.js.map +1 -1
- package/dist/server/storage/memory.test.js +17 -0
- package/dist/server/storage/memory.test.js.map +1 -1
- package/dist/server/storage/postgres.d.ts +1 -0
- package/dist/server/storage/postgres.js +12 -0
- package/dist/server/storage/postgres.js.map +1 -1
- package/dist/server/storage/postgres.test.js +17 -0
- package/dist/server/storage/postgres.test.js.map +1 -1
- package/dist/server/storage/sqlite.d.ts +1 -0
- package/dist/server/storage/sqlite.js +21 -0
- package/dist/server/storage/sqlite.js.map +1 -1
- package/dist/server/storage/sqlite.test.js +17 -0
- package/dist/server/storage/sqlite.test.js.map +1 -1
- package/dist/server/storage/types.d.ts +6 -0
- package/dist/server/storage/types.js.map +1 -1
- package/package.json +2 -2
|
@@ -116,6 +116,23 @@ describe("PostgresStorageAdapter (pg-mem)", () => {
|
|
|
116
116
|
actor: "system",
|
|
117
117
|
});
|
|
118
118
|
});
|
|
119
|
+
it("clearConfig removes the row and audits the reset", async () => {
|
|
120
|
+
await store.setConfig("k", "v1");
|
|
121
|
+
await store.clearConfig("k", "bob");
|
|
122
|
+
expect(await store.getConfig("k")).toBeUndefined();
|
|
123
|
+
expect(await store.allConfig()).toEqual({});
|
|
124
|
+
const audit = await store.getConfigAudit();
|
|
125
|
+
expect(audit[0]).toMatchObject({
|
|
126
|
+
key: "k",
|
|
127
|
+
oldValue: "v1",
|
|
128
|
+
newValue: null,
|
|
129
|
+
actor: "bob",
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
it("clearConfig is a no-op (no audit) when the key was never set", async () => {
|
|
133
|
+
await store.clearConfig("absent");
|
|
134
|
+
expect(await store.getConfigAudit()).toHaveLength(0);
|
|
135
|
+
});
|
|
119
136
|
});
|
|
120
137
|
describe("persistence within a session", () => {
|
|
121
138
|
it("retains writes across queries on the same pool", async () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres.test.js","sourceRoot":"","sources":["../../../src/server/storage/postgres.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,EAEL,sBAAsB,EACtB,+BAA+B,GAChC,MAAM,eAAe,CAAC;AAEvB;;;;;;;GAOG;AACH,SAAS,QAAQ;IACf,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;IACxC,OAAO,IAAI,IAAI,EAA2B,CAAC;AAC7C,CAAC;AAED,QAAQ,CAAC,iCAAiC,EAAE,GAAG,EAAE;IAC/C,IAAI,KAA6B,CAAC;IAElC,UAAU,CAAC,KAAK,IAAI,EAAE;QACpB,KAAK,GAAG,IAAI,sBAAsB,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;QACzD,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,KAAK,IAAI,EAAE;QACnB,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;QACtB,EAAE,CAAC,+DAA+D,EAAE,KAAK,IAAI,EAAE;YAC7E,MAAM,KAAK,CAAC,WAAW,CAAC;gBACtB,EAAE,EAAE,CAAC;gBACL,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,GAAG;gBACT,EAAE,EAAE,CAAC;gBACL,EAAE,EAAE,IAAI;aACT,CAAC,CAAC;YACH,MAAM,KAAK,CAAC,WAAW,CAAC;gBACtB,EAAE,EAAE,CAAC;gBACL,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,GAAG;gBACT,EAAE,EAAE,CAAC;gBACL,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,MAAM;aACd,CAAC,CAAC;YACH,MAAM,KAAK,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;YAE7D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;YACxC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACjD,MAAM,CAAC,MAAM,KAAK,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC9D,MAAM,CAAC,MAAM,KAAK,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC/D,MAAM,CAAC,MAAM,KAAK,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAClE,MAAM,CAAC,MAAM,KAAK,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAE9D,MAAM,OAAO,GAAG,CAAC,MAAM,KAAK,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACtE,MAAM,CAAC,OAAO,CAAC,CAAC,aAAa,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;QACrE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6BAA6B,EAAE,KAAK,IAAI,EAAE;YAC3C,MAAM,KAAK,CAAC,WAAW,CAAC;gBACtB,EAAE,EAAE,CAAC;gBACL,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;aACtC,CAAC,CAAC;YACH,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;YACxC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QAC3D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE;QACpB,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;YAC/D,MAAM,KAAK,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;YAC5D,MAAM,KAAK,CAAC,SAAS,CAAC;gBACpB,EAAE,EAAE,CAAC;gBACL,KAAK,EAAE,OAAO;gBACd,GAAG,EAAE,KAAK;gBACV,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;aACnB,CAAC,CAAC;YACH,MAAM,KAAK,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;YAE9D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YACtC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;YAC/D,MAAM,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAClE,MAAM,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC5D,MAAM,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC5D,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC;gBACnE,IAAI,EAAE,EAAE;aACT,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;QAC9B,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;YACrD,MAAM,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;YACzD,MAAM,KAAK,CAAC,SAAS,CAAC,mBAAmB,EAAE,IAAI,CAAC,CAAC;YACjD,MAAM,KAAK,CAAC,SAAS,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;YAChD,MAAM,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC9D,MAAM,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;YAChE,MAAM,CAAC,MAAM,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,OAAO,CAAC;gBACtC,mBAAmB,EAAE,IAAI;gBACzB,SAAS,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE;aACvB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;YAChE,MAAM,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACjC,MAAM,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACjC,MAAM,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC9C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,mFAAmF,EAAE,KAAK,IAAI,EAAE;YACjG,MAAM,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACjC,MAAM,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;YAC1C,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,cAAc,EAAE,CAAC;YAC3C,MAAM,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC9B,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC;gBAC7B,GAAG,EAAE,GAAG;gBACR,QAAQ,EAAE,IAAI;gBACd,QAAQ,EAAE,IAAI;gBACd,KAAK,EAAE,OAAO;aACf,CAAC,CAAC;YACH,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC;gBAC7B,GAAG,EAAE,GAAG;gBACR,QAAQ,EAAE,SAAS;gBACnB,QAAQ,EAAE,IAAI;gBACd,KAAK,EAAE,QAAQ;aAChB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,8BAA8B,EAAE,GAAG,EAAE;QAC5C,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;YAC9D,MAAM,KAAK,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;YACzE,MAAM,KAAK,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;YACjE,MAAM,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,KAAK,CAAC,CAAC;YAEnD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;YAC3C,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC/B,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAE1C,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YACvC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAEnC,MAAM,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;YAC5D,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,cAAc,EAAE,CAAC;YAC3C,MAAM,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC9B,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAClC,EAAE,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;YACjD,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;YACnB,MAAM,KAAK,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;YACtD,MAAM,CAAC,MAAM,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACtD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;QACrC,EAAE,CAAC,kCAAkC,EAAE,KAAK,IAAI,EAAE;YAChD,MAAM,CAAC,GAAG,IAAI,sBAAsB,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;YAC3D,MAAM,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC;QACzE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,iCAAiC,EAAE,GAAG,EAAE;IAC/C,MAAM,KAAK,GAAG;QACZ,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,eAAe;QAChC,EAAE,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY;KAC7B,CAAC;IACF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;YAC5B,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;QACrC,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,eAAe,GAAG,KAAK,CAAC,GAAG,CAAC;QAC1C,CAAC;QACD,IAAI,KAAK,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;YAC3B,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;QAClC,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,YAAY,GAAG,KAAK,CAAC,EAAE,CAAC;QACtC,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;QACnC,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;QAChC,MAAM,CAAC,+BAA+B,EAAE,CAAC,CAAC,aAAa,EAAE,CAAC;QAE1D,OAAO,CAAC,GAAG,CAAC,YAAY,GAAG,wBAAwB,CAAC;QACpD,MAAM,CAAC,+BAA+B,EAAE,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;QAEzE,OAAO,CAAC,GAAG,CAAC,eAAe,GAAG,wBAAwB,CAAC;QACvD,MAAM,CAAC,+BAA+B,EAAE,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;QAEzE,mEAAmE;QACnE,OAAO,CAAC,GAAG,CAAC,eAAe,GAAG,KAAK,CAAC;QACpC,MAAM,CAAC,+BAA+B,EAAE,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { newDb } from \"pg-mem\";\nimport { afterEach, beforeEach, describe, expect, it } from \"vitest\";\nimport {\n type PgPoolLike,\n PostgresStorageAdapter,\n resolvePostgresConnectionString,\n} from \"./postgres.js\";\n\n/**\n * Tests run against `pg-mem` (pure-JS in-memory Postgres). pg-mem's\n * `createPg().Pool` is a drop-in `pg.Pool`, so the adapter exercises the SAME\n * code paths it would against a real Postgres — only the engine differs. We\n * inject the pool via `opts.pool` so no real connection / env is touched.\n *\n * Each test gets a fresh `newDb()` so state is isolated and deterministic.\n */\nfunction makePool(): PgPoolLike {\n const db = newDb();\n const { Pool } = db.adapters.createPg();\n return new Pool() as unknown as PgPoolLike;\n}\n\ndescribe(\"PostgresStorageAdapter (pg-mem)\", () => {\n let store: PostgresStorageAdapter;\n\n beforeEach(async () => {\n store = new PostgresStorageAdapter({ pool: makePool() });\n await store.init();\n });\n\n afterEach(async () => {\n await store.close();\n });\n\n describe(\"events\", () => {\n it(\"records and queries events with since/tool/type/limit filters\", async () => {\n await store.recordEvent({\n ts: 1,\n type: \"tool_call\",\n tool: \"a\",\n ms: 5,\n ok: true,\n });\n await store.recordEvent({\n ts: 5,\n type: \"tool_call\",\n tool: \"a\",\n ms: 9,\n ok: false,\n error: \"boom\",\n });\n await store.recordEvent({ ts: 10, type: \"ping\", tool: \"b\" });\n\n const all = await store.queryEvents({});\n expect(all.map((e) => e.ts)).toEqual([10, 5, 1]);\n expect(await store.queryEvents({ since: 5 })).toHaveLength(2);\n expect(await store.queryEvents({ tool: \"a\" })).toHaveLength(2);\n expect(await store.queryEvents({ type: \"ping\" })).toHaveLength(1);\n expect(await store.queryEvents({ limit: 1 })).toHaveLength(1);\n\n const errored = (await store.queryEvents({ since: 5, tool: \"a\" }))[0];\n expect(errored).toMatchObject({ ok: false, error: \"boom\", ms: 9 });\n });\n\n it(\"round-trips the meta object\", async () => {\n await store.recordEvent({\n ts: 1,\n type: \"tool_call\",\n meta: { user: \"x\", nested: { n: 1 } },\n });\n const [e] = await store.queryEvents({});\n expect(e?.meta).toEqual({ user: \"x\", nested: { n: 1 } });\n });\n });\n\n describe(\"logs\", () => {\n it(\"appends and queries logs with level/limit/since\", async () => {\n await store.appendLog({ ts: 1, level: \"info\", msg: \"one\" });\n await store.appendLog({\n ts: 2,\n level: \"error\",\n msg: \"two\",\n data: { code: 42 },\n });\n await store.appendLog({ ts: 3, level: \"info\", msg: \"three\" });\n\n const all = await store.queryLogs({});\n expect(all.map((l) => l.msg)).toEqual([\"three\", \"two\", \"one\"]);\n expect(await store.queryLogs({ level: \"error\" })).toHaveLength(1);\n expect(await store.queryLogs({ since: 2 })).toHaveLength(2);\n expect(await store.queryLogs({ limit: 1 })).toHaveLength(1);\n expect((await store.queryLogs({ level: \"error\" }))[0]?.data).toEqual({\n code: 42,\n });\n });\n });\n\n describe(\"config + audit\", () => {\n it(\"get/set/all round-trips opaque values\", async () => {\n expect(await store.getConfig(\"missing\")).toBeUndefined();\n await store.setConfig(\"analytics.enabled\", true);\n await store.setConfig(\"retention\", { days: 7 });\n expect(await store.getConfig(\"analytics.enabled\")).toBe(true);\n expect(await store.getConfig(\"retention\")).toEqual({ days: 7 });\n expect(await store.allConfig()).toEqual({\n \"analytics.enabled\": true,\n retention: { days: 7 },\n });\n });\n\n it(\"upserts on repeated setConfig (no duplicate key)\", async () => {\n await store.setConfig(\"k\", \"v1\");\n await store.setConfig(\"k\", \"v2\");\n expect(await store.getConfig(\"k\")).toBe(\"v2\");\n expect(Object.keys(await store.allConfig())).toEqual([\"k\"]);\n });\n\n it(\"writes a config_audit row on setConfig with old → new + actor (most recent first)\", async () => {\n await store.setConfig(\"k\", \"v1\");\n await store.setConfig(\"k\", \"v2\", \"alice\");\n const audit = await store.getConfigAudit();\n expect(audit).toHaveLength(2);\n expect(audit[0]).toMatchObject({\n key: \"k\",\n oldValue: \"v1\",\n newValue: \"v2\",\n actor: \"alice\",\n });\n expect(audit[1]).toMatchObject({\n key: \"k\",\n oldValue: undefined,\n newValue: \"v1\",\n actor: \"system\",\n });\n });\n });\n\n describe(\"persistence within a session\", () => {\n it(\"retains writes across queries on the same pool\", async () => {\n await store.recordEvent({ ts: 1, type: \"tool_call\", tool: \"persisted\" });\n await store.appendLog({ ts: 1, level: \"warning\", msg: \"stays\" });\n await store.setConfig(\"flag\", { on: true }, \"bob\");\n\n const events = await store.queryEvents({});\n expect(events).toHaveLength(1);\n expect(events[0]?.tool).toBe(\"persisted\");\n\n const logs = await store.queryLogs({});\n expect(logs[0]?.msg).toBe(\"stays\");\n\n expect(await store.getConfig(\"flag\")).toEqual({ on: true });\n const audit = await store.getConfigAudit();\n expect(audit).toHaveLength(1);\n expect(audit[0]).toMatchObject({ actor: \"bob\" });\n });\n });\n\n describe(\"init is idempotent\", () => {\n it(\"can be called twice without error\", async () => {\n await store.init();\n await store.recordEvent({ ts: 1, type: \"tool_call\" });\n expect(await store.queryEvents({})).toHaveLength(1);\n });\n });\n\n describe(\"use-before-init guard\", () => {\n it(\"throws when querying before init\", async () => {\n const s = new PostgresStorageAdapter({ pool: makePool() });\n await expect(s.queryEvents({})).rejects.toThrow(/init\\(\\) before use/);\n });\n });\n});\n\ndescribe(\"resolvePostgresConnectionString\", () => {\n const saved = {\n url: process.env.ENPILINK_DB_URL,\n db: process.env.DATABASE_URL,\n };\n afterEach(() => {\n if (saved.url === undefined) {\n delete process.env.ENPILINK_DB_URL;\n } else {\n process.env.ENPILINK_DB_URL = saved.url;\n }\n if (saved.db === undefined) {\n delete process.env.DATABASE_URL;\n } else {\n process.env.DATABASE_URL = saved.db;\n }\n });\n\n it(\"prefers ENPILINK_DB_URL, falls back to DATABASE_URL, else undefined\", () => {\n delete process.env.ENPILINK_DB_URL;\n delete process.env.DATABASE_URL;\n expect(resolvePostgresConnectionString()).toBeUndefined();\n\n process.env.DATABASE_URL = \"postgres://db/standard\";\n expect(resolvePostgresConnectionString()).toBe(\"postgres://db/standard\");\n\n process.env.ENPILINK_DB_URL = \"postgres://db/enpilink\";\n expect(resolvePostgresConnectionString()).toBe(\"postgres://db/enpilink\");\n\n // Whitespace-only is treated as unset (no hardcoded default ever).\n process.env.ENPILINK_DB_URL = \" \";\n expect(resolvePostgresConnectionString()).toBe(\"postgres://db/standard\");\n });\n});\n"]}
|
|
1
|
+
{"version":3,"file":"postgres.test.js","sourceRoot":"","sources":["../../../src/server/storage/postgres.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { newDb } from \"pg-mem\";\nimport { afterEach, beforeEach, describe, expect, it } from \"vitest\";\nimport {\n type PgPoolLike,\n PostgresStorageAdapter,\n resolvePostgresConnectionString,\n} from \"./postgres.js\";\n\n/**\n * Tests run against `pg-mem` (pure-JS in-memory Postgres). pg-mem's\n * `createPg().Pool` is a drop-in `pg.Pool`, so the adapter exercises the SAME\n * code paths it would against a real Postgres — only the engine differs. We\n * inject the pool via `opts.pool` so no real connection / env is touched.\n *\n * Each test gets a fresh `newDb()` so state is isolated and deterministic.\n */\nfunction makePool(): PgPoolLike {\n const db = newDb();\n const { Pool } = db.adapters.createPg();\n return new Pool() as unknown as PgPoolLike;\n}\n\ndescribe(\"PostgresStorageAdapter (pg-mem)\", () => {\n let store: PostgresStorageAdapter;\n\n beforeEach(async () => {\n store = new PostgresStorageAdapter({ pool: makePool() });\n await store.init();\n });\n\n afterEach(async () => {\n await store.close();\n });\n\n describe(\"events\", () => {\n it(\"records and queries events with since/tool/type/limit filters\", async () => {\n await store.recordEvent({\n ts: 1,\n type: \"tool_call\",\n tool: \"a\",\n ms: 5,\n ok: true,\n });\n await store.recordEvent({\n ts: 5,\n type: \"tool_call\",\n tool: \"a\",\n ms: 9,\n ok: false,\n error: \"boom\",\n });\n await store.recordEvent({ ts: 10, type: \"ping\", tool: \"b\" });\n\n const all = await store.queryEvents({});\n expect(all.map((e) => e.ts)).toEqual([10, 5, 1]);\n expect(await store.queryEvents({ since: 5 })).toHaveLength(2);\n expect(await store.queryEvents({ tool: \"a\" })).toHaveLength(2);\n expect(await store.queryEvents({ type: \"ping\" })).toHaveLength(1);\n expect(await store.queryEvents({ limit: 1 })).toHaveLength(1);\n\n const errored = (await store.queryEvents({ since: 5, tool: \"a\" }))[0];\n expect(errored).toMatchObject({ ok: false, error: \"boom\", ms: 9 });\n });\n\n it(\"round-trips the meta object\", async () => {\n await store.recordEvent({\n ts: 1,\n type: \"tool_call\",\n meta: { user: \"x\", nested: { n: 1 } },\n });\n const [e] = await store.queryEvents({});\n expect(e?.meta).toEqual({ user: \"x\", nested: { n: 1 } });\n });\n });\n\n describe(\"logs\", () => {\n it(\"appends and queries logs with level/limit/since\", async () => {\n await store.appendLog({ ts: 1, level: \"info\", msg: \"one\" });\n await store.appendLog({\n ts: 2,\n level: \"error\",\n msg: \"two\",\n data: { code: 42 },\n });\n await store.appendLog({ ts: 3, level: \"info\", msg: \"three\" });\n\n const all = await store.queryLogs({});\n expect(all.map((l) => l.msg)).toEqual([\"three\", \"two\", \"one\"]);\n expect(await store.queryLogs({ level: \"error\" })).toHaveLength(1);\n expect(await store.queryLogs({ since: 2 })).toHaveLength(2);\n expect(await store.queryLogs({ limit: 1 })).toHaveLength(1);\n expect((await store.queryLogs({ level: \"error\" }))[0]?.data).toEqual({\n code: 42,\n });\n });\n });\n\n describe(\"config + audit\", () => {\n it(\"get/set/all round-trips opaque values\", async () => {\n expect(await store.getConfig(\"missing\")).toBeUndefined();\n await store.setConfig(\"analytics.enabled\", true);\n await store.setConfig(\"retention\", { days: 7 });\n expect(await store.getConfig(\"analytics.enabled\")).toBe(true);\n expect(await store.getConfig(\"retention\")).toEqual({ days: 7 });\n expect(await store.allConfig()).toEqual({\n \"analytics.enabled\": true,\n retention: { days: 7 },\n });\n });\n\n it(\"upserts on repeated setConfig (no duplicate key)\", async () => {\n await store.setConfig(\"k\", \"v1\");\n await store.setConfig(\"k\", \"v2\");\n expect(await store.getConfig(\"k\")).toBe(\"v2\");\n expect(Object.keys(await store.allConfig())).toEqual([\"k\"]);\n });\n\n it(\"writes a config_audit row on setConfig with old → new + actor (most recent first)\", async () => {\n await store.setConfig(\"k\", \"v1\");\n await store.setConfig(\"k\", \"v2\", \"alice\");\n const audit = await store.getConfigAudit();\n expect(audit).toHaveLength(2);\n expect(audit[0]).toMatchObject({\n key: \"k\",\n oldValue: \"v1\",\n newValue: \"v2\",\n actor: \"alice\",\n });\n expect(audit[1]).toMatchObject({\n key: \"k\",\n oldValue: undefined,\n newValue: \"v1\",\n actor: \"system\",\n });\n });\n\n it(\"clearConfig removes the row and audits the reset\", async () => {\n await store.setConfig(\"k\", \"v1\");\n await store.clearConfig(\"k\", \"bob\");\n expect(await store.getConfig(\"k\")).toBeUndefined();\n expect(await store.allConfig()).toEqual({});\n const audit = await store.getConfigAudit();\n expect(audit[0]).toMatchObject({\n key: \"k\",\n oldValue: \"v1\",\n newValue: null,\n actor: \"bob\",\n });\n });\n\n it(\"clearConfig is a no-op (no audit) when the key was never set\", async () => {\n await store.clearConfig(\"absent\");\n expect(await store.getConfigAudit()).toHaveLength(0);\n });\n });\n\n describe(\"persistence within a session\", () => {\n it(\"retains writes across queries on the same pool\", async () => {\n await store.recordEvent({ ts: 1, type: \"tool_call\", tool: \"persisted\" });\n await store.appendLog({ ts: 1, level: \"warning\", msg: \"stays\" });\n await store.setConfig(\"flag\", { on: true }, \"bob\");\n\n const events = await store.queryEvents({});\n expect(events).toHaveLength(1);\n expect(events[0]?.tool).toBe(\"persisted\");\n\n const logs = await store.queryLogs({});\n expect(logs[0]?.msg).toBe(\"stays\");\n\n expect(await store.getConfig(\"flag\")).toEqual({ on: true });\n const audit = await store.getConfigAudit();\n expect(audit).toHaveLength(1);\n expect(audit[0]).toMatchObject({ actor: \"bob\" });\n });\n });\n\n describe(\"init is idempotent\", () => {\n it(\"can be called twice without error\", async () => {\n await store.init();\n await store.recordEvent({ ts: 1, type: \"tool_call\" });\n expect(await store.queryEvents({})).toHaveLength(1);\n });\n });\n\n describe(\"use-before-init guard\", () => {\n it(\"throws when querying before init\", async () => {\n const s = new PostgresStorageAdapter({ pool: makePool() });\n await expect(s.queryEvents({})).rejects.toThrow(/init\\(\\) before use/);\n });\n });\n});\n\ndescribe(\"resolvePostgresConnectionString\", () => {\n const saved = {\n url: process.env.ENPILINK_DB_URL,\n db: process.env.DATABASE_URL,\n };\n afterEach(() => {\n if (saved.url === undefined) {\n delete process.env.ENPILINK_DB_URL;\n } else {\n process.env.ENPILINK_DB_URL = saved.url;\n }\n if (saved.db === undefined) {\n delete process.env.DATABASE_URL;\n } else {\n process.env.DATABASE_URL = saved.db;\n }\n });\n\n it(\"prefers ENPILINK_DB_URL, falls back to DATABASE_URL, else undefined\", () => {\n delete process.env.ENPILINK_DB_URL;\n delete process.env.DATABASE_URL;\n expect(resolvePostgresConnectionString()).toBeUndefined();\n\n process.env.DATABASE_URL = \"postgres://db/standard\";\n expect(resolvePostgresConnectionString()).toBe(\"postgres://db/standard\");\n\n process.env.ENPILINK_DB_URL = \"postgres://db/enpilink\";\n expect(resolvePostgresConnectionString()).toBe(\"postgres://db/enpilink\");\n\n // Whitespace-only is treated as unset (no hardcoded default ever).\n process.env.ENPILINK_DB_URL = \" \";\n expect(resolvePostgresConnectionString()).toBe(\"postgres://db/standard\");\n });\n});\n"]}
|
|
@@ -25,6 +25,7 @@ export declare class SqliteStorageAdapter implements StorageAdapter {
|
|
|
25
25
|
queryLogs(f?: LogQuery): Promise<LogEntry[]>;
|
|
26
26
|
getConfig(key: string): Promise<unknown>;
|
|
27
27
|
setConfig(key: string, value: unknown, actor?: string): Promise<void>;
|
|
28
|
+
clearConfig(key: string, actor?: string): Promise<void>;
|
|
28
29
|
allConfig(): Promise<Record<string, unknown>>;
|
|
29
30
|
getConfigAudit(): Promise<ConfigAuditEntry[]>;
|
|
30
31
|
close(): Promise<void>;
|
|
@@ -36,6 +36,7 @@ export class SqliteStorageAdapter {
|
|
|
36
36
|
insertLog: db.prepare("INSERT INTO logs (ts, level, msg, data) VALUES (@ts, @level, @msg, @data)"),
|
|
37
37
|
getConfig: db.prepare("SELECT value FROM config WHERE key = ?"),
|
|
38
38
|
upsertConfig: db.prepare("INSERT INTO config (key, value) VALUES (@key, @value) ON CONFLICT(key) DO UPDATE SET value = @value"),
|
|
39
|
+
deleteConfig: db.prepare("DELETE FROM config WHERE key = ?"),
|
|
39
40
|
insertAudit: db.prepare("INSERT INTO config_audit (ts, key, old_value, new_value, actor) VALUES (@ts, @key, @old_value, @new_value, @actor)"),
|
|
40
41
|
allConfig: db.prepare("SELECT key, value FROM config"),
|
|
41
42
|
};
|
|
@@ -132,6 +133,26 @@ export class SqliteStorageAdapter {
|
|
|
132
133
|
});
|
|
133
134
|
tx();
|
|
134
135
|
}
|
|
136
|
+
async clearConfig(key, actor) {
|
|
137
|
+
const { db, stmts } = this.require();
|
|
138
|
+
const tx = db.transaction(() => {
|
|
139
|
+
const existing = stmts.getConfig.get(key);
|
|
140
|
+
if (existing === undefined) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
stmts.deleteConfig.run(key);
|
|
144
|
+
// `new_value` is NOT NULL; a reset stores JSON `null` (which getAuditLog
|
|
145
|
+
// parses back to `null`) to represent "reset to default".
|
|
146
|
+
stmts.insertAudit.run({
|
|
147
|
+
ts: Date.now(),
|
|
148
|
+
key,
|
|
149
|
+
old_value: existing.value,
|
|
150
|
+
new_value: "null",
|
|
151
|
+
actor: actor ?? "system",
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
tx();
|
|
155
|
+
}
|
|
135
156
|
async allConfig() {
|
|
136
157
|
const { stmts } = this.require();
|
|
137
158
|
const rows = stmts.allConfig.all();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../../src/server/storage/sqlite.ts"],"names":[],"mappings":"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","sourcesContent":["import type DatabaseConstructor from \"better-sqlite3\";\nimport type { Database, Statement } from \"better-sqlite3\";\nimport type {\n AnalyticsEvent,\n ConfigAuditEntry,\n EventQuery,\n LogEntry,\n LogQuery,\n StorageAdapter,\n StorageAdapterOptions,\n} from \"./types.js\";\n\n/** Default on-disk database path. Overridable via `ENPILINK_DB_PATH`. */\nexport const DEFAULT_DB_PATH = \"./enpilink.db\";\n\n/**\n * Embedded-SQLite {@link StorageAdapter}, backed by `better-sqlite3`.\n *\n * `better-sqlite3` was chosen over `node:sqlite` (experimental, warns on every\n * load) and `@libsql/client` (async, remote-oriented, heavier): it is mature,\n * synchronous (simple adapter code), and ships prebuilt binaries for both\n * macOS-arm64 and linux-x64, so `pnpm install` succeeds without node-gyp.\n *\n * Tables: `events`, `logs`, `config`, `config_audit`. Config values are stored\n * as opaque JSON — no special-casing of keys. The DB path is gitignored.\n */\nexport class SqliteStorageAdapter implements StorageAdapter {\n private readonly path: string;\n private db: Database | null = null;\n private stmts: {\n insertEvent: Statement;\n insertLog: Statement;\n getConfig: Statement;\n upsertConfig: Statement;\n insertAudit: Statement;\n allConfig: Statement;\n } | null = null;\n\n constructor(opts?: StorageAdapterOptions) {\n this.path = opts?.path ?? DEFAULT_DB_PATH;\n }\n\n async init(): Promise<void> {\n if (this.db) {\n return;\n }\n // Dynamic import keeps the native module out of the load path until a\n // sqlite adapter is actually instantiated (memory-only users pay nothing).\n const mod = await import(\"better-sqlite3\");\n const Database = (mod.default ??\n mod) as unknown as typeof DatabaseConstructor;\n const db = new Database(this.path);\n db.pragma(\"journal_mode = WAL\");\n db.exec(SCHEMA);\n this.db = db;\n this.stmts = {\n insertEvent: db.prepare(\n \"INSERT INTO events (ts, type, tool, method, ms, ok, error, meta) VALUES (@ts, @type, @tool, @method, @ms, @ok, @error, @meta)\",\n ),\n insertLog: db.prepare(\n \"INSERT INTO logs (ts, level, msg, data) VALUES (@ts, @level, @msg, @data)\",\n ),\n getConfig: db.prepare(\"SELECT value FROM config WHERE key = ?\"),\n upsertConfig: db.prepare(\n \"INSERT INTO config (key, value) VALUES (@key, @value) ON CONFLICT(key) DO UPDATE SET value = @value\",\n ),\n insertAudit: db.prepare(\n \"INSERT INTO config_audit (ts, key, old_value, new_value, actor) VALUES (@ts, @key, @old_value, @new_value, @actor)\",\n ),\n allConfig: db.prepare(\"SELECT key, value FROM config\"),\n };\n }\n\n private require() {\n if (!this.db || !this.stmts) {\n throw new Error(\"SqliteStorageAdapter: call init() before use\");\n }\n return { db: this.db, stmts: this.stmts };\n }\n\n async recordEvent(e: AnalyticsEvent): Promise<void> {\n const { stmts } = this.require();\n stmts.insertEvent.run({\n ts: e.ts,\n type: e.type,\n tool: e.tool ?? null,\n method: e.method ?? null,\n ms: e.ms ?? null,\n ok: e.ok === undefined ? null : e.ok ? 1 : 0,\n error: e.error ?? null,\n meta: e.meta === undefined ? null : JSON.stringify(e.meta),\n });\n }\n\n async queryEvents(f: EventQuery = {}): Promise<AnalyticsEvent[]> {\n const { db } = this.require();\n const where: string[] = [];\n const params: Record<string, unknown> = {};\n if (f.since !== undefined) {\n where.push(\"ts >= @since\");\n params.since = f.since;\n }\n if (f.type !== undefined) {\n where.push(\"type = @type\");\n params.type = f.type;\n }\n if (f.tool !== undefined) {\n where.push(\"tool = @tool\");\n params.tool = f.tool;\n }\n const clause = where.length ? `WHERE ${where.join(\" AND \")}` : \"\";\n const limit = limitClause(f.limit, params);\n const rows = db\n .prepare(\n `SELECT ts, type, tool, method, ms, ok, error, meta FROM events ${clause} ORDER BY id DESC ${limit}`,\n )\n .all(params) as EventRow[];\n return rows.map(rowToEvent);\n }\n\n async appendLog(l: LogEntry): Promise<void> {\n const { stmts } = this.require();\n stmts.insertLog.run({\n ts: l.ts,\n level: l.level,\n msg: l.msg,\n data: l.data === undefined ? null : JSON.stringify(l.data),\n });\n }\n\n async queryLogs(f: LogQuery = {}): Promise<LogEntry[]> {\n const { db } = this.require();\n const where: string[] = [];\n const params: Record<string, unknown> = {};\n if (f.since !== undefined) {\n where.push(\"ts >= @since\");\n params.since = f.since;\n }\n if (f.level !== undefined) {\n where.push(\"level = @level\");\n params.level = f.level;\n }\n const clause = where.length ? `WHERE ${where.join(\" AND \")}` : \"\";\n const limit = limitClause(f.limit, params);\n const rows = db\n .prepare(\n `SELECT ts, level, msg, data FROM logs ${clause} ORDER BY id DESC ${limit}`,\n )\n .all(params) as LogRow[];\n return rows.map(rowToLog);\n }\n\n async getConfig(key: string): Promise<unknown> {\n const { stmts } = this.require();\n const row = stmts.getConfig.get(key) as { value: string } | undefined;\n return row === undefined ? undefined : JSON.parse(row.value);\n }\n\n async setConfig(key: string, value: unknown, actor?: string): Promise<void> {\n const { db, stmts } = this.require();\n const serialized = JSON.stringify(value);\n // Atomically read-old → write-new → write-audit.\n const tx = db.transaction(() => {\n const existing = stmts.getConfig.get(key) as\n | { value: string }\n | undefined;\n stmts.upsertConfig.run({ key, value: serialized });\n stmts.insertAudit.run({\n ts: Date.now(),\n key,\n old_value: existing === undefined ? null : existing.value,\n new_value: serialized,\n actor: actor ?? \"system\",\n });\n });\n tx();\n }\n\n async allConfig(): Promise<Record<string, unknown>> {\n const { stmts } = this.require();\n const rows = stmts.allConfig.all() as { key: string; value: string }[];\n const out: Record<string, unknown> = {};\n for (const r of rows) {\n out[r.key] = JSON.parse(r.value);\n }\n return out;\n }\n\n async getConfigAudit(): Promise<ConfigAuditEntry[]> {\n return this.getAuditLog();\n }\n\n async close(): Promise<void> {\n if (this.db) {\n this.db.close();\n this.db = null;\n this.stmts = null;\n }\n }\n\n /** Audit trail of config writes (most recent first). Synchronous helper. */\n getAuditLog(): ConfigAuditEntry[] {\n const { db } = this.require();\n const rows = db\n .prepare(\n \"SELECT ts, key, old_value, new_value, actor FROM config_audit ORDER BY id DESC\",\n )\n .all() as AuditRow[];\n return rows.map((r) => ({\n ts: r.ts,\n key: r.key,\n oldValue: r.old_value === null ? undefined : JSON.parse(r.old_value),\n newValue: JSON.parse(r.new_value),\n actor: r.actor,\n }));\n }\n}\n\nfunction limitClause(\n limit: number | undefined,\n params: Record<string, unknown>,\n): string {\n if (limit === undefined || limit < 0) {\n return \"\";\n }\n params.limit = limit;\n return \"LIMIT @limit\";\n}\n\ninterface EventRow {\n ts: number;\n type: string;\n tool: string | null;\n method: string | null;\n ms: number | null;\n ok: number | null;\n error: string | null;\n meta: string | null;\n}\n\ninterface LogRow {\n ts: number;\n level: string;\n msg: string;\n data: string | null;\n}\n\ninterface AuditRow {\n ts: number;\n key: string;\n old_value: string | null;\n new_value: string;\n actor: string;\n}\n\nfunction rowToEvent(r: EventRow): AnalyticsEvent {\n const e: AnalyticsEvent = { ts: r.ts, type: r.type };\n if (r.tool !== null) {\n e.tool = r.tool;\n }\n if (r.method !== null) {\n e.method = r.method;\n }\n if (r.ms !== null) {\n e.ms = r.ms;\n }\n if (r.ok !== null) {\n e.ok = r.ok === 1;\n }\n if (r.error !== null) {\n e.error = r.error;\n }\n if (r.meta !== null) {\n e.meta = JSON.parse(r.meta);\n }\n return e;\n}\n\nfunction rowToLog(r: LogRow): LogEntry {\n const l: LogEntry = {\n ts: r.ts,\n level: r.level as LogEntry[\"level\"],\n msg: r.msg,\n };\n if (r.data !== null) {\n l.data = JSON.parse(r.data);\n }\n return l;\n}\n\nconst SCHEMA = `\nCREATE TABLE IF NOT EXISTS events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n ts INTEGER NOT NULL,\n type TEXT NOT NULL,\n tool TEXT,\n method TEXT,\n ms INTEGER,\n ok INTEGER,\n error TEXT,\n meta TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_events_ts ON events (ts);\nCREATE INDEX IF NOT EXISTS idx_events_type ON events (type);\nCREATE INDEX IF NOT EXISTS idx_events_tool ON events (tool);\n\nCREATE TABLE IF NOT EXISTS logs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n ts INTEGER NOT NULL,\n level TEXT NOT NULL,\n msg TEXT NOT NULL,\n data TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_logs_ts ON logs (ts);\nCREATE INDEX IF NOT EXISTS idx_logs_level ON logs (level);\n\nCREATE TABLE IF NOT EXISTS config (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS config_audit (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n ts INTEGER NOT NULL,\n key TEXT NOT NULL,\n old_value TEXT,\n new_value TEXT NOT NULL,\n actor TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_audit_key ON config_audit (key);\n`;\n"]}
|
|
1
|
+
{"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../../src/server/storage/sqlite.ts"],"names":[],"mappings":"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","sourcesContent":["import type DatabaseConstructor from \"better-sqlite3\";\nimport type { Database, Statement } from \"better-sqlite3\";\nimport type {\n AnalyticsEvent,\n ConfigAuditEntry,\n EventQuery,\n LogEntry,\n LogQuery,\n StorageAdapter,\n StorageAdapterOptions,\n} from \"./types.js\";\n\n/** Default on-disk database path. Overridable via `ENPILINK_DB_PATH`. */\nexport const DEFAULT_DB_PATH = \"./enpilink.db\";\n\n/**\n * Embedded-SQLite {@link StorageAdapter}, backed by `better-sqlite3`.\n *\n * `better-sqlite3` was chosen over `node:sqlite` (experimental, warns on every\n * load) and `@libsql/client` (async, remote-oriented, heavier): it is mature,\n * synchronous (simple adapter code), and ships prebuilt binaries for both\n * macOS-arm64 and linux-x64, so `pnpm install` succeeds without node-gyp.\n *\n * Tables: `events`, `logs`, `config`, `config_audit`. Config values are stored\n * as opaque JSON — no special-casing of keys. The DB path is gitignored.\n */\nexport class SqliteStorageAdapter implements StorageAdapter {\n private readonly path: string;\n private db: Database | null = null;\n private stmts: {\n insertEvent: Statement;\n insertLog: Statement;\n getConfig: Statement;\n upsertConfig: Statement;\n deleteConfig: Statement;\n insertAudit: Statement;\n allConfig: Statement;\n } | null = null;\n\n constructor(opts?: StorageAdapterOptions) {\n this.path = opts?.path ?? DEFAULT_DB_PATH;\n }\n\n async init(): Promise<void> {\n if (this.db) {\n return;\n }\n // Dynamic import keeps the native module out of the load path until a\n // sqlite adapter is actually instantiated (memory-only users pay nothing).\n const mod = await import(\"better-sqlite3\");\n const Database = (mod.default ??\n mod) as unknown as typeof DatabaseConstructor;\n const db = new Database(this.path);\n db.pragma(\"journal_mode = WAL\");\n db.exec(SCHEMA);\n this.db = db;\n this.stmts = {\n insertEvent: db.prepare(\n \"INSERT INTO events (ts, type, tool, method, ms, ok, error, meta) VALUES (@ts, @type, @tool, @method, @ms, @ok, @error, @meta)\",\n ),\n insertLog: db.prepare(\n \"INSERT INTO logs (ts, level, msg, data) VALUES (@ts, @level, @msg, @data)\",\n ),\n getConfig: db.prepare(\"SELECT value FROM config WHERE key = ?\"),\n upsertConfig: db.prepare(\n \"INSERT INTO config (key, value) VALUES (@key, @value) ON CONFLICT(key) DO UPDATE SET value = @value\",\n ),\n deleteConfig: db.prepare(\"DELETE FROM config WHERE key = ?\"),\n insertAudit: db.prepare(\n \"INSERT INTO config_audit (ts, key, old_value, new_value, actor) VALUES (@ts, @key, @old_value, @new_value, @actor)\",\n ),\n allConfig: db.prepare(\"SELECT key, value FROM config\"),\n };\n }\n\n private require() {\n if (!this.db || !this.stmts) {\n throw new Error(\"SqliteStorageAdapter: call init() before use\");\n }\n return { db: this.db, stmts: this.stmts };\n }\n\n async recordEvent(e: AnalyticsEvent): Promise<void> {\n const { stmts } = this.require();\n stmts.insertEvent.run({\n ts: e.ts,\n type: e.type,\n tool: e.tool ?? null,\n method: e.method ?? null,\n ms: e.ms ?? null,\n ok: e.ok === undefined ? null : e.ok ? 1 : 0,\n error: e.error ?? null,\n meta: e.meta === undefined ? null : JSON.stringify(e.meta),\n });\n }\n\n async queryEvents(f: EventQuery = {}): Promise<AnalyticsEvent[]> {\n const { db } = this.require();\n const where: string[] = [];\n const params: Record<string, unknown> = {};\n if (f.since !== undefined) {\n where.push(\"ts >= @since\");\n params.since = f.since;\n }\n if (f.type !== undefined) {\n where.push(\"type = @type\");\n params.type = f.type;\n }\n if (f.tool !== undefined) {\n where.push(\"tool = @tool\");\n params.tool = f.tool;\n }\n const clause = where.length ? `WHERE ${where.join(\" AND \")}` : \"\";\n const limit = limitClause(f.limit, params);\n const rows = db\n .prepare(\n `SELECT ts, type, tool, method, ms, ok, error, meta FROM events ${clause} ORDER BY id DESC ${limit}`,\n )\n .all(params) as EventRow[];\n return rows.map(rowToEvent);\n }\n\n async appendLog(l: LogEntry): Promise<void> {\n const { stmts } = this.require();\n stmts.insertLog.run({\n ts: l.ts,\n level: l.level,\n msg: l.msg,\n data: l.data === undefined ? null : JSON.stringify(l.data),\n });\n }\n\n async queryLogs(f: LogQuery = {}): Promise<LogEntry[]> {\n const { db } = this.require();\n const where: string[] = [];\n const params: Record<string, unknown> = {};\n if (f.since !== undefined) {\n where.push(\"ts >= @since\");\n params.since = f.since;\n }\n if (f.level !== undefined) {\n where.push(\"level = @level\");\n params.level = f.level;\n }\n const clause = where.length ? `WHERE ${where.join(\" AND \")}` : \"\";\n const limit = limitClause(f.limit, params);\n const rows = db\n .prepare(\n `SELECT ts, level, msg, data FROM logs ${clause} ORDER BY id DESC ${limit}`,\n )\n .all(params) as LogRow[];\n return rows.map(rowToLog);\n }\n\n async getConfig(key: string): Promise<unknown> {\n const { stmts } = this.require();\n const row = stmts.getConfig.get(key) as { value: string } | undefined;\n return row === undefined ? undefined : JSON.parse(row.value);\n }\n\n async setConfig(key: string, value: unknown, actor?: string): Promise<void> {\n const { db, stmts } = this.require();\n const serialized = JSON.stringify(value);\n // Atomically read-old → write-new → write-audit.\n const tx = db.transaction(() => {\n const existing = stmts.getConfig.get(key) as\n | { value: string }\n | undefined;\n stmts.upsertConfig.run({ key, value: serialized });\n stmts.insertAudit.run({\n ts: Date.now(),\n key,\n old_value: existing === undefined ? null : existing.value,\n new_value: serialized,\n actor: actor ?? \"system\",\n });\n });\n tx();\n }\n\n async clearConfig(key: string, actor?: string): Promise<void> {\n const { db, stmts } = this.require();\n const tx = db.transaction(() => {\n const existing = stmts.getConfig.get(key) as\n | { value: string }\n | undefined;\n if (existing === undefined) {\n return;\n }\n stmts.deleteConfig.run(key);\n // `new_value` is NOT NULL; a reset stores JSON `null` (which getAuditLog\n // parses back to `null`) to represent \"reset to default\".\n stmts.insertAudit.run({\n ts: Date.now(),\n key,\n old_value: existing.value,\n new_value: \"null\",\n actor: actor ?? \"system\",\n });\n });\n tx();\n }\n\n async allConfig(): Promise<Record<string, unknown>> {\n const { stmts } = this.require();\n const rows = stmts.allConfig.all() as { key: string; value: string }[];\n const out: Record<string, unknown> = {};\n for (const r of rows) {\n out[r.key] = JSON.parse(r.value);\n }\n return out;\n }\n\n async getConfigAudit(): Promise<ConfigAuditEntry[]> {\n return this.getAuditLog();\n }\n\n async close(): Promise<void> {\n if (this.db) {\n this.db.close();\n this.db = null;\n this.stmts = null;\n }\n }\n\n /** Audit trail of config writes (most recent first). Synchronous helper. */\n getAuditLog(): ConfigAuditEntry[] {\n const { db } = this.require();\n const rows = db\n .prepare(\n \"SELECT ts, key, old_value, new_value, actor FROM config_audit ORDER BY id DESC\",\n )\n .all() as AuditRow[];\n return rows.map((r) => ({\n ts: r.ts,\n key: r.key,\n oldValue: r.old_value === null ? undefined : JSON.parse(r.old_value),\n newValue: JSON.parse(r.new_value),\n actor: r.actor,\n }));\n }\n}\n\nfunction limitClause(\n limit: number | undefined,\n params: Record<string, unknown>,\n): string {\n if (limit === undefined || limit < 0) {\n return \"\";\n }\n params.limit = limit;\n return \"LIMIT @limit\";\n}\n\ninterface EventRow {\n ts: number;\n type: string;\n tool: string | null;\n method: string | null;\n ms: number | null;\n ok: number | null;\n error: string | null;\n meta: string | null;\n}\n\ninterface LogRow {\n ts: number;\n level: string;\n msg: string;\n data: string | null;\n}\n\ninterface AuditRow {\n ts: number;\n key: string;\n old_value: string | null;\n new_value: string;\n actor: string;\n}\n\nfunction rowToEvent(r: EventRow): AnalyticsEvent {\n const e: AnalyticsEvent = { ts: r.ts, type: r.type };\n if (r.tool !== null) {\n e.tool = r.tool;\n }\n if (r.method !== null) {\n e.method = r.method;\n }\n if (r.ms !== null) {\n e.ms = r.ms;\n }\n if (r.ok !== null) {\n e.ok = r.ok === 1;\n }\n if (r.error !== null) {\n e.error = r.error;\n }\n if (r.meta !== null) {\n e.meta = JSON.parse(r.meta);\n }\n return e;\n}\n\nfunction rowToLog(r: LogRow): LogEntry {\n const l: LogEntry = {\n ts: r.ts,\n level: r.level as LogEntry[\"level\"],\n msg: r.msg,\n };\n if (r.data !== null) {\n l.data = JSON.parse(r.data);\n }\n return l;\n}\n\nconst SCHEMA = `\nCREATE TABLE IF NOT EXISTS events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n ts INTEGER NOT NULL,\n type TEXT NOT NULL,\n tool TEXT,\n method TEXT,\n ms INTEGER,\n ok INTEGER,\n error TEXT,\n meta TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_events_ts ON events (ts);\nCREATE INDEX IF NOT EXISTS idx_events_type ON events (type);\nCREATE INDEX IF NOT EXISTS idx_events_tool ON events (tool);\n\nCREATE TABLE IF NOT EXISTS logs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n ts INTEGER NOT NULL,\n level TEXT NOT NULL,\n msg TEXT NOT NULL,\n data TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_logs_ts ON logs (ts);\nCREATE INDEX IF NOT EXISTS idx_logs_level ON logs (level);\n\nCREATE TABLE IF NOT EXISTS config (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS config_audit (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n ts INTEGER NOT NULL,\n key TEXT NOT NULL,\n old_value TEXT,\n new_value TEXT NOT NULL,\n actor TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_audit_key ON config_audit (key);\n`;\n"]}
|
|
@@ -105,6 +105,23 @@ describe("SqliteStorageAdapter", () => {
|
|
|
105
105
|
actor: "system",
|
|
106
106
|
});
|
|
107
107
|
});
|
|
108
|
+
it("clearConfig removes the row and audits the reset", async () => {
|
|
109
|
+
await store.setConfig("k", "v1");
|
|
110
|
+
await store.clearConfig("k", "bob");
|
|
111
|
+
expect(await store.getConfig("k")).toBeUndefined();
|
|
112
|
+
expect(await store.allConfig()).toEqual({});
|
|
113
|
+
const audit = await store.getConfigAudit();
|
|
114
|
+
expect(audit[0]).toMatchObject({
|
|
115
|
+
key: "k",
|
|
116
|
+
oldValue: "v1",
|
|
117
|
+
newValue: null,
|
|
118
|
+
actor: "bob",
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
it("clearConfig is a no-op (no audit) when the key was never set", async () => {
|
|
122
|
+
await store.clearConfig("absent");
|
|
123
|
+
expect(await store.getConfigAudit()).toHaveLength(0);
|
|
124
|
+
});
|
|
108
125
|
});
|
|
109
126
|
describe("persistence", () => {
|
|
110
127
|
it("retains data across close + reopen on the same path", async () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite.test.js","sourceRoot":"","sources":["../../../src/server/storage/sqlite.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdtempSync, rmSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { afterEach, beforeEach, describe, expect, it } from \"vitest\";\nimport { SqliteStorageAdapter } from \"./sqlite.js\";\n\ndescribe(\"SqliteStorageAdapter\", () => {\n let dir: string;\n let dbPath: string;\n let store: SqliteStorageAdapter;\n\n beforeEach(async () => {\n dir = mkdtempSync(join(tmpdir(), \"enpilink-sqlite-\"));\n dbPath = join(dir, \"test.db\");\n store = new SqliteStorageAdapter({ path: dbPath });\n await store.init();\n });\n\n afterEach(async () => {\n await store.close();\n rmSync(dir, { recursive: true, force: true });\n });\n\n describe(\"events\", () => {\n it(\"records and queries events with since/tool/type/limit filters\", async () => {\n await store.recordEvent({\n ts: 1,\n type: \"tool_call\",\n tool: \"a\",\n ms: 5,\n ok: true,\n });\n await store.recordEvent({\n ts: 5,\n type: \"tool_call\",\n tool: \"a\",\n ms: 9,\n ok: false,\n error: \"boom\",\n });\n await store.recordEvent({ ts: 10, type: \"ping\", tool: \"b\" });\n\n const all = await store.queryEvents({});\n expect(all.map((e) => e.ts)).toEqual([10, 5, 1]);\n expect(await store.queryEvents({ since: 5 })).toHaveLength(2);\n expect(await store.queryEvents({ tool: \"a\" })).toHaveLength(2);\n expect(await store.queryEvents({ type: \"ping\" })).toHaveLength(1);\n expect(await store.queryEvents({ limit: 1 })).toHaveLength(1);\n\n const errored = (await store.queryEvents({ since: 5, tool: \"a\" }))[0];\n expect(errored).toMatchObject({ ok: false, error: \"boom\", ms: 9 });\n });\n\n it(\"round-trips the meta object\", async () => {\n await store.recordEvent({\n ts: 1,\n type: \"tool_call\",\n meta: { user: \"x\", nested: { n: 1 } },\n });\n const [e] = await store.queryEvents({});\n expect(e?.meta).toEqual({ user: \"x\", nested: { n: 1 } });\n });\n });\n\n describe(\"logs\", () => {\n it(\"appends and queries logs with level/limit/since\", async () => {\n await store.appendLog({ ts: 1, level: \"info\", msg: \"one\" });\n await store.appendLog({\n ts: 2,\n level: \"error\",\n msg: \"two\",\n data: { code: 42 },\n });\n await store.appendLog({ ts: 3, level: \"info\", msg: \"three\" });\n\n const all = await store.queryLogs({});\n expect(all.map((l) => l.msg)).toEqual([\"three\", \"two\", \"one\"]);\n expect(await store.queryLogs({ level: \"error\" })).toHaveLength(1);\n expect(await store.queryLogs({ since: 2 })).toHaveLength(2);\n expect(await store.queryLogs({ limit: 1 })).toHaveLength(1);\n expect((await store.queryLogs({ level: \"error\" }))[0]?.data).toEqual({\n code: 42,\n });\n });\n });\n\n describe(\"config + audit\", () => {\n it(\"get/set/all round-trips opaque values\", async () => {\n expect(await store.getConfig(\"missing\")).toBeUndefined();\n await store.setConfig(\"analytics.enabled\", true);\n await store.setConfig(\"retention\", { days: 7 });\n expect(await store.getConfig(\"analytics.enabled\")).toBe(true);\n expect(await store.getConfig(\"retention\")).toEqual({ days: 7 });\n expect(await store.allConfig()).toEqual({\n \"analytics.enabled\": true,\n retention: { days: 7 },\n });\n });\n\n it(\"writes a config_audit row on setConfig with old → new + actor\", async () => {\n await store.setConfig(\"k\", \"v1\");\n await store.setConfig(\"k\", \"v2\", \"alice\");\n const audit = store.getAuditLog();\n expect(audit).toHaveLength(2);\n // Most recent first.\n expect(audit[0]).toMatchObject({\n key: \"k\",\n oldValue: \"v1\",\n newValue: \"v2\",\n actor: \"alice\",\n });\n expect(audit[1]).toMatchObject({\n key: \"k\",\n oldValue: undefined,\n newValue: \"v1\",\n actor: \"system\",\n });\n });\n });\n\n describe(\"persistence\", () => {\n it(\"retains data across close + reopen on the same path\", async () => {\n await store.recordEvent({ ts: 1, type: \"tool_call\", tool: \"persisted\" });\n await store.appendLog({ ts: 1, level: \"warning\", msg: \"stays\" });\n await store.setConfig(\"flag\", { on: true }, \"bob\");\n await store.close();\n\n const reopened = new SqliteStorageAdapter({ path: dbPath });\n await reopened.init();\n try {\n const events = await reopened.queryEvents({});\n expect(events).toHaveLength(1);\n expect(events[0]?.tool).toBe(\"persisted\");\n\n const logs = await reopened.queryLogs({});\n expect(logs[0]?.msg).toBe(\"stays\");\n\n expect(await reopened.getConfig(\"flag\")).toEqual({ on: true });\n expect(reopened.getAuditLog()).toHaveLength(1);\n expect(reopened.getAuditLog()[0]).toMatchObject({ actor: \"bob\" });\n } finally {\n await reopened.close();\n }\n });\n });\n});\n"]}
|
|
1
|
+
{"version":3,"file":"sqlite.test.js","sourceRoot":"","sources":["../../../src/server/storage/sqlite.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdtempSync, rmSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { afterEach, beforeEach, describe, expect, it } from \"vitest\";\nimport { SqliteStorageAdapter } from \"./sqlite.js\";\n\ndescribe(\"SqliteStorageAdapter\", () => {\n let dir: string;\n let dbPath: string;\n let store: SqliteStorageAdapter;\n\n beforeEach(async () => {\n dir = mkdtempSync(join(tmpdir(), \"enpilink-sqlite-\"));\n dbPath = join(dir, \"test.db\");\n store = new SqliteStorageAdapter({ path: dbPath });\n await store.init();\n });\n\n afterEach(async () => {\n await store.close();\n rmSync(dir, { recursive: true, force: true });\n });\n\n describe(\"events\", () => {\n it(\"records and queries events with since/tool/type/limit filters\", async () => {\n await store.recordEvent({\n ts: 1,\n type: \"tool_call\",\n tool: \"a\",\n ms: 5,\n ok: true,\n });\n await store.recordEvent({\n ts: 5,\n type: \"tool_call\",\n tool: \"a\",\n ms: 9,\n ok: false,\n error: \"boom\",\n });\n await store.recordEvent({ ts: 10, type: \"ping\", tool: \"b\" });\n\n const all = await store.queryEvents({});\n expect(all.map((e) => e.ts)).toEqual([10, 5, 1]);\n expect(await store.queryEvents({ since: 5 })).toHaveLength(2);\n expect(await store.queryEvents({ tool: \"a\" })).toHaveLength(2);\n expect(await store.queryEvents({ type: \"ping\" })).toHaveLength(1);\n expect(await store.queryEvents({ limit: 1 })).toHaveLength(1);\n\n const errored = (await store.queryEvents({ since: 5, tool: \"a\" }))[0];\n expect(errored).toMatchObject({ ok: false, error: \"boom\", ms: 9 });\n });\n\n it(\"round-trips the meta object\", async () => {\n await store.recordEvent({\n ts: 1,\n type: \"tool_call\",\n meta: { user: \"x\", nested: { n: 1 } },\n });\n const [e] = await store.queryEvents({});\n expect(e?.meta).toEqual({ user: \"x\", nested: { n: 1 } });\n });\n });\n\n describe(\"logs\", () => {\n it(\"appends and queries logs with level/limit/since\", async () => {\n await store.appendLog({ ts: 1, level: \"info\", msg: \"one\" });\n await store.appendLog({\n ts: 2,\n level: \"error\",\n msg: \"two\",\n data: { code: 42 },\n });\n await store.appendLog({ ts: 3, level: \"info\", msg: \"three\" });\n\n const all = await store.queryLogs({});\n expect(all.map((l) => l.msg)).toEqual([\"three\", \"two\", \"one\"]);\n expect(await store.queryLogs({ level: \"error\" })).toHaveLength(1);\n expect(await store.queryLogs({ since: 2 })).toHaveLength(2);\n expect(await store.queryLogs({ limit: 1 })).toHaveLength(1);\n expect((await store.queryLogs({ level: \"error\" }))[0]?.data).toEqual({\n code: 42,\n });\n });\n });\n\n describe(\"config + audit\", () => {\n it(\"get/set/all round-trips opaque values\", async () => {\n expect(await store.getConfig(\"missing\")).toBeUndefined();\n await store.setConfig(\"analytics.enabled\", true);\n await store.setConfig(\"retention\", { days: 7 });\n expect(await store.getConfig(\"analytics.enabled\")).toBe(true);\n expect(await store.getConfig(\"retention\")).toEqual({ days: 7 });\n expect(await store.allConfig()).toEqual({\n \"analytics.enabled\": true,\n retention: { days: 7 },\n });\n });\n\n it(\"writes a config_audit row on setConfig with old → new + actor\", async () => {\n await store.setConfig(\"k\", \"v1\");\n await store.setConfig(\"k\", \"v2\", \"alice\");\n const audit = store.getAuditLog();\n expect(audit).toHaveLength(2);\n // Most recent first.\n expect(audit[0]).toMatchObject({\n key: \"k\",\n oldValue: \"v1\",\n newValue: \"v2\",\n actor: \"alice\",\n });\n expect(audit[1]).toMatchObject({\n key: \"k\",\n oldValue: undefined,\n newValue: \"v1\",\n actor: \"system\",\n });\n });\n\n it(\"clearConfig removes the row and audits the reset\", async () => {\n await store.setConfig(\"k\", \"v1\");\n await store.clearConfig(\"k\", \"bob\");\n expect(await store.getConfig(\"k\")).toBeUndefined();\n expect(await store.allConfig()).toEqual({});\n const audit = await store.getConfigAudit();\n expect(audit[0]).toMatchObject({\n key: \"k\",\n oldValue: \"v1\",\n newValue: null,\n actor: \"bob\",\n });\n });\n\n it(\"clearConfig is a no-op (no audit) when the key was never set\", async () => {\n await store.clearConfig(\"absent\");\n expect(await store.getConfigAudit()).toHaveLength(0);\n });\n });\n\n describe(\"persistence\", () => {\n it(\"retains data across close + reopen on the same path\", async () => {\n await store.recordEvent({ ts: 1, type: \"tool_call\", tool: \"persisted\" });\n await store.appendLog({ ts: 1, level: \"warning\", msg: \"stays\" });\n await store.setConfig(\"flag\", { on: true }, \"bob\");\n await store.close();\n\n const reopened = new SqliteStorageAdapter({ path: dbPath });\n await reopened.init();\n try {\n const events = await reopened.queryEvents({});\n expect(events).toHaveLength(1);\n expect(events[0]?.tool).toBe(\"persisted\");\n\n const logs = await reopened.queryLogs({});\n expect(logs[0]?.msg).toBe(\"stays\");\n\n expect(await reopened.getConfig(\"flag\")).toEqual({ on: true });\n expect(reopened.getAuditLog()).toHaveLength(1);\n expect(reopened.getAuditLog()[0]).toMatchObject({ actor: \"bob\" });\n } finally {\n await reopened.close();\n }\n });\n });\n});\n"]}
|
|
@@ -84,6 +84,12 @@ export interface StorageAdapter {
|
|
|
84
84
|
getConfig(key: string): Promise<unknown>;
|
|
85
85
|
/** Write a config value AND append a `config_audit` row (old → new). */
|
|
86
86
|
setConfig(key: string, value: unknown, actor?: string): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Remove a config override (reset to default) AND append a `config_audit`
|
|
89
|
+
* row (old → undefined). A no-op when the key was not set. After clearing,
|
|
90
|
+
* config resolution falls back to file/env/default for the key.
|
|
91
|
+
*/
|
|
92
|
+
clearConfig(key: string, actor?: string): Promise<void>;
|
|
87
93
|
/** Read all config as a plain object. */
|
|
88
94
|
allConfig(): Promise<Record<string, unknown>>;
|
|
89
95
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/server/storage/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG","sourcesContent":["/**\n * Storage layer types for enpilink's admin / observability / config plane.\n *\n * The {@link StorageAdapter} is the single pluggable persistence surface used by\n * analytics (events), log capture (logs), and the config/admin layer (config +\n * audit). Built-in adapters: `memory` (dev default, zero deps) and `sqlite`\n * (prod default, embedded). Custom adapters register via\n * `registerStorageAdapter`.\n */\n\n/**\n * A single analytics event. Emitted (in M2) by the analytics middleware for\n * each MCP request — most commonly a `tool_call`. `type` is open-ended so other\n * event kinds can be recorded without a schema change.\n */\nexport interface AnalyticsEvent {\n /** Unix epoch milliseconds when the event occurred. */\n ts: number;\n /** Event kind. `\"tool_call\"` is the built-in, but any string is allowed. */\n type: \"tool_call\" | string;\n /** Tool name (for `tools/call`), from `params.name`. */\n tool?: string;\n /** MCP method, e.g. `\"tools/call\"`. */\n method?: string;\n /** Duration of the call in milliseconds. */\n ms?: number;\n /** Whether the call succeeded (false when the result was an error). */\n ok?: boolean;\n /** Error message, when the call failed. */\n error?: string;\n /** Arbitrary extra structured data. */\n meta?: Record<string, unknown>;\n}\n\n/** A captured server log line. */\nexport interface LogEntry {\n /** Unix epoch milliseconds. */\n ts: number;\n /** Log severity. */\n level: \"debug\" | \"info\" | \"warning\" | \"error\";\n /** The log message. */\n msg: string;\n /** Optional structured payload. */\n data?: unknown;\n}\n\n/** Filter for {@link StorageAdapter.queryEvents}. */\nexport interface EventQuery {\n /** Only events with `ts >= since` (epoch ms). */\n since?: number;\n /** Only events whose `type` matches. */\n type?: string;\n /** Only events whose `tool` matches. */\n tool?: string;\n /** Maximum number of events returned (most recent first). */\n limit?: number;\n}\n\n/** Filter for {@link StorageAdapter.queryLogs}. */\nexport interface LogQuery {\n /** Only logs with `ts >= since` (epoch ms). */\n since?: number;\n /** Only logs whose `level` matches. */\n level?: string;\n /** Maximum number of logs returned (most recent first). */\n limit?: number;\n}\n\n/**\n * Pluggable persistence interface backing the observability + config plane.\n *\n * Implementations MUST:\n * - treat config values as opaque (no special-casing of keys/secrets);\n * - write a `config_audit` entry on every {@link setConfig} call;\n * - return query results most-recent-first.\n */\nexport interface StorageAdapter {\n /** Open connections / create schema. Safe to call once before use. */\n init(): Promise<void>;\n /** Record an analytics event. Must never throw into the caller's hot path. */\n recordEvent(e: AnalyticsEvent): Promise<void>;\n /** Query events, most recent first. */\n queryEvents(f: EventQuery): Promise<AnalyticsEvent[]>;\n /** Append a captured log line. */\n appendLog(l: LogEntry): Promise<void>;\n /** Query logs, most recent first. */\n queryLogs(f: LogQuery): Promise<LogEntry[]>;\n /** Read a single config value (or `undefined` if unset). */\n getConfig(key: string): Promise<unknown>;\n /** Write a config value AND append a `config_audit` row (old → new). */\n setConfig(key: string, value: unknown, actor?: string): Promise<void>;\n /** Read all config as a plain object. */\n allConfig(): Promise<Record<string, unknown>>;\n /**\n * Read the config-change audit trail, most recent first. Surfaces the\n * `config_audit` rows that {@link setConfig} writes, for the admin UI's\n * change history.\n */\n getConfigAudit(): Promise<ConfigAuditEntry[]>;\n /** Release resources / close connections. */\n close(): Promise<void>;\n}\n\n/** A single config-change audit record. */\nexport interface ConfigAuditEntry {\n /** Unix epoch milliseconds. */\n ts: number;\n /** Config key that changed. */\n key: string;\n /** Previous value (JSON-serializable), or `undefined` if newly set. */\n oldValue: unknown;\n /** New value (JSON-serializable). */\n newValue: unknown;\n /** Who made the change (defaults to `\"system\"`). */\n actor: string;\n}\n\n/** Options passed to a storage adapter factory. */\nexport interface StorageAdapterOptions {\n /** Ring-buffer capacity (memory) — max events/logs retained. */\n cap?: number;\n /** Database path (sqlite). */\n path?: string;\n}\n\n/** Factory signature for {@link registerStorageAdapter}. */\nexport type StorageAdapterFactory = (\n opts?: StorageAdapterOptions,\n) => StorageAdapter;\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/server/storage/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG","sourcesContent":["/**\n * Storage layer types for enpilink's admin / observability / config plane.\n *\n * The {@link StorageAdapter} is the single pluggable persistence surface used by\n * analytics (events), log capture (logs), and the config/admin layer (config +\n * audit). Built-in adapters: `memory` (dev default, zero deps) and `sqlite`\n * (prod default, embedded). Custom adapters register via\n * `registerStorageAdapter`.\n */\n\n/**\n * A single analytics event. Emitted (in M2) by the analytics middleware for\n * each MCP request — most commonly a `tool_call`. `type` is open-ended so other\n * event kinds can be recorded without a schema change.\n */\nexport interface AnalyticsEvent {\n /** Unix epoch milliseconds when the event occurred. */\n ts: number;\n /** Event kind. `\"tool_call\"` is the built-in, but any string is allowed. */\n type: \"tool_call\" | string;\n /** Tool name (for `tools/call`), from `params.name`. */\n tool?: string;\n /** MCP method, e.g. `\"tools/call\"`. */\n method?: string;\n /** Duration of the call in milliseconds. */\n ms?: number;\n /** Whether the call succeeded (false when the result was an error). */\n ok?: boolean;\n /** Error message, when the call failed. */\n error?: string;\n /** Arbitrary extra structured data. */\n meta?: Record<string, unknown>;\n}\n\n/** A captured server log line. */\nexport interface LogEntry {\n /** Unix epoch milliseconds. */\n ts: number;\n /** Log severity. */\n level: \"debug\" | \"info\" | \"warning\" | \"error\";\n /** The log message. */\n msg: string;\n /** Optional structured payload. */\n data?: unknown;\n}\n\n/** Filter for {@link StorageAdapter.queryEvents}. */\nexport interface EventQuery {\n /** Only events with `ts >= since` (epoch ms). */\n since?: number;\n /** Only events whose `type` matches. */\n type?: string;\n /** Only events whose `tool` matches. */\n tool?: string;\n /** Maximum number of events returned (most recent first). */\n limit?: number;\n}\n\n/** Filter for {@link StorageAdapter.queryLogs}. */\nexport interface LogQuery {\n /** Only logs with `ts >= since` (epoch ms). */\n since?: number;\n /** Only logs whose `level` matches. */\n level?: string;\n /** Maximum number of logs returned (most recent first). */\n limit?: number;\n}\n\n/**\n * Pluggable persistence interface backing the observability + config plane.\n *\n * Implementations MUST:\n * - treat config values as opaque (no special-casing of keys/secrets);\n * - write a `config_audit` entry on every {@link setConfig} call;\n * - return query results most-recent-first.\n */\nexport interface StorageAdapter {\n /** Open connections / create schema. Safe to call once before use. */\n init(): Promise<void>;\n /** Record an analytics event. Must never throw into the caller's hot path. */\n recordEvent(e: AnalyticsEvent): Promise<void>;\n /** Query events, most recent first. */\n queryEvents(f: EventQuery): Promise<AnalyticsEvent[]>;\n /** Append a captured log line. */\n appendLog(l: LogEntry): Promise<void>;\n /** Query logs, most recent first. */\n queryLogs(f: LogQuery): Promise<LogEntry[]>;\n /** Read a single config value (or `undefined` if unset). */\n getConfig(key: string): Promise<unknown>;\n /** Write a config value AND append a `config_audit` row (old → new). */\n setConfig(key: string, value: unknown, actor?: string): Promise<void>;\n /**\n * Remove a config override (reset to default) AND append a `config_audit`\n * row (old → undefined). A no-op when the key was not set. After clearing,\n * config resolution falls back to file/env/default for the key.\n */\n clearConfig(key: string, actor?: string): Promise<void>;\n /** Read all config as a plain object. */\n allConfig(): Promise<Record<string, unknown>>;\n /**\n * Read the config-change audit trail, most recent first. Surfaces the\n * `config_audit` rows that {@link setConfig} writes, for the admin UI's\n * change history.\n */\n getConfigAudit(): Promise<ConfigAuditEntry[]>;\n /** Release resources / close connections. */\n close(): Promise<void>;\n}\n\n/** A single config-change audit record. */\nexport interface ConfigAuditEntry {\n /** Unix epoch milliseconds. */\n ts: number;\n /** Config key that changed. */\n key: string;\n /** Previous value (JSON-serializable), or `undefined` if newly set. */\n oldValue: unknown;\n /** New value (JSON-serializable). */\n newValue: unknown;\n /** Who made the change (defaults to `\"system\"`). */\n actor: string;\n}\n\n/** Options passed to a storage adapter factory. */\nexport interface StorageAdapterOptions {\n /** Ring-buffer capacity (memory) — max events/logs retained. */\n cap?: number;\n /** Database path (sqlite). */\n path?: string;\n}\n\n/** Factory signature for {@link registerStorageAdapter}. */\nexport type StorageAdapterFactory = (\n opts?: StorageAdapterOptions,\n) => StorageAdapter;\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "enpilink",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "enpilink is a framework for building ChatGPT and MCP Apps",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"react": ">=18.0.0",
|
|
48
48
|
"react-dom": ">=18.0.0",
|
|
49
49
|
"vite": ">=7.3.1",
|
|
50
|
-
"@enpilink/console": "1.0.
|
|
50
|
+
"@enpilink/console": "1.0.3"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@babel/core": "^7.29.0",
|