@peers-app/peers-sdk 0.19.6 → 0.19.7
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.
|
@@ -33,16 +33,35 @@ export declare const consoleLogSchema: z.ZodObject<{
|
|
|
33
33
|
stackTrace?: string | undefined;
|
|
34
34
|
}>;
|
|
35
35
|
export type IConsoleLog = z.infer<typeof consoleLogSchema>;
|
|
36
|
+
/** Cutoff within this many ms of `Date.now()` is treated as "clear all" (manual clear). */
|
|
37
|
+
export declare const CLEAR_ALL_CUTOFF_SLACK_MS = 5000;
|
|
38
|
+
/**
|
|
39
|
+
* Returns whether a cutoff timestamp should trigger a full-table clear (drop + recreate)
|
|
40
|
+
* rather than a conditional delete. Used when the UI calls `deleteOldLogs(Date.now())`.
|
|
41
|
+
* @param cutoffTimestamp - Millisecond cutoff passed to `deleteOldLogs`
|
|
42
|
+
*/
|
|
43
|
+
export declare function isClearAllCutoff(cutoffTimestamp: number): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* System table for cross-process console log storage with automatic TTL cleanup.
|
|
46
|
+
*/
|
|
36
47
|
export declare class ConsoleLogsTable extends Table<IConsoleLog> {
|
|
37
48
|
readonly LOG_TTL: number;
|
|
38
49
|
readonly LOG_CLEANUP_INTERVAL: number;
|
|
39
50
|
constructor(metaData: ITableMetaData, deps: ITableDependencies);
|
|
40
51
|
private logCleanupInitialized;
|
|
52
|
+
/** Schedules startup and daily cleanup of logs older than {@link LOG_TTL}. */
|
|
41
53
|
initializeLogCleanup(): Promise<void>;
|
|
54
|
+
private resolveSqlDataSource;
|
|
55
|
+
/**
|
|
56
|
+
* Remove console logs older than `cutoffTimestamp`.
|
|
57
|
+
*
|
|
58
|
+
* When the cutoff is near `Date.now()` (within {@link CLEAR_ALL_CUTOFF_SLACK_MS}), uses
|
|
59
|
+
* drop + recreate for a fast full clear. Otherwise deletes rows with `timestamp < cutoff`.
|
|
60
|
+
* @param cutoffTimestamp - Defaults to seven days ago for scheduled TTL cleanup
|
|
61
|
+
*/
|
|
42
62
|
deleteOldLogs(cutoffTimestamp?: number): Promise<void>;
|
|
43
63
|
}
|
|
44
64
|
/**
|
|
45
|
-
*
|
|
46
|
-
* that the table be accessed in an asynchronous way
|
|
65
|
+
* Returns the user's personal {@link ConsoleLogsTable} (logs are not synced across devices).
|
|
47
66
|
*/
|
|
48
67
|
export declare function ConsoleLogs(): Promise<ConsoleLogsTable>;
|
|
@@ -34,7 +34,8 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
|
|
|
34
34
|
done = true;
|
|
35
35
|
};
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
exports.ConsoleLogsTable = exports.consoleLogSchema = void 0;
|
|
37
|
+
exports.ConsoleLogsTable = exports.CLEAR_ALL_CUTOFF_SLACK_MS = exports.consoleLogSchema = void 0;
|
|
38
|
+
exports.isClearAllCutoff = isClearAllCutoff;
|
|
38
39
|
exports.ConsoleLogs = ConsoleLogs;
|
|
39
40
|
const zod_1 = require("zod");
|
|
40
41
|
const user_context_singleton_1 = require("../context/user-context-singleton");
|
|
@@ -57,6 +58,8 @@ exports.consoleLogSchema = zod_1.z.object({
|
|
|
57
58
|
context: zod_types_1.zodAnyObjectOrArray.optional().describe("Additional structured context data"),
|
|
58
59
|
stackTrace: zod_1.z.string().optional().describe("Stack trace for errors"),
|
|
59
60
|
});
|
|
61
|
+
/** Cutoff within this many ms of `Date.now()` is treated as "clear all" (manual clear). */
|
|
62
|
+
exports.CLEAR_ALL_CUTOFF_SLACK_MS = 5000;
|
|
60
63
|
const metaData = {
|
|
61
64
|
name: "ConsoleLogs",
|
|
62
65
|
description: "System-wide console log entries with cross-process visibility.",
|
|
@@ -70,6 +73,17 @@ const metaData = {
|
|
|
70
73
|
{ fields: ["processInstanceId"] },
|
|
71
74
|
],
|
|
72
75
|
};
|
|
76
|
+
/**
|
|
77
|
+
* Returns whether a cutoff timestamp should trigger a full-table clear (drop + recreate)
|
|
78
|
+
* rather than a conditional delete. Used when the UI calls `deleteOldLogs(Date.now())`.
|
|
79
|
+
* @param cutoffTimestamp - Millisecond cutoff passed to `deleteOldLogs`
|
|
80
|
+
*/
|
|
81
|
+
function isClearAllCutoff(cutoffTimestamp) {
|
|
82
|
+
return cutoffTimestamp >= Date.now() - exports.CLEAR_ALL_CUTOFF_SLACK_MS;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* System table for cross-process console log storage with automatic TTL cleanup.
|
|
86
|
+
*/
|
|
73
87
|
let ConsoleLogsTable = (() => {
|
|
74
88
|
let _classSuper = orm_1.Table;
|
|
75
89
|
let _instanceExtraInitializers = [];
|
|
@@ -93,6 +107,7 @@ let ConsoleLogsTable = (() => {
|
|
|
93
107
|
}
|
|
94
108
|
}
|
|
95
109
|
logCleanupInitialized = false;
|
|
110
|
+
/** Schedules startup and daily cleanup of logs older than {@link LOG_TTL}. */
|
|
96
111
|
async initializeLogCleanup() {
|
|
97
112
|
if (this.logCleanupInitialized) {
|
|
98
113
|
return;
|
|
@@ -104,30 +119,57 @@ let ConsoleLogsTable = (() => {
|
|
|
104
119
|
// TODO - consider doing this as a workflow
|
|
105
120
|
setInterval(() => this.deleteOldLogs(), this.LOG_CLEANUP_INTERVAL);
|
|
106
121
|
}
|
|
107
|
-
|
|
122
|
+
resolveSqlDataSource() {
|
|
123
|
+
let ds = this.dataSource;
|
|
124
|
+
while (!(ds instanceof orm_1.SQLDataSource) && ds.dataSource) {
|
|
125
|
+
ds = ds.dataSource;
|
|
126
|
+
}
|
|
127
|
+
return ds instanceof orm_1.SQLDataSource ? ds : undefined;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Remove console logs older than `cutoffTimestamp`.
|
|
131
|
+
*
|
|
132
|
+
* When the cutoff is near `Date.now()` (within {@link CLEAR_ALL_CUTOFF_SLACK_MS}), uses
|
|
133
|
+
* drop + recreate for a fast full clear. Otherwise deletes rows with `timestamp < cutoff`.
|
|
134
|
+
* @param cutoffTimestamp - Defaults to seven days ago for scheduled TTL cleanup
|
|
135
|
+
*/
|
|
136
|
+
async deleteOldLogs(cutoffTimestamp = Date.now() - 24 * 60 * 60 * 1000) {
|
|
137
|
+
if (isClearAllCutoff(cutoffTimestamp)) {
|
|
138
|
+
const sqlDs = this.resolveSqlDataSource();
|
|
139
|
+
if (sqlDs) {
|
|
140
|
+
console.log("clearing all console logs via drop+recreate");
|
|
141
|
+
await sqlDs.dropTableIfExists();
|
|
142
|
+
await sqlDs.initTable(true);
|
|
143
|
+
console.log("cleared all console logs");
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (typeof this.dataSource.clearTable === "function") {
|
|
147
|
+
await this.dataSource.clearTable();
|
|
148
|
+
console.log("cleared all console logs via clearTable");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
console.warn("clear-all requested but no SQL data source available");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
108
154
|
const count = await this.count({ timestamp: { $lt: cutoffTimestamp } });
|
|
109
155
|
if (count === 0) {
|
|
110
156
|
console.log(`No old logs to clean up older than ${new Date(cutoffTimestamp).toISOString()}`);
|
|
111
157
|
return;
|
|
112
158
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (ds instanceof orm_1.SQLDataSource) {
|
|
118
|
-
const db = ds.db;
|
|
119
|
-
const result = await db.exec(`delete from ConsoleLogs where timestamp < $timestamp`, {
|
|
120
|
-
timestamp: cutoffTimestamp,
|
|
121
|
-
});
|
|
159
|
+
const sqlDs = this.resolveSqlDataSource();
|
|
160
|
+
if (sqlDs) {
|
|
161
|
+
const db = sqlDs.db;
|
|
162
|
+
const result = await db.exec(`DELETE FROM "${sqlDs.tableName}" WHERE "timestamp" < $timestamp`, { timestamp: cutoffTimestamp });
|
|
122
163
|
console.log(`bulk deleted ${count} logs, changes: ${result.changes}`);
|
|
123
164
|
}
|
|
124
165
|
else {
|
|
125
166
|
const cursor = this.cursor({ timestamp: { $lt: cutoffTimestamp } }, { sortBy: ["-timestamp"] });
|
|
126
|
-
|
|
167
|
+
let deleted = 0;
|
|
127
168
|
for await (const log of cursor) {
|
|
128
|
-
this.delete(log.logId);
|
|
169
|
+
await this.delete(log.logId);
|
|
170
|
+
deleted++;
|
|
129
171
|
}
|
|
130
|
-
console.log(`cursor deleted ${
|
|
172
|
+
console.log(`cursor deleted ${deleted} logs`);
|
|
131
173
|
}
|
|
132
174
|
console.log(`cleaned up console logs`, { expectedCount: count });
|
|
133
175
|
}
|
|
@@ -136,8 +178,7 @@ let ConsoleLogsTable = (() => {
|
|
|
136
178
|
exports.ConsoleLogsTable = ConsoleLogsTable;
|
|
137
179
|
(0, table_definitions_system_1.registerSystemTableDefinition)(metaData, exports.consoleLogSchema, ConsoleLogsTable);
|
|
138
180
|
/**
|
|
139
|
-
*
|
|
140
|
-
* that the table be accessed in an asynchronous way
|
|
181
|
+
* Returns the user's personal {@link ConsoleLogsTable} (logs are not synced across devices).
|
|
141
182
|
*/
|
|
142
183
|
async function ConsoleLogs() {
|
|
143
184
|
const userContext = await (0, user_context_singleton_1.getUserContext)();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const SQLiteDB = require("better-sqlite3");
|
|
4
|
+
const event_registry_1 = require("../data/orm/event-registry");
|
|
5
|
+
const sql_data_source_1 = require("../data/orm/sql.data-source");
|
|
6
|
+
const types_1 = require("../data/orm/types");
|
|
7
|
+
const utils_1 = require("../utils");
|
|
8
|
+
const console_logs_table_1 = require("./console-logs.table");
|
|
9
|
+
class DBHarness {
|
|
10
|
+
_db = null;
|
|
11
|
+
get db() {
|
|
12
|
+
if (!this._db) {
|
|
13
|
+
this._db = new SQLiteDB(":memory:");
|
|
14
|
+
this._db.pragma("journal_mode = WAL");
|
|
15
|
+
}
|
|
16
|
+
return this._db;
|
|
17
|
+
}
|
|
18
|
+
async get(sql, params = []) {
|
|
19
|
+
return this.db.prepare(sql).get(params);
|
|
20
|
+
}
|
|
21
|
+
async all(sql, params = []) {
|
|
22
|
+
return this.db.prepare(sql).all(params);
|
|
23
|
+
}
|
|
24
|
+
async exec(sql, params = []) {
|
|
25
|
+
const result = this.db.prepare(sql).run(params);
|
|
26
|
+
return { changes: result.changes };
|
|
27
|
+
}
|
|
28
|
+
async close() {
|
|
29
|
+
this._db?.close();
|
|
30
|
+
this._db = null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const consoleLogsMetaData = {
|
|
34
|
+
name: "ConsoleLogs",
|
|
35
|
+
description: "System-wide console log entries with cross-process visibility.",
|
|
36
|
+
primaryKeyName: "logId",
|
|
37
|
+
fields: (0, types_1.schemaToFields)(console_logs_table_1.consoleLogSchema),
|
|
38
|
+
localOnly: true,
|
|
39
|
+
indexes: [
|
|
40
|
+
{ fields: ["timestamp"] },
|
|
41
|
+
{ fields: ["level"] },
|
|
42
|
+
{ fields: ["process"] },
|
|
43
|
+
{ fields: ["processInstanceId"] },
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
function createConsoleLogsTable(db) {
|
|
47
|
+
const sqlDs = new sql_data_source_1.SQLDataSource(db, consoleLogsMetaData, console_logs_table_1.consoleLogSchema);
|
|
48
|
+
const eventRegistry = new event_registry_1.EventRegistry("test-context");
|
|
49
|
+
const deps = {
|
|
50
|
+
dataSource: sqlDs,
|
|
51
|
+
eventRegistry,
|
|
52
|
+
schema: console_logs_table_1.consoleLogSchema,
|
|
53
|
+
};
|
|
54
|
+
return { table: new console_logs_table_1.ConsoleLogsTable(consoleLogsMetaData, deps), sqlDs };
|
|
55
|
+
}
|
|
56
|
+
describe("isClearAllCutoff", () => {
|
|
57
|
+
it("returns true when cutoff is now", () => {
|
|
58
|
+
expect((0, console_logs_table_1.isClearAllCutoff)(Date.now())).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
it("returns true when cutoff is within slack window", () => {
|
|
61
|
+
expect((0, console_logs_table_1.isClearAllCutoff)(Date.now() - console_logs_table_1.CLEAR_ALL_CUTOFF_SLACK_MS + 1)).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
it("returns false when cutoff is older than slack window", () => {
|
|
64
|
+
expect((0, console_logs_table_1.isClearAllCutoff)(Date.now() - console_logs_table_1.CLEAR_ALL_CUTOFF_SLACK_MS - 1)).toBe(false);
|
|
65
|
+
expect((0, console_logs_table_1.isClearAllCutoff)(Date.now() - 7 * 24 * 60 * 60 * 1000)).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
describe("ConsoleLogsTable.deleteOldLogs", () => {
|
|
69
|
+
const db = new DBHarness();
|
|
70
|
+
afterAll(async () => {
|
|
71
|
+
await db.close();
|
|
72
|
+
});
|
|
73
|
+
beforeEach(async () => {
|
|
74
|
+
const { sqlDs } = createConsoleLogsTable(db);
|
|
75
|
+
await sqlDs.dropTableIfExists();
|
|
76
|
+
await sqlDs.initTable(true);
|
|
77
|
+
});
|
|
78
|
+
it("uses drop+recreate for clear-all cutoff without calling count", async () => {
|
|
79
|
+
const { table, sqlDs } = createConsoleLogsTable(db);
|
|
80
|
+
await sqlDs.initTable(true);
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
await table.insert({
|
|
83
|
+
logId: (0, utils_1.newid)(),
|
|
84
|
+
timestamp: now - 1000,
|
|
85
|
+
level: "info",
|
|
86
|
+
process: "test",
|
|
87
|
+
processInstanceId: (0, utils_1.newid)(),
|
|
88
|
+
message: "old log",
|
|
89
|
+
});
|
|
90
|
+
const countSpy = jest.spyOn(table, "count");
|
|
91
|
+
const dropSpy = jest.spyOn(sqlDs, "dropTableIfExists");
|
|
92
|
+
const initSpy = jest.spyOn(sqlDs, "initTable");
|
|
93
|
+
await table.deleteOldLogs(Date.now());
|
|
94
|
+
expect(countSpy).not.toHaveBeenCalled();
|
|
95
|
+
expect(dropSpy).toHaveBeenCalled();
|
|
96
|
+
expect(initSpy).toHaveBeenCalledWith(true);
|
|
97
|
+
const remaining = await table.count();
|
|
98
|
+
expect(remaining).toBe(0);
|
|
99
|
+
countSpy.mockRestore();
|
|
100
|
+
dropSpy.mockRestore();
|
|
101
|
+
initSpy.mockRestore();
|
|
102
|
+
});
|
|
103
|
+
it("uses conditional delete for TTL cutoff and calls count", async () => {
|
|
104
|
+
const { table, sqlDs } = createConsoleLogsTable(db);
|
|
105
|
+
await sqlDs.initTable(true);
|
|
106
|
+
const now = Date.now();
|
|
107
|
+
const oldCutoff = now - 7 * 24 * 60 * 60 * 1000;
|
|
108
|
+
await table.insert({
|
|
109
|
+
logId: (0, utils_1.newid)(),
|
|
110
|
+
timestamp: oldCutoff - 1000,
|
|
111
|
+
level: "info",
|
|
112
|
+
process: "test",
|
|
113
|
+
processInstanceId: (0, utils_1.newid)(),
|
|
114
|
+
message: "expired log",
|
|
115
|
+
});
|
|
116
|
+
await table.insert({
|
|
117
|
+
logId: (0, utils_1.newid)(),
|
|
118
|
+
timestamp: now,
|
|
119
|
+
level: "info",
|
|
120
|
+
process: "test",
|
|
121
|
+
processInstanceId: (0, utils_1.newid)(),
|
|
122
|
+
message: "recent log",
|
|
123
|
+
});
|
|
124
|
+
const countSpy = jest.spyOn(table, "count");
|
|
125
|
+
const dropSpy = jest.spyOn(sqlDs, "dropTableIfExists");
|
|
126
|
+
const execSpy = jest.spyOn(sqlDs.db, "exec");
|
|
127
|
+
await table.deleteOldLogs(oldCutoff);
|
|
128
|
+
expect(countSpy).toHaveBeenCalledWith({ timestamp: { $lt: oldCutoff } });
|
|
129
|
+
expect(dropSpy).not.toHaveBeenCalled();
|
|
130
|
+
expect(execSpy).toHaveBeenCalledWith(`DELETE FROM "${sqlDs.tableName}" WHERE "timestamp" < $timestamp`, { timestamp: oldCutoff });
|
|
131
|
+
const remaining = await table.count();
|
|
132
|
+
expect(remaining).toBe(1);
|
|
133
|
+
countSpy.mockRestore();
|
|
134
|
+
dropSpy.mockRestore();
|
|
135
|
+
execSpy.mockRestore();
|
|
136
|
+
});
|
|
137
|
+
it("does nothing on partial delete when no rows match cutoff", async () => {
|
|
138
|
+
const { table, sqlDs } = createConsoleLogsTable(db);
|
|
139
|
+
await sqlDs.initTable(true);
|
|
140
|
+
const now = Date.now();
|
|
141
|
+
await table.insert({
|
|
142
|
+
logId: (0, utils_1.newid)(),
|
|
143
|
+
timestamp: now,
|
|
144
|
+
level: "info",
|
|
145
|
+
process: "test",
|
|
146
|
+
processInstanceId: (0, utils_1.newid)(),
|
|
147
|
+
message: "recent log",
|
|
148
|
+
});
|
|
149
|
+
const execSpy = jest.spyOn(sqlDs.db, "exec");
|
|
150
|
+
const oldCutoff = now - 7 * 24 * 60 * 60 * 1000;
|
|
151
|
+
await table.deleteOldLogs(oldCutoff);
|
|
152
|
+
expect(execSpy).not.toHaveBeenCalled();
|
|
153
|
+
expect(await table.count()).toBe(1);
|
|
154
|
+
execSpy.mockRestore();
|
|
155
|
+
});
|
|
156
|
+
});
|