Line data Source code
1 : import type { ConnectionState } from '../../connections/connection_state.js';
2 : import type { SessionConnection, SessionData, SessionScript } from '../../storage/storage_backend.js';
3 : import { reconcileSessionScripts, sanitizeSessionFiles, withSessionScripts } from '../../storage/session_files.js';
4 :
5 : /// A script of a session file with the connection it runs on
6 : export interface ResolvedSessionScript {
7 : /// The file name
8 : name: string;
9 : /// The connection of the app the script runs on
10 : connection: ConnectionState;
11 : /// The connection that the session file names for the script if the app doesn't know it
12 : unresolvedConnectionId?: string;
13 : }
14 :
15 : /// The scripts of a session file resolved to connections of the app
16 : export interface ResolvedSessionScripts {
17 : /// The scripts in feed order
18 : scripts: ResolvedSessionScript[];
19 : /// The connection that the first script of a session without scripts runs on, null if the session has scripts
20 : pendingConnection: ConnectionState | null;
21 : /// The session file that describes the resolved scripts. It differs from the stored one when
22 : /// script files were added or removed, connections were consolidated, or the session held
23 : /// copies of connections that no script uses.
24 : session: SessionData;
25 : }
26 :
27 : /// Resolve a connection of a session to a connection of the app, null if it is unknown.
28 : /// `copy` is the copy that the session file holds, null if it holds none. A resolver may register
29 : /// an unknown connection from its copy.
30 : export type SessionConnectionResolver = (connectionId: string, copy: SessionConnection | null) => ConnectionState | null;
31 :
32 : /// Resolve the scripts of a session file to the connections of the app.
33 : ///
34 : /// Every path that turns a session file into scripts resolves here: the restoration of the app, an
35 : /// import (through the restoration) and the reload of a native folder that changed on disk.
36 : /// The session file lists the scripts in feed order with the ids of their connections, script files
37 : /// without an entry are appended on the connection of the script before them. A script whose
38 : /// connection can't be resolved runs on the fallback connection, the embedded database, and the
39 : /// session file keeps its reference. A session without scripts resolves the connection it holds.
40 1 : export function resolveSessionScripts(
41 1 : session: SessionData,
42 1 : fileNames: readonly string[],
43 1 : fallback: () => ConnectionState,
44 1 : resolve: SessionConnectionResolver,
45 1 : onUnresolved: (connectionId: string) => void = () => { },
46 1 : ): ResolvedSessionScripts {
47 1 : const copies = new Map(session.connections.map(connection => [connection.connectionId, connection]));
48 1 : const resolved = new Map<string, ConnectionState | null>();
49 1 : const resolveId = (connectionId: string): ConnectionState | null => {
50 1 : if (!resolved.has(connectionId)) {
51 1 : const connection = resolve(connectionId, copies.get(connectionId) ?? null);
52 1 : if (connection == null) onUnresolved(connectionId);
53 1 : resolved.set(connectionId, connection);
54 1 : }
55 1 : return resolved.get(connectionId) ?? null;
56 1 : };
57 1 : const scriptOf = (name: string, connectionId: string): ResolvedSessionScript => {
58 1 : const connection = resolveId(connectionId);
59 1 : return connection != null
60 1 : ? { name, connection }
61 1 : : { name, connection: fallback(), unresolvedConnectionId: connectionId };
62 1 : };
63 1 : const firstConnectionId = session.connections[0]?.connectionId ?? null;
64 1 : const listed = reconcileSessionScripts(sanitizeSessionFiles(session).scripts, fileNames, firstConnectionId ?? '').scripts;
65 1 : const scripts = listed.map(entry => scriptOf(entry.name, entry.connectionId));
66 1 : const pendingConnection = scripts.length > 0 ? null
67 1 : : (firstConnectionId == null ? null : resolveId(firstConnectionId)) ?? fallback();
68 1 : const list: SessionScript[] = scripts.map(script => ({ name: script.name, connectionId: script.unresolvedConnectionId ?? script.connection.connectionId }));
69 1 : const used = [...(pendingConnection == null ? [] : [pendingConnection]), ...scripts.map(script => script.connection)]
70 1 : .map(connection => ({ connectionId: connection.connectionId, params: connection.params }));
71 1 : return {
72 1 : scripts,
73 1 : pendingConnection,
74 1 : session: withSessionScripts(session, list, used),
75 1 : };
76 1 : }
77 :
78 : /// Does a resolved session file differ from the stored one in its scripts or connections?
79 1 : export function sessionScriptsResolutionChanged(stored: SessionData, resolved: SessionData): boolean {
80 1 : const refs = (session: SessionData) => JSON.stringify([
81 1 : session.connections.map(connection => connection.connectionId),
82 1 : session.scripts ?? [],
83 1 : ]);
84 1 : return refs(stored) !== refs(resolved);
85 1 : }
|