@undefineds.co/xpod 0.4.9 → 0.4.10

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.
Files changed (96) hide show
  1. package/dist/identity/drizzle/PodLookupRepository.d.ts +5 -16
  2. package/dist/identity/drizzle/PodLookupRepository.js +40 -201
  3. package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsMain.js +1 -0
  4. package/node_modules/@undefineds.co/ai-connections/dist/controller.d.ts +1 -0
  5. package/node_modules/@undefineds.co/ai-connections/dist/controller.js +94 -16
  6. package/node_modules/@undefineds.co/solid-sdk/dist/index.cjs +112 -11
  7. package/node_modules/@undefineds.co/solid-sdk/dist/session.d.ts +2 -0
  8. package/node_modules/@undefineds.co/solid-sdk/dist/session.js +135 -11
  9. package/package.json +2 -2
  10. package/static/app/assets/main.js +12 -12
  11. package/static/auth-callback/assets/{AiConfigContext-DJKvxduw.js → AiConfigContext-C81deMM9.js} +1 -1
  12. package/static/auth-callback/assets/{AiConfigPage-DgGmHb18.js → AiConfigPage-evWI8T56.js} +1 -1
  13. package/static/auth-callback/assets/{DocumentProcessingPanel-CJmxu8x1.js → DocumentProcessingPanel-B9GrDEMe.js} +1 -1
  14. package/static/auth-callback/assets/{IndexLifecyclePanel-byCa7h7L.js → IndexLifecyclePanel-qCo8ABQZ.js} +1 -1
  15. package/static/auth-callback/assets/{IndexSubjectPanel-CDwT4Yx1.js → IndexSubjectPanel-C4YG0whI.js} +1 -1
  16. package/static/auth-callback/assets/{ModelAssignmentsPanel-CwM3k8w7.js → ModelAssignmentsPanel-DG7Lxh3t.js} +1 -1
  17. package/static/auth-callback/assets/ModelsPage-CgEuqE6p.js +10 -0
  18. package/static/auth-callback/assets/{NetworkPage-DkVgxtNj.js → NetworkPage-Cbdn_P1e.js} +1 -1
  19. package/static/auth-callback/assets/{PaneListHeader-DRI0hjmt.js → PaneListHeader-jy70Q00L.js} +1 -1
  20. package/static/auth-callback/assets/{SearchIndexingPanel-mbppDZFE.js → SearchIndexingPanel-ByU3u7kp.js} +1 -1
  21. package/static/auth-callback/assets/{StatusSubjectPanel-DNsW76aC.js → StatusSubjectPanel-hfjoB5aS.js} +1 -1
  22. package/static/auth-callback/assets/{StatusWorkspace-DGcTbHT5.js → StatusWorkspace-BZd617sV.js} +1 -1
  23. package/static/auth-callback/assets/{SystemSettingsPage-C6qklJ9k.js → SystemSettingsPage-BirzWAQM.js} +1 -1
  24. package/static/auth-callback/assets/{SystemSettingsSubjectPanel-B4VV9xkG.js → SystemSettingsSubjectPanel-CmsVWNCx.js} +1 -1
  25. package/static/auth-callback/assets/{activity-Ck-Mfxxo.js → activity-BcsZZxxw.js} +1 -1
  26. package/static/auth-callback/assets/{auth-callback-DG4Ge40c.js → auth-callback-atKg_oig.js} +124 -124
  27. package/static/auth-callback/assets/{badge-GSSnw1VV.js → badge-BWbRHThQ.js} +1 -1
  28. package/static/auth-callback/assets/{download-BYyRX-K2.js → download-BmvG1Vya.js} +1 -1
  29. package/static/auth-callback/assets/{external-link-WEC0Ki-0.js → external-link-DZ34d3g3.js} +1 -1
  30. package/static/auth-callback/assets/{index-kju2k4Dp.js → index-BpV1hsqs.js} +1 -1
  31. package/static/auth-callback/assets/{index-DlsMCgo8.js → index-D9oE6FXK.js} +1 -1
  32. package/static/auth-callback/assets/{index-browser-D8YW6coF.js → index-browser-B9Z7GU-T.js} +1 -1
  33. package/static/auth-callback/assets/{key-round-DJUT5Nh0.js → key-round-tjP76Se-.js} +1 -1
  34. package/static/auth-callback/assets/{rotate-ccw-CXwfZUKU.js → rotate-ccw-C_L5ZLx1.js} +1 -1
  35. package/static/auth-callback/assets/{skeleton-DWPtNhUB.js → skeleton-C7tGDQIs.js} +1 -1
  36. package/static/auth-callback/assets/{waypoints-l1oDnRJ0.js → waypoints-VMNQlWdU.js} +1 -1
  37. package/static/auth-callback/assets/{workspace-layout-B7iKpK-Q.js → workspace-layout-SAp6BtYK.js} +1 -1
  38. package/static/auth-callback/auth-callback.html +1 -1
  39. package/static/dashboard/assets/{AiConfigContext-U0cCSY28.js → AiConfigContext-De-OtAWo.js} +1 -1
  40. package/static/dashboard/assets/{AiConfigPage-CMi0uJdo.js → AiConfigPage-macP4Mop.js} +1 -1
  41. package/static/dashboard/assets/{DocumentProcessingPanel-DEZiEpft.js → DocumentProcessingPanel-C5ObvCCl.js} +1 -1
  42. package/static/dashboard/assets/{IndexLifecyclePanel-CGZfLUog.js → IndexLifecyclePanel-xVNa2l40.js} +1 -1
  43. package/static/dashboard/assets/{IndexSubjectPanel-DiDZauHk.js → IndexSubjectPanel-DQFPP6-p.js} +1 -1
  44. package/static/dashboard/assets/{ModelAssignmentsPanel-DaGGZKuf.js → ModelAssignmentsPanel-DMOATFpQ.js} +1 -1
  45. package/static/dashboard/assets/ModelsPage-91UbnETA.js +25 -0
  46. package/static/dashboard/assets/{NetworkPage-DI5-O7GT.js → NetworkPage-CHQ3YxIB.js} +1 -1
  47. package/static/dashboard/assets/{PaneListHeader-4oa7uf59.js → PaneListHeader-DsXemAdR.js} +1 -1
  48. package/static/dashboard/assets/{SearchIndexingPanel-DoqEwocD.js → SearchIndexingPanel-Dh70PNFZ.js} +1 -1
  49. package/static/dashboard/assets/{StatusSubjectPanel-CLzBC5fu.js → StatusSubjectPanel-D3PGEYT4.js} +1 -1
  50. package/static/dashboard/assets/{StatusWorkspace-UyKADZoM.js → StatusWorkspace-BcpePYUW.js} +1 -1
  51. package/static/dashboard/assets/{SystemSettingsPage-qP0hLHNV.js → SystemSettingsPage-BiN5GcKx.js} +1 -1
  52. package/static/dashboard/assets/{SystemSettingsSubjectPanel-EaWax9Ot.js → SystemSettingsSubjectPanel-B9QrSiFp.js} +1 -1
  53. package/static/dashboard/assets/{activity-CjIoNU4S.js → activity-j5zrznu-.js} +1 -1
  54. package/static/dashboard/assets/{badge-dcDZpKh3.js → badge-CzeBdG7h.js} +1 -1
  55. package/static/dashboard/assets/{dashboard-BY8GdnEI.js → dashboard-D-xANHk1.js} +103 -103
  56. package/static/dashboard/assets/{download-BeYCrc-_.js → download-IcNdEZWi.js} +1 -1
  57. package/static/dashboard/assets/{external-link-626-5KVx.js → external-link-DxBxK8Vu.js} +1 -1
  58. package/static/dashboard/assets/{index-CBbk6hx-.js → index-FWZ-s52O.js} +1 -1
  59. package/static/dashboard/assets/{index-CsxH6Hmz.js → index-ZiQZ96oC.js} +1 -1
  60. package/static/dashboard/assets/{key-round-B9IL4Wua.js → key-round-D1K7warM.js} +1 -1
  61. package/static/dashboard/assets/{rotate-ccw-DM4vCfiI.js → rotate-ccw-CcN1N6p9.js} +1 -1
  62. package/static/dashboard/assets/{skeleton-ChlFAPvP.js → skeleton-CKnmJMUH.js} +1 -1
  63. package/static/dashboard/assets/{waypoints-CvpZzf_I.js → waypoints-DuZPO_wu.js} +1 -1
  64. package/static/dashboard/assets/{workspace-layout-Bxv7V-tt.js → workspace-layout-BUj3wZ_m.js} +1 -1
  65. package/static/dashboard/dashboard.html +1 -1
  66. package/static/settings/assets/{AiConfigContext-CDVIHLBb.js → AiConfigContext-C0cQMPfG.js} +1 -1
  67. package/static/settings/assets/{AiConfigPage-C7qfiiaJ.js → AiConfigPage-CPhB4iFb.js} +1 -1
  68. package/static/settings/assets/{DocumentProcessingPanel-B59QTe-V.js → DocumentProcessingPanel-sYI7OpuH.js} +1 -1
  69. package/static/settings/assets/{IndexLifecyclePanel-C-e-3eVi.js → IndexLifecyclePanel-yj8iyiT8.js} +1 -1
  70. package/static/settings/assets/{IndexSubjectPanel-B-scRZ1u.js → IndexSubjectPanel-73sM16Ma.js} +1 -1
  71. package/static/settings/assets/{ModelAssignmentsPanel-Wy8jzVg4.js → ModelAssignmentsPanel-BFIgUX5v.js} +1 -1
  72. package/static/settings/assets/ModelsPage-BdppXl7E.js +10 -0
  73. package/static/settings/assets/{NetworkPage-tBB-4Ox8.js → NetworkPage-i6j-M2RY.js} +1 -1
  74. package/static/settings/assets/{PaneListHeader-DuD6glzn.js → PaneListHeader-DpqC0jd5.js} +1 -1
  75. package/static/settings/assets/{SearchIndexingPanel-CiDHc0nx.js → SearchIndexingPanel-BlPnrkT3.js} +1 -1
  76. package/static/settings/assets/{StatusSubjectPanel-OF5cgWwt.js → StatusSubjectPanel-Dv4q3PGh.js} +1 -1
  77. package/static/settings/assets/{StatusWorkspace-CZ8Oa0gV.js → StatusWorkspace-CKD_Rzdr.js} +1 -1
  78. package/static/settings/assets/{SystemSettingsPage-DT2tC0F_.js → SystemSettingsPage-CiOm2NzV.js} +1 -1
  79. package/static/settings/assets/{SystemSettingsSubjectPanel-Bkta1oDw.js → SystemSettingsSubjectPanel-Cd-GECon.js} +1 -1
  80. package/static/settings/assets/{activity-2M1f5eBS.js → activity-DaFkk8f3.js} +1 -1
  81. package/static/settings/assets/{badge-CRKan0qX.js → badge-d5SHdc-x.js} +1 -1
  82. package/static/settings/assets/{download-PCHldCdu.js → download-CpkwomDr.js} +1 -1
  83. package/static/settings/assets/{external-link-tmKe7eYO.js → external-link-_4YT42Mf.js} +1 -1
  84. package/static/settings/assets/{index-Cek02M79.js → index-BFi9yn7_.js} +1 -1
  85. package/static/settings/assets/{index-BDCNTwWz.js → index-BGgEhIUy.js} +1 -1
  86. package/static/settings/assets/{index-browser-EVDVm9T5.js → index-browser-B06xjBdZ.js} +1 -1
  87. package/static/settings/assets/{key-round-CUGvQCkt.js → key-round-Dlqmp1Kq.js} +1 -1
  88. package/static/settings/assets/{rotate-ccw-BRA3lIHM.js → rotate-ccw-TAY65xWT.js} +1 -1
  89. package/static/settings/assets/{settings-BdlKrzJ3.js → settings-ExBXGgbj.js} +103 -103
  90. package/static/settings/assets/{skeleton-CuDeifWb.js → skeleton-DKIWjNr9.js} +1 -1
  91. package/static/settings/assets/{waypoints-w6LxsFY1.js → waypoints-9erLIxyR.js} +1 -1
  92. package/static/settings/assets/{workspace-layout-TN-BWmT5.js → workspace-layout-CYXfYIa3.js} +1 -1
  93. package/static/settings/settings.html +1 -1
  94. package/static/auth-callback/assets/ModelsPage-DapY2Dwf.js +0 -10
  95. package/static/dashboard/assets/ModelsPage-DZgAOdxO.js +0 -25
  96. package/static/settings/assets/ModelsPage-DHNiaodk.js +0 -10
@@ -29,15 +29,14 @@ export declare class PodLookupRepository {
29
29
  */
30
30
  findById(podId: string): Promise<PodLookupResult | undefined>;
31
31
  /**
32
- * Find Pod by a linked WebID URL.
32
+ * Find Pod by an explicitly recorded owner WebID URL.
33
33
  *
34
- * CSS account data stores WebID links separately from Pod base URLs. This is
35
- * the precise lookup for IdP/SP split deployments where the WebID path does
36
- * not have to match the storage base URL.
34
+ * Account WebID links are not ownership. IdP/SP split deployments still use
35
+ * the complete recorded WebID, independently of the storage base URL.
37
36
  */
38
37
  findByWebId(webId: string): Promise<PodLookupResult | undefined>;
39
38
  /**
40
- * Find all Pods linked to a WebID.
39
+ * Find all Pods explicitly owned by a WebID.
41
40
  *
42
41
  * A Cloud WebID can legitimately back both a Cloud Pod and a Local SP Pod.
43
42
  * Callers that are scoped to a storage provider must inspect all candidates
@@ -45,7 +44,7 @@ export declare class PodLookupRepository {
45
44
  */
46
45
  findAllByWebId(webId: string): Promise<PodLookupResult[]>;
47
46
  /**
48
- * Find Pods by linked WebID URLs in one scan.
47
+ * Find Pods by explicit owner WebID URLs in one scan.
49
48
  */
50
49
  findByWebIds(webIds: string[]): Promise<PodLookupResult[]>;
51
50
  /**
@@ -62,16 +61,6 @@ export declare class PodLookupRepository {
62
61
  private getAllPods;
63
62
  private getAccountRowsFromKv;
64
63
  private getNodeAssignments;
65
- /**
66
- * Fast path for CSS WrappedIndexedStorage. WebID indexes point to the root
67
- * account id, so a single indexed key plus account data row can resolve the
68
- * profile without scanning all account records.
69
- */
70
- private findByWebIdIndex;
71
- private readAccountData;
72
- private readStringArrayFromKv;
73
- private readKvValue;
74
- private extractPodsFromAccountData;
75
64
  /**
76
65
  * DrizzleIndexedStorage stores CSS identity facts as typed rows in
77
66
  * identity_store; this is the canonical clustered identity source.
@@ -39,17 +39,16 @@ class PodLookupRepository {
39
39
  return pods.find((p) => p.podId === podId);
40
40
  }
41
41
  /**
42
- * Find Pod by a linked WebID URL.
42
+ * Find Pod by an explicitly recorded owner WebID URL.
43
43
  *
44
- * CSS account data stores WebID links separately from Pod base URLs. This is
45
- * the precise lookup for IdP/SP split deployments where the WebID path does
46
- * not have to match the storage base URL.
44
+ * Account WebID links are not ownership. IdP/SP split deployments still use
45
+ * the complete recorded WebID, independently of the storage base URL.
47
46
  */
48
47
  async findByWebId(webId) {
49
48
  return (await this.findAllByWebId(webId))[0];
50
49
  }
51
50
  /**
52
- * Find all Pods linked to a WebID.
51
+ * Find all Pods explicitly owned by a WebID.
53
52
  *
54
53
  * A Cloud WebID can legitimately back both a Cloud Pod and a Local SP Pod.
55
54
  * Callers that are scoped to a storage provider must inspect all candidates
@@ -71,19 +70,10 @@ class PodLookupRepository {
71
70
  });
72
71
  }
73
72
  }
74
- if (results.length > 0) {
75
- return results;
76
- }
77
- const indexed = await this.findByWebIdIndex(exact);
78
- if (!indexed) {
79
- return [];
80
- }
81
- return [{
82
- ...indexed,
83
- }];
73
+ return results;
84
74
  }
85
75
  /**
86
- * Find Pods by linked WebID URLs in one scan.
76
+ * Find Pods by explicit owner WebID URLs in one scan.
87
77
  */
88
78
  async findByWebIds(webIds) {
89
79
  const exactTargets = new Set(webIds.map(exactWebId).filter((value) => Boolean(value)));
@@ -105,18 +95,6 @@ class PodLookupRepository {
105
95
  webId: matchedWebId,
106
96
  });
107
97
  }
108
- if (results.length < exactTargets.size) {
109
- const seen = new Set(results.map((result) => exactWebId(result.webId)).filter(Boolean));
110
- for (const exact of exactTargets) {
111
- if (seen.has(exact)) {
112
- continue;
113
- }
114
- const indexed = await this.findByWebIdIndex(exact);
115
- if (indexed) {
116
- results.push(indexed);
117
- }
118
- }
119
- }
120
98
  return results;
121
99
  }
122
100
  /**
@@ -150,18 +128,17 @@ class PodLookupRepository {
150
128
  }
151
129
  const data = unwrapStoredValue(typeof row.value === 'string' ? JSON.parse(row.value) : row.value);
152
130
  const podMap = data['**pod**'] || data.pod || {};
153
- const webIds = extractAccountWebIds(data);
154
131
  for (const [podId, podData] of Object.entries(podMap)) {
155
132
  const pod = podData;
156
133
  if (pod.baseUrl && typeof pod.baseUrl === 'string') {
157
134
  const storageUrl = stringValue(pod.storageUrl) ?? stringValue(pod.storage);
158
- const podWebIds = resolvePodWebIds(pod, webIds);
135
+ const podWebIds = dedupeStrings(explicitPodWebIds(pod));
159
136
  pods.push({
160
137
  podId,
161
138
  accountId,
162
139
  baseUrl: pod.baseUrl,
163
140
  storageUrl,
164
- webId: dedupeStrings(podWebIds)[0],
141
+ webId: podWebIds[0],
165
142
  ...webIdsProperty(podWebIds),
166
143
  nodeId: typeof pod.nodeId === 'string' ? pod.nodeId : findNodeIdForPod(nodeAssignments, [storageUrl, pod.baseUrl]),
167
144
  edgeNodeId: typeof pod.edgeNodeId === 'string' ? pod.edgeNodeId : undefined,
@@ -173,10 +150,13 @@ class PodLookupRepository {
173
150
  // Skip malformed entries.
174
151
  }
175
152
  }
176
- return mergePodLookupResults([
177
- ...pods,
178
- ...await this.getPodsFromIndexedStore(nodeAssignments),
179
- ]);
153
+ const indexed = await this.getPodsFromIndexedStore(nodeAssignments);
154
+ // Presence in the canonical table shadows the whole legacy record, even
155
+ // when the canonical payload is incomplete. Never resurrect old owners.
156
+ return [...new Map([
157
+ ...pods.filter((pod) => !indexed.podIds.has(pod.podId)),
158
+ ...indexed.pods,
159
+ ].map((pod) => [pod.podId, pod])).values()];
180
160
  }
181
161
  async getAccountRowsFromKv() {
182
162
  const kvTableId = drizzle_orm_1.sql.identifier(this.kvTableName);
@@ -213,90 +193,6 @@ class PodLookupRepository {
213
193
  return [];
214
194
  }
215
195
  }
216
- /**
217
- * Fast path for CSS WrappedIndexedStorage. WebID indexes point to the root
218
- * account id, so a single indexed key plus account data row can resolve the
219
- * profile without scanning all account records.
220
- */
221
- async findByWebIdIndex(webId) {
222
- const accountIds = await this.readStringArrayFromKv(`accounts/index/webIdLink/webId/${encodeURIComponent(webId)}`);
223
- for (const accountId of accountIds) {
224
- const account = await this.readAccountData(accountId);
225
- if (!account) {
226
- continue;
227
- }
228
- const pods = this.extractPodsFromAccountData(accountId, account);
229
- const match = pods.find((pod) => getPodWebIds(pod).some((candidate) => exactWebId(candidate) === webId));
230
- if (match) {
231
- return {
232
- ...match,
233
- webId,
234
- };
235
- }
236
- if (pods.length === 1 && !hasExplicitPodWebIds(account, pods[0].podId)) {
237
- return {
238
- ...pods[0],
239
- webId,
240
- ...webIdsProperty([webId, ...getPodWebIds(pods[0])]),
241
- };
242
- }
243
- }
244
- return undefined;
245
- }
246
- async readAccountData(accountId) {
247
- for (const key of [`accounts/data/${accountId}`, `/.internal/accounts/data/${accountId}`]) {
248
- const value = await this.readKvValue(key);
249
- const record = parsePayloadRecord(value);
250
- if (record) {
251
- return record;
252
- }
253
- }
254
- return undefined;
255
- }
256
- async readStringArrayFromKv(key) {
257
- const value = await this.readKvValue(key);
258
- return Array.isArray(value) ? value.filter((entry) => typeof entry === 'string') : [];
259
- }
260
- async readKvValue(key) {
261
- const tableId = drizzle_orm_1.sql.identifier(this.kvTableName);
262
- try {
263
- const result = await (0, db_1.executeQuery)(this.db, (0, drizzle_orm_1.sql) `
264
- SELECT value FROM ${tableId}
265
- WHERE key = ${key}
266
- LIMIT 1
267
- `);
268
- if (result.rows.length === 0) {
269
- return undefined;
270
- }
271
- return parseStoredValue(result.rows[0].value);
272
- }
273
- catch {
274
- return undefined;
275
- }
276
- }
277
- extractPodsFromAccountData(accountId, data) {
278
- const podMap = data['**pod**'] || data.pod || {};
279
- const webIds = extractAccountWebIds(data);
280
- const pods = [];
281
- for (const [podId, podData] of Object.entries(podMap)) {
282
- const pod = podData;
283
- if (pod.baseUrl && typeof pod.baseUrl === 'string') {
284
- const storageUrl = stringValue(pod.storageUrl) ?? stringValue(pod.storage);
285
- const podWebIds = resolvePodWebIds(pod, webIds);
286
- pods.push({
287
- podId,
288
- accountId,
289
- baseUrl: pod.baseUrl,
290
- storageUrl,
291
- webId: dedupeStrings(podWebIds)[0],
292
- ...webIdsProperty(podWebIds),
293
- nodeId: typeof pod.nodeId === 'string' ? pod.nodeId : undefined,
294
- edgeNodeId: typeof pod.edgeNodeId === 'string' ? pod.edgeNodeId : undefined,
295
- });
296
- }
297
- }
298
- return pods;
299
- }
300
196
  /**
301
197
  * DrizzleIndexedStorage stores CSS identity facts as typed rows in
302
198
  * identity_store; this is the canonical clustered identity source.
@@ -307,19 +203,25 @@ class PodLookupRepository {
307
203
  try {
308
204
  result = await (0, db_1.executeQuery)(this.db, (0, drizzle_orm_1.sql) `
309
205
  SELECT container, id, payload FROM ${storeTableId}
310
- WHERE container IN ('pod', 'owner', 'webIdLink')
206
+ WHERE container IN ('pod', 'owner')
311
207
  `);
312
208
  }
313
- catch {
314
- return [];
209
+ catch (error) {
210
+ // Legacy-only installations have no canonical table. An unavailable or
211
+ // unreadable existing table must not reactivate legacy ownership.
212
+ if (!isMissingIdentityStore(error))
213
+ throw error;
214
+ return { pods: [], podIds: new Set() };
315
215
  }
216
+ const podIds = new Set();
316
217
  const podPayloads = new Map();
317
218
  const ownerWebIdsByPodId = new Map();
318
- const webIdsByAccountId = new Map();
319
219
  for (const row of result?.rows ?? []) {
320
220
  if (!row.id || !row.container) {
321
221
  continue;
322
222
  }
223
+ if (row.container === 'pod')
224
+ podIds.add(row.id);
323
225
  const payload = parsePayloadRecord(row.payload);
324
226
  if (!payload) {
325
227
  continue;
@@ -334,14 +236,6 @@ class PodLookupRepository {
334
236
  if (podId && webId) {
335
237
  appendMapValue(ownerWebIdsByPodId, podId, webId);
336
238
  }
337
- continue;
338
- }
339
- if (row.container === 'webIdLink') {
340
- const accountId = stringValue(payload.accountId);
341
- const webId = stringValue(payload.webId);
342
- if (accountId && webId) {
343
- appendMapValue(webIdsByAccountId, accountId, webId);
344
- }
345
239
  }
346
240
  }
347
241
  const pods = [];
@@ -355,9 +249,7 @@ class PodLookupRepository {
355
249
  stringValue(pod.webId),
356
250
  ...(ownerWebIdsByPodId.get(podId) ?? []),
357
251
  ].filter((value) => typeof value === 'string');
358
- const podWebIds = dedupeStrings(explicitPodWebIds.length > 0
359
- ? explicitPodWebIds
360
- : webIdsByAccountId.get(accountId) ?? []);
252
+ const podWebIds = dedupeStrings(explicitPodWebIds);
361
253
  const storageUrl = stringValue(pod.storageUrl) ?? stringValue(pod.storage);
362
254
  pods.push({
363
255
  podId,
@@ -370,10 +262,23 @@ class PodLookupRepository {
370
262
  edgeNodeId: stringValue(pod.edgeNodeId),
371
263
  });
372
264
  }
373
- return pods;
265
+ return { pods, podIds };
374
266
  }
375
267
  }
376
268
  exports.PodLookupRepository = PodLookupRepository;
269
+ function isMissingIdentityStore(error) {
270
+ const visited = new Set();
271
+ while (error && typeof error === 'object' && !visited.has(error)) {
272
+ visited.add(error);
273
+ const failure = error;
274
+ if (failure.message === 'no such table: identity_store'
275
+ || (failure.code === '42P01' && failure.message === 'relation "identity_store" does not exist')) {
276
+ return true;
277
+ }
278
+ error = failure.cause;
279
+ }
280
+ return false;
281
+ }
377
282
  function extractAccountIdFromAccountDataKey(key) {
378
283
  const marker = 'accounts/data/';
379
284
  const index = key.indexOf(marker);
@@ -383,25 +288,6 @@ function extractAccountIdFromAccountDataKey(key) {
383
288
  const accountId = key.slice(index + marker.length).replace(/\.json$/u, '');
384
289
  return accountId || undefined;
385
290
  }
386
- function extractAccountWebIds(data) {
387
- if (!data || typeof data !== 'object') {
388
- return [];
389
- }
390
- const record = data;
391
- const linkMap = record['**webIdLink**'] || record.webIdLink || {};
392
- if (!linkMap || typeof linkMap !== 'object') {
393
- return [];
394
- }
395
- return Object.values(linkMap)
396
- .map((value) => {
397
- if (!value || typeof value !== 'object') {
398
- return undefined;
399
- }
400
- const webId = value.webId;
401
- return typeof webId === 'string' ? webId : undefined;
402
- })
403
- .filter((value) => typeof value === 'string');
404
- }
405
291
  function extractPodOwnerWebIds(pod) {
406
292
  const ownerMap = pod['**owner**'] || pod.owner || {};
407
293
  if (!ownerMap || typeof ownerMap !== 'object') {
@@ -423,17 +309,6 @@ function explicitPodWebIds(pod) {
423
309
  ...extractPodOwnerWebIds(pod),
424
310
  ].filter((value) => typeof value === 'string');
425
311
  }
426
- function hasExplicitPodWebIds(account, podId) {
427
- const podMap = account['**pod**'] ?? account.pod;
428
- if (!podMap || typeof podMap !== 'object')
429
- return false;
430
- const pod = podMap[podId];
431
- return Boolean(pod && typeof pod === 'object' && explicitPodWebIds(pod).length > 0);
432
- }
433
- function resolvePodWebIds(pod, accountWebIds) {
434
- const explicit = explicitPodWebIds(pod);
435
- return dedupeStrings(explicit.length > 0 ? explicit : accountWebIds);
436
- }
437
312
  function exactWebId(webId) {
438
313
  // WebID identity is the complete original string, not URL equivalence.
439
314
  return webId && webId === webId.trim() && !/[\r\n\t]/u.test(webId) ? webId : undefined;
@@ -466,31 +341,6 @@ function webIdsProperty(values) {
466
341
  const webIds = dedupeStrings(values);
467
342
  return webIds.length > 1 ? { webIds } : {};
468
343
  }
469
- function mergePodLookupResults(values) {
470
- const byPodId = new Map();
471
- for (const value of values) {
472
- const existing = byPodId.get(value.podId);
473
- if (!existing) {
474
- byPodId.set(value.podId, value);
475
- continue;
476
- }
477
- const webIds = dedupeStrings([
478
- ...getPodWebIds(existing),
479
- ...getPodWebIds(value),
480
- ]);
481
- byPodId.set(value.podId, {
482
- ...existing,
483
- baseUrl: existing.baseUrl || value.baseUrl,
484
- storageUrl: existing.storageUrl ?? value.storageUrl,
485
- accountId: existing.accountId || value.accountId,
486
- webId: webIds[0],
487
- ...webIdsProperty(webIds),
488
- nodeId: existing.nodeId ?? value.nodeId,
489
- edgeNodeId: existing.edgeNodeId ?? value.edgeNodeId,
490
- });
491
- }
492
- return [...byPodId.values()];
493
- }
494
344
  function parsePayloadRecord(value) {
495
345
  if (!value) {
496
346
  return undefined;
@@ -508,17 +358,6 @@ function parsePayloadRecord(value) {
508
358
  const unwrapped = unwrapStoredValue(value);
509
359
  return typeof unwrapped === 'object' ? unwrapped : undefined;
510
360
  }
511
- function parseStoredValue(value) {
512
- if (typeof value === 'string') {
513
- try {
514
- return unwrapStoredValue(JSON.parse(value));
515
- }
516
- catch {
517
- return undefined;
518
- }
519
- }
520
- return unwrapStoredValue(value);
521
- }
522
361
  function unwrapStoredValue(value) {
523
362
  if (value && typeof value === 'object' && 'key' in value && 'payload' in value) {
524
363
  return value.payload;
@@ -31,6 +31,7 @@ export function AiConnectionsMain({ controller, renderToaster = true }) {
31
31
  useEffect(() => {
32
32
  if (controller.client)
33
33
  void controller.loadProviders();
34
+ return () => controller.cancelProviderLoads();
34
35
  }, [controller]);
35
36
  if (!controller.client) {
36
37
  return (_jsxs("section", { role: "alert", "aria-label": "AI Connections unavailable", children: [_jsx("h2", { children: "AI Connections \u5C1A\u672A\u5C31\u7EEA" }), _jsx("p", { children: "\u5BBF\u4E3B\u9700\u8981\u5148\u63D0\u4F9B\u5DF2\u767B\u5F55\u7684 WebID \u548C\u53EF\u7528\u7684 Pod\u3002" })] }));
@@ -38,6 +38,7 @@ export interface AiConnectionsController {
38
38
  setSearchQuery(value: string): void;
39
39
  setProviderState(provider: AiConnectionsProvider, state: ProviderProductState): void;
40
40
  loadProviders(): Promise<void>;
41
+ cancelProviderLoads(): void;
41
42
  subscribe(listener: () => void): () => void;
42
43
  }
43
44
  export declare function createAiConnectionsController(host: WebExtensionHost): AiConnectionsController;
@@ -16,18 +16,72 @@ export const AI_CONNECTIONS_PINNED_SECTIONS = [
16
16
  if (PROVIDERS.map((provider) => provider.id).join(',') !== AI_CONNECTIONS_PROVIDERS.join(',')) {
17
17
  throw new Error('AI Connection provider UI is out of sync with the client catalog');
18
18
  }
19
+ class ProviderLoadCancelled extends Error {
20
+ }
19
21
  export function createAiConnectionsController(host) {
20
22
  const sessionSnapshot = host.solid.session.getSnapshot();
21
23
  const pod = host.solid.pod;
22
24
  const readyPod = pod?.status === 'ready' ? pod : undefined;
23
25
  const authenticated = sessionSnapshot.status === 'authenticated'
24
26
  && readyPod !== undefined;
27
+ let providerLoadGeneration = 0;
28
+ let providerLoadPromise;
29
+ let providerLoadOperation;
30
+ const activeProviderLoads = new Set();
31
+ const isCurrentSession = () => {
32
+ const current = host.solid.session.getSnapshot();
33
+ return authenticated && current.status === 'authenticated' && current.webId === sessionSnapshot.webId;
34
+ };
35
+ const cancelProviderLoads = () => {
36
+ providerLoadGeneration += 1;
37
+ providerLoadOperation = undefined;
38
+ providerLoadPromise = undefined;
39
+ for (const guard of activeProviderLoads)
40
+ guard.cancel();
41
+ };
42
+ const beginProviderLoad = () => {
43
+ const generation = providerLoadGeneration;
44
+ let invalidated = !isCurrentSession();
45
+ let released = false;
46
+ let unsubscribe = () => undefined;
47
+ const guard = {
48
+ assertCurrent() {
49
+ if (invalidated || generation !== providerLoadGeneration || !isCurrentSession())
50
+ throw new ProviderLoadCancelled();
51
+ },
52
+ dispose() {
53
+ if (released)
54
+ return;
55
+ released = true;
56
+ activeProviderLoads.delete(guard);
57
+ unsubscribe();
58
+ },
59
+ cancel() {
60
+ invalidated = true;
61
+ guard.dispose();
62
+ },
63
+ };
64
+ activeProviderLoads.add(guard);
65
+ let subscribing = true;
66
+ unsubscribe = host.solid.session.subscribe((current) => {
67
+ // The Solid SDK deduplicates unchanged state except new LOGIN/RESTORED
68
+ // sessions; SESSION_EXTENDED does not notify. Accept a synchronous initial
69
+ // snapshot, then invalidate on every published session transition.
70
+ if (!subscribing || current.status !== 'authenticated' || current.webId !== sessionSnapshot.webId)
71
+ cancelProviderLoads();
72
+ });
73
+ subscribing = false;
74
+ // Also handle a host that synchronously publishes its state on subscribe.
75
+ if (released)
76
+ unsubscribe();
77
+ return guard;
78
+ };
25
79
  const client = authenticated
26
80
  ? createInteractiveAiConnectionsClient(withAccountClientCredentials(createAiConnectionsClient({
27
81
  webId: sessionSnapshot.webId,
28
82
  podBaseUrl: readyPod.current.podUrl,
29
83
  authenticatedFetch: host.solid.session.fetch,
30
- }), host.capabilities.aiClientCredentials), host.capabilities.aiConnectionsPodStore)
84
+ }), host.capabilities.aiClientCredentials), host.capabilities.aiConnectionsPodStore, beginProviderLoad)
31
85
  : null;
32
86
  let selectedSection = 'keys';
33
87
  let selectedProvider = 'openai';
@@ -36,8 +90,6 @@ export function createAiConnectionsController(host) {
36
90
  let providerStates = {};
37
91
  let providerSummaries = {};
38
92
  let providerLoadError;
39
- let providerLoadGeneration = 0;
40
- let providerLoadPromise;
41
93
  const listeners = new Set();
42
94
  const notify = () => listeners.forEach((listener) => listener());
43
95
  const controller = {
@@ -111,19 +163,23 @@ export function createAiConnectionsController(host) {
111
163
  }
112
164
  notify();
113
165
  },
166
+ cancelProviderLoads,
114
167
  async loadProviders() {
115
168
  if (!client)
116
169
  return;
117
170
  if (providerLoadPromise)
118
171
  return providerLoadPromise;
119
- providerLoadPromise = (async () => {
120
- const generation = providerLoadGeneration + 1;
121
- providerLoadGeneration = generation;
172
+ const operation = {};
173
+ providerLoadOperation = operation;
174
+ const generation = ++providerLoadGeneration;
175
+ const load = Promise.resolve().then(async () => {
176
+ if (providerLoadOperation !== operation)
177
+ return;
122
178
  providerLoadError = undefined;
123
179
  notify();
124
180
  try {
125
181
  const summaries = await client.listProviders();
126
- if (generation !== providerLoadGeneration)
182
+ if (generation !== providerLoadGeneration || !isCurrentSession())
127
183
  return;
128
184
  providerSummaries = Object.fromEntries(summaries.map((summary) => [summary.id, summary]));
129
185
  providerStates = Object.fromEntries(PROVIDERS.map((provider) => [
@@ -133,16 +189,20 @@ export function createAiConnectionsController(host) {
133
189
  notify();
134
190
  }
135
191
  catch (error) {
136
- if (generation !== providerLoadGeneration)
192
+ if (error instanceof ProviderLoadCancelled || generation !== providerLoadGeneration || !isCurrentSession())
137
193
  return;
138
194
  providerLoadError = errorMessage(error);
139
195
  notify();
140
196
  }
141
197
  finally {
142
- providerLoadPromise = undefined;
198
+ if (providerLoadOperation === operation) {
199
+ providerLoadOperation = undefined;
200
+ providerLoadPromise = undefined;
201
+ }
143
202
  }
144
- })();
145
- return providerLoadPromise;
203
+ });
204
+ providerLoadPromise = load;
205
+ return load;
146
206
  },
147
207
  subscribe(listener) {
148
208
  listeners.add(listener);
@@ -195,9 +255,29 @@ function withAccountClientCredentials(client, credentials) {
195
255
  },
196
256
  };
197
257
  }
198
- function createInteractiveAiConnectionsClient(operationsClient, podStore) {
258
+ function createInteractiveAiConnectionsClient(operationsClient, podStore, beginProviderLoad) {
259
+ const listProviders = async () => {
260
+ const guard = beginProviderLoad();
261
+ try {
262
+ guard.assertCurrent();
263
+ const providers = podStore
264
+ ? await podStore.listProviders()
265
+ : await operationsClient.listProviders();
266
+ guard.assertCurrent();
267
+ if (!podStore)
268
+ return providers;
269
+ const methods = operationsClient.listAuthorizationMethods
270
+ ? await operationsClient.listAuthorizationMethods()
271
+ : [];
272
+ guard.assertCurrent();
273
+ return mergeAuthorizationMethodsIntoProviders(providers, methods);
274
+ }
275
+ finally {
276
+ guard.dispose();
277
+ }
278
+ };
199
279
  if (!podStore)
200
- return operationsClient;
280
+ return { ...operationsClient, listProviders };
201
281
  const refreshTasks = new Map();
202
282
  const refreshOAuthCredential = podStore.readCredentialSecret && podStore.updateOAuthCredential
203
283
  ? (provider, credentialId) => {
@@ -249,9 +329,7 @@ function createInteractiveAiConnectionsClient(operationsClient, podStore) {
249
329
  };
250
330
  return {
251
331
  ...operationsClient,
252
- listProviders: async () => mergeAuthorizationMethodsIntoProviders(await podStore.listProviders(), operationsClient.listAuthorizationMethods
253
- ? await operationsClient.listAuthorizationMethods()
254
- : []),
332
+ listProviders,
255
333
  listModels: podStore.listModels
256
334
  ? async () => podStore.listModels()
257
335
  : operationsClient.listModels,