@daeda/mcp-pro 0.1.30 → 0.1.31
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/index.js +229 -37
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1549,6 +1549,7 @@ var assertWritable = (masterLock) => {
|
|
|
1549
1549
|
}
|
|
1550
1550
|
};
|
|
1551
1551
|
var escapeSqlString = (value) => value.replace(/'/g, "''");
|
|
1552
|
+
var escapeSqlIdentifier = (value) => `"${value.replace(/"/g, '""')}"`;
|
|
1552
1553
|
var buildSqlPlaceholders = (count, startAt = 1) => Array.from({ length: count }, (_, index) => `$${index + startAt}`).join(",");
|
|
1553
1554
|
var buildDatabaseCacheKey = (portalId, mode, encryptionKey) => `${portalId}:${mode}:${encryptionKey ?? ""}`;
|
|
1554
1555
|
var makeAssociationStagingTableName = (seed = Date.now()) => `_staging_assoc_sync_${seed}`;
|
|
@@ -1733,7 +1734,7 @@ async function openDatabaseConnection(portalId, _encryptionKey, options2) {
|
|
|
1733
1734
|
connection,
|
|
1734
1735
|
mode,
|
|
1735
1736
|
sourcePath,
|
|
1736
|
-
replicaFingerprint:
|
|
1737
|
+
replicaFingerprint: null
|
|
1737
1738
|
};
|
|
1738
1739
|
}
|
|
1739
1740
|
var OBJECT_CSV_COLUMNS = ["id", "properties", "last_synced"];
|
|
@@ -1798,13 +1799,24 @@ async function bulkAppend(connection, tableName, columns) {
|
|
|
1798
1799
|
function cleanupDbArtifacts(filePath) {
|
|
1799
1800
|
try {
|
|
1800
1801
|
fs3.unlinkSync(filePath);
|
|
1801
|
-
} catch {
|
|
1802
|
+
} catch (error) {
|
|
1803
|
+
logFsCleanupError("remove file", filePath, error);
|
|
1802
1804
|
}
|
|
1803
1805
|
try {
|
|
1804
1806
|
fs3.unlinkSync(`${filePath}.wal`);
|
|
1805
|
-
} catch {
|
|
1807
|
+
} catch (error) {
|
|
1808
|
+
logFsCleanupError("remove wal", `${filePath}.wal`, error);
|
|
1806
1809
|
}
|
|
1807
1810
|
}
|
|
1811
|
+
function logFsCleanupError(action, filePath, error) {
|
|
1812
|
+
const code = error?.code;
|
|
1813
|
+
if (code === "ENOENT") return;
|
|
1814
|
+
console.error("[replica] Cleanup step failed", {
|
|
1815
|
+
action,
|
|
1816
|
+
filePath,
|
|
1817
|
+
error: describeError(error)
|
|
1818
|
+
});
|
|
1819
|
+
}
|
|
1808
1820
|
function nextReplicaFileName(seed = Date.now()) {
|
|
1809
1821
|
return `${REPLICA_VERSION_PREFIX2}${seed}-${process.pid}-${Math.random().toString(36).slice(2, 8)}${REPLICA_VERSION_SUFFIX2}`;
|
|
1810
1822
|
}
|
|
@@ -1816,7 +1828,8 @@ function writeReplicaPointer(portalId, fileName) {
|
|
|
1816
1828
|
`, "utf-8");
|
|
1817
1829
|
try {
|
|
1818
1830
|
fs3.unlinkSync(pointerPath);
|
|
1819
|
-
} catch {
|
|
1831
|
+
} catch (error) {
|
|
1832
|
+
logFsCleanupError("remove pointer", pointerPath, error);
|
|
1820
1833
|
}
|
|
1821
1834
|
fs3.renameSync(nextPointerPath, pointerPath);
|
|
1822
1835
|
}
|
|
@@ -1832,8 +1845,51 @@ function cleanupStaleReplicaFiles(portalId, currentReplicaPath) {
|
|
|
1832
1845
|
}
|
|
1833
1846
|
cleanupDbArtifacts(path2.join(portalDir(portalId), entry));
|
|
1834
1847
|
}
|
|
1848
|
+
} catch (error) {
|
|
1849
|
+
console.error("[replica] Failed stale replica cleanup", {
|
|
1850
|
+
portalId,
|
|
1851
|
+
currentReplicaPath,
|
|
1852
|
+
error: describeError(error)
|
|
1853
|
+
});
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
function getSourceDatabaseName(filePath) {
|
|
1857
|
+
return path2.parse(filePath).name;
|
|
1858
|
+
}
|
|
1859
|
+
function getFileSnapshot(filePath) {
|
|
1860
|
+
try {
|
|
1861
|
+
const stats = fs3.statSync(filePath);
|
|
1862
|
+
return {
|
|
1863
|
+
exists: true,
|
|
1864
|
+
sizeBytes: stats.size,
|
|
1865
|
+
mtimeMs: stats.mtimeMs
|
|
1866
|
+
};
|
|
1835
1867
|
} catch {
|
|
1868
|
+
return {
|
|
1869
|
+
exists: false,
|
|
1870
|
+
sizeBytes: null,
|
|
1871
|
+
mtimeMs: null
|
|
1872
|
+
};
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
function describeError(error) {
|
|
1876
|
+
if (error instanceof Error) {
|
|
1877
|
+
const errnoError = error;
|
|
1878
|
+
return {
|
|
1879
|
+
name: error.name,
|
|
1880
|
+
message: error.message,
|
|
1881
|
+
stack: error.stack,
|
|
1882
|
+
code: errnoError.code,
|
|
1883
|
+
errno: errnoError.errno,
|
|
1884
|
+
syscall: errnoError.syscall,
|
|
1885
|
+
path: errnoError.path,
|
|
1886
|
+
dest: errnoError.dest,
|
|
1887
|
+
cause: errnoError.cause ? describeError(errnoError.cause) : void 0
|
|
1888
|
+
};
|
|
1836
1889
|
}
|
|
1890
|
+
return {
|
|
1891
|
+
value: String(error)
|
|
1892
|
+
};
|
|
1837
1893
|
}
|
|
1838
1894
|
async function publishReplica({
|
|
1839
1895
|
masterLock,
|
|
@@ -1844,11 +1900,87 @@ async function publishReplica({
|
|
|
1844
1900
|
const liveDbPath = dbPath(portalId);
|
|
1845
1901
|
const nextReplicaName = nextReplicaFileName();
|
|
1846
1902
|
const nextReplicaPath = replicaVersionDbPath(portalId, nextReplicaName);
|
|
1903
|
+
const nextReplicaTempPath = `${nextReplicaPath}.next`;
|
|
1904
|
+
const sourceDatabaseName = getSourceDatabaseName(liveDbPath);
|
|
1905
|
+
const targetDatabaseName = "portal_replica_next";
|
|
1906
|
+
console.error("[replica] Publish starting", {
|
|
1907
|
+
portalId,
|
|
1908
|
+
liveDbPath,
|
|
1909
|
+
nextReplicaPath,
|
|
1910
|
+
nextReplicaTempPath,
|
|
1911
|
+
liveDb: getFileSnapshot(liveDbPath)
|
|
1912
|
+
});
|
|
1847
1913
|
cleanupDbArtifacts(nextReplicaPath);
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1914
|
+
cleanupDbArtifacts(nextReplicaTempPath);
|
|
1915
|
+
try {
|
|
1916
|
+
console.error("[replica] Checkpointing live database", {
|
|
1917
|
+
portalId,
|
|
1918
|
+
liveDbPath
|
|
1919
|
+
});
|
|
1920
|
+
await handle.connection.run("CHECKPOINT");
|
|
1921
|
+
console.error("[replica] Building replica snapshot via DuckDB", {
|
|
1922
|
+
portalId,
|
|
1923
|
+
sourceDatabaseName,
|
|
1924
|
+
targetDatabaseName,
|
|
1925
|
+
nextReplicaTempPath
|
|
1926
|
+
});
|
|
1927
|
+
await handle.connection.run(
|
|
1928
|
+
`ATTACH '${escapeSqlString(nextReplicaTempPath)}' AS ${escapeSqlIdentifier(targetDatabaseName)}`
|
|
1929
|
+
);
|
|
1930
|
+
try {
|
|
1931
|
+
await handle.connection.run(
|
|
1932
|
+
`COPY FROM DATABASE ${escapeSqlIdentifier(sourceDatabaseName)} TO ${escapeSqlIdentifier(targetDatabaseName)}`
|
|
1933
|
+
);
|
|
1934
|
+
await handle.connection.run(`CHECKPOINT ${escapeSqlIdentifier(targetDatabaseName)}`);
|
|
1935
|
+
} finally {
|
|
1936
|
+
await handle.connection.run(`DETACH ${escapeSqlIdentifier(targetDatabaseName)}`);
|
|
1937
|
+
}
|
|
1938
|
+
console.error("[replica] Verifying replica snapshot", {
|
|
1939
|
+
portalId,
|
|
1940
|
+
nextReplicaTempPath,
|
|
1941
|
+
snapshot: getFileSnapshot(nextReplicaTempPath)
|
|
1942
|
+
});
|
|
1943
|
+
const verifyInstance = await DuckDBInstance.create(":memory:");
|
|
1944
|
+
const verifyConnection = await verifyInstance.connect();
|
|
1945
|
+
try {
|
|
1946
|
+
await verifyConnection.run(
|
|
1947
|
+
`ATTACH '${escapeSqlString(nextReplicaTempPath)}' AS portal_replica_verify (READ_ONLY)`
|
|
1948
|
+
);
|
|
1949
|
+
await verifyConnection.run("DETACH portal_replica_verify");
|
|
1950
|
+
} finally {
|
|
1951
|
+
try {
|
|
1952
|
+
verifyConnection.closeSync();
|
|
1953
|
+
} catch {
|
|
1954
|
+
}
|
|
1955
|
+
try {
|
|
1956
|
+
verifyInstance.closeSync();
|
|
1957
|
+
} catch {
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
fs3.renameSync(nextReplicaTempPath, nextReplicaPath);
|
|
1961
|
+
console.error("[replica] Writing replica pointer", {
|
|
1962
|
+
portalId,
|
|
1963
|
+
nextReplicaName,
|
|
1964
|
+
nextReplicaPath
|
|
1965
|
+
});
|
|
1966
|
+
writeReplicaPointer(portalId, nextReplicaName);
|
|
1967
|
+
cleanupStaleReplicaFiles(portalId, nextReplicaPath);
|
|
1968
|
+
console.error("[replica] Publish complete", {
|
|
1969
|
+
portalId,
|
|
1970
|
+
nextReplicaPath,
|
|
1971
|
+
replica: getFileSnapshot(nextReplicaPath)
|
|
1972
|
+
});
|
|
1973
|
+
} catch (error) {
|
|
1974
|
+
console.error("[replica] Publish failed", {
|
|
1975
|
+
portalId,
|
|
1976
|
+
liveDbPath,
|
|
1977
|
+
nextReplicaPath,
|
|
1978
|
+
nextReplicaTempPath,
|
|
1979
|
+
error: describeError(error)
|
|
1980
|
+
});
|
|
1981
|
+
cleanupDbArtifacts(nextReplicaTempPath);
|
|
1982
|
+
throw error;
|
|
1983
|
+
}
|
|
1852
1984
|
}
|
|
1853
1985
|
async function ensureTable(connection, ddlStatements) {
|
|
1854
1986
|
for (const statement of ddlStatements) {
|
|
@@ -2860,8 +2992,11 @@ var makeReplicaLive = (config = {}) => {
|
|
|
2860
2992
|
dirty: true
|
|
2861
2993
|
})),
|
|
2862
2994
|
Effect25.flatMap(
|
|
2863
|
-
() =>
|
|
2864
|
-
|
|
2995
|
+
() => Effect25.sync(
|
|
2996
|
+
() => console.error("[replica] Failed to publish replica", {
|
|
2997
|
+
portalId,
|
|
2998
|
+
error
|
|
2999
|
+
})
|
|
2865
3000
|
)
|
|
2866
3001
|
),
|
|
2867
3002
|
Effect25.flatMap(() => Effect25.fail(error))
|
|
@@ -3933,26 +4068,64 @@ var setSelectedPortal = (portalId) => {
|
|
|
3933
4068
|
// src/effects/read-connection.ts
|
|
3934
4069
|
import { Effect as Effect31, pipe as pipe19 } from "effect";
|
|
3935
4070
|
import fs6 from "fs";
|
|
3936
|
-
var
|
|
4071
|
+
var openLiveConnection = (portalId, cause) => Effect31.tryPromise({
|
|
4072
|
+
try: async () => {
|
|
4073
|
+
const handle = await getDatabaseConnection(portalId, null);
|
|
4074
|
+
return handle.connection;
|
|
4075
|
+
},
|
|
4076
|
+
catch: (error) => new DatabaseError({
|
|
4077
|
+
message: `Failed to open live fallback connection for portal ${portalId}`,
|
|
4078
|
+
cause: { replicaError: cause, fallbackError: error }
|
|
4079
|
+
})
|
|
4080
|
+
});
|
|
4081
|
+
var getReadConnection = (portalId, options2) => pipe19(
|
|
3937
4082
|
Effect31.sync(() => replicaDbPath(portalId)),
|
|
3938
|
-
Effect31.
|
|
3939
|
-
(
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
4083
|
+
Effect31.flatMap((dbFile) => {
|
|
4084
|
+
if (!fs6.existsSync(dbFile)) {
|
|
4085
|
+
if (options2?.allowLiveFallback === true && fs6.existsSync(dbPath(portalId))) {
|
|
4086
|
+
console.error("[read-connection] Replica missing, falling back to live database", {
|
|
4087
|
+
portalId,
|
|
4088
|
+
replicaPath: dbFile,
|
|
4089
|
+
livePath: dbPath(portalId)
|
|
4090
|
+
});
|
|
4091
|
+
return openLiveConnection(
|
|
4092
|
+
portalId,
|
|
4093
|
+
new DatabaseError({
|
|
4094
|
+
message: `Read replica not found for portal ${portalId}. Has it been synced?`
|
|
4095
|
+
})
|
|
4096
|
+
);
|
|
4097
|
+
}
|
|
4098
|
+
return Effect31.fail(
|
|
4099
|
+
new DatabaseError({
|
|
4100
|
+
message: `Read replica not found for portal ${portalId}. Has it been synced?`
|
|
4101
|
+
})
|
|
4102
|
+
);
|
|
4103
|
+
}
|
|
4104
|
+
return Effect31.tryPromise({
|
|
3946
4105
|
try: async () => {
|
|
3947
4106
|
const handle = await getDatabaseConnection(portalId, null, { readOnly: true });
|
|
3948
4107
|
return handle.connection;
|
|
3949
4108
|
},
|
|
3950
|
-
catch: (
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
4109
|
+
catch: (error) => error
|
|
4110
|
+
});
|
|
4111
|
+
}),
|
|
4112
|
+
Effect31.catchAll((error) => {
|
|
4113
|
+
if (!(options2?.allowLiveFallback === true) || !fs6.existsSync(dbPath(portalId))) {
|
|
4114
|
+
return Effect31.fail(
|
|
4115
|
+
error instanceof DatabaseError ? error : new DatabaseError({
|
|
4116
|
+
message: `Failed to open read connection for portal ${portalId}`,
|
|
4117
|
+
cause: error
|
|
4118
|
+
})
|
|
4119
|
+
);
|
|
4120
|
+
}
|
|
4121
|
+
console.error("[read-connection] Read-only replica open failed, falling back to live database", {
|
|
4122
|
+
portalId,
|
|
4123
|
+
replicaPath: replicaDbPath(portalId),
|
|
4124
|
+
livePath: dbPath(portalId),
|
|
4125
|
+
error
|
|
4126
|
+
});
|
|
4127
|
+
return openLiveConnection(portalId, error);
|
|
4128
|
+
})
|
|
3956
4129
|
);
|
|
3957
4130
|
var closeReadConnection = (portalId) => Effect31.promise(() => evictDatabaseConnections(portalId));
|
|
3958
4131
|
|
|
@@ -5634,8 +5807,10 @@ var resolvePortalIds = (explicitPortalIds, selectedPortalId2, portals) => {
|
|
|
5634
5807
|
|
|
5635
5808
|
// src/tools/query.ts
|
|
5636
5809
|
var MAX_ROWS = 200;
|
|
5637
|
-
var runQueryForPortal = async (portalId, finalSql, autoCapped) => {
|
|
5638
|
-
const conn = await Effect50.runPromise(
|
|
5810
|
+
var runQueryForPortal = async (portalId, finalSql, autoCapped, allowLiveFallback) => {
|
|
5811
|
+
const conn = await Effect50.runPromise(
|
|
5812
|
+
getReadConnection(portalId, { allowLiveFallback })
|
|
5813
|
+
);
|
|
5639
5814
|
const start = performance.now();
|
|
5640
5815
|
const reader = await conn.runAndReadAll(finalSql);
|
|
5641
5816
|
const rows = reader.getRowObjects();
|
|
@@ -5711,7 +5886,8 @@ Example queries:
|
|
|
5711
5886
|
const result = await runQueryForPortal(
|
|
5712
5887
|
portalId,
|
|
5713
5888
|
finalSql,
|
|
5714
|
-
!hasExplicitLimit
|
|
5889
|
+
!hasExplicitLimit,
|
|
5890
|
+
deps.isMasterClient()
|
|
5715
5891
|
);
|
|
5716
5892
|
return {
|
|
5717
5893
|
content: [{ type: "text", text: stringifyResult(result) }]
|
|
@@ -5732,7 +5908,8 @@ Example queries:
|
|
|
5732
5908
|
keyedResults[String(portalId)] = await runQueryForPortal(
|
|
5733
5909
|
portalId,
|
|
5734
5910
|
finalSql,
|
|
5735
|
-
!hasExplicitLimit
|
|
5911
|
+
!hasExplicitLimit,
|
|
5912
|
+
deps.isMasterClient()
|
|
5736
5913
|
);
|
|
5737
5914
|
} catch (e) {
|
|
5738
5915
|
hadAnyErrors = true;
|
|
@@ -5952,8 +6129,10 @@ function openInBrowser(target) {
|
|
|
5952
6129
|
|
|
5953
6130
|
// src/tools/chart.ts
|
|
5954
6131
|
var MAX_ROWS2 = 200;
|
|
5955
|
-
var runQueryForPortal2 = async (portalId, sql) => {
|
|
5956
|
-
const conn = await Effect51.runPromise(
|
|
6132
|
+
var runQueryForPortal2 = async (portalId, sql, allowLiveFallback) => {
|
|
6133
|
+
const conn = await Effect51.runPromise(
|
|
6134
|
+
getReadConnection(portalId, { allowLiveFallback })
|
|
6135
|
+
);
|
|
5957
6136
|
const start = performance.now();
|
|
5958
6137
|
const reader = await conn.runAndReadAll(sql);
|
|
5959
6138
|
const rows = reader.getRowObjects();
|
|
@@ -6065,7 +6244,11 @@ SELECT json_extract_string(properties, '$.dealstage') as stage, COUNT(*) as coun
|
|
|
6065
6244
|
const [portalId] = portalResolution.portalIds;
|
|
6066
6245
|
try {
|
|
6067
6246
|
await deps.ensureFresh(portalId);
|
|
6068
|
-
const result = await runQueryForPortal2(
|
|
6247
|
+
const result = await runQueryForPortal2(
|
|
6248
|
+
portalId,
|
|
6249
|
+
finalSql,
|
|
6250
|
+
deps.isMasterClient()
|
|
6251
|
+
);
|
|
6069
6252
|
if (result.rows.length === 0) {
|
|
6070
6253
|
return {
|
|
6071
6254
|
content: [{ type: "text", text: "Query returned no rows - nothing to chart." }],
|
|
@@ -6119,7 +6302,11 @@ SELECT json_extract_string(properties, '$.dealstage') as stage, COUNT(*) as coun
|
|
|
6119
6302
|
for (const portalId of portalResolution.portalIds) {
|
|
6120
6303
|
try {
|
|
6121
6304
|
await deps.ensureFresh(portalId);
|
|
6122
|
-
const result = await runQueryForPortal2(
|
|
6305
|
+
const result = await runQueryForPortal2(
|
|
6306
|
+
portalId,
|
|
6307
|
+
finalSql,
|
|
6308
|
+
deps.isMasterClient()
|
|
6309
|
+
);
|
|
6123
6310
|
if (result.rows.length === 0) {
|
|
6124
6311
|
hadErrors = true;
|
|
6125
6312
|
keyedPortalResults[String(portalId)] = { error: "Query returned no rows - nothing to chart." };
|
|
@@ -6218,8 +6405,10 @@ async function buildConnectionSection(deps) {
|
|
|
6218
6405
|
totalPortals: portals.length
|
|
6219
6406
|
};
|
|
6220
6407
|
}
|
|
6221
|
-
async function buildSchemaSection(portalId) {
|
|
6222
|
-
const conn = await Effect52.runPromise(
|
|
6408
|
+
async function buildSchemaSection(portalId, deps) {
|
|
6409
|
+
const conn = await Effect52.runPromise(
|
|
6410
|
+
getReadConnection(portalId, { allowLiveFallback: deps.isMasterClient() })
|
|
6411
|
+
);
|
|
6223
6412
|
const allPluginTableNames = new Set(getAllTableNames());
|
|
6224
6413
|
const sectionResults = await Promise.all(
|
|
6225
6414
|
getPlugins().map(async (plugin) => {
|
|
@@ -6290,7 +6479,7 @@ Use "all" to get everything at once.`,
|
|
|
6290
6479
|
for (const portalId of portalResolution.portalIds) {
|
|
6291
6480
|
try {
|
|
6292
6481
|
await deps.ensureFresh(portalId);
|
|
6293
|
-
schemaByPortal[String(portalId)] = await buildSchemaSection(portalId);
|
|
6482
|
+
schemaByPortal[String(portalId)] = await buildSchemaSection(portalId, deps);
|
|
6294
6483
|
} catch (e) {
|
|
6295
6484
|
explicitSchemaHadErrors = true;
|
|
6296
6485
|
const message = e instanceof Error ? e.message : String(e);
|
|
@@ -6301,7 +6490,7 @@ Use "all" to get everything at once.`,
|
|
|
6301
6490
|
} else {
|
|
6302
6491
|
const [portalId] = portalResolution.portalIds;
|
|
6303
6492
|
await deps.ensureFresh(portalId);
|
|
6304
|
-
result.schema = await buildSchemaSection(portalId);
|
|
6493
|
+
result.schema = await buildSchemaSection(portalId, deps);
|
|
6305
6494
|
}
|
|
6306
6495
|
}
|
|
6307
6496
|
return {
|
|
@@ -19994,17 +20183,20 @@ var mainProgram = Effect108.gen(function* () {
|
|
|
19994
20183
|
};
|
|
19995
20184
|
registerPortalSelection(server, { ws, config, handoffSelectedPortal });
|
|
19996
20185
|
registerQueryTool(server, {
|
|
20186
|
+
isMasterClient,
|
|
19997
20187
|
getSelectedPortalId: getPortalId,
|
|
19998
20188
|
getPortals,
|
|
19999
20189
|
ensureFresh
|
|
20000
20190
|
});
|
|
20001
20191
|
registerChartTool(server, {
|
|
20192
|
+
isMasterClient,
|
|
20002
20193
|
getSelectedPortalId: getPortalId,
|
|
20003
20194
|
getPortals,
|
|
20004
20195
|
ensureFresh
|
|
20005
20196
|
});
|
|
20006
20197
|
registerStatusTool(server, {
|
|
20007
20198
|
getConnectionState: () => Effect108.runSync(ws.getState()),
|
|
20199
|
+
isMasterClient,
|
|
20008
20200
|
getPortals,
|
|
20009
20201
|
getSelectedPortalId: getPortalId,
|
|
20010
20202
|
ensureFresh
|