@token-dashboard/typeless-usage-uploader 0.1.1 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -30
- package/dist/bin/typeless-usage-uploader.js +4 -4
- package/package.json +3 -10
- package/dist/cli.js +0 -430
- package/dist/collector.js +0 -495
- package/dist/constants.js +0 -43
- package/dist/launchd.js +0 -154
- package/dist/runtime-config.js +0 -158
- package/dist/state-db.js +0 -280
- package/dist/utils.js +0 -52
package/dist/runtime-config.js
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import {
|
|
5
|
-
CLI_NAME,
|
|
6
|
-
DEFAULT_BACKEND_URL,
|
|
7
|
-
DEFAULT_CONFIG_FILE,
|
|
8
|
-
DEFAULT_INSTALL_ROOT,
|
|
9
|
-
DEFAULT_LAUNCHD_LABEL,
|
|
10
|
-
DEFAULT_LAUNCH_AGENT_PATH,
|
|
11
|
-
DEFAULT_STATE_DB,
|
|
12
|
-
DEFAULT_STDERR_LOG_PATH,
|
|
13
|
-
DEFAULT_STDOUT_LOG_PATH,
|
|
14
|
-
DEFAULT_TYPELESS_DB_PATH,
|
|
15
|
-
DEFAULT_TYPELESS_STORAGE_PATH,
|
|
16
|
-
STATUS_ONLINE_THRESHOLD_SECONDS,
|
|
17
|
-
} from './constants.js';
|
|
18
|
-
import { stableStringify } from './utils.js';
|
|
19
|
-
|
|
20
|
-
const DEFAULT_INSTALL_ROOT_BASE = path.basename(DEFAULT_INSTALL_ROOT);
|
|
21
|
-
|
|
22
|
-
export function deriveEnvSuffix(installRoot) {
|
|
23
|
-
const base = path.basename(installRoot);
|
|
24
|
-
if (!base.startsWith(`${DEFAULT_INSTALL_ROOT_BASE}-`)) return '';
|
|
25
|
-
const envName = base.slice(DEFAULT_INSTALL_ROOT_BASE.length + 1);
|
|
26
|
-
return envName ? `-${envName}` : '';
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function defaultRuntimeConfig(configFile = DEFAULT_CONFIG_FILE) {
|
|
30
|
-
const installRoot = path.dirname(configFile);
|
|
31
|
-
const envSuffix = deriveEnvSuffix(installRoot);
|
|
32
|
-
const launchdLabel = `${DEFAULT_LAUNCHD_LABEL}${envSuffix}`;
|
|
33
|
-
return {
|
|
34
|
-
configFile,
|
|
35
|
-
installRoot,
|
|
36
|
-
backendUrl: DEFAULT_BACKEND_URL,
|
|
37
|
-
intervalSeconds: 300,
|
|
38
|
-
typelessDbPath: DEFAULT_TYPELESS_DB_PATH,
|
|
39
|
-
typelessStoragePath: DEFAULT_TYPELESS_STORAGE_PATH,
|
|
40
|
-
stateDbPath: path.join(installRoot, path.basename(DEFAULT_STATE_DB)),
|
|
41
|
-
nodePath: process.execPath,
|
|
42
|
-
entryFile: '',
|
|
43
|
-
localBinDir: path.join(installRoot, 'bin'),
|
|
44
|
-
localBinPath: path.join(installRoot, 'bin', CLI_NAME),
|
|
45
|
-
homeBinLink: path.join(os.homedir(), 'bin', `${CLI_NAME}${envSuffix}`),
|
|
46
|
-
stdoutLogPath: path.join(installRoot, 'logs', path.basename(DEFAULT_STDOUT_LOG_PATH)),
|
|
47
|
-
stderrLogPath: path.join(installRoot, 'logs', path.basename(DEFAULT_STDERR_LOG_PATH)),
|
|
48
|
-
launchdLabel,
|
|
49
|
-
plistPath: path.join(installRoot, 'launchd', `${launchdLabel}.plist`),
|
|
50
|
-
launchAgentPath: path.join(
|
|
51
|
-
path.dirname(DEFAULT_LAUNCH_AGENT_PATH),
|
|
52
|
-
`${launchdLabel}.plist`,
|
|
53
|
-
),
|
|
54
|
-
autoStartOnLogin: true,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function normalizeRuntimeConfig(runtime) {
|
|
59
|
-
const installRoot = runtime.installRoot || DEFAULT_INSTALL_ROOT;
|
|
60
|
-
const envSuffix = deriveEnvSuffix(installRoot);
|
|
61
|
-
const launchdLabel = runtime.launchdLabel || `${DEFAULT_LAUNCHD_LABEL}${envSuffix}`;
|
|
62
|
-
const localBinDir = runtime.localBinDir || path.join(installRoot, 'bin');
|
|
63
|
-
return {
|
|
64
|
-
...runtime,
|
|
65
|
-
configFile: runtime.configFile || DEFAULT_CONFIG_FILE,
|
|
66
|
-
installRoot,
|
|
67
|
-
backendUrl: runtime.backendUrl?.trim()
|
|
68
|
-
? runtime.backendUrl.replace(/\/+$/, '')
|
|
69
|
-
: null,
|
|
70
|
-
intervalSeconds: Number(runtime.intervalSeconds) || 300,
|
|
71
|
-
typelessDbPath: runtime.typelessDbPath || DEFAULT_TYPELESS_DB_PATH,
|
|
72
|
-
typelessStoragePath:
|
|
73
|
-
runtime.typelessStoragePath || DEFAULT_TYPELESS_STORAGE_PATH,
|
|
74
|
-
stateDbPath: runtime.stateDbPath || path.join(installRoot, 'state.sqlite'),
|
|
75
|
-
nodePath: runtime.nodePath || process.execPath,
|
|
76
|
-
entryFile: runtime.entryFile || '',
|
|
77
|
-
localBinDir,
|
|
78
|
-
localBinPath: runtime.localBinPath || path.join(localBinDir, CLI_NAME),
|
|
79
|
-
homeBinLink:
|
|
80
|
-
runtime.homeBinLink || path.join(os.homedir(), 'bin', `${CLI_NAME}${envSuffix}`),
|
|
81
|
-
stdoutLogPath: runtime.stdoutLogPath || path.join(installRoot, 'logs', 'stdout.log'),
|
|
82
|
-
stderrLogPath: runtime.stderrLogPath || path.join(installRoot, 'logs', 'stderr.log'),
|
|
83
|
-
launchdLabel,
|
|
84
|
-
plistPath:
|
|
85
|
-
runtime.plistPath || path.join(installRoot, 'launchd', `${launchdLabel}.plist`),
|
|
86
|
-
launchAgentPath:
|
|
87
|
-
runtime.launchAgentPath ||
|
|
88
|
-
path.join(path.dirname(DEFAULT_LAUNCH_AGENT_PATH), `${launchdLabel}.plist`),
|
|
89
|
-
autoStartOnLogin:
|
|
90
|
-
runtime.autoStartOnLogin === undefined ? true : Boolean(runtime.autoStartOnLogin),
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function loadRuntimeConfig(configFile = DEFAULT_CONFIG_FILE) {
|
|
95
|
-
const runtime = defaultRuntimeConfig(configFile);
|
|
96
|
-
try {
|
|
97
|
-
const payload = JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
|
98
|
-
if (!payload || typeof payload !== 'object') return runtime;
|
|
99
|
-
return normalizeRuntimeConfig({ ...runtime, ...payload, configFile });
|
|
100
|
-
} catch {
|
|
101
|
-
return runtime;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export function saveRuntimeConfig(runtime) {
|
|
106
|
-
const normalized = normalizeRuntimeConfig(runtime);
|
|
107
|
-
fs.mkdirSync(path.dirname(normalized.configFile), { recursive: true });
|
|
108
|
-
fs.mkdirSync(path.dirname(normalized.stdoutLogPath), { recursive: true });
|
|
109
|
-
fs.writeFileSync(normalized.configFile, `${stableStringify({
|
|
110
|
-
backendUrl: normalized.backendUrl,
|
|
111
|
-
intervalSeconds: normalized.intervalSeconds,
|
|
112
|
-
typelessDbPath: normalized.typelessDbPath,
|
|
113
|
-
typelessStoragePath: normalized.typelessStoragePath,
|
|
114
|
-
stateDbPath: normalized.stateDbPath,
|
|
115
|
-
installRoot: normalized.installRoot,
|
|
116
|
-
nodePath: normalized.nodePath,
|
|
117
|
-
entryFile: normalized.entryFile,
|
|
118
|
-
localBinDir: normalized.localBinDir,
|
|
119
|
-
localBinPath: normalized.localBinPath,
|
|
120
|
-
homeBinLink: normalized.homeBinLink,
|
|
121
|
-
stdoutLogPath: normalized.stdoutLogPath,
|
|
122
|
-
stderrLogPath: normalized.stderrLogPath,
|
|
123
|
-
launchdLabel: normalized.launchdLabel,
|
|
124
|
-
plistPath: normalized.plistPath,
|
|
125
|
-
launchAgentPath: normalized.launchAgentPath,
|
|
126
|
-
autoStartOnLogin: normalized.autoStartOnLogin,
|
|
127
|
-
})}\n`);
|
|
128
|
-
return normalized;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export function mergeRuntimeConfig(configFile, overrides = {}) {
|
|
132
|
-
const base = loadRuntimeConfig(configFile);
|
|
133
|
-
return normalizeRuntimeConfig({ ...base, ...overrides, configFile });
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export function formatStatusOutput(runtime, status) {
|
|
137
|
-
return {
|
|
138
|
-
configExists: fs.existsSync(runtime.configFile),
|
|
139
|
-
loaded: status.loaded,
|
|
140
|
-
running: status.running,
|
|
141
|
-
pid: status.pid,
|
|
142
|
-
state: status.state,
|
|
143
|
-
lastExitCode: status.lastExitCode,
|
|
144
|
-
backendUrl: runtime.backendUrl,
|
|
145
|
-
intervalSeconds: runtime.intervalSeconds,
|
|
146
|
-
typelessDbPath: runtime.typelessDbPath,
|
|
147
|
-
typelessStoragePath: runtime.typelessStoragePath,
|
|
148
|
-
configFile: runtime.configFile,
|
|
149
|
-
stateDbPath: runtime.stateDbPath,
|
|
150
|
-
stdoutLogPath: runtime.stdoutLogPath,
|
|
151
|
-
stderrLogPath: runtime.stderrLogPath,
|
|
152
|
-
plistPath: runtime.plistPath,
|
|
153
|
-
launchAgentPath: runtime.launchAgentPath,
|
|
154
|
-
label: runtime.launchdLabel,
|
|
155
|
-
autoStartOnLogin: runtime.autoStartOnLogin,
|
|
156
|
-
onlineThresholdSeconds: STATUS_ONLINE_THRESHOLD_SECONDS,
|
|
157
|
-
};
|
|
158
|
-
}
|
package/dist/state-db.js
DELETED
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { Buffer } from 'node:buffer';
|
|
4
|
-
import { DatabaseSync } from 'node:sqlite';
|
|
5
|
-
import {
|
|
6
|
-
MAX_BATCH_BYTES,
|
|
7
|
-
MAX_BUFFER_AGE_SECONDS,
|
|
8
|
-
MAX_EVENTS_PER_BATCH,
|
|
9
|
-
} from './constants.js';
|
|
10
|
-
import { nowTs, stableStringify } from './utils.js';
|
|
11
|
-
|
|
12
|
-
export class StateDb {
|
|
13
|
-
constructor(dbPath) {
|
|
14
|
-
this.dbPath = dbPath;
|
|
15
|
-
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
16
|
-
this.db = new DatabaseSync(dbPath, { readBigInts: true });
|
|
17
|
-
this.initSchema();
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
close() {
|
|
21
|
-
this.db.close();
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
initSchema() {
|
|
25
|
-
this.db.exec(`
|
|
26
|
-
CREATE TABLE IF NOT EXISTS identity_config (
|
|
27
|
-
key TEXT PRIMARY KEY,
|
|
28
|
-
value TEXT NOT NULL,
|
|
29
|
-
updated_at REAL NOT NULL
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
CREATE TABLE IF NOT EXISTS typeless_event_state (
|
|
33
|
-
typeless_user_id TEXT NOT NULL,
|
|
34
|
-
record_uid TEXT NOT NULL,
|
|
35
|
-
fingerprint TEXT NOT NULL,
|
|
36
|
-
event_json TEXT NOT NULL,
|
|
37
|
-
is_deleted INTEGER NOT NULL DEFAULT 0,
|
|
38
|
-
updated_at REAL NOT NULL,
|
|
39
|
-
PRIMARY KEY (typeless_user_id, record_uid)
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
CREATE TABLE IF NOT EXISTS pending_batches (
|
|
43
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
44
|
-
batch_key TEXT NOT NULL UNIQUE,
|
|
45
|
-
status TEXT NOT NULL,
|
|
46
|
-
payload_json TEXT NOT NULL,
|
|
47
|
-
payload_bytes INTEGER NOT NULL DEFAULT 0,
|
|
48
|
-
event_count INTEGER NOT NULL DEFAULT 0,
|
|
49
|
-
attempt_count INTEGER NOT NULL DEFAULT 0,
|
|
50
|
-
next_retry_at REAL,
|
|
51
|
-
last_error TEXT,
|
|
52
|
-
created_at REAL NOT NULL,
|
|
53
|
-
updated_at REAL NOT NULL
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
CREATE TABLE IF NOT EXISTS upload_checkpoint (
|
|
57
|
-
key TEXT PRIMARY KEY,
|
|
58
|
-
value TEXT NOT NULL,
|
|
59
|
-
updated_at REAL NOT NULL
|
|
60
|
-
);
|
|
61
|
-
`);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
getIdentity() {
|
|
65
|
-
const rows = this.db.prepare('SELECT key, value FROM identity_config').all();
|
|
66
|
-
return Object.fromEntries(rows.map((row) => [row.key, row.value]));
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
setIdentity(values) {
|
|
70
|
-
const ts = nowTs();
|
|
71
|
-
const upsert = this.db.prepare(`
|
|
72
|
-
INSERT INTO identity_config(key, value, updated_at)
|
|
73
|
-
VALUES (?, ?, ?)
|
|
74
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
|
|
75
|
-
`);
|
|
76
|
-
const del = this.db.prepare('DELETE FROM identity_config WHERE key = ?');
|
|
77
|
-
this.db.exec('BEGIN');
|
|
78
|
-
try {
|
|
79
|
-
for (const [key, value] of Object.entries(values)) {
|
|
80
|
-
if (value == null) del.run(key);
|
|
81
|
-
else upsert.run(key, value, ts);
|
|
82
|
-
}
|
|
83
|
-
this.db.exec('COMMIT');
|
|
84
|
-
} catch (error) {
|
|
85
|
-
this.db.exec('ROLLBACK');
|
|
86
|
-
throw error;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
getCheckpoint(key) {
|
|
91
|
-
const row = this.db.prepare('SELECT value FROM upload_checkpoint WHERE key = ?').get(key);
|
|
92
|
-
return row?.value ?? null;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
setCheckpoint(key, value) {
|
|
96
|
-
this.db.prepare(`
|
|
97
|
-
INSERT INTO upload_checkpoint(key, value, updated_at)
|
|
98
|
-
VALUES (?, ?, ?)
|
|
99
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
|
|
100
|
-
`).run(key, value, nowTs());
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
getEventStates(typelessUserId) {
|
|
104
|
-
return this.db.prepare(`
|
|
105
|
-
SELECT * FROM typeless_event_state
|
|
106
|
-
WHERE typeless_user_id = ?
|
|
107
|
-
`).all(typelessUserId);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
upsertEventStates(typelessUserId, states) {
|
|
111
|
-
if (!states.length) return;
|
|
112
|
-
const ts = nowTs();
|
|
113
|
-
const stmt = this.db.prepare(`
|
|
114
|
-
INSERT INTO typeless_event_state(
|
|
115
|
-
typeless_user_id, record_uid, fingerprint, event_json, is_deleted, updated_at
|
|
116
|
-
)
|
|
117
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
118
|
-
ON CONFLICT(typeless_user_id, record_uid) DO UPDATE SET
|
|
119
|
-
fingerprint = excluded.fingerprint,
|
|
120
|
-
event_json = excluded.event_json,
|
|
121
|
-
is_deleted = excluded.is_deleted,
|
|
122
|
-
updated_at = excluded.updated_at
|
|
123
|
-
`);
|
|
124
|
-
this.db.exec('BEGIN');
|
|
125
|
-
try {
|
|
126
|
-
for (const state of states) {
|
|
127
|
-
stmt.run(
|
|
128
|
-
typelessUserId,
|
|
129
|
-
state.recordUid,
|
|
130
|
-
state.fingerprint,
|
|
131
|
-
stableStringify(state.event),
|
|
132
|
-
state.isDeleted ? 1 : 0,
|
|
133
|
-
ts,
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
this.db.exec('COMMIT');
|
|
137
|
-
} catch (error) {
|
|
138
|
-
this.db.exec('ROLLBACK');
|
|
139
|
-
throw error;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
getBufferingBatch() {
|
|
144
|
-
const stmt = this.db.prepare(`
|
|
145
|
-
SELECT * FROM pending_batches
|
|
146
|
-
WHERE status = 'buffering'
|
|
147
|
-
ORDER BY id ASC
|
|
148
|
-
LIMIT 1
|
|
149
|
-
`);
|
|
150
|
-
stmt.setReadBigInts(true);
|
|
151
|
-
return stmt.get() ?? null;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
saveBufferingPayload(payload) {
|
|
155
|
-
const existing = this.getBufferingBatch();
|
|
156
|
-
const payloadJson = stableStringify(payload);
|
|
157
|
-
const payloadBytes = Buffer.byteLength(payloadJson, 'utf8');
|
|
158
|
-
const eventCount = payload.events?.length ?? 0;
|
|
159
|
-
const ts = nowTs();
|
|
160
|
-
if (existing) {
|
|
161
|
-
this.db.prepare(`
|
|
162
|
-
UPDATE pending_batches
|
|
163
|
-
SET payload_json = ?, payload_bytes = ?, event_count = ?, updated_at = ?
|
|
164
|
-
WHERE id = ?
|
|
165
|
-
`).run(payloadJson, payloadBytes, eventCount, ts, existing.id);
|
|
166
|
-
} else {
|
|
167
|
-
this.db.prepare(`
|
|
168
|
-
INSERT INTO pending_batches(
|
|
169
|
-
batch_key, status, payload_json, payload_bytes, event_count,
|
|
170
|
-
attempt_count, created_at, updated_at
|
|
171
|
-
) VALUES (?, 'buffering', ?, ?, ?, 0, ?, ?)
|
|
172
|
-
`).run(randomBatchKey(), payloadJson, payloadBytes, eventCount, ts, ts);
|
|
173
|
-
}
|
|
174
|
-
return this.getBufferingBatch();
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
sealStaleBatches(force = false) {
|
|
178
|
-
const selectStmt = this.db.prepare(
|
|
179
|
-
"SELECT id, created_at FROM pending_batches WHERE status = 'buffering'",
|
|
180
|
-
);
|
|
181
|
-
selectStmt.setReadBigInts(true);
|
|
182
|
-
const rows = selectStmt.all();
|
|
183
|
-
let sealed = 0;
|
|
184
|
-
const ts = nowTs();
|
|
185
|
-
const updateStmt = this.db.prepare(`
|
|
186
|
-
UPDATE pending_batches
|
|
187
|
-
SET status = 'pending', updated_at = ?
|
|
188
|
-
WHERE id = ?
|
|
189
|
-
`);
|
|
190
|
-
for (const row of rows) {
|
|
191
|
-
if (force || ts - Number(row.created_at) >= MAX_BUFFER_AGE_SECONDS) {
|
|
192
|
-
updateStmt.run(ts, row.id);
|
|
193
|
-
sealed += 1;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
return sealed;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
sealBufferIfThresholdHit() {
|
|
200
|
-
const row = this.getBufferingBatch();
|
|
201
|
-
if (!row) return false;
|
|
202
|
-
const shouldSeal =
|
|
203
|
-
Number(row.event_count) >= MAX_EVENTS_PER_BATCH ||
|
|
204
|
-
Number(row.payload_bytes) >= MAX_BATCH_BYTES ||
|
|
205
|
-
nowTs() - Number(row.created_at) >= MAX_BUFFER_AGE_SECONDS;
|
|
206
|
-
if (shouldSeal) {
|
|
207
|
-
this.db.prepare(`
|
|
208
|
-
UPDATE pending_batches
|
|
209
|
-
SET status = 'pending', updated_at = ?
|
|
210
|
-
WHERE id = ?
|
|
211
|
-
`).run(nowTs(), row.id);
|
|
212
|
-
}
|
|
213
|
-
return shouldSeal;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
iterPendingBatches() {
|
|
217
|
-
const stmt = this.db.prepare(`
|
|
218
|
-
SELECT * FROM pending_batches
|
|
219
|
-
WHERE status = 'pending'
|
|
220
|
-
ORDER BY created_at ASC, id ASC
|
|
221
|
-
`);
|
|
222
|
-
stmt.setReadBigInts(true);
|
|
223
|
-
return stmt.all();
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
markBatchUploaded(batchId) {
|
|
227
|
-
this.db.prepare('DELETE FROM pending_batches WHERE id = ?').run(batchId);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
markBatchFailed(batchId, attemptCount, errorMessage) {
|
|
231
|
-
this.db.prepare(`
|
|
232
|
-
UPDATE pending_batches
|
|
233
|
-
SET attempt_count = ?, last_error = ?, updated_at = ?
|
|
234
|
-
WHERE id = ?
|
|
235
|
-
`).run(attemptCount, String(errorMessage).slice(0, 1000), nowTs(), batchId);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
resetBackfillState() {
|
|
239
|
-
this.db.exec('BEGIN');
|
|
240
|
-
try {
|
|
241
|
-
this.db.prepare('DELETE FROM typeless_event_state').run();
|
|
242
|
-
this.db.prepare('DELETE FROM pending_batches').run();
|
|
243
|
-
this.db.prepare('DELETE FROM upload_checkpoint').run();
|
|
244
|
-
this.db.exec('COMMIT');
|
|
245
|
-
} catch (error) {
|
|
246
|
-
this.db.exec('ROLLBACK');
|
|
247
|
-
throw error;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
getQueueStats() {
|
|
252
|
-
const stmt = this.db.prepare(`
|
|
253
|
-
SELECT
|
|
254
|
-
COALESCE(SUM(CASE WHEN status = 'buffering' THEN 1 ELSE 0 END), 0) AS buffering_batch_count,
|
|
255
|
-
COALESCE(SUM(CASE WHEN status = 'pending' AND attempt_count = 0 THEN 1 ELSE 0 END), 0) AS pending_batch_count,
|
|
256
|
-
COALESCE(SUM(CASE WHEN status = 'pending' AND attempt_count > 0 THEN 1 ELSE 0 END), 0) AS retrying_batch_count,
|
|
257
|
-
COALESCE(SUM(event_count), 0) AS queued_events,
|
|
258
|
-
MIN(created_at) AS oldest_created_at
|
|
259
|
-
FROM pending_batches
|
|
260
|
-
`);
|
|
261
|
-
stmt.setReadBigInts(true);
|
|
262
|
-
const row = stmt.get() ?? {};
|
|
263
|
-
const oldestCreatedAt =
|
|
264
|
-
row.oldest_created_at == null ? null : Number(row.oldest_created_at);
|
|
265
|
-
return {
|
|
266
|
-
bufferingBatchCount: Number(row.buffering_batch_count ?? 0),
|
|
267
|
-
pendingBatchCount: Number(row.pending_batch_count ?? 0),
|
|
268
|
-
retryingBatchCount: Number(row.retrying_batch_count ?? 0),
|
|
269
|
-
queuedEvents: Number(row.queued_events ?? 0),
|
|
270
|
-
oldestPendingAgeSeconds:
|
|
271
|
-
oldestCreatedAt == null
|
|
272
|
-
? null
|
|
273
|
-
: Math.max(0, Math.floor(nowTs() - oldestCreatedAt)),
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function randomBatchKey() {
|
|
279
|
-
return `${Date.now().toString(16)}${Math.random().toString(16).slice(2, 14)}`;
|
|
280
|
-
}
|
package/dist/utils.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import { Buffer } from 'node:buffer';
|
|
3
|
-
import { createHash } from 'node:crypto';
|
|
4
|
-
|
|
5
|
-
export function nowTs() {
|
|
6
|
-
return Date.now() / 1000;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function sleep(ms) {
|
|
10
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function stableStringify(value) {
|
|
14
|
-
return JSON.stringify(sortForJson(value));
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function sortForJson(value) {
|
|
18
|
-
if (Array.isArray(value)) return value.map(sortForJson);
|
|
19
|
-
if (value && typeof value === 'object') {
|
|
20
|
-
return Object.fromEntries(
|
|
21
|
-
Object.keys(value)
|
|
22
|
-
.sort()
|
|
23
|
-
.map((key) => [key, sortForJson(value[key])]),
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
return value;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function sha1(value) {
|
|
30
|
-
return createHash('sha1').update(Buffer.from(String(value), 'utf8')).digest('hex');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function isoToEpochMs(value) {
|
|
34
|
-
if (!value) return null;
|
|
35
|
-
const timestamp = Date.parse(String(value).replace('Z', '+00:00'));
|
|
36
|
-
return Number.isNaN(timestamp) ? null : timestamp;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function readPersistentCollectorId(filePath) {
|
|
40
|
-
try {
|
|
41
|
-
const content = fs.readFileSync(filePath, 'utf8').trim();
|
|
42
|
-
return content || null;
|
|
43
|
-
} catch {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function writePersistentCollectorId(filePath, id) {
|
|
49
|
-
const tmpPath = `${filePath}.${process.pid}.tmp`;
|
|
50
|
-
fs.writeFileSync(tmpPath, id, 'utf8');
|
|
51
|
-
fs.renameSync(tmpPath, filePath);
|
|
52
|
-
}
|