@undefineds.co/xpod 0.4.10 → 0.4.11

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 (121) hide show
  1. package/dist/api/auth/ClientCredentialsAuthenticator.d.ts +0 -1
  2. package/dist/api/auth/ClientCredentialsAuthenticator.js +0 -3
  3. package/dist/api/auth/index.d.ts +1 -0
  4. package/dist/api/auth/index.js +1 -1
  5. package/dist/identity/drizzle/AccountRoleRepository.d.ts +30 -2
  6. package/dist/identity/drizzle/AccountRoleRepository.js +163 -15
  7. package/dist/identity/drizzle/PodLookupRepository.d.ts +58 -4
  8. package/dist/identity/drizzle/PodLookupRepository.js +248 -105
  9. package/dist/identity/drizzle/db.d.ts +19 -0
  10. package/dist/identity/drizzle/db.js +39 -4
  11. package/node_modules/@undefineds.co/shared-ui/dist/index.d.ts +0 -1
  12. package/node_modules/@undefineds.co/shared-ui/dist/index.js +0 -1
  13. package/package.json +2 -2
  14. package/static/app/assets/main.css +1 -1
  15. package/static/app/assets/main.js +12 -13
  16. package/static/auth-callback/assets/{AiConfigContext-C81deMM9.js → AiConfigContext-DqWRSm7n.js} +1 -1
  17. package/static/auth-callback/assets/{AiConfigPage-evWI8T56.js → AiConfigPage-C5YYyvzl.js} +1 -1
  18. package/static/auth-callback/assets/{DocumentProcessingPanel-B9GrDEMe.js → DocumentProcessingPanel-DZCiacBz.js} +1 -1
  19. package/static/auth-callback/assets/{IndexLifecyclePanel-qCo8ABQZ.js → IndexLifecyclePanel-CCLeOljB.js} +1 -1
  20. package/static/auth-callback/assets/{IndexSubjectPanel-C4YG0whI.js → IndexSubjectPanel-BbUqop4b.js} +1 -1
  21. package/static/auth-callback/assets/{ModelAssignmentsPanel-DG7Lxh3t.js → ModelAssignmentsPanel-jUUTDKuh.js} +1 -1
  22. package/static/auth-callback/assets/{ModelsPage-CgEuqE6p.js → ModelsPage-B08zdtff.js} +4 -4
  23. package/static/auth-callback/assets/{NetworkPage-Cbdn_P1e.js → NetworkPage-Vp2PrMxs.js} +1 -1
  24. package/static/auth-callback/assets/{PaneListHeader-jy70Q00L.js → PaneListHeader-DY7baSFl.js} +1 -1
  25. package/static/auth-callback/assets/{SearchIndexingPanel-ByU3u7kp.js → SearchIndexingPanel-D4YRg1-8.js} +1 -1
  26. package/static/auth-callback/assets/{StatusSubjectPanel-hfjoB5aS.js → StatusSubjectPanel-ArIS-TxT.js} +1 -1
  27. package/static/auth-callback/assets/{StatusWorkspace-BZd617sV.js → StatusWorkspace-Cp5LQ9Yx.js} +1 -1
  28. package/static/auth-callback/assets/SystemSettingsPage-BDvT1NUz.js +1 -0
  29. package/static/auth-callback/assets/SystemSettingsSubjectPanel-CDqRuC4t.js +33 -0
  30. package/static/auth-callback/assets/{activity-BcsZZxxw.js → activity-D1Dsfa_0.js} +1 -1
  31. package/static/auth-callback/assets/admin-C6qCRFiY.js +1 -0
  32. package/static/auth-callback/assets/{auth-callback-atKg_oig.js → auth-callback-Cmf4hXVZ.js} +80 -84
  33. package/static/auth-callback/assets/auth-callback-DWMMiFjh.css +1 -0
  34. package/static/auth-callback/assets/{badge-BWbRHThQ.js → badge-CZsHq3Ih.js} +1 -1
  35. package/static/auth-callback/assets/{download-BmvG1Vya.js → download-DU9ntlHw.js} +1 -1
  36. package/static/auth-callback/assets/{external-link-DZ34d3g3.js → external-link-BfzaaZDx.js} +1 -1
  37. package/static/auth-callback/assets/{index-BpV1hsqs.js → index-DSNnLUbU.js} +4 -4
  38. package/static/auth-callback/assets/{index-D9oE6FXK.js → index-FHhHoKgp.js} +1 -1
  39. package/static/auth-callback/assets/{index-browser-B9Z7GU-T.js → index-browser-BNFqqC74.js} +1 -1
  40. package/static/auth-callback/assets/{key-round-tjP76Se-.js → key-round-BwP3DBWo.js} +1 -1
  41. package/static/auth-callback/assets/{rotate-ccw-C_L5ZLx1.js → rotate-ccw-VMwnmyab.js} +1 -1
  42. package/static/auth-callback/assets/settings-projection-CUkyprDQ.js +1 -0
  43. package/static/auth-callback/assets/{skeleton-C7tGDQIs.js → skeleton-CoD55rBT.js} +1 -1
  44. package/static/auth-callback/assets/{waypoints-VMNQlWdU.js → waypoints-DRo_1Ca7.js} +1 -1
  45. package/static/auth-callback/assets/{workspace-layout-SAp6BtYK.js → workspace-layout--mcXl3My.js} +1 -1
  46. package/static/auth-callback/auth-callback.html +2 -2
  47. package/static/dashboard/assets/{AiConfigContext-De-OtAWo.js → AiConfigContext-BpPDKSzI.js} +1 -1
  48. package/static/dashboard/assets/{AiConfigPage-macP4Mop.js → AiConfigPage-wsqEZDIX.js} +1 -1
  49. package/static/dashboard/assets/{DocumentProcessingPanel-C5ObvCCl.js → DocumentProcessingPanel-CDSy3juc.js} +1 -1
  50. package/static/dashboard/assets/{IndexLifecyclePanel-xVNa2l40.js → IndexLifecyclePanel-CVd6vQCX.js} +1 -1
  51. package/static/dashboard/assets/{IndexSubjectPanel-DQFPP6-p.js → IndexSubjectPanel-B2cXwl8X.js} +1 -1
  52. package/static/dashboard/assets/{ModelAssignmentsPanel-DMOATFpQ.js → ModelAssignmentsPanel-BeBZhnQd.js} +1 -1
  53. package/static/dashboard/assets/{ModelsPage-91UbnETA.js → ModelsPage-B6McSCJw.js} +1 -1
  54. package/static/dashboard/assets/{NetworkPage-CHQ3YxIB.js → NetworkPage-DV2V0qWO.js} +1 -1
  55. package/static/dashboard/assets/{PaneListHeader-DsXemAdR.js → PaneListHeader-D0c1yCFH.js} +1 -1
  56. package/static/dashboard/assets/{SearchIndexingPanel-Dh70PNFZ.js → SearchIndexingPanel-CGs0OZ12.js} +1 -1
  57. package/static/dashboard/assets/{StatusSubjectPanel-D3PGEYT4.js → StatusSubjectPanel-oPSECj7i.js} +1 -1
  58. package/static/dashboard/assets/{StatusWorkspace-BcpePYUW.js → StatusWorkspace-PdovQPKa.js} +1 -1
  59. package/static/dashboard/assets/SystemSettingsPage-CtNDklkx.js +1 -0
  60. package/static/dashboard/assets/SystemSettingsSubjectPanel-WaE8mTQi.js +33 -0
  61. package/static/dashboard/assets/{activity-j5zrznu-.js → activity-B9ylSoyr.js} +1 -1
  62. package/static/dashboard/assets/admin-C6qCRFiY.js +1 -0
  63. package/static/dashboard/assets/{badge-CzeBdG7h.js → badge-BNbFbpIb.js} +1 -1
  64. package/static/dashboard/assets/{dashboard-D-xANHk1.js → dashboard-CD-FFVRD.js} +105 -109
  65. package/static/dashboard/assets/dashboard-DWMMiFjh.css +1 -0
  66. package/static/dashboard/assets/{download-IcNdEZWi.js → download-nOP38LtW.js} +1 -1
  67. package/static/dashboard/assets/{external-link-DxBxK8Vu.js → external-link-Bh2ewN9E.js} +1 -1
  68. package/static/dashboard/assets/{index-FWZ-s52O.js → index-PgS-Bt7X.js} +1 -1
  69. package/static/dashboard/assets/{index-ZiQZ96oC.js → index-QN2QgEwr.js} +4 -4
  70. package/static/dashboard/assets/{key-round-D1K7warM.js → key-round-bGmguBLv.js} +1 -1
  71. package/static/dashboard/assets/{rotate-ccw-CcN1N6p9.js → rotate-ccw-D2x__ODW.js} +1 -1
  72. package/static/dashboard/assets/settings-projection-CUkyprDQ.js +1 -0
  73. package/static/dashboard/assets/{skeleton-CKnmJMUH.js → skeleton-VOqft8WS.js} +1 -1
  74. package/static/dashboard/assets/{waypoints-DuZPO_wu.js → waypoints-fgRqm4oH.js} +1 -1
  75. package/static/dashboard/assets/{workspace-layout-BUj3wZ_m.js → workspace-layout-DV4dgeHX.js} +1 -1
  76. package/static/dashboard/dashboard.html +2 -2
  77. package/static/settings/assets/{AiConfigContext-C0cQMPfG.js → AiConfigContext-zKQYdfai.js} +1 -1
  78. package/static/settings/assets/{AiConfigPage-CPhB4iFb.js → AiConfigPage-Bs1EE3Ei.js} +1 -1
  79. package/static/settings/assets/{DocumentProcessingPanel-sYI7OpuH.js → DocumentProcessingPanel-CSFfdOXS.js} +1 -1
  80. package/static/settings/assets/{IndexLifecyclePanel-yj8iyiT8.js → IndexLifecyclePanel-BR9Su49B.js} +1 -1
  81. package/static/settings/assets/{IndexSubjectPanel-73sM16Ma.js → IndexSubjectPanel-BgbI9c-a.js} +1 -1
  82. package/static/settings/assets/{ModelAssignmentsPanel-BFIgUX5v.js → ModelAssignmentsPanel-C0BE7nbk.js} +1 -1
  83. package/static/settings/assets/{ModelsPage-BdppXl7E.js → ModelsPage-CU-YQ8_7.js} +4 -4
  84. package/static/settings/assets/{NetworkPage-i6j-M2RY.js → NetworkPage-C8OA9Mtp.js} +1 -1
  85. package/static/settings/assets/{PaneListHeader-DpqC0jd5.js → PaneListHeader-COvlPJWj.js} +1 -1
  86. package/static/settings/assets/{SearchIndexingPanel-BlPnrkT3.js → SearchIndexingPanel-Bdd46mIV.js} +1 -1
  87. package/static/settings/assets/{StatusSubjectPanel-Dv4q3PGh.js → StatusSubjectPanel-BuLml7qn.js} +1 -1
  88. package/static/settings/assets/{StatusWorkspace-CKD_Rzdr.js → StatusWorkspace-CoMFRXoV.js} +1 -1
  89. package/static/settings/assets/SystemSettingsPage-DlzmaWV-.js +1 -0
  90. package/static/settings/assets/SystemSettingsSubjectPanel-DExjDo_v.js +33 -0
  91. package/static/settings/assets/{activity-DaFkk8f3.js → activity-TLerXgMs.js} +1 -1
  92. package/static/settings/assets/admin-C6qCRFiY.js +1 -0
  93. package/static/settings/assets/{badge-d5SHdc-x.js → badge-Bcs8iWrH.js} +1 -1
  94. package/static/settings/assets/{download-CpkwomDr.js → download-Ct3QmHPt.js} +1 -1
  95. package/static/settings/assets/{external-link-_4YT42Mf.js → external-link-C8KiHbss.js} +1 -1
  96. package/static/settings/assets/{index-BFi9yn7_.js → index-1Q2cRjZd.js} +1 -1
  97. package/static/settings/assets/{index-BGgEhIUy.js → index-DBO_Vq0P.js} +4 -4
  98. package/static/settings/assets/{index-browser-B06xjBdZ.js → index-browser-CazX1pc2.js} +1 -1
  99. package/static/settings/assets/{key-round-Dlqmp1Kq.js → key-round-B19YD1GA.js} +1 -1
  100. package/static/settings/assets/{rotate-ccw-TAY65xWT.js → rotate-ccw-AQm2kMgU.js} +1 -1
  101. package/static/settings/assets/{settings-ExBXGgbj.js → settings-C-VBtt9r.js} +105 -109
  102. package/static/settings/assets/settings-DWMMiFjh.css +1 -0
  103. package/static/settings/assets/settings-projection-CUkyprDQ.js +1 -0
  104. package/static/settings/assets/{skeleton-DKIWjNr9.js → skeleton-DyZ1OBYf.js} +1 -1
  105. package/static/settings/assets/{waypoints-9erLIxyR.js → waypoints-CC3wq-SU.js} +1 -1
  106. package/static/settings/assets/{workspace-layout-CYXfYIa3.js → workspace-layout-BxXNKgf9.js} +1 -1
  107. package/static/settings/settings.html +2 -2
  108. package/node_modules/@undefineds.co/shared-ui/dist/oidc-consent.d.ts +0 -54
  109. package/node_modules/@undefineds.co/shared-ui/dist/oidc-consent.js +0 -21
  110. package/static/auth-callback/assets/SystemSettingsPage-BirzWAQM.js +0 -1
  111. package/static/auth-callback/assets/SystemSettingsSubjectPanel-CmsVWNCx.js +0 -31
  112. package/static/auth-callback/assets/admin-BU4m6Ko2.js +0 -1
  113. package/static/auth-callback/assets/auth-callback-C_7MUXJb.css +0 -1
  114. package/static/dashboard/assets/SystemSettingsPage-BiN5GcKx.js +0 -1
  115. package/static/dashboard/assets/SystemSettingsSubjectPanel-B9QrSiFp.js +0 -31
  116. package/static/dashboard/assets/admin-BU4m6Ko2.js +0 -1
  117. package/static/dashboard/assets/dashboard-C_7MUXJb.css +0 -1
  118. package/static/settings/assets/SystemSettingsPage-CiOm2NzV.js +0 -1
  119. package/static/settings/assets/SystemSettingsSubjectPanel-Cd-GECon.js +0 -31
  120. package/static/settings/assets/admin-BU4m6Ko2.js +0 -1
  121. package/static/settings/assets/settings-C_7MUXJb.css +0 -1
@@ -8,6 +8,12 @@ const db_1 = require("./db");
8
8
  *
9
9
  * Reads Pod facts from the canonical identity_store table and from CSS
10
10
  * WrappedIndexedStorage rows when that storage backend is active.
11
+ *
12
+ * 点查(findById / findAllByWebId / findByWebIds / listByAccountId)走字段下推
13
+ * 快路径,避免每请求全扫 identity_store;KV 历史账户记录独立读取,
14
+ * 同 ID 的 canonical 记录整条优先,包括无法解析的记录,不能恢复旧身份。
15
+ * findByResourceIdentifier / listAllPods 本质上需要
16
+ * 候选全集,仍保留全量读取(identity_store 侧用反向前缀 LIKE 下推预筛)。
11
17
  */
12
18
  class PodLookupRepository {
13
19
  constructor(db, kvTableName) {
@@ -17,9 +23,13 @@ class PodLookupRepository {
17
23
  }
18
24
  /**
19
25
  * Find Pod by resource path (matches longest canonical storage/base URL prefix).
26
+ *
27
+ * kv 账户行无法按 URL 下推,保持全量解析(cloud 部署 kv 为空,local 规模有限)。
20
28
  */
21
29
  async findByResourceIdentifier(resourcePath) {
22
- const pods = await this.getAllPods();
30
+ const kvRows = await this.getAccountRowsFromKv();
31
+ const nodeAssignments = await this.getNodeAssignments();
32
+ const pods = await this.mergeCanonicalPods(this.extractKvPods(kvRows, nodeAssignments), await this.findIndexedStorePodsByResource(resourcePath, nodeAssignments), nodeAssignments);
23
33
  let bestMatch;
24
34
  let bestLength = 0;
25
35
  for (const pod of pods) {
@@ -33,16 +43,21 @@ class PodLookupRepository {
33
43
  }
34
44
  /**
35
45
  * Get Pod by ID.
46
+ *
47
+ * 快路径按主键直查 identity_store 的 pod 行并补齐 owner/node 关联;
48
+ * 独立读取 KV 历史记录,并按原始 ID 检查 canonical 遮蔽。
36
49
  */
37
50
  async findById(podId) {
38
- const pods = await this.getAllPods();
39
- return pods.find((p) => p.podId === podId);
51
+ const fast = await this.getIndexedStorePodsByIds([podId]);
52
+ const legacy = (await this.getLegacyPods()).filter((pod) => pod.podId === podId);
53
+ return (await this.mergeCanonicalPods(legacy, fast)).find((pod) => pod.podId === podId);
40
54
  }
41
55
  /**
42
- * Find Pod by an explicitly recorded owner WebID URL.
56
+ * Find Pod by an explicit owner or pod.webId URL.
43
57
  *
44
- * Account WebID links are not ownership. IdP/SP split deployments still use
45
- * the complete recorded WebID, independently of the storage base URL.
58
+ * Pod ownership stores WebIDs separately from Pod base URLs. This is
59
+ * the precise lookup for IdP/SP split deployments where the WebID path does
60
+ * not have to match the storage base URL.
46
61
  */
47
62
  async findByWebId(webId) {
48
63
  return (await this.findAllByWebId(webId))[0];
@@ -55,54 +70,32 @@ class PodLookupRepository {
55
70
  * instead of accepting the first account record returned by CSS storage.
56
71
  */
57
72
  async findAllByWebId(webId) {
58
- const exact = exactWebId(webId);
59
- if (!exact) {
60
- return [];
61
- }
62
- const results = [];
63
- const pods = await this.getAllPods();
64
- for (const pod of pods) {
65
- const matchedWebId = getPodWebIds(pod).find((candidate) => exactWebId(candidate) === exact);
66
- if (matchedWebId) {
67
- results.push({
68
- ...pod,
69
- webId: matchedWebId,
70
- });
71
- }
72
- }
73
- return results;
73
+ return this.findByWebIds([webId]);
74
74
  }
75
75
  /**
76
- * Find Pods by explicit owner WebID URLs in one scan.
76
+ * Find Pods by explicit WebID bindings with batched canonical queries.
77
77
  */
78
78
  async findByWebIds(webIds) {
79
79
  const exactTargets = new Set(webIds.map(exactWebId).filter((value) => Boolean(value)));
80
80
  if (exactTargets.size === 0) {
81
81
  return [];
82
82
  }
83
- const results = [];
84
- const pods = await this.getAllPods();
85
- for (const pod of pods) {
86
- const matchedWebId = getPodWebIds(pod).find((candidate) => {
87
- const exact = exactWebId(candidate);
88
- return exact ? exactTargets.has(exact) : false;
89
- });
90
- if (!matchedWebId) {
91
- continue;
92
- }
93
- results.push({
94
- ...pod,
95
- webId: matchedWebId,
96
- });
97
- }
98
- return results;
83
+ const legacy = await this.getLegacyPods();
84
+ const candidates = await this.findIndexedStorePodsByWebIds([...exactTargets]);
85
+ return this.filterPodsByWebIds(await this.mergeCanonicalPods(legacy, candidates), exactTargets);
99
86
  }
100
87
  /**
101
88
  * List Pods for a specific account.
89
+ *
90
+ * 定向查询与全扫描等价:kv 按账户键直取,identity_store 按 accountId 下推,
91
+ * canonical 整条优先与 getAllPods 一致,KV 扫描补齐 .json 等历史键。
102
92
  */
103
93
  async listByAccountId(accountId) {
104
- const pods = await this.getAllPods();
105
- return pods.filter((pod) => pod.accountId === accountId);
94
+ const nodeAssignments = await this.getNodeAssignments();
95
+ const storePods = await this.getIndexedStorePodsByAccountId(accountId, nodeAssignments);
96
+ const legacy = (await this.getLegacyPods(nodeAssignments)).filter((pod) => pod.accountId === accountId);
97
+ return (await this.mergeCanonicalPods(legacy, storePods, nodeAssignments))
98
+ .filter((pod) => pod.accountId === accountId);
106
99
  }
107
100
  /**
108
101
  * List all pods.
@@ -116,8 +109,18 @@ class PodLookupRepository {
116
109
  async getAllPods() {
117
110
  const result = await this.getAccountRowsFromKv();
118
111
  const nodeAssignments = await this.getNodeAssignments();
112
+ return this.mergeCanonicalPods(this.extractKvPods(result, nodeAssignments), await this.getPodsFromIndexedStore(nodeAssignments), nodeAssignments);
113
+ }
114
+ // Legacy KV rows need independent discovery: an indexed match says nothing
115
+ // about other unindexed Pods or overlapping records. Keep identity_store
116
+ // queries targeted while applying canonical precedence to overlapping IDs.
117
+ async getLegacyPods(nodeAssignments) {
118
+ const rows = await this.getAccountRowsFromKv();
119
+ return this.extractKvPods(rows, nodeAssignments ?? await this.getNodeAssignments());
120
+ }
121
+ extractKvPods(rows, nodeAssignments) {
119
122
  const pods = [];
120
- for (const row of result) {
123
+ for (const row of rows) {
121
124
  if (!row.key || row.value === undefined) {
122
125
  continue;
123
126
  }
@@ -127,36 +130,13 @@ class PodLookupRepository {
127
130
  continue;
128
131
  }
129
132
  const data = unwrapStoredValue(typeof row.value === 'string' ? JSON.parse(row.value) : row.value);
130
- const podMap = data['**pod**'] || data.pod || {};
131
- for (const [podId, podData] of Object.entries(podMap)) {
132
- const pod = podData;
133
- if (pod.baseUrl && typeof pod.baseUrl === 'string') {
134
- const storageUrl = stringValue(pod.storageUrl) ?? stringValue(pod.storage);
135
- const podWebIds = dedupeStrings(explicitPodWebIds(pod));
136
- pods.push({
137
- podId,
138
- accountId,
139
- baseUrl: pod.baseUrl,
140
- storageUrl,
141
- webId: podWebIds[0],
142
- ...webIdsProperty(podWebIds),
143
- nodeId: typeof pod.nodeId === 'string' ? pod.nodeId : findNodeIdForPod(nodeAssignments, [storageUrl, pod.baseUrl]),
144
- edgeNodeId: typeof pod.edgeNodeId === 'string' ? pod.edgeNodeId : undefined,
145
- });
146
- }
147
- }
133
+ pods.push(...this.extractPodsFromAccountData(accountId, data, nodeAssignments));
148
134
  }
149
135
  catch {
150
136
  // Skip malformed entries.
151
137
  }
152
138
  }
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()];
139
+ return pods;
160
140
  }
161
141
  async getAccountRowsFromKv() {
162
142
  const kvTableId = drizzle_orm_1.sql.identifier(this.kvTableName);
@@ -193,50 +173,197 @@ class PodLookupRepository {
193
173
  return [];
194
174
  }
195
175
  }
176
+ // Predicate pushdown can omit a canonical replacement with a different owner,
177
+ // account or URL. Check legacy IDs separately, registering existence before
178
+ // parsing so a malformed canonical record also suppresses the old Pod.
179
+ async mergeCanonicalPods(legacy, candidates, nodeAssignments) {
180
+ const ids = dedupeStrings(legacy.map((pod) => pod.podId));
181
+ const rows = ids.length > 0
182
+ ? await this.queryIndexedStoreRows((0, drizzle_orm_1.sql) `container = 'pod' AND ${idIn(ids)}`)
183
+ : [];
184
+ const canonicalIds = new Set(rows.map((row) => row.id));
185
+ const replacements = rows.length > 0
186
+ ? await this.enrichIndexedStorePods(rowsToPodPayloadMap(rows), nodeAssignments ?? await this.getNodeAssignments())
187
+ : [];
188
+ const all = [...legacy.filter((pod) => !canonicalIds.has(pod.podId)), ...candidates, ...replacements];
189
+ return [...new Map(all.map((pod) => [pod.podId, pod])).values()];
190
+ }
191
+ extractPodsFromAccountData(accountId, data, nodeAssignments = []) {
192
+ const podMap = data['**pod**'] || data.pod || {};
193
+ const pods = [];
194
+ for (const [podId, podData] of Object.entries(podMap)) {
195
+ const pod = podData;
196
+ if (pod.baseUrl && typeof pod.baseUrl === 'string') {
197
+ const storageUrl = stringValue(pod.storageUrl) ?? stringValue(pod.storage);
198
+ const podWebIds = dedupeStrings(explicitPodWebIds(pod));
199
+ pods.push({
200
+ podId,
201
+ accountId,
202
+ baseUrl: pod.baseUrl,
203
+ storageUrl,
204
+ webId: dedupeStrings(podWebIds)[0],
205
+ ...webIdsProperty(podWebIds),
206
+ nodeId: typeof pod.nodeId === 'string' ? pod.nodeId : findNodeIdForPod(nodeAssignments, [storageUrl, pod.baseUrl]),
207
+ edgeNodeId: typeof pod.edgeNodeId === 'string' ? pod.edgeNodeId : undefined,
208
+ });
209
+ }
210
+ }
211
+ return pods;
212
+ }
213
+ /**
214
+ * 与全扫描一致的 WebID 匹配过滤:按 Pod 解析后的 WebID 列表按原字符串比较,
215
+ * 命中时附加该 Pod 自身存储的 WebID。
216
+ */
217
+ filterPodsByWebIds(pods, exactTargets) {
218
+ const results = [];
219
+ for (const pod of pods) {
220
+ const matchedWebId = getPodWebIds(pod).find((candidate) => {
221
+ const exact = exactWebId(candidate);
222
+ return exact ? exactTargets.has(exact) : false;
223
+ });
224
+ if (matchedWebId) {
225
+ results.push({
226
+ ...pod,
227
+ webId: matchedWebId,
228
+ });
229
+ }
230
+ }
231
+ return results;
232
+ }
196
233
  /**
197
234
  * DrizzleIndexedStorage stores CSS identity facts as typed rows in
198
235
  * identity_store; this is the canonical clustered identity source.
199
236
  */
200
237
  async getPodsFromIndexedStore(nodeAssignments = []) {
201
238
  const storeTableId = drizzle_orm_1.sql.identifier(this.indexedStoreTableName);
202
- let result;
239
+ let rows = [];
203
240
  try {
204
- result = await (0, db_1.executeQuery)(this.db, (0, drizzle_orm_1.sql) `
241
+ const result = await (0, db_1.executeQuery)(this.db, (0, drizzle_orm_1.sql) `
205
242
  SELECT container, id, payload FROM ${storeTableId}
206
243
  WHERE container IN ('pod', 'owner')
207
244
  `);
245
+ rows = result.rows;
208
246
  }
209
247
  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() };
248
+ if (isMissingIdentityStore(error))
249
+ return [];
250
+ throw error;
215
251
  }
216
- const podIds = new Set();
217
252
  const podPayloads = new Map();
218
- const ownerWebIdsByPodId = new Map();
219
- for (const row of result?.rows ?? []) {
253
+ const ownerRows = [];
254
+ for (const row of rows) {
220
255
  if (!row.id || !row.container) {
221
256
  continue;
222
257
  }
223
- if (row.container === 'pod')
224
- podIds.add(row.id);
225
- const payload = parsePayloadRecord(row.payload);
226
- if (!payload) {
227
- continue;
228
- }
229
258
  if (row.container === 'pod') {
230
- podPayloads.set(row.id, payload);
231
- continue;
232
- }
233
- if (row.container === 'owner') {
234
- const podId = stringValue(payload.podId);
235
- const webId = stringValue(payload.webId);
236
- if (podId && webId) {
237
- appendMapValue(ownerWebIdsByPodId, podId, webId);
259
+ const payload = parsePayloadRecord(row.payload);
260
+ if (payload) {
261
+ podPayloads.set(row.id, payload);
238
262
  }
239
263
  }
264
+ else if (row.container === 'owner') {
265
+ ownerRows.push(row);
266
+ }
267
+ }
268
+ return this.buildIndexedStorePods(podPayloads, ownerRows, nodeAssignments);
269
+ }
270
+ /**
271
+ * 按主键直查 identity_store pod 行并补齐关联。
272
+ */
273
+ async getIndexedStorePodsByIds(podIds) {
274
+ if (podIds.length === 0) {
275
+ return [];
276
+ }
277
+ const rows = await this.queryIndexedStoreRows((0, drizzle_orm_1.sql) `container = 'pod' AND ${idIn(podIds)}`);
278
+ return this.enrichIndexedStorePods(rowsToPodPayloadMap(rows), await this.getNodeAssignments());
279
+ }
280
+ /**
281
+ * 按 payload.accountId 下推查询 identity_store pod 行并补齐关联。
282
+ */
283
+ async getIndexedStorePodsByAccountId(accountId, nodeAssignments) {
284
+ const rows = await this.queryIndexedStoreRows((0, drizzle_orm_1.sql) `container = 'pod'`, (0, db_1.jsonFieldEquals)(this.db, 'accountId', accountId));
285
+ return this.enrichIndexedStorePods(rowsToPodPayloadMap(rows), nodeAssignments);
286
+ }
287
+ /**
288
+ * 反向前缀 LIKE 预筛候选 pod 行(baseUrl/storageUrl/storage 任一前缀),
289
+ * JS 侧 startsWith 复核由调用方(findByResourceIdentifier)执行。
290
+ */
291
+ async findIndexedStorePodsByResource(resourcePath, nodeAssignments) {
292
+ const conditions = ['baseUrl', 'storageUrl', 'storage']
293
+ .map((field) => (0, db_1.jsonFieldIsPrefixOf)(this.db, field, resourcePath));
294
+ const rows = await this.queryIndexedStoreRows((0, drizzle_orm_1.sql) `container = 'pod'`, (0, drizzle_orm_1.sql) `(${drizzle_orm_1.sql.join(conditions, (0, drizzle_orm_1.sql) ` OR `)})`);
295
+ return this.enrichIndexedStorePods(rowsToPodPayloadMap(rows), nodeAssignments);
296
+ }
297
+ /**
298
+ * WebID → Pod 的下推解析:owner 按 webId 命中拿 podId,pod.webId
299
+ * 直查;Account webIdLink 不建立 Pod ownership。
300
+ * webIdForms 只携带完整原字符串;URL 等价不代表相同身份。
301
+ */
302
+ async findIndexedStorePodsByWebIds(webIdForms) {
303
+ const forms = dedupeStrings(webIdForms);
304
+ if (forms.length === 0) {
305
+ return [];
306
+ }
307
+ const [ownerMatchRows, directPodRows] = await Promise.all([
308
+ this.queryIndexedStoreRows((0, drizzle_orm_1.sql) `container = 'owner'`, (0, db_1.jsonFieldIn)(this.db, 'webId', forms)),
309
+ this.queryIndexedStoreRows((0, drizzle_orm_1.sql) `container = 'pod'`, (0, db_1.jsonFieldIn)(this.db, 'webId', forms)),
310
+ ]);
311
+ const podIds = dedupeStrings([
312
+ ...ownerMatchRows.map((row) => stringValue(parsePayloadRecord(row.payload)?.podId)),
313
+ ...directPodRows.map((row) => row.id),
314
+ ].filter((value) => Boolean(value)));
315
+ if (podIds.length === 0) {
316
+ return [];
317
+ }
318
+ const podRows = await this.queryIndexedStoreRows((0, drizzle_orm_1.sql) `container = 'pod' AND ${idIn(podIds)}`);
319
+ return this.enrichIndexedStorePods(rowsToPodPayloadMap(podRows), await this.getNodeAssignments());
320
+ }
321
+ /**
322
+ * 为候选 pod 行补齐 owner 关联与 nodeAssignments,组装最终结果。
323
+ * 与全扫描共用 buildIndexedStorePods,保证返回形状一致。
324
+ */
325
+ async enrichIndexedStorePods(podPayloads, nodeAssignments) {
326
+ if (podPayloads.size === 0) {
327
+ return [];
328
+ }
329
+ const podIds = [...podPayloads.keys()];
330
+ const ownerRows = await this.queryIndexedStoreRows((0, drizzle_orm_1.sql) `container = 'owner'`, (0, db_1.jsonFieldIn)(this.db, 'podId', podIds));
331
+ return this.buildIndexedStorePods(podPayloads, ownerRows, nodeAssignments);
332
+ }
333
+ async queryIndexedStoreRows(condition, jsonCondition) {
334
+ const storeTableId = drizzle_orm_1.sql.identifier(this.indexedStoreTableName);
335
+ // Keep indexed columns outside CASE; only JSON evaluation needs protection.
336
+ // Raw ID queries must still see malformed rows for canonical shadowing.
337
+ const safeJsonCondition = jsonCondition && (0, db_1.isDatabaseSqlite)(this.db)
338
+ ? (0, drizzle_orm_1.sql) `CASE WHEN json_valid(payload) THEN ${jsonCondition} ELSE 0 END`
339
+ : jsonCondition;
340
+ const safeCondition = safeJsonCondition ? (0, drizzle_orm_1.sql) `${condition} AND (${safeJsonCondition})` : condition;
341
+ try {
342
+ const result = await (0, db_1.executeQuery)(this.db, (0, drizzle_orm_1.sql) `
343
+ SELECT id, payload FROM ${storeTableId}
344
+ WHERE ${safeCondition}
345
+ `);
346
+ return result.rows;
347
+ }
348
+ catch (error) {
349
+ if (isMissingIdentityStore(error))
350
+ return [];
351
+ throw error;
352
+ }
353
+ }
354
+ /**
355
+ * 组装 identity_store Pod 视图:pod 行为主体,owner 行补充 WebID,
356
+ * nodeAssignments 兜底 nodeId。全扫描与下推快路径共用,保证返回形状一致。
357
+ */
358
+ buildIndexedStorePods(podPayloads, ownerRows, nodeAssignments) {
359
+ const ownerWebIdsByPodId = new Map();
360
+ for (const row of ownerRows) {
361
+ const payload = parsePayloadRecord(row.payload);
362
+ const podId = payload ? stringValue(payload.podId) : undefined;
363
+ const webId = payload ? stringValue(payload.webId) : undefined;
364
+ if (podId && webId) {
365
+ appendMapValue(ownerWebIdsByPodId, podId, webId);
366
+ }
240
367
  }
241
368
  const pods = [];
242
369
  for (const [podId, pod] of podPayloads) {
@@ -262,23 +389,10 @@ class PodLookupRepository {
262
389
  edgeNodeId: stringValue(pod.edgeNodeId),
263
390
  });
264
391
  }
265
- return { pods, podIds };
392
+ return pods;
266
393
  }
267
394
  }
268
395
  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
- }
282
396
  function extractAccountIdFromAccountDataKey(key) {
283
397
  const marker = 'accounts/data/';
284
398
  const index = key.indexOf(marker);
@@ -341,6 +455,22 @@ function webIdsProperty(values) {
341
455
  const webIds = dedupeStrings(values);
342
456
  return webIds.length > 1 ? { webIds } : {};
343
457
  }
458
+ function idIn(ids) {
459
+ return (0, drizzle_orm_1.sql) `id IN (${drizzle_orm_1.sql.join(ids.map((id) => (0, drizzle_orm_1.sql) `${id}`), (0, drizzle_orm_1.sql) `, `)})`;
460
+ }
461
+ function rowsToPodPayloadMap(rows) {
462
+ const map = new Map();
463
+ for (const row of rows) {
464
+ if (!row.id) {
465
+ continue;
466
+ }
467
+ const payload = parsePayloadRecord(row.payload);
468
+ if (payload) {
469
+ map.set(row.id, payload);
470
+ }
471
+ }
472
+ return map;
473
+ }
344
474
  function parsePayloadRecord(value) {
345
475
  if (!value) {
346
476
  return undefined;
@@ -414,4 +544,17 @@ function findNodeIdForPod(assignments, urls) {
414
544
  }
415
545
  return bestMatch?.nodeId;
416
546
  }
547
+ function isMissingIdentityStore(error) {
548
+ const visited = new Set();
549
+ while (error && typeof error === 'object' && !visited.has(error)) {
550
+ visited.add(error);
551
+ const failure = error;
552
+ if (failure.message === 'no such table: identity_store'
553
+ || (failure.code === '42P01' && failure.message === 'relation "identity_store" does not exist')) {
554
+ return true;
555
+ }
556
+ error = failure.cause;
557
+ }
558
+ return false;
559
+ }
417
560
  //# sourceMappingURL=PodLookupRepository.js.map
@@ -69,3 +69,22 @@ export declare function toDbTimestamp(db: IdentityDatabase, date: Date): number
69
69
  * Handles both Unix timestamps (SQLite) and Date objects (PostgreSQL).
70
70
  */
71
71
  export declare function fromDbTimestamp(value: unknown): Date | undefined;
72
+ /**
73
+ * `payload` JSON 字段提取表达式,按方言生成(SQLite json_extract / PG ->>)。
74
+ * alias 用于 join 场景下限定来源表。
75
+ */
76
+ export declare function jsonFieldExtract(db: IdentityDatabase, field: string, alias?: string): SQL;
77
+ /**
78
+ * `payload` 字段等于标量值的方言条件。
79
+ */
80
+ export declare function jsonFieldEquals(db: IdentityDatabase, field: string, value: string, alias?: string): SQL;
81
+ /**
82
+ * `payload` 字段落在值集合内的方言条件。values 为空时调用方应跳过查询。
83
+ */
84
+ export declare function jsonFieldIn(db: IdentityDatabase, field: string, values: string[], alias?: string): SQL;
85
+ /**
86
+ * 反向前缀匹配:`value LIKE payload->field || '%'`。
87
+ * 用于"资源 URL 以存储的 baseUrl 为前缀"的下推预筛;存储值中的 LIKE 元字符只会多匹配,
88
+ * 调用方需在 JS 侧用 startsWith 复核,不会漏匹配。
89
+ */
90
+ export declare function jsonFieldIsPrefixOf(db: IdentityDatabase, field: string, value: string, alias?: string): SQL;
@@ -36,6 +36,10 @@ exports.executePostgresLockedStatements = executePostgresLockedStatements;
36
36
  exports.ensureCloudClusterTables = ensureCloudClusterTables;
37
37
  exports.toDbTimestamp = toDbTimestamp;
38
38
  exports.fromDbTimestamp = fromDbTimestamp;
39
+ exports.jsonFieldExtract = jsonFieldExtract;
40
+ exports.jsonFieldEquals = jsonFieldEquals;
41
+ exports.jsonFieldIn = jsonFieldIn;
42
+ exports.jsonFieldIsPrefixOf = jsonFieldIsPrefixOf;
39
43
  const pg_1 = require("pg");
40
44
  const node_postgres_1 = require("drizzle-orm/node-postgres");
41
45
  const drizzle_orm_1 = require("drizzle-orm");
@@ -425,6 +429,41 @@ function fromDbTimestamp(value) {
425
429
  }
426
430
  return undefined;
427
431
  }
432
+ function jsonFieldPath(field) {
433
+ return `$."${field.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
434
+ }
435
+ /**
436
+ * `payload` JSON 字段提取表达式,按方言生成(SQLite json_extract / PG ->>)。
437
+ * alias 用于 join 场景下限定来源表。
438
+ */
439
+ function jsonFieldExtract(db, field, alias) {
440
+ const target = alias ? (0, drizzle_orm_1.sql) `${drizzle_orm_1.sql.identifier(alias)}.payload` : (0, drizzle_orm_1.sql) `payload`;
441
+ if (isDatabaseSqlite(db)) {
442
+ return (0, drizzle_orm_1.sql) `json_extract(${target}, ${jsonFieldPath(field)})`;
443
+ }
444
+ return (0, drizzle_orm_1.sql) `${target} ->> ${field}`;
445
+ }
446
+ /**
447
+ * `payload` 字段等于标量值的方言条件。
448
+ */
449
+ function jsonFieldEquals(db, field, value, alias) {
450
+ return (0, drizzle_orm_1.sql) `${jsonFieldExtract(db, field, alias)} = ${value}`;
451
+ }
452
+ /**
453
+ * `payload` 字段落在值集合内的方言条件。values 为空时调用方应跳过查询。
454
+ */
455
+ function jsonFieldIn(db, field, values, alias) {
456
+ const list = drizzle_orm_1.sql.join(values.map((value) => (0, drizzle_orm_1.sql) `${value}`), (0, drizzle_orm_1.sql) `, `);
457
+ return (0, drizzle_orm_1.sql) `${jsonFieldExtract(db, field, alias)} IN (${list})`;
458
+ }
459
+ /**
460
+ * 反向前缀匹配:`value LIKE payload->field || '%'`。
461
+ * 用于"资源 URL 以存储的 baseUrl 为前缀"的下推预筛;存储值中的 LIKE 元字符只会多匹配,
462
+ * 调用方需在 JS 侧用 startsWith 复核,不会漏匹配。
463
+ */
464
+ function jsonFieldIsPrefixOf(db, field, value, alias) {
465
+ return (0, drizzle_orm_1.sql) `${value} LIKE (${jsonFieldExtract(db, field, alias)}) || '%'`;
466
+ }
428
467
  /**
429
468
  * Ensure SQLite tables exist (simple DDL for local/dev mode).
430
469
  */
@@ -520,9 +559,5 @@ async function ensurePostgresTables(pool) {
520
559
  );
521
560
 
522
561
  `);
523
- await migratePostgresColumns(pool);
524
- }
525
- async function migratePostgresColumns(pool) {
526
- void pool;
527
562
  }
528
563
  //# sourceMappingURL=db.js.map
@@ -17,7 +17,6 @@ export { LocalReachabilitySummary, } from './login/LocalReachabilitySummary';
17
17
  export { getProviderActionLabel, getProviderDisplayLabel, getProviderInfoText, getProviderSourceLabel, getProviderStatusBadge, getProviderSubtitle, type ProviderStatusBadge, } from './login/presentation';
18
18
  export { isLocalLoginProvider, isLocalLoginProviderSource, resolveLoginProviderSource, } from './login/provider-model';
19
19
  export type { AuthWindowStatus, ConnectingProviderInfo, LocalLoginProviderSource, LocalLoginStatus, LocalOnboardingCapabilities, LocalOnboardingConnectivity, LocalOnboardingProgress, LocalOnboardingRouteKind, LocalOnboardingRouteProbe, LocalOnboardingSnapshot, LocalOnboardingState, LocalOnboardingTunnel, LocalPodRuntime, LocalSpaceKind, LoginEndpoint, LoginEndpointKind, LoginModalProps, LoginProviderOption, LoginProviderSource, LoginState, ProviderOption, StorageConflict, StoredAccount, } from './login/types';
20
- export * from './oidc-consent';
21
20
  export * from './scroll-area';
22
21
  export * from './separator';
23
22
  export * from './skeleton';
@@ -16,7 +16,6 @@ export { LoginView, } from './login/LoginView.js';
16
16
  export { LocalReachabilitySummary, } from './login/LocalReachabilitySummary.js';
17
17
  export { getProviderActionLabel, getProviderDisplayLabel, getProviderInfoText, getProviderSourceLabel, getProviderStatusBadge, getProviderSubtitle, } from './login/presentation.js';
18
18
  export { isLocalLoginProvider, isLocalLoginProviderSource, resolveLoginProviderSource, } from './login/provider-model.js';
19
- export * from './oidc-consent.js';
20
19
  export * from './scroll-area.js';
21
20
  export * from './separator.js';
22
21
  export * from './skeleton.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@undefineds.co/xpod",
3
- "version": "0.4.10",
3
+ "version": "0.4.11",
4
4
  "description": "Xpod is an extended Community Solid Server, offering rich-feature, production-level Solid Pod and identity management.",
5
5
  "repository": "https://github.com/undefinedsco/xpod",
6
6
  "author": "developer@undefineds.co",
@@ -376,7 +376,7 @@
376
376
  }
377
377
  },
378
378
  "optionalDependencies": {
379
- "@undefineds.co/xpod-darwin-arm64": "0.4.10"
379
+ "@undefineds.co/xpod-darwin-arm64": "0.4.11"
380
380
  },
381
381
  "resolutions": {
382
382
  "@inrupt/solid-client-authn-browser": "^3.1.1",