@peers-app/peers-sdk 0.31.4 → 0.31.6
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/define/context.d.ts
CHANGED
|
@@ -27,6 +27,19 @@ export interface IPackageContext {
|
|
|
27
27
|
* never use it to decide what a caller may do.
|
|
28
28
|
*/
|
|
29
29
|
readonly deviceId: string;
|
|
30
|
+
/**
|
|
31
|
+
* The user this call acts for: the signed-in user for a call from this device's own screens or
|
|
32
|
+
* CLI, or the member whose device called. Use it to record who did something, such as who
|
|
33
|
+
* created a row; don't use it to decide what the caller may do. The host has already checked
|
|
34
|
+
* the call against the member's access rules.
|
|
35
|
+
*
|
|
36
|
+
* A call the device makes for itself, such as a System Scheduler booking, acts for the
|
|
37
|
+
* signed-in user. Contract event deliveries act for the subscriber, not the event's author.
|
|
38
|
+
* It's `undefined` in `onStart`, when no user identity is available, and on older hosts.
|
|
39
|
+
* Timers and owned-table watches have no caller of their own. A `ctx` captured by a callback
|
|
40
|
+
* keeps its original caller metadata, even after that call's authority has expired.
|
|
41
|
+
*/
|
|
42
|
+
readonly callerUserId?: string;
|
|
30
43
|
/** Run a tool on a contract this package consumes (or provides), as this call's caller. */
|
|
31
44
|
callConsumedTool(contractId: string, version: number, toolName: string, args?: unknown, options?: IPackageProviderOptions): Promise<unknown>;
|
|
32
45
|
/**
|
|
@@ -1,18 +1,32 @@
|
|
|
1
1
|
import type { IDeviceConnection, INetworkInfo } from "../types/peer-device";
|
|
2
|
+
/** Local connections and remote topology snapshots used to choose a group's sync edges. */
|
|
2
3
|
export interface IElectionData {
|
|
3
4
|
deviceId: string;
|
|
4
5
|
myConnections: IDeviceConnection[];
|
|
5
6
|
allNetworkInfo: INetworkInfo[];
|
|
6
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Choose the devices this one syncs with in a group: its preferred devices, plus the devices
|
|
10
|
+
* that prefer it.
|
|
11
|
+
*
|
|
12
|
+
* Once preferences are established, ongoing changes move along these edges in either direction:
|
|
13
|
+
* a write notifies the writer's preferred and preferred-by devices, and periodic resync pulls
|
|
14
|
+
* only from preferred devices. The edges must therefore join every device in the group.
|
|
15
|
+
*
|
|
16
|
+
* @param data - This device, its direct connections, and each connected device's network info.
|
|
17
|
+
* @returns The devices to prefer, and the devices whose network info says they prefer this one.
|
|
18
|
+
*/
|
|
7
19
|
export declare function electDevices({ deviceId, myConnections, allNetworkInfo }: IElectionData): {
|
|
8
20
|
preferredDeviceIds: string[];
|
|
9
21
|
preferredByDeviceIds: string[];
|
|
10
22
|
};
|
|
23
|
+
/** A remote topology snapshot annotated with its local link and incoming preferences. */
|
|
11
24
|
export interface ISortableConnection extends INetworkInfo {
|
|
12
25
|
connectedDeviceIds: string[];
|
|
13
26
|
preferredByDeviceIds: string[];
|
|
14
27
|
myConnection: IDeviceConnection | undefined;
|
|
15
28
|
}
|
|
29
|
+
/** Candidate connections and the devices not yet covered by the greedy election pass. */
|
|
16
30
|
export interface ISortConnectionsArgs {
|
|
17
31
|
myDeviceId: string;
|
|
18
32
|
unconnectedDeviceIds: Set<string>;
|
|
@@ -3,6 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.electDevices = electDevices;
|
|
4
4
|
exports.getLeastPreferredConnection = getLeastPreferredConnection;
|
|
5
5
|
const lodash_1 = require("lodash");
|
|
6
|
+
/**
|
|
7
|
+
* Choose the devices this one syncs with in a group: its preferred devices, plus the devices
|
|
8
|
+
* that prefer it.
|
|
9
|
+
*
|
|
10
|
+
* Once preferences are established, ongoing changes move along these edges in either direction:
|
|
11
|
+
* a write notifies the writer's preferred and preferred-by devices, and periodic resync pulls
|
|
12
|
+
* only from preferred devices. The edges must therefore join every device in the group.
|
|
13
|
+
*
|
|
14
|
+
* @param data - This device, its direct connections, and each connected device's network info.
|
|
15
|
+
* @returns The devices to prefer, and the devices whose network info says they prefer this one.
|
|
16
|
+
*/
|
|
6
17
|
function electDevices({ deviceId, myConnections, allNetworkInfo }) {
|
|
7
18
|
const allDeviceIdsSet = new Set();
|
|
8
19
|
const _preferredByDeviceIdsSet = new Set();
|
|
@@ -47,11 +58,55 @@ function electDevices({ deviceId, myConnections, allNetworkInfo }) {
|
|
|
47
58
|
break;
|
|
48
59
|
}
|
|
49
60
|
}
|
|
61
|
+
const preferredDeviceIds = preferredConnections.map((c) => c.deviceId);
|
|
62
|
+
preferredDeviceIds.push(...connectCompleteViews({
|
|
63
|
+
deviceId,
|
|
64
|
+
myConnections,
|
|
65
|
+
preferredDeviceIds,
|
|
66
|
+
connections: allConnectionInfos,
|
|
67
|
+
}));
|
|
50
68
|
return {
|
|
51
|
-
preferredDeviceIds
|
|
69
|
+
preferredDeviceIds,
|
|
52
70
|
preferredByDeviceIds: Array.from(_preferredByDeviceIdsSet),
|
|
53
71
|
};
|
|
54
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Add stable sync edges when this device has network info for every known device directly.
|
|
75
|
+
*
|
|
76
|
+
* The greedy pass counts a device as covered once a preferred device is connected to it, but
|
|
77
|
+
* ongoing changes move along preference edges. In a full mesh the greedy pass prefers one other,
|
|
78
|
+
* and two devices that pick each other form an island: their writes never reach the rest, and
|
|
79
|
+
* the rest's never reach them.
|
|
80
|
+
*
|
|
81
|
+
* Devices connected to everyone form an id-ordered chain: each prefers its predecessor. The
|
|
82
|
+
* first also prefers every device without a complete view, since those cannot build the chain
|
|
83
|
+
* themselves. In a full mesh this adds at most one preference per device and at most two incident
|
|
84
|
+
* edges per device, rather than concentrating the extra sync traffic on one hub.
|
|
85
|
+
*
|
|
86
|
+
* These edges depend only on topology, never on cached preferences. Bridging only the components
|
|
87
|
+
* in a snapshot is insufficient: concurrent elections can each rely on edges the others remove,
|
|
88
|
+
* repeatedly splitting a full mesh even when just one device adds bridges. Once topology and
|
|
89
|
+
* network-info availability settle, every complete view builds the same chain regardless of
|
|
90
|
+
* election order or stale preferences. Partial views still use only the greedy pass.
|
|
91
|
+
*/
|
|
92
|
+
function connectCompleteViews({ deviceId, myConnections, preferredDeviceIds, connections, }) {
|
|
93
|
+
const direct = connections.filter((c) => c.myConnection);
|
|
94
|
+
const visibleIds = new Set(direct.map((c) => c.deviceId));
|
|
95
|
+
const knownIds = new Set([
|
|
96
|
+
deviceId,
|
|
97
|
+
...myConnections.map((c) => c.deviceId),
|
|
98
|
+
...connections.flatMap((c) => c.connectedDeviceIds),
|
|
99
|
+
]);
|
|
100
|
+
if ([...knownIds].some((id) => id !== deviceId && !visibleIds.has(id)))
|
|
101
|
+
return [];
|
|
102
|
+
const seesEveryone = (c) => [...knownIds].every((id) => c.connectedDeviceIds.includes(id));
|
|
103
|
+
const completeIds = [deviceId, ...direct.filter(seesEveryone).map((c) => c.deviceId)].sort();
|
|
104
|
+
const index = completeIds.indexOf(deviceId);
|
|
105
|
+
const backbone = index > 0
|
|
106
|
+
? [completeIds[index - 1]]
|
|
107
|
+
: direct.filter((c) => !seesEveryone(c)).map((c) => c.deviceId);
|
|
108
|
+
return backbone.filter((id) => !preferredDeviceIds.includes(id));
|
|
109
|
+
}
|
|
55
110
|
/**
|
|
56
111
|
* Sort connections from most preferred to least preferred
|
|
57
112
|
* The most preferred connections are ones that link us to the most new devices
|
|
@@ -66,12 +121,9 @@ function sortConnections({ myDeviceId, connections, unconnectedDeviceIds }) {
|
|
|
66
121
|
const bLatency = (b.myConnection?.latencyMs || 1) * (b.myConnection?.errorRate || 0.00001);
|
|
67
122
|
return aLatency - bLatency;
|
|
68
123
|
});
|
|
69
|
-
// sort by
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const bPreferredCount = b.preferredDeviceIds.length;
|
|
73
|
-
return bPreferredCount - aPreferredCount;
|
|
74
|
-
});
|
|
124
|
+
// sort by how many other devices prefer it (higher is better), so devices gather on one
|
|
125
|
+
// another's choices instead of pairing off
|
|
126
|
+
connections.sort((a, b) => b.preferredByDeviceIds.length - a.preferredByDeviceIds.length);
|
|
75
127
|
// sort by links to new devices - in general, this is most important except for the later edge-cases
|
|
76
128
|
connections.sort((a, b) => {
|
|
77
129
|
const linksToDeviceA = a.connectedDeviceIds.filter((id) => unconnectedDeviceIds.has(id)).length;
|
|
@@ -24,8 +24,12 @@ const system_ids_1 = require("../system-ids");
|
|
|
24
24
|
const package_remote_checker_1 = require("./package-remote-checker");
|
|
25
25
|
const package_seed_installer_1 = require("./package-seed-installer");
|
|
26
26
|
const peers_core_trust_anchor_1 = require("./peers-core-trust-anchor");
|
|
27
|
+
// The name and description a new install's Package record gets. peers-core's id now holds only
|
|
28
|
+
// Tasks, but the name stays "peers-core": a dev build's source folder falls back to
|
|
29
|
+
// `~/peers/packages/<name-slug>` (`getPackageLocalPath`), which is the peers-core checkout. Rename
|
|
30
|
+
// it when Tasks gets a folder of its own. Existing records keep the name they were created with.
|
|
27
31
|
const PEERS_CORE_NAME = "peers-core";
|
|
28
|
-
const PEERS_CORE_DESCRIPTION = "
|
|
32
|
+
const PEERS_CORE_DESCRIPTION = "Tasks, with subtasks, comments, repeats and a timer.";
|
|
29
33
|
/**
|
|
30
34
|
* Ensure peers-core is installed (with a usable, loaded version) in
|
|
31
35
|
* `dataContext`, applying the layered strategy described in the module docs.
|
|
@@ -324,7 +324,8 @@ export interface IIsolatedContractProviderGateway {
|
|
|
324
324
|
* Invoked only by the host for guest-originated gateway calls. The user id and
|
|
325
325
|
* data context must come from authenticated host state, never guest payloads.
|
|
326
326
|
* Return `undefined` when no signed-in identity is available; protected tools
|
|
327
|
-
* then retain their existing missing-caller-identity denial.
|
|
327
|
+
* then retain their existing missing-caller-identity denial. Its user id is also a guest
|
|
328
|
+
* handler's `ctx.callerUserId` when the call's principal names no user.
|
|
328
329
|
*/
|
|
329
330
|
export type IsolatedLocalUserContextResolver = () => {
|
|
330
331
|
userId: string;
|