@daeda/mcp-pro 0.1.54 → 0.1.55
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 +217 -33
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -84524,6 +84524,20 @@ async function evictDatabaseConnections(portalId) {
|
|
|
84524
84524
|
}
|
|
84525
84525
|
}
|
|
84526
84526
|
}
|
|
84527
|
+
async function evictReadOnlyDatabaseConnections(portalId) {
|
|
84528
|
+
const readOnlyCachePrefix = `${portalId}:read_only:`;
|
|
84529
|
+
const matchingEntries = [...databaseConnectionCache.entries()].filter(
|
|
84530
|
+
([cacheKey]) => cacheKey.startsWith(readOnlyCachePrefix)
|
|
84531
|
+
);
|
|
84532
|
+
for (const [cacheKey, handlePromise] of matchingEntries) {
|
|
84533
|
+
databaseConnectionCache.delete(cacheKey);
|
|
84534
|
+
try {
|
|
84535
|
+
const handle = await handlePromise;
|
|
84536
|
+
await closeDatabaseHandle(handle);
|
|
84537
|
+
} catch {
|
|
84538
|
+
}
|
|
84539
|
+
}
|
|
84540
|
+
}
|
|
84527
84541
|
|
|
84528
84542
|
// src/pure/query-sql.ts
|
|
84529
84543
|
var isWordCharacter = (char) => char !== void 0 && /[A-Za-z0-9_]/.test(char);
|
|
@@ -84635,6 +84649,7 @@ var resolveMasterLockDependencies = (config, defaults) => ({
|
|
|
84635
84649
|
|
|
84636
84650
|
// src/pure/master-lock-events.ts
|
|
84637
84651
|
var promotionListeners = /* @__PURE__ */ new Set();
|
|
84652
|
+
var demotionListeners = /* @__PURE__ */ new Set();
|
|
84638
84653
|
var onPromotedToMaster = (listener) => {
|
|
84639
84654
|
promotionListeners.add(listener);
|
|
84640
84655
|
return () => {
|
|
@@ -84650,6 +84665,21 @@ var emitPromotedToMaster = () => {
|
|
|
84650
84665
|
}
|
|
84651
84666
|
}
|
|
84652
84667
|
};
|
|
84668
|
+
var onDemotedToReadOnly = (listener) => {
|
|
84669
|
+
demotionListeners.add(listener);
|
|
84670
|
+
return () => {
|
|
84671
|
+
demotionListeners.delete(listener);
|
|
84672
|
+
};
|
|
84673
|
+
};
|
|
84674
|
+
var emitDemotedToReadOnly = () => {
|
|
84675
|
+
for (const listener of demotionListeners) {
|
|
84676
|
+
try {
|
|
84677
|
+
listener();
|
|
84678
|
+
} catch (error) {
|
|
84679
|
+
console.error("[master-lock] Demotion listener failed:", error);
|
|
84680
|
+
}
|
|
84681
|
+
}
|
|
84682
|
+
};
|
|
84653
84683
|
|
|
84654
84684
|
// src/layers/MasterLockLive.ts
|
|
84655
84685
|
var PROMOTION_POLL_MS = 3e3;
|
|
@@ -84700,6 +84730,7 @@ var handleCompromisedLease = (runtime, error) => pipe19(
|
|
|
84700
84730
|
`[master-lock] master lease compromised: ${error.message}`
|
|
84701
84731
|
),
|
|
84702
84732
|
Effect30.flatMap(() => Ref.set(runtime.connectionTypeRef, "READ_ONLY")),
|
|
84733
|
+
Effect30.tap(() => Effect30.sync(() => emitDemotedToReadOnly())),
|
|
84703
84734
|
Effect30.flatMap(() => startPromotionPolling(runtime))
|
|
84704
84735
|
);
|
|
84705
84736
|
})
|
|
@@ -85202,6 +85233,32 @@ var DuckDBInterfaceLiveBase = Layer4.effect(
|
|
|
85202
85233
|
}))
|
|
85203
85234
|
)
|
|
85204
85235
|
);
|
|
85236
|
+
const setMetadataWithoutReplica = (portalId, key2, value) => withPortalWritePermit(
|
|
85237
|
+
portalId,
|
|
85238
|
+
"setMetadataWithoutReplica",
|
|
85239
|
+
Effect32.orDie(
|
|
85240
|
+
Effect32.tryPromise({
|
|
85241
|
+
try: () => setPortalMetadata({
|
|
85242
|
+
masterLock: { isMaster: true },
|
|
85243
|
+
portalId,
|
|
85244
|
+
key: key2,
|
|
85245
|
+
value
|
|
85246
|
+
}),
|
|
85247
|
+
catch: (error) => error
|
|
85248
|
+
})
|
|
85249
|
+
)
|
|
85250
|
+
);
|
|
85251
|
+
const publishReplica2 = (input) => withPortalWritePermit(
|
|
85252
|
+
input.portalId,
|
|
85253
|
+
"publishReplica",
|
|
85254
|
+
Effect32.orDie(
|
|
85255
|
+
replica.syncReplica({
|
|
85256
|
+
portalId: input.portalId,
|
|
85257
|
+
reason: input.reason,
|
|
85258
|
+
mode: input.mode
|
|
85259
|
+
})
|
|
85260
|
+
)
|
|
85261
|
+
);
|
|
85205
85262
|
const syncObjectArtifact2 = (input) => withPortalWritePermit(
|
|
85206
85263
|
input.portalId,
|
|
85207
85264
|
"syncObjectArtifact",
|
|
@@ -85406,6 +85463,8 @@ var DuckDBInterfaceLiveBase = Layer4.effect(
|
|
|
85406
85463
|
getPropertyDefinitionStateSummary: getPropertyDefinitionStateSummary2,
|
|
85407
85464
|
getWorkflowStateSummary: getWorkflowStateSummary2,
|
|
85408
85465
|
setMetadata: setMetadata2,
|
|
85466
|
+
setMetadataWithoutReplica,
|
|
85467
|
+
publishReplica: publishReplica2,
|
|
85409
85468
|
syncObjectArtifact: syncObjectArtifact2,
|
|
85410
85469
|
syncAssociationArtifact: syncAssociationArtifact2,
|
|
85411
85470
|
syncPlugin: syncPlugin2,
|
|
@@ -85420,9 +85479,13 @@ var DuckDBInterfaceLiveBase = Layer4.effect(
|
|
|
85420
85479
|
})
|
|
85421
85480
|
)
|
|
85422
85481
|
);
|
|
85482
|
+
var SharedMasterLockLive = Layer4.orDie(MasterLockLive);
|
|
85483
|
+
var SharedReplicaLive = makeReplicaLive({
|
|
85484
|
+
masterLockLayer: SharedMasterLockLive
|
|
85485
|
+
});
|
|
85423
85486
|
var DuckDBInterfaceLive = pipe21(
|
|
85424
85487
|
DuckDBInterfaceLiveBase,
|
|
85425
|
-
Layer4.provide(Layer4.mergeAll(
|
|
85488
|
+
Layer4.provide(Layer4.mergeAll(SharedMasterLockLive, SharedReplicaLive))
|
|
85426
85489
|
);
|
|
85427
85490
|
|
|
85428
85491
|
// src/layers/FileDownloadLive.ts
|
|
@@ -86281,7 +86344,7 @@ var getReadConnection = (portalId, options8) => pipe25(
|
|
|
86281
86344
|
return openLiveConnection(portalId, error);
|
|
86282
86345
|
})
|
|
86283
86346
|
);
|
|
86284
|
-
var closeReadConnection = (portalId) => Effect37.promise(() =>
|
|
86347
|
+
var closeReadConnection = (portalId) => Effect37.promise(() => evictReadOnlyDatabaseConnections(portalId));
|
|
86285
86348
|
|
|
86286
86349
|
// src/layers/PortalFileStateLive.ts
|
|
86287
86350
|
import { Effect as Effect38, Layer as Layer6, pipe as pipe26 } from "effect";
|
|
@@ -88702,7 +88765,7 @@ Optional: pass portalIds to run the same query across one or more specific porta
|
|
|
88702
88765
|
|
|
88703
88766
|
Data freshness: enabled portals are automatically checked for freshness before querying. If portal sync is disabled, queries use stale local data when available and otherwise return an error.
|
|
88704
88767
|
|
|
88705
|
-
IMPORTANT: the automatic freshness check covers artifact-backed CRM data, but it does NOT make lightweight message-plugin tables current. Before querying plugin-backed tables such as lists, owners, pipelines, forms, inboxes, or sequences when current values matter, first inspect status(section="schema"), call refresh_plugins if needed, and wait for completion via status(section="schema") before running the query.
|
|
88768
|
+
IMPORTANT: the automatic freshness check covers artifact-backed CRM data, but it does NOT make lightweight message-plugin tables current. Before querying plugin-backed tables such as lists, owners, pipelines, forms, inboxes, or sequences when current values matter, first inspect status(section="schema"), call refresh_plugins if needed, and wait for replica-verified completion via status(section="schema") before running the query.
|
|
88706
88769
|
|
|
88707
88770
|
If your task depends on both artifact-backed data and plugin-backed metadata, refresh the plugin data first, wait for completion, then run the query.
|
|
88708
88771
|
|
|
@@ -89067,7 +89130,7 @@ Optional: pass portalIds to run the same chart query across one or more specific
|
|
|
89067
89130
|
|
|
89068
89131
|
Data freshness: enabled portals are automatically checked for freshness before charting. If portal sync is disabled, charting uses stale local data when available and otherwise returns an error.
|
|
89069
89132
|
|
|
89070
|
-
IMPORTANT: the automatic freshness check covers artifact-backed CRM data, but it does NOT automatically refresh lightweight message-plugin tables. Before charting plugin-backed tables such as lists, owners, pipelines, forms, inboxes, or sequences when current values matter, first inspect status(section="schema"), call refresh_plugins if needed, and wait for completion via status(section="schema") before charting.
|
|
89133
|
+
IMPORTANT: the automatic freshness check covers artifact-backed CRM data, but it does NOT automatically refresh lightweight message-plugin tables. Before charting plugin-backed tables such as lists, owners, pipelines, forms, inboxes, or sequences when current values matter, first inspect status(section="schema"), call refresh_plugins if needed, and wait for replica-verified completion via status(section="schema") before charting.
|
|
89071
89134
|
|
|
89072
89135
|
The query results map to the chart as follows:
|
|
89073
89136
|
- First column \u2192 X-axis labels (categories)
|
|
@@ -89243,6 +89306,7 @@ SELECT json_extract_string(properties, '$.dealstage') as stage, COUNT(*) as coun
|
|
|
89243
89306
|
import { z as z25 } from "zod";
|
|
89244
89307
|
import fs12 from "fs";
|
|
89245
89308
|
import { Effect as Effect66 } from "effect";
|
|
89309
|
+
var PLUGIN_SYNC_METADATA_PREFIX = "last_synced:plugin:";
|
|
89246
89310
|
function formatBytes(bytes) {
|
|
89247
89311
|
if (bytes === 0) return "0 B";
|
|
89248
89312
|
const units = ["B", "KB", "MB", "GB"];
|
|
@@ -89255,12 +89319,16 @@ function getDbFileSize(portalId) {
|
|
|
89255
89319
|
if (!fs12.existsSync(db2)) return null;
|
|
89256
89320
|
return fs12.statSync(db2).size;
|
|
89257
89321
|
}
|
|
89258
|
-
var buildLightweightPluginFreshnessSection = (portalState, now = Date.now()) => Object.fromEntries(
|
|
89322
|
+
var buildLightweightPluginFreshnessSection = (portalState, now = Date.now(), replicaPluginSyncMetadata = {}) => Object.fromEntries(
|
|
89259
89323
|
getMessagePlugins().map((plugin) => {
|
|
89260
89324
|
const pluginState = portalState?.plugins.find((entry) => entry.name === plugin.name) ?? null;
|
|
89261
|
-
const
|
|
89325
|
+
const replicaLastSynced = replicaPluginSyncMetadata[`${PLUGIN_SYNC_METADATA_PREFIX}${plugin.name}`] ?? null;
|
|
89326
|
+
const freshness = buildPluginFreshness(
|
|
89327
|
+
replicaLastSynced ?? pluginState?.lastSynced?.toISOString() ?? null,
|
|
89328
|
+
now
|
|
89329
|
+
);
|
|
89262
89330
|
return [plugin.name, {
|
|
89263
|
-
status: pluginState?.status ?? "NOT_STARTED",
|
|
89331
|
+
status: pluginState?.status ?? (replicaLastSynced === null ? "NOT_STARTED" : "SYNCED"),
|
|
89264
89332
|
error: pluginState?.error ?? null,
|
|
89265
89333
|
lastRefreshedAt: freshness.lastRefreshedAt,
|
|
89266
89334
|
ageMs: freshness.ageMs,
|
|
@@ -89268,6 +89336,29 @@ var buildLightweightPluginFreshnessSection = (portalState, now = Date.now()) =>
|
|
|
89268
89336
|
}];
|
|
89269
89337
|
})
|
|
89270
89338
|
);
|
|
89339
|
+
var isMissingSyncMetadataTableError2 = (error) => {
|
|
89340
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
89341
|
+
return message.includes("sync_metadata") && message.includes("does not exist");
|
|
89342
|
+
};
|
|
89343
|
+
var loadReplicaPluginSyncMetadata = async (conn) => {
|
|
89344
|
+
try {
|
|
89345
|
+
const reader = await conn.runAndReadAll(
|
|
89346
|
+
`SELECT key, value FROM sync_metadata WHERE key LIKE '${PLUGIN_SYNC_METADATA_PREFIX}%'`
|
|
89347
|
+
);
|
|
89348
|
+
const rows = reader.getRowObjects();
|
|
89349
|
+
const result = {};
|
|
89350
|
+
for (const row of rows) {
|
|
89351
|
+
if (typeof row.key !== "string" || typeof row.value !== "string") continue;
|
|
89352
|
+
result[row.key] = row.value;
|
|
89353
|
+
}
|
|
89354
|
+
return result;
|
|
89355
|
+
} catch (error) {
|
|
89356
|
+
if (isMissingSyncMetadataTableError2(error)) {
|
|
89357
|
+
return {};
|
|
89358
|
+
}
|
|
89359
|
+
throw error;
|
|
89360
|
+
}
|
|
89361
|
+
};
|
|
89271
89362
|
async function buildConnectionSection(deps) {
|
|
89272
89363
|
const selectedPortalId2 = deps.getSelectedPortalId();
|
|
89273
89364
|
const connectionState = deps.getConnectionState();
|
|
@@ -89310,7 +89401,12 @@ async function buildSchemaSection(portalId, deps) {
|
|
|
89310
89401
|
);
|
|
89311
89402
|
const allPluginTableNames = new Set(getAllTableNames());
|
|
89312
89403
|
const portalState = await deps.getPortalState(portalId);
|
|
89313
|
-
const
|
|
89404
|
+
const replicaPluginSyncMetadata = await loadReplicaPluginSyncMetadata(conn);
|
|
89405
|
+
const lightweightPlugins = buildLightweightPluginFreshnessSection(
|
|
89406
|
+
portalState,
|
|
89407
|
+
Date.now(),
|
|
89408
|
+
replicaPluginSyncMetadata
|
|
89409
|
+
);
|
|
89314
89410
|
const sectionResults = await Promise.all(
|
|
89315
89411
|
getPlugins().map(async (plugin) => {
|
|
89316
89412
|
try {
|
|
@@ -89384,7 +89480,7 @@ The schema output also includes lightweight plugin freshness data so you can see
|
|
|
89384
89480
|
|
|
89385
89481
|
Status output also includes active and recent lightweight plugin refresh jobs so callers can inspect queued, running, completed, or failed manual refresh requests without blocking on the initial tool call.
|
|
89386
89482
|
|
|
89387
|
-
Use status(section="schema") as the authoritative freshness gate for lightweight plugins. After calling refresh_plugins, poll status(section="schema") for the same portal until the matching refresh job is COMPLETED, none of the requested plugins are still QUEUED, RUNNING, or REPLICATING, and the requested plugins show updated lastRefreshedAt values. Do not proceed with plugin-dependent work before those conditions are met.
|
|
89483
|
+
Use status(section="schema") as the authoritative freshness gate for lightweight plugins. After calling refresh_plugins, poll status(section="schema") for the same portal until the matching refresh job is COMPLETED, none of the requested plugins are still QUEUED, RUNNING, or REPLICATING, and the requested plugins show updated replica-backed lastRefreshedAt values. COMPLETED means the executor verified the newly published read replica, not merely that remote execution finished. Do not proceed with plugin-dependent work before those conditions are met.
|
|
89388
89484
|
|
|
89389
89485
|
Artifact-backed CRM data and lightweight plugin metadata have different freshness rules. Artifact freshness is handled automatically for query/chart reads, but lightweight plugin freshness is not.
|
|
89390
89486
|
|
|
@@ -133781,7 +133877,7 @@ VIEWING THE PLAN:
|
|
|
133781
133877
|
|
|
133782
133878
|
IMPORTANT: After building the plan, present it to the user and ask for confirmation before calling submit_plan.
|
|
133783
133879
|
|
|
133784
|
-
When building operations that depend on current lightweight metadata or IDs from local tables, such as owners, lists, pipelines, forms, inboxes, or sequences, first ensure those plugin tables are fresh. Use status(section="schema") to inspect freshness, refresh_plugins to refresh the required plugin set, and wait for completion with status(section="schema") before resolving IDs or constructing operations that depend on them.
|
|
133880
|
+
When building operations that depend on current lightweight metadata or IDs from local tables, such as owners, lists, pipelines, forms, inboxes, or sequences, first ensure those plugin tables are fresh. Use status(section="schema") to inspect freshness, refresh_plugins to refresh the required plugin set, and wait for replica-verified completion with status(section="schema") before resolving IDs or constructing operations that depend on them.
|
|
133785
133881
|
|
|
133786
133882
|
WORKFLOW:
|
|
133787
133883
|
1. describe_operations - Discover available operation types and their required fields
|
|
@@ -134449,7 +134545,7 @@ The requested pluginNames array is required. Optional portalIds lets you refresh
|
|
|
134449
134545
|
|
|
134450
134546
|
This tool starts an asynchronous refresh job and returns immediately. It does NOT wait for fresh data to be available.
|
|
134451
134547
|
|
|
134452
|
-
After calling refresh_plugins, do not run any query, chart, or plan-building step that depends on those plugin tables until you confirm completion with status(section="schema"). Wait until the relevant refresh job is COMPLETED and the requested plugins show updated freshness data. If the job FAILED, surface the error instead of proceeding.
|
|
134548
|
+
After calling refresh_plugins, do not run any query, chart, or plan-building step that depends on those plugin tables until you confirm completion with status(section="schema"). Wait until the relevant refresh job is COMPLETED and the requested plugins show updated replica-backed freshness data. COMPLETED means the executor verified the newly published read replica, not merely that remote execution finished. If the job FAILED, surface the error instead of proceeding.
|
|
134453
134549
|
|
|
134454
134550
|
If the same plugin set is needed across multiple portals, prefer one call using portalIds. Use \`status\` to inspect in-flight and recent refresh jobs.`,
|
|
134455
134551
|
inputSchema: {
|
|
@@ -134734,6 +134830,25 @@ var createPluginRefreshJobStore = () => {
|
|
|
134734
134830
|
var earliestStartedAt = (plugins2) => plugins2.map((plugin) => plugin.startedAt).filter((value) => value !== null).sort()[0] ?? null;
|
|
134735
134831
|
var finalizeManualPluginRefreshJob = async (input) => {
|
|
134736
134832
|
const startedAt = earliestStartedAt(input.plugins);
|
|
134833
|
+
try {
|
|
134834
|
+
await input.prepareReplicaVerification();
|
|
134835
|
+
} catch (error) {
|
|
134836
|
+
const message = input.formatError(error);
|
|
134837
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
134838
|
+
await input.updateJobProgress({
|
|
134839
|
+
jobId: input.jobId,
|
|
134840
|
+
portalId: input.portalId,
|
|
134841
|
+
pluginNames: input.pluginNames,
|
|
134842
|
+
createdAt: input.createdAt,
|
|
134843
|
+
status: "FAILED",
|
|
134844
|
+
plugins: input.plugins,
|
|
134845
|
+
startedAt,
|
|
134846
|
+
finishedAt,
|
|
134847
|
+
error: message,
|
|
134848
|
+
relayToServer: input.relayToServer
|
|
134849
|
+
});
|
|
134850
|
+
return;
|
|
134851
|
+
}
|
|
134737
134852
|
await input.updateJobProgress({
|
|
134738
134853
|
jobId: input.jobId,
|
|
134739
134854
|
portalId: input.portalId,
|
|
@@ -134746,6 +134861,7 @@ var finalizeManualPluginRefreshJob = async (input) => {
|
|
|
134746
134861
|
});
|
|
134747
134862
|
try {
|
|
134748
134863
|
await input.publishReplica();
|
|
134864
|
+
await input.verifyReplica();
|
|
134749
134865
|
} catch (error) {
|
|
134750
134866
|
const message = input.formatError(error);
|
|
134751
134867
|
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -135172,6 +135288,7 @@ var STATUS_METADATA = {
|
|
|
135172
135288
|
};
|
|
135173
135289
|
var UPDATE_CHECK_TIMEOUT_MS = 2e3;
|
|
135174
135290
|
var UPDATE_CHECK_CACHE_TTL_MS = 5 * 6e4;
|
|
135291
|
+
var PLUGIN_REFRESH_VERIFICATION_PREFIX = "plugin_refresh_verification:";
|
|
135175
135292
|
var latestPackageVersionCache = null;
|
|
135176
135293
|
var fetchLatestPackageVersion = async (packageName) => {
|
|
135177
135294
|
const now = Date.now();
|
|
@@ -135247,6 +135364,26 @@ var mainProgram = Effect147.gen(function* () {
|
|
|
135247
135364
|
const isMasterClient = () => getConnectionType() === "MASTER";
|
|
135248
135365
|
const getSyncRole = () => isMasterClient() ? "master" : "read_only";
|
|
135249
135366
|
const formatErrorMessage2 = (error) => error instanceof Error ? error.message : String(error);
|
|
135367
|
+
const buildPluginRefreshVerificationKey = (jobId) => `${PLUGIN_REFRESH_VERIFICATION_PREFIX}${jobId}`;
|
|
135368
|
+
const buildStalePluginRefreshExecutorProgress = (jobId, portalId, pluginNames) => {
|
|
135369
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
135370
|
+
const error = `Rejected plugin refresh execute for portal ${portalId} because this client is read-only locally.`;
|
|
135371
|
+
return {
|
|
135372
|
+
job_id: jobId,
|
|
135373
|
+
target_portal: portalId,
|
|
135374
|
+
plugin_names: [...pluginNames],
|
|
135375
|
+
status: "FAILED",
|
|
135376
|
+
plugins: pluginNames.map((pluginName) => ({
|
|
135377
|
+
plugin_name: pluginName,
|
|
135378
|
+
status: "FAILED",
|
|
135379
|
+
error,
|
|
135380
|
+
finished_at: finishedAt
|
|
135381
|
+
})),
|
|
135382
|
+
created_at: finishedAt,
|
|
135383
|
+
finished_at: finishedAt,
|
|
135384
|
+
error
|
|
135385
|
+
};
|
|
135386
|
+
};
|
|
135250
135387
|
const logReadOnlySkip = (action, portalId) => {
|
|
135251
135388
|
const portalPart = portalId === void 0 ? "" : ` for portal ${portalId}`;
|
|
135252
135389
|
console.error(`[sync] Skipping ${action}${portalPart} on read-only client`);
|
|
@@ -135273,6 +135410,20 @@ var mainProgram = Effect147.gen(function* () {
|
|
|
135273
135410
|
syncRole: getSyncRole()
|
|
135274
135411
|
})
|
|
135275
135412
|
);
|
|
135413
|
+
const refreshRegistrationForConnectionTypeChange = (reason) => {
|
|
135414
|
+
scheduleRuntimeReconcile();
|
|
135415
|
+
Effect147.runFork(
|
|
135416
|
+
pipe122(
|
|
135417
|
+
Effect147.promise(() => syncRegistrationContext()),
|
|
135418
|
+
Effect147.flatMap(() => ws6.refreshRegistration()),
|
|
135419
|
+
Effect147.catchAll(
|
|
135420
|
+
(error) => Effect147.sync(() => {
|
|
135421
|
+
console.error(`[ws] Failed to refresh registration after ${reason}:`, error);
|
|
135422
|
+
})
|
|
135423
|
+
)
|
|
135424
|
+
)
|
|
135425
|
+
);
|
|
135426
|
+
};
|
|
135276
135427
|
const ensureFresh = makeEnsureFresh({
|
|
135277
135428
|
getSelectedPortalId: getPortalId,
|
|
135278
135429
|
isMasterClient,
|
|
@@ -135502,6 +135653,10 @@ var mainProgram = Effect147.gen(function* () {
|
|
|
135502
135653
|
});
|
|
135503
135654
|
return;
|
|
135504
135655
|
}
|
|
135656
|
+
const verificationMarker = {
|
|
135657
|
+
key: buildPluginRefreshVerificationKey(input.jobId),
|
|
135658
|
+
value: (/* @__PURE__ */ new Date()).toISOString()
|
|
135659
|
+
};
|
|
135505
135660
|
await finalizeManualPluginRefreshJob({
|
|
135506
135661
|
jobId: input.jobId,
|
|
135507
135662
|
portalId: input.portalId,
|
|
@@ -135509,20 +135664,36 @@ var mainProgram = Effect147.gen(function* () {
|
|
|
135509
135664
|
createdAt: input.createdAt,
|
|
135510
135665
|
plugins: plugins2,
|
|
135511
135666
|
relayToServer: input.relayToServer,
|
|
135667
|
+
prepareReplicaVerification: async () => {
|
|
135668
|
+
await Effect147.runPromise(
|
|
135669
|
+
duckDb.setMetadataWithoutReplica(
|
|
135670
|
+
input.portalId,
|
|
135671
|
+
verificationMarker.key,
|
|
135672
|
+
verificationMarker.value
|
|
135673
|
+
)
|
|
135674
|
+
);
|
|
135675
|
+
},
|
|
135512
135676
|
updateJobProgress,
|
|
135513
135677
|
publishReplica: () => Effect147.runPromise(
|
|
135514
|
-
|
|
135515
|
-
|
|
135516
|
-
|
|
135517
|
-
|
|
135518
|
-
|
|
135519
|
-
reason: `manualPluginRefresh:${input.jobId}`,
|
|
135520
|
-
mode: "immediate"
|
|
135521
|
-
})
|
|
135522
|
-
),
|
|
135523
|
-
Effect147.provide(ReplicaLive)
|
|
135524
|
-
)
|
|
135678
|
+
duckDb.publishReplica({
|
|
135679
|
+
portalId: input.portalId,
|
|
135680
|
+
reason: `manualPluginRefresh:${input.jobId}`,
|
|
135681
|
+
mode: "immediate"
|
|
135682
|
+
})
|
|
135525
135683
|
),
|
|
135684
|
+
verifyReplica: async () => {
|
|
135685
|
+
await evictReadOnlyDatabaseConnections(input.portalId);
|
|
135686
|
+
const replicatedValue = await getPortalMetadata({
|
|
135687
|
+
portalId: input.portalId,
|
|
135688
|
+
key: verificationMarker.key,
|
|
135689
|
+
masterLock: { isMaster: false }
|
|
135690
|
+
});
|
|
135691
|
+
if (replicatedValue !== verificationMarker.value) {
|
|
135692
|
+
throw new Error(
|
|
135693
|
+
`Replica verification failed for portal ${input.portalId}: expected marker '${verificationMarker.key}' to equal '${verificationMarker.value}', got '${replicatedValue ?? "null"}'.`
|
|
135694
|
+
);
|
|
135695
|
+
}
|
|
135696
|
+
},
|
|
135526
135697
|
formatError: formatErrorMessage2
|
|
135527
135698
|
});
|
|
135528
135699
|
}
|
|
@@ -135926,17 +136097,10 @@ var mainProgram = Effect147.gen(function* () {
|
|
|
135926
136097
|
scheduleRuntimeReconcile = runtimeReconciler.scheduleReconcile;
|
|
135927
136098
|
runtimeReconciler.startPolling();
|
|
135928
136099
|
onPromotedToMaster(() => {
|
|
135929
|
-
|
|
135930
|
-
|
|
135931
|
-
|
|
135932
|
-
|
|
135933
|
-
Effect147.catchAll(
|
|
135934
|
-
(error) => Effect147.sync(() => {
|
|
135935
|
-
console.error("[ws] Failed to refresh registration after promotion:", error);
|
|
135936
|
-
})
|
|
135937
|
-
)
|
|
135938
|
-
)
|
|
135939
|
-
);
|
|
136100
|
+
refreshRegistrationForConnectionTypeChange("promotion");
|
|
136101
|
+
});
|
|
136102
|
+
onDemotedToReadOnly(() => {
|
|
136103
|
+
refreshRegistrationForConnectionTypeChange("demotion");
|
|
135940
136104
|
});
|
|
135941
136105
|
runtimeReconciler.scheduleReconcile();
|
|
135942
136106
|
configureArtifactQueue(
|
|
@@ -136214,6 +136378,26 @@ var mainProgram = Effect147.gen(function* () {
|
|
|
136214
136378
|
onPluginRefreshExecute: (payload) => {
|
|
136215
136379
|
if (!isMasterClient()) {
|
|
136216
136380
|
logReadOnlySkip("plugin refresh execute", payload.target_portal);
|
|
136381
|
+
refreshRegistrationForConnectionTypeChange("demotion");
|
|
136382
|
+
Effect147.runFork(
|
|
136383
|
+
pipe122(
|
|
136384
|
+
ws6.sendPluginRefreshProgress(
|
|
136385
|
+
buildStalePluginRefreshExecutorProgress(
|
|
136386
|
+
payload.job_id,
|
|
136387
|
+
payload.target_portal,
|
|
136388
|
+
payload.plugin_names
|
|
136389
|
+
)
|
|
136390
|
+
),
|
|
136391
|
+
Effect147.catchAll(
|
|
136392
|
+
(error) => Effect147.sync(() => {
|
|
136393
|
+
console.error(
|
|
136394
|
+
`[plugin-refresh] Failed to reject stale executor for portal ${payload.target_portal}:`,
|
|
136395
|
+
error
|
|
136396
|
+
);
|
|
136397
|
+
})
|
|
136398
|
+
)
|
|
136399
|
+
)
|
|
136400
|
+
);
|
|
136217
136401
|
return;
|
|
136218
136402
|
}
|
|
136219
136403
|
const existing = refreshJobStore.getSnapshot(payload.job_id);
|