@codemem/server 0.32.3 → 0.33.0-alpha.2
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 +93 -23
- package/dist/index.js.map +1 -1
- package/dist/routes/sync.d.ts.map +1 -1
- package/package.json +2 -2
- package/static/app.js +247 -463
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
-
import { CODEMEM_CONFIG_ENV_OVERRIDES, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, MemoryStore, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, addSyncScopeToBoundary, analyzeProjectScopeMappingChangeGuardrails, applyReplicationOps, buildBaseUrl, buildFilterClausesWithContext, buildRawEventEnvelopeFromHook, canonicalMemoryKind, canonicalWorkspaceIdentity, cleanupNonces, coordinatorArchiveGroupAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnableDeviceAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, coordinatorUpdateScopeAction, createCoordinatorReciprocalApproval, defaultSpaceScopeIdForGroup, deleteProjectScopeSettingsMapping, ensureDeviceIdentity, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, formatHostPort, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorGroupPreference, getSemanticIndexDiagnostics, getSyncResetState, isSummaryLikeMemory, listCoordinatorJoinRequests, listInboundScopeRejections, listMaintenanceJobs, listObserverProviderOptions, listProjectScopeCandidates, listProjectScopeInventory, listProjectScopeSettingsMappings, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, negotiateSyncCapability, normalizeAddress, normalizeSyncCapability, parseStrictInteger, parseSyncScopeRequest, personalScopeGrantStatusForPeer, probeAvailableCredentials, readCodememConfigFile, readCoordinatorSyncConfig, reassignProjectScopeInventoryProject, recordNonce, rejectInboundScopeFailures, requestJson, resolveDbPath, runSyncPass, schema, stripJsonComments, stripPrivateObj, stripTrailingCommas, summarizeInboundScopeRejections, syncScopeResetRequiredPayload, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMapping, verifySignature, writeCodememConfigFile } from "@codemem/core";
|
|
3
|
+
import { CODEMEM_CONFIG_ENV_OVERRIDES, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, MemoryStore, SYNC_CAPABILITY_HEADER, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, addSyncScopeToBoundary, analyzeProjectScopeMappingChangeGuardrails, applyReplicationOps, buildBaseUrl, buildFilterClausesWithContext, buildRawEventEnvelopeFromHook, canonicalMemoryKind, canonicalWorkspaceIdentity, cleanupNonces, coordinatorArchiveGroupAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnableDeviceAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, coordinatorUpdateScopeAction, createCoordinatorReciprocalApproval, defaultSpaceScopeIdForGroup, deleteProjectScopeSettingsMapping, ensureDeviceIdentity, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, formatHostPort, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorGroupPreference, getSemanticIndexDiagnostics, getSyncResetState, isScopedSyncCapability, isSummaryLikeMemory, listAuthorizedScopesForPeer, listCoordinatorJoinRequests, listInboundScopeRejections, listMaintenanceJobs, listObserverProviderOptions, listPerPeerScopeSyncState, listProjectScopeCandidates, listProjectScopeInventory, listProjectScopeSettingsMappings, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, negotiateSyncCapability, normalizeAddress, normalizeSyncCapability, parseStrictInteger, parseSyncScopeRequest, personalScopeGrantStatusForPeer, probeAvailableCredentials, readCodememConfigFile, readCoordinatorSyncConfig, reassignProjectScopeInventoryProject, recordNonce, rejectInboundScopeFailures, requestJson, resolveDbPath, runSyncPass, schema, stripJsonComments, stripPrivateObj, stripTrailingCommas, summarizeInboundScopeRejections, syncScopeResetRequiredPayload, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMapping, verifySignature, writeCodememConfigFile } from "@codemem/core";
|
|
4
4
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
5
5
|
import { Hono } from "hono";
|
|
6
6
|
import { createMiddleware } from "hono/factory";
|
|
@@ -2258,8 +2258,18 @@ function peerAuthorizedScopes(store, peerDeviceId) {
|
|
|
2258
2258
|
}
|
|
2259
2259
|
function ensureLocalActorRecord(store) {
|
|
2260
2260
|
const d = drizzle(store.db, { schema });
|
|
2261
|
-
if (d.select({ actor_id: schema.actors.actor_id }).from(schema.actors).where(eq(schema.actors.actor_id, store.actorId)).get()) return;
|
|
2262
2261
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2262
|
+
d.update(schema.actors).set({
|
|
2263
|
+
is_local: 0,
|
|
2264
|
+
updated_at: now
|
|
2265
|
+
}).where(and(eq(schema.actors.is_local, 1), ne(schema.actors.actor_id, store.actorId))).run();
|
|
2266
|
+
if (d.select({ actor_id: schema.actors.actor_id }).from(schema.actors).where(eq(schema.actors.actor_id, store.actorId)).get()) {
|
|
2267
|
+
d.update(schema.actors).set({
|
|
2268
|
+
is_local: 1,
|
|
2269
|
+
updated_at: now
|
|
2270
|
+
}).where(eq(schema.actors.actor_id, store.actorId)).run();
|
|
2271
|
+
return;
|
|
2272
|
+
}
|
|
2263
2273
|
d.insert(schema.actors).values({
|
|
2264
2274
|
actor_id: store.actorId,
|
|
2265
2275
|
display_name: store.actorDisplayName,
|
|
@@ -2335,13 +2345,25 @@ function redactCoordinatorStatus(coordinator, showDiag) {
|
|
|
2335
2345
|
discovered_devices: discovered
|
|
2336
2346
|
};
|
|
2337
2347
|
}
|
|
2348
|
+
function detailForNotConfiguredReason(reason) {
|
|
2349
|
+
switch (reason) {
|
|
2350
|
+
case "coordinator_url_missing": return "Configure a coordinator URL before pairing with discovered peers.";
|
|
2351
|
+
case "coordinator_groups_empty": return "Join a coordinator team before pairing with discovered peers.";
|
|
2352
|
+
case "sync_disabled": return "Enable sync on this device before pairing with discovered peers.";
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2338
2355
|
async function acceptDiscoveredPeer(store, input) {
|
|
2339
2356
|
const config = readCoordinatorSyncConfig();
|
|
2340
|
-
|
|
2357
|
+
let notConfiguredReason = null;
|
|
2358
|
+
if (!config.syncCoordinatorUrl) notConfiguredReason = "coordinator_url_missing";
|
|
2359
|
+
else if (config.syncCoordinatorGroups.length === 0) notConfiguredReason = "coordinator_groups_empty";
|
|
2360
|
+
else if (!config.syncEnabled) notConfiguredReason = "sync_disabled";
|
|
2361
|
+
if (notConfiguredReason) return {
|
|
2341
2362
|
ok: false,
|
|
2342
2363
|
status: 400,
|
|
2343
2364
|
error: "coordinator_not_configured",
|
|
2344
|
-
|
|
2365
|
+
reason: notConfiguredReason,
|
|
2366
|
+
detail: detailForNotConfiguredReason(notConfiguredReason)
|
|
2345
2367
|
};
|
|
2346
2368
|
const discovered = await lookupCoordinatorPeers(store, config);
|
|
2347
2369
|
const inputFingerprint = String(input.fingerprint ?? "").trim();
|
|
@@ -2536,7 +2558,7 @@ function claimedLocalActorScopeStatus(store, row) {
|
|
|
2536
2558
|
* When showDiag is false, sensitive fields (fingerprint, last_error, addresses)
|
|
2537
2559
|
* are redacted.
|
|
2538
2560
|
*/
|
|
2539
|
-
function mapPeerRow(store, row, showDiag, recentOpsByPeer, scopeRejectionsByPeer) {
|
|
2561
|
+
function mapPeerRow(store, row, showDiag, recentOpsByPeer, scopeRejectionsByPeer, localDeviceId) {
|
|
2540
2562
|
const peerId = String(row.peer_device_id ?? "");
|
|
2541
2563
|
const recentOps = recentOpsByPeer?.get(peerId) ?? {
|
|
2542
2564
|
in: 0,
|
|
@@ -2544,6 +2566,10 @@ function mapPeerRow(store, row, showDiag, recentOpsByPeer, scopeRejectionsByPeer
|
|
|
2544
2566
|
};
|
|
2545
2567
|
const scopeRejections = scopeRejectionsByPeer?.get(peerId);
|
|
2546
2568
|
const addresses = safeJsonList(row.addresses_json);
|
|
2569
|
+
const perScopeSync = listPerPeerScopeSyncState(store.db, {
|
|
2570
|
+
localDeviceId: localDeviceId ?? null,
|
|
2571
|
+
peerDeviceId: peerId
|
|
2572
|
+
});
|
|
2547
2573
|
return {
|
|
2548
2574
|
peer_device_id: row.peer_device_id,
|
|
2549
2575
|
name: row.name,
|
|
@@ -2560,6 +2586,7 @@ function mapPeerRow(store, row, showDiag, recentOpsByPeer, scopeRejectionsByPeer
|
|
|
2560
2586
|
actor_id: row.actor_id ?? null,
|
|
2561
2587
|
actor_display_name: row.actor_display_name ?? null,
|
|
2562
2588
|
authorized_scopes: peerAuthorizedScopes(store, peerId),
|
|
2589
|
+
per_scope_sync: perScopeSync,
|
|
2563
2590
|
project_scope: { ...currentProjectScope(row, readPeerProjectFilters(store, String(row.peer_device_id ?? ""))) },
|
|
2564
2591
|
recent_ops: {
|
|
2565
2592
|
in: recentOps.in,
|
|
@@ -2901,6 +2928,20 @@ var PEERS_QUERY = `
|
|
|
2901
2928
|
ORDER BY name, peer_device_id
|
|
2902
2929
|
`;
|
|
2903
2930
|
/**
|
|
2931
|
+
* Compute the negotiated sync capability for a request by combining the
|
|
2932
|
+
* server's local capability with the value the caller advertised in the
|
|
2933
|
+
* `X-Codemem-Sync-Capability` header. Routes use this to decide whether to
|
|
2934
|
+
* honor scoped-sync wire features such as explicit `scope_id` parameters
|
|
2935
|
+
* and `/v1/status` `authorized_scopes` enumeration.
|
|
2936
|
+
*
|
|
2937
|
+
* Unsigned headers are fine here: the server is computing what it is
|
|
2938
|
+
* willing to do, not granting access. Per-scope authorization is enforced
|
|
2939
|
+
* separately via the membership cache when a scope_id is actually used.
|
|
2940
|
+
*/
|
|
2941
|
+
function negotiatedSyncCapability(c) {
|
|
2942
|
+
return negotiateSyncCapability(LOCAL_SYNC_CAPABILITY, normalizeSyncCapability(c.req.header(SYNC_CAPABILITY_HEADER) ?? null));
|
|
2943
|
+
}
|
|
2944
|
+
/**
|
|
2904
2945
|
* Peer-to-peer sync protocol routes (/v1/*).
|
|
2905
2946
|
*
|
|
2906
2947
|
* These are mounted on the sync listener (0.0.0.0:7337) and are
|
|
@@ -2955,13 +2996,19 @@ function syncProtocolRoutes(getStore, opts = {}) {
|
|
|
2955
2996
|
try {
|
|
2956
2997
|
const [deviceId, fingerprint] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
|
|
2957
2998
|
const syncReset = getSyncResetState(store.db);
|
|
2958
|
-
|
|
2999
|
+
const authorizedScopes = isScopedSyncCapability(negotiatedSyncCapability(c)) ? listAuthorizedScopesForPeer(store.db, {
|
|
3000
|
+
localDeviceId: deviceId,
|
|
3001
|
+
peerDeviceId: auth.deviceId
|
|
3002
|
+
}) : null;
|
|
3003
|
+
const response = {
|
|
2959
3004
|
device_id: deviceId,
|
|
2960
3005
|
protocol_version: SYNC_PROTOCOL_VERSION,
|
|
2961
3006
|
fingerprint,
|
|
2962
3007
|
sync_reset: addSyncScopeToBoundary(syncReset, null),
|
|
2963
3008
|
sync_capability: LOCAL_SYNC_CAPABILITY
|
|
2964
|
-
}
|
|
3009
|
+
};
|
|
3010
|
+
if (authorizedScopes !== null) response.authorized_scopes = authorizedScopes;
|
|
3011
|
+
return c.json(response);
|
|
2965
3012
|
} catch {
|
|
2966
3013
|
return c.json({ error: "internal_error" }, 500);
|
|
2967
3014
|
}
|
|
@@ -2977,8 +3024,15 @@ function syncProtocolRoutes(getStore, opts = {}) {
|
|
|
2977
3024
|
const peerDeviceId = auth.deviceId;
|
|
2978
3025
|
try {
|
|
2979
3026
|
const rawScopeId = c.req.query(SYNC_SCOPE_QUERY_PARAM);
|
|
2980
|
-
const
|
|
2981
|
-
|
|
3027
|
+
const negotiated = negotiatedSyncCapability(c);
|
|
3028
|
+
const [localDeviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
|
|
3029
|
+
const scopeRequest = parseSyncScopeRequest(rawScopeId, rawScopeId !== void 0, {
|
|
3030
|
+
db: store.db,
|
|
3031
|
+
localDeviceId,
|
|
3032
|
+
negotiatedCapability: negotiated,
|
|
3033
|
+
peerDeviceId
|
|
3034
|
+
});
|
|
3035
|
+
if (!scopeRequest.ok) return c.json(syncScopeResetRequiredPayload(getSyncResetState(store.db), scopeRequest.reason, LOCAL_SYNC_CAPABILITY, null), 409);
|
|
2982
3036
|
const since = c.req.query("since") ?? null;
|
|
2983
3037
|
const rawLimit = Number.parseInt(c.req.query("limit") ?? "200", 10);
|
|
2984
3038
|
const rawGeneration = c.req.query("generation");
|
|
@@ -2986,14 +3040,14 @@ function syncProtocolRoutes(getStore, opts = {}) {
|
|
|
2986
3040
|
const snapshotId = c.req.query("snapshot_id") ?? null;
|
|
2987
3041
|
const baselineCursor = c.req.query("baseline_cursor") ?? null;
|
|
2988
3042
|
const limit = Number.isFinite(rawLimit) ? Math.max(1, Math.min(rawLimit, 1e3)) : 200;
|
|
2989
|
-
const [localDeviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
|
|
2990
3043
|
const result = loadReplicationOpsForPeer(store.db, {
|
|
2991
3044
|
since,
|
|
2992
3045
|
limit,
|
|
2993
3046
|
deviceId: localDeviceId,
|
|
2994
3047
|
generation: Number.isFinite(generation) ? generation : null,
|
|
2995
3048
|
snapshotId,
|
|
2996
|
-
baselineCursor
|
|
3049
|
+
baselineCursor,
|
|
3050
|
+
scopeId: scopeRequest.mode === "scoped" ? scopeRequest.scope_id : void 0
|
|
2997
3051
|
});
|
|
2998
3052
|
if (result.reset_required) return c.json({
|
|
2999
3053
|
error: "reset_required",
|
|
@@ -3052,8 +3106,15 @@ function syncProtocolRoutes(getStore, opts = {}) {
|
|
|
3052
3106
|
if (limited) return limited;
|
|
3053
3107
|
try {
|
|
3054
3108
|
const rawScopeId = c.req.query(SYNC_SCOPE_QUERY_PARAM);
|
|
3055
|
-
const
|
|
3056
|
-
|
|
3109
|
+
const negotiated = negotiatedSyncCapability(c);
|
|
3110
|
+
const [localDeviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
|
|
3111
|
+
const scopeRequest = parseSyncScopeRequest(rawScopeId, rawScopeId !== void 0, {
|
|
3112
|
+
db: store.db,
|
|
3113
|
+
localDeviceId,
|
|
3114
|
+
negotiatedCapability: negotiated,
|
|
3115
|
+
peerDeviceId: auth.deviceId
|
|
3116
|
+
});
|
|
3117
|
+
if (!scopeRequest.ok) return c.json(syncScopeResetRequiredPayload(getSyncResetState(store.db), scopeRequest.reason, LOCAL_SYNC_CAPABILITY, null), 409);
|
|
3057
3118
|
const rawGeneration = c.req.query("generation");
|
|
3058
3119
|
const generation = rawGeneration != null && rawGeneration.trim().length > 0 ? Number.parseInt(rawGeneration, 10) : null;
|
|
3059
3120
|
const snapshotId = c.req.query("snapshot_id") ?? null;
|
|
@@ -3069,7 +3130,8 @@ function syncProtocolRoutes(getStore, opts = {}) {
|
|
|
3069
3130
|
baselineCursor,
|
|
3070
3131
|
pageToken,
|
|
3071
3132
|
limit,
|
|
3072
|
-
peerDeviceId: auth.deviceId
|
|
3133
|
+
peerDeviceId: auth.deviceId,
|
|
3134
|
+
scopeId: scopeRequest.mode === "scoped" ? scopeRequest.scope_id : void 0
|
|
3073
3135
|
});
|
|
3074
3136
|
return c.json({
|
|
3075
3137
|
scope_id: scopeRequest.scope_id,
|
|
@@ -3107,8 +3169,15 @@ function syncProtocolRoutes(getStore, opts = {}) {
|
|
|
3107
3169
|
} catch {
|
|
3108
3170
|
return c.json({ error: "invalid_json" }, 400);
|
|
3109
3171
|
}
|
|
3110
|
-
const
|
|
3111
|
-
|
|
3172
|
+
const negotiated = negotiateSyncCapability(LOCAL_SYNC_CAPABILITY, normalizeSyncCapability(body.sync_capability));
|
|
3173
|
+
const [localDeviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
|
|
3174
|
+
const scopeRequest = parseSyncScopeRequest(body.scope_id, Object.hasOwn(body, "scope_id"), {
|
|
3175
|
+
db: store.db,
|
|
3176
|
+
localDeviceId,
|
|
3177
|
+
negotiatedCapability: negotiated,
|
|
3178
|
+
peerDeviceId
|
|
3179
|
+
});
|
|
3180
|
+
if (!scopeRequest.ok) return c.json(syncScopeResetRequiredPayload(getSyncResetState(store.db), scopeRequest.reason, LOCAL_SYNC_CAPABILITY, null), 409);
|
|
3112
3181
|
if (!Array.isArray(body.ops)) return c.json({ error: "invalid_ops" }, 400);
|
|
3113
3182
|
if (body.ops.length > MAX_SYNC_OPS) return c.json({ error: "too_many_ops" }, 413);
|
|
3114
3183
|
const normalizedOps = extractReplicationOps(body);
|
|
@@ -3117,11 +3186,9 @@ function syncProtocolRoutes(getStore, opts = {}) {
|
|
|
3117
3186
|
reason: "device_id_mismatch",
|
|
3118
3187
|
op_id: op.op_id
|
|
3119
3188
|
}, 400);
|
|
3120
|
-
const [localDeviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
|
|
3121
|
-
const negotiatedCapability = negotiateSyncCapability(LOCAL_SYNC_CAPABILITY, normalizeSyncCapability(body.sync_capability));
|
|
3122
3189
|
const filtered = filterOpsForPeer(store, peerDeviceId, localDeviceId, normalizedOps, { applyScopeFilter: false });
|
|
3123
3190
|
const rejected = rejectInboundScopeFailures(store.db, normalizedOps, localDeviceId, {
|
|
3124
|
-
enabled:
|
|
3191
|
+
enabled: negotiated !== "unsupported",
|
|
3125
3192
|
peerDeviceId
|
|
3126
3193
|
});
|
|
3127
3194
|
if (rejected) return c.json({
|
|
@@ -3245,7 +3312,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
|
|
|
3245
3312
|
const recentOpsByPeer = traceSync("recentPeerOps", () => recentPeerOps(store));
|
|
3246
3313
|
const scopeRejectionsByPeer = traceSync("recentScopeRejectionsByPeer", () => recentScopeRejectionsByPeer(store));
|
|
3247
3314
|
const peersItems = peerRows.map((row) => {
|
|
3248
|
-
const peer = mapPeerRow(store, row, showDiag, recentOpsByPeer, scopeRejectionsByPeer);
|
|
3315
|
+
const peer = mapPeerRow(store, row, showDiag, recentOpsByPeer, scopeRejectionsByPeer, deviceRow?.device_id ?? null);
|
|
3249
3316
|
peer.status = peerStatus(peer);
|
|
3250
3317
|
return peer;
|
|
3251
3318
|
});
|
|
@@ -3331,7 +3398,8 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
|
|
|
3331
3398
|
const rows = store.db.prepare(PEERS_QUERY).all();
|
|
3332
3399
|
const recentOpsByPeer = recentPeerOps(store);
|
|
3333
3400
|
const scopeRejectionsByPeer = recentScopeRejectionsByPeer(store);
|
|
3334
|
-
const
|
|
3401
|
+
const localDeviceId = drizzle(store.db, { schema }).select({ device_id: schema.syncDevice.device_id }).from(schema.syncDevice).limit(1).get()?.device_id ?? null;
|
|
3402
|
+
const peers = rows.map((row) => mapPeerRow(store, row, showDiag, recentOpsByPeer, scopeRejectionsByPeer, localDeviceId));
|
|
3335
3403
|
return c.json({
|
|
3336
3404
|
items: peers,
|
|
3337
3405
|
redacted: !showDiag
|
|
@@ -3881,7 +3949,8 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
|
|
|
3881
3949
|
});
|
|
3882
3950
|
if (!result.ok) return c.json({
|
|
3883
3951
|
error: result.error,
|
|
3884
|
-
detail: result.detail
|
|
3952
|
+
detail: result.detail,
|
|
3953
|
+
...result.reason ? { reason: result.reason } : {}
|
|
3885
3954
|
}, { status: result.status });
|
|
3886
3955
|
return c.json({
|
|
3887
3956
|
ok: true,
|
|
@@ -4981,7 +5050,8 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
|
|
|
4981
5050
|
if (result.ok) return c.json(result);
|
|
4982
5051
|
return c.json({
|
|
4983
5052
|
error: result.error,
|
|
4984
|
-
detail: result.detail
|
|
5053
|
+
detail: result.detail,
|
|
5054
|
+
...result.reason ? { reason: result.reason } : {}
|
|
4985
5055
|
}, result.status);
|
|
4986
5056
|
} catch (error) {
|
|
4987
5057
|
return c.json({
|