@undefineds.co/xpod 0.4.9 → 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 (130) 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 +55 -12
  8. package/dist/identity/drizzle/PodLookupRepository.js +225 -243
  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/ai-connections/dist/AiConnectionsMain.js +1 -0
  12. package/node_modules/@undefineds.co/ai-connections/dist/controller.d.ts +1 -0
  13. package/node_modules/@undefineds.co/ai-connections/dist/controller.js +94 -16
  14. package/node_modules/@undefineds.co/shared-ui/dist/index.d.ts +0 -1
  15. package/node_modules/@undefineds.co/shared-ui/dist/index.js +0 -1
  16. package/node_modules/@undefineds.co/solid-sdk/dist/index.cjs +112 -11
  17. package/node_modules/@undefineds.co/solid-sdk/dist/session.d.ts +2 -0
  18. package/node_modules/@undefineds.co/solid-sdk/dist/session.js +135 -11
  19. package/package.json +2 -2
  20. package/static/app/assets/main.css +1 -1
  21. package/static/app/assets/main.js +12 -13
  22. package/static/auth-callback/assets/{AiConfigContext-DJKvxduw.js → AiConfigContext-DqWRSm7n.js} +1 -1
  23. package/static/auth-callback/assets/{AiConfigPage-DgGmHb18.js → AiConfigPage-C5YYyvzl.js} +1 -1
  24. package/static/auth-callback/assets/{DocumentProcessingPanel-CJmxu8x1.js → DocumentProcessingPanel-DZCiacBz.js} +1 -1
  25. package/static/auth-callback/assets/{IndexLifecyclePanel-byCa7h7L.js → IndexLifecyclePanel-CCLeOljB.js} +1 -1
  26. package/static/auth-callback/assets/{IndexSubjectPanel-CDwT4Yx1.js → IndexSubjectPanel-BbUqop4b.js} +1 -1
  27. package/static/auth-callback/assets/{ModelAssignmentsPanel-CwM3k8w7.js → ModelAssignmentsPanel-jUUTDKuh.js} +1 -1
  28. package/static/auth-callback/assets/ModelsPage-B08zdtff.js +10 -0
  29. package/static/auth-callback/assets/{NetworkPage-DkVgxtNj.js → NetworkPage-Vp2PrMxs.js} +1 -1
  30. package/static/auth-callback/assets/{PaneListHeader-DRI0hjmt.js → PaneListHeader-DY7baSFl.js} +1 -1
  31. package/static/auth-callback/assets/{SearchIndexingPanel-mbppDZFE.js → SearchIndexingPanel-D4YRg1-8.js} +1 -1
  32. package/static/auth-callback/assets/{StatusSubjectPanel-DNsW76aC.js → StatusSubjectPanel-ArIS-TxT.js} +1 -1
  33. package/static/auth-callback/assets/{StatusWorkspace-DGcTbHT5.js → StatusWorkspace-Cp5LQ9Yx.js} +1 -1
  34. package/static/auth-callback/assets/SystemSettingsPage-BDvT1NUz.js +1 -0
  35. package/static/auth-callback/assets/SystemSettingsSubjectPanel-CDqRuC4t.js +33 -0
  36. package/static/auth-callback/assets/{activity-Ck-Mfxxo.js → activity-D1Dsfa_0.js} +1 -1
  37. package/static/auth-callback/assets/admin-C6qCRFiY.js +1 -0
  38. package/static/auth-callback/assets/{auth-callback-DG4Ge40c.js → auth-callback-Cmf4hXVZ.js} +105 -109
  39. package/static/auth-callback/assets/auth-callback-DWMMiFjh.css +1 -0
  40. package/static/auth-callback/assets/{badge-GSSnw1VV.js → badge-CZsHq3Ih.js} +1 -1
  41. package/static/auth-callback/assets/{download-BYyRX-K2.js → download-DU9ntlHw.js} +1 -1
  42. package/static/auth-callback/assets/{external-link-WEC0Ki-0.js → external-link-BfzaaZDx.js} +1 -1
  43. package/static/auth-callback/assets/{index-kju2k4Dp.js → index-DSNnLUbU.js} +4 -4
  44. package/static/auth-callback/assets/{index-DlsMCgo8.js → index-FHhHoKgp.js} +1 -1
  45. package/static/auth-callback/assets/{index-browser-D8YW6coF.js → index-browser-BNFqqC74.js} +1 -1
  46. package/static/auth-callback/assets/{key-round-DJUT5Nh0.js → key-round-BwP3DBWo.js} +1 -1
  47. package/static/auth-callback/assets/{rotate-ccw-CXwfZUKU.js → rotate-ccw-VMwnmyab.js} +1 -1
  48. package/static/auth-callback/assets/settings-projection-CUkyprDQ.js +1 -0
  49. package/static/auth-callback/assets/{skeleton-DWPtNhUB.js → skeleton-CoD55rBT.js} +1 -1
  50. package/static/auth-callback/assets/{waypoints-l1oDnRJ0.js → waypoints-DRo_1Ca7.js} +1 -1
  51. package/static/auth-callback/assets/{workspace-layout-B7iKpK-Q.js → workspace-layout--mcXl3My.js} +1 -1
  52. package/static/auth-callback/auth-callback.html +2 -2
  53. package/static/dashboard/assets/{AiConfigContext-U0cCSY28.js → AiConfigContext-BpPDKSzI.js} +1 -1
  54. package/static/dashboard/assets/{AiConfigPage-CMi0uJdo.js → AiConfigPage-wsqEZDIX.js} +1 -1
  55. package/static/dashboard/assets/{DocumentProcessingPanel-DEZiEpft.js → DocumentProcessingPanel-CDSy3juc.js} +1 -1
  56. package/static/dashboard/assets/{IndexLifecyclePanel-CGZfLUog.js → IndexLifecyclePanel-CVd6vQCX.js} +1 -1
  57. package/static/dashboard/assets/{IndexSubjectPanel-DiDZauHk.js → IndexSubjectPanel-B2cXwl8X.js} +1 -1
  58. package/static/dashboard/assets/{ModelAssignmentsPanel-DaGGZKuf.js → ModelAssignmentsPanel-BeBZhnQd.js} +1 -1
  59. package/static/dashboard/assets/ModelsPage-B6McSCJw.js +25 -0
  60. package/static/dashboard/assets/{NetworkPage-DI5-O7GT.js → NetworkPage-DV2V0qWO.js} +1 -1
  61. package/static/dashboard/assets/{PaneListHeader-4oa7uf59.js → PaneListHeader-D0c1yCFH.js} +1 -1
  62. package/static/dashboard/assets/{SearchIndexingPanel-DoqEwocD.js → SearchIndexingPanel-CGs0OZ12.js} +1 -1
  63. package/static/dashboard/assets/{StatusSubjectPanel-CLzBC5fu.js → StatusSubjectPanel-oPSECj7i.js} +1 -1
  64. package/static/dashboard/assets/{StatusWorkspace-UyKADZoM.js → StatusWorkspace-PdovQPKa.js} +1 -1
  65. package/static/dashboard/assets/SystemSettingsPage-CtNDklkx.js +1 -0
  66. package/static/dashboard/assets/SystemSettingsSubjectPanel-WaE8mTQi.js +33 -0
  67. package/static/dashboard/assets/{activity-CjIoNU4S.js → activity-B9ylSoyr.js} +1 -1
  68. package/static/dashboard/assets/admin-C6qCRFiY.js +1 -0
  69. package/static/dashboard/assets/{badge-dcDZpKh3.js → badge-BNbFbpIb.js} +1 -1
  70. package/static/dashboard/assets/{dashboard-BY8GdnEI.js → dashboard-CD-FFVRD.js} +129 -133
  71. package/static/dashboard/assets/dashboard-DWMMiFjh.css +1 -0
  72. package/static/dashboard/assets/{download-BeYCrc-_.js → download-nOP38LtW.js} +1 -1
  73. package/static/dashboard/assets/{external-link-626-5KVx.js → external-link-Bh2ewN9E.js} +1 -1
  74. package/static/dashboard/assets/{index-CBbk6hx-.js → index-PgS-Bt7X.js} +1 -1
  75. package/static/dashboard/assets/{index-CsxH6Hmz.js → index-QN2QgEwr.js} +4 -4
  76. package/static/dashboard/assets/{key-round-B9IL4Wua.js → key-round-bGmguBLv.js} +1 -1
  77. package/static/dashboard/assets/{rotate-ccw-DM4vCfiI.js → rotate-ccw-D2x__ODW.js} +1 -1
  78. package/static/dashboard/assets/settings-projection-CUkyprDQ.js +1 -0
  79. package/static/dashboard/assets/{skeleton-ChlFAPvP.js → skeleton-VOqft8WS.js} +1 -1
  80. package/static/dashboard/assets/{waypoints-CvpZzf_I.js → waypoints-fgRqm4oH.js} +1 -1
  81. package/static/dashboard/assets/{workspace-layout-Bxv7V-tt.js → workspace-layout-DV4dgeHX.js} +1 -1
  82. package/static/dashboard/dashboard.html +2 -2
  83. package/static/settings/assets/{AiConfigContext-CDVIHLBb.js → AiConfigContext-zKQYdfai.js} +1 -1
  84. package/static/settings/assets/{AiConfigPage-C7qfiiaJ.js → AiConfigPage-Bs1EE3Ei.js} +1 -1
  85. package/static/settings/assets/{DocumentProcessingPanel-B59QTe-V.js → DocumentProcessingPanel-CSFfdOXS.js} +1 -1
  86. package/static/settings/assets/{IndexLifecyclePanel-C-e-3eVi.js → IndexLifecyclePanel-BR9Su49B.js} +1 -1
  87. package/static/settings/assets/{IndexSubjectPanel-B-scRZ1u.js → IndexSubjectPanel-BgbI9c-a.js} +1 -1
  88. package/static/settings/assets/{ModelAssignmentsPanel-Wy8jzVg4.js → ModelAssignmentsPanel-C0BE7nbk.js} +1 -1
  89. package/static/settings/assets/ModelsPage-CU-YQ8_7.js +10 -0
  90. package/static/settings/assets/{NetworkPage-tBB-4Ox8.js → NetworkPage-C8OA9Mtp.js} +1 -1
  91. package/static/settings/assets/{PaneListHeader-DuD6glzn.js → PaneListHeader-COvlPJWj.js} +1 -1
  92. package/static/settings/assets/{SearchIndexingPanel-CiDHc0nx.js → SearchIndexingPanel-Bdd46mIV.js} +1 -1
  93. package/static/settings/assets/{StatusSubjectPanel-OF5cgWwt.js → StatusSubjectPanel-BuLml7qn.js} +1 -1
  94. package/static/settings/assets/{StatusWorkspace-CZ8Oa0gV.js → StatusWorkspace-CoMFRXoV.js} +1 -1
  95. package/static/settings/assets/SystemSettingsPage-DlzmaWV-.js +1 -0
  96. package/static/settings/assets/SystemSettingsSubjectPanel-DExjDo_v.js +33 -0
  97. package/static/settings/assets/{activity-2M1f5eBS.js → activity-TLerXgMs.js} +1 -1
  98. package/static/settings/assets/admin-C6qCRFiY.js +1 -0
  99. package/static/settings/assets/{badge-CRKan0qX.js → badge-Bcs8iWrH.js} +1 -1
  100. package/static/settings/assets/{download-PCHldCdu.js → download-Ct3QmHPt.js} +1 -1
  101. package/static/settings/assets/{external-link-tmKe7eYO.js → external-link-C8KiHbss.js} +1 -1
  102. package/static/settings/assets/{index-Cek02M79.js → index-1Q2cRjZd.js} +1 -1
  103. package/static/settings/assets/{index-BDCNTwWz.js → index-DBO_Vq0P.js} +4 -4
  104. package/static/settings/assets/{index-browser-EVDVm9T5.js → index-browser-CazX1pc2.js} +1 -1
  105. package/static/settings/assets/{key-round-CUGvQCkt.js → key-round-B19YD1GA.js} +1 -1
  106. package/static/settings/assets/{rotate-ccw-BRA3lIHM.js → rotate-ccw-AQm2kMgU.js} +1 -1
  107. package/static/settings/assets/{settings-BdlKrzJ3.js → settings-C-VBtt9r.js} +129 -133
  108. package/static/settings/assets/settings-DWMMiFjh.css +1 -0
  109. package/static/settings/assets/settings-projection-CUkyprDQ.js +1 -0
  110. package/static/settings/assets/{skeleton-CuDeifWb.js → skeleton-DyZ1OBYf.js} +1 -1
  111. package/static/settings/assets/{waypoints-w6LxsFY1.js → waypoints-CC3wq-SU.js} +1 -1
  112. package/static/settings/assets/{workspace-layout-TN-BWmT5.js → workspace-layout-BxXNKgf9.js} +1 -1
  113. package/static/settings/settings.html +2 -2
  114. package/node_modules/@undefineds.co/shared-ui/dist/oidc-consent.d.ts +0 -54
  115. package/node_modules/@undefineds.co/shared-ui/dist/oidc-consent.js +0 -21
  116. package/static/auth-callback/assets/ModelsPage-DapY2Dwf.js +0 -10
  117. package/static/auth-callback/assets/SystemSettingsPage-C6qklJ9k.js +0 -1
  118. package/static/auth-callback/assets/SystemSettingsSubjectPanel-B4VV9xkG.js +0 -31
  119. package/static/auth-callback/assets/admin-BU4m6Ko2.js +0 -1
  120. package/static/auth-callback/assets/auth-callback-C_7MUXJb.css +0 -1
  121. package/static/dashboard/assets/ModelsPage-DZgAOdxO.js +0 -25
  122. package/static/dashboard/assets/SystemSettingsPage-qP0hLHNV.js +0 -1
  123. package/static/dashboard/assets/SystemSettingsSubjectPanel-EaWax9Ot.js +0 -31
  124. package/static/dashboard/assets/admin-BU4m6Ko2.js +0 -1
  125. package/static/dashboard/assets/dashboard-C_7MUXJb.css +0 -1
  126. package/static/settings/assets/ModelsPage-DHNiaodk.js +0 -10
  127. package/static/settings/assets/SystemSettingsPage-DT2tC0F_.js +0 -1
  128. package/static/settings/assets/SystemSettingsSubjectPanel-Bkta1oDw.js +0 -31
  129. package/static/settings/assets/admin-BU4m6Ko2.js +0 -1
  130. 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,15 +43,19 @@ 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 a linked WebID URL.
56
+ * Find Pod by an explicit owner or pod.webId URL.
43
57
  *
44
- * CSS account data stores WebID links separately from Pod base URLs. This is
58
+ * Pod ownership stores WebIDs separately from Pod base URLs. This is
45
59
  * the precise lookup for IdP/SP split deployments where the WebID path does
46
60
  * not have to match the storage base URL.
47
61
  */
@@ -49,82 +63,39 @@ class PodLookupRepository {
49
63
  return (await this.findAllByWebId(webId))[0];
50
64
  }
51
65
  /**
52
- * Find all Pods linked to a WebID.
66
+ * Find all Pods explicitly owned by a WebID.
53
67
  *
54
68
  * A Cloud WebID can legitimately back both a Cloud Pod and a Local SP Pod.
55
69
  * Callers that are scoped to a storage provider must inspect all candidates
56
70
  * instead of accepting the first account record returned by CSS storage.
57
71
  */
58
72
  async findAllByWebId(webId) {
59
- const exact = exactWebId(webId);
60
- if (!exact) {
61
- return [];
62
- }
63
- const results = [];
64
- const pods = await this.getAllPods();
65
- for (const pod of pods) {
66
- const matchedWebId = getPodWebIds(pod).find((candidate) => exactWebId(candidate) === exact);
67
- if (matchedWebId) {
68
- results.push({
69
- ...pod,
70
- webId: matchedWebId,
71
- });
72
- }
73
- }
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 this.findByWebIds([webId]);
84
74
  }
85
75
  /**
86
- * Find Pods by linked WebID URLs in one scan.
76
+ * Find Pods by explicit WebID bindings with batched canonical queries.
87
77
  */
88
78
  async findByWebIds(webIds) {
89
79
  const exactTargets = new Set(webIds.map(exactWebId).filter((value) => Boolean(value)));
90
80
  if (exactTargets.size === 0) {
91
81
  return [];
92
82
  }
93
- const results = [];
94
- const pods = await this.getAllPods();
95
- for (const pod of pods) {
96
- const matchedWebId = getPodWebIds(pod).find((candidate) => {
97
- const exact = exactWebId(candidate);
98
- return exact ? exactTargets.has(exact) : false;
99
- });
100
- if (!matchedWebId) {
101
- continue;
102
- }
103
- results.push({
104
- ...pod,
105
- webId: matchedWebId,
106
- });
107
- }
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
- 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);
121
86
  }
122
87
  /**
123
88
  * List Pods for a specific account.
89
+ *
90
+ * 定向查询与全扫描等价:kv 按账户键直取,identity_store 按 accountId 下推,
91
+ * canonical 整条优先与 getAllPods 一致,KV 扫描补齐 .json 等历史键。
124
92
  */
125
93
  async listByAccountId(accountId) {
126
- const pods = await this.getAllPods();
127
- 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);
128
99
  }
129
100
  /**
130
101
  * List all pods.
@@ -138,8 +109,18 @@ class PodLookupRepository {
138
109
  async getAllPods() {
139
110
  const result = await this.getAccountRowsFromKv();
140
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) {
141
122
  const pods = [];
142
- for (const row of result) {
123
+ for (const row of rows) {
143
124
  if (!row.key || row.value === undefined) {
144
125
  continue;
145
126
  }
@@ -149,34 +130,13 @@ class PodLookupRepository {
149
130
  continue;
150
131
  }
151
132
  const data = unwrapStoredValue(typeof row.value === 'string' ? JSON.parse(row.value) : row.value);
152
- const podMap = data['**pod**'] || data.pod || {};
153
- const webIds = extractAccountWebIds(data);
154
- for (const [podId, podData] of Object.entries(podMap)) {
155
- const pod = podData;
156
- if (pod.baseUrl && typeof pod.baseUrl === 'string') {
157
- const storageUrl = stringValue(pod.storageUrl) ?? stringValue(pod.storage);
158
- const podWebIds = resolvePodWebIds(pod, webIds);
159
- pods.push({
160
- podId,
161
- accountId,
162
- baseUrl: pod.baseUrl,
163
- storageUrl,
164
- webId: dedupeStrings(podWebIds)[0],
165
- ...webIdsProperty(podWebIds),
166
- nodeId: typeof pod.nodeId === 'string' ? pod.nodeId : findNodeIdForPod(nodeAssignments, [storageUrl, pod.baseUrl]),
167
- edgeNodeId: typeof pod.edgeNodeId === 'string' ? pod.edgeNodeId : undefined,
168
- });
169
- }
170
- }
133
+ pods.push(...this.extractPodsFromAccountData(accountId, data, nodeAssignments));
171
134
  }
172
135
  catch {
173
136
  // Skip malformed entries.
174
137
  }
175
138
  }
176
- return mergePodLookupResults([
177
- ...pods,
178
- ...await this.getPodsFromIndexedStore(nodeAssignments),
179
- ]);
139
+ return pods;
180
140
  }
181
141
  async getAccountRowsFromKv() {
182
142
  const kvTableId = drizzle_orm_1.sql.identifier(this.kvTableName);
@@ -213,76 +173,29 @@ class PodLookupRepository {
213
173
  return [];
214
174
  }
215
175
  }
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
- }
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()];
276
190
  }
277
- extractPodsFromAccountData(accountId, data) {
191
+ extractPodsFromAccountData(accountId, data, nodeAssignments = []) {
278
192
  const podMap = data['**pod**'] || data.pod || {};
279
- const webIds = extractAccountWebIds(data);
280
193
  const pods = [];
281
194
  for (const [podId, podData] of Object.entries(podMap)) {
282
195
  const pod = podData;
283
196
  if (pod.baseUrl && typeof pod.baseUrl === 'string') {
284
197
  const storageUrl = stringValue(pod.storageUrl) ?? stringValue(pod.storage);
285
- const podWebIds = resolvePodWebIds(pod, webIds);
198
+ const podWebIds = dedupeStrings(explicitPodWebIds(pod));
286
199
  pods.push({
287
200
  podId,
288
201
  accountId,
@@ -290,58 +203,166 @@ class PodLookupRepository {
290
203
  storageUrl,
291
204
  webId: dedupeStrings(podWebIds)[0],
292
205
  ...webIdsProperty(podWebIds),
293
- nodeId: typeof pod.nodeId === 'string' ? pod.nodeId : undefined,
206
+ nodeId: typeof pod.nodeId === 'string' ? pod.nodeId : findNodeIdForPod(nodeAssignments, [storageUrl, pod.baseUrl]),
294
207
  edgeNodeId: typeof pod.edgeNodeId === 'string' ? pod.edgeNodeId : undefined,
295
208
  });
296
209
  }
297
210
  }
298
211
  return pods;
299
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
+ }
300
233
  /**
301
234
  * DrizzleIndexedStorage stores CSS identity facts as typed rows in
302
235
  * identity_store; this is the canonical clustered identity source.
303
236
  */
304
237
  async getPodsFromIndexedStore(nodeAssignments = []) {
305
238
  const storeTableId = drizzle_orm_1.sql.identifier(this.indexedStoreTableName);
306
- let result;
239
+ let rows = [];
307
240
  try {
308
- 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) `
309
242
  SELECT container, id, payload FROM ${storeTableId}
310
- WHERE container IN ('pod', 'owner', 'webIdLink')
243
+ WHERE container IN ('pod', 'owner')
311
244
  `);
245
+ rows = result.rows;
312
246
  }
313
- catch {
314
- return [];
247
+ catch (error) {
248
+ if (isMissingIdentityStore(error))
249
+ return [];
250
+ throw error;
315
251
  }
316
252
  const podPayloads = new Map();
317
- const ownerWebIdsByPodId = new Map();
318
- const webIdsByAccountId = new Map();
319
- for (const row of result?.rows ?? []) {
253
+ const ownerRows = [];
254
+ for (const row of rows) {
320
255
  if (!row.id || !row.container) {
321
256
  continue;
322
257
  }
323
- const payload = parsePayloadRecord(row.payload);
324
- if (!payload) {
325
- continue;
326
- }
327
258
  if (row.container === 'pod') {
328
- podPayloads.set(row.id, payload);
329
- continue;
330
- }
331
- if (row.container === 'owner') {
332
- const podId = stringValue(payload.podId);
333
- const webId = stringValue(payload.webId);
334
- if (podId && webId) {
335
- appendMapValue(ownerWebIdsByPodId, podId, webId);
259
+ const payload = parsePayloadRecord(row.payload);
260
+ if (payload) {
261
+ podPayloads.set(row.id, payload);
336
262
  }
337
- continue;
338
263
  }
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
- }
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);
345
366
  }
346
367
  }
347
368
  const pods = [];
@@ -355,9 +376,7 @@ class PodLookupRepository {
355
376
  stringValue(pod.webId),
356
377
  ...(ownerWebIdsByPodId.get(podId) ?? []),
357
378
  ].filter((value) => typeof value === 'string');
358
- const podWebIds = dedupeStrings(explicitPodWebIds.length > 0
359
- ? explicitPodWebIds
360
- : webIdsByAccountId.get(accountId) ?? []);
379
+ const podWebIds = dedupeStrings(explicitPodWebIds);
361
380
  const storageUrl = stringValue(pod.storageUrl) ?? stringValue(pod.storage);
362
381
  pods.push({
363
382
  podId,
@@ -383,25 +402,6 @@ function extractAccountIdFromAccountDataKey(key) {
383
402
  const accountId = key.slice(index + marker.length).replace(/\.json$/u, '');
384
403
  return accountId || undefined;
385
404
  }
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
405
  function extractPodOwnerWebIds(pod) {
406
406
  const ownerMap = pod['**owner**'] || pod.owner || {};
407
407
  if (!ownerMap || typeof ownerMap !== 'object') {
@@ -423,17 +423,6 @@ function explicitPodWebIds(pod) {
423
423
  ...extractPodOwnerWebIds(pod),
424
424
  ].filter((value) => typeof value === 'string');
425
425
  }
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
426
  function exactWebId(webId) {
438
427
  // WebID identity is the complete original string, not URL equivalence.
439
428
  return webId && webId === webId.trim() && !/[\r\n\t]/u.test(webId) ? webId : undefined;
@@ -466,30 +455,21 @@ function webIdsProperty(values) {
466
455
  const webIds = dedupeStrings(values);
467
456
  return webIds.length > 1 ? { webIds } : {};
468
457
  }
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);
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) {
475
465
  continue;
476
466
  }
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()];
467
+ const payload = parsePayloadRecord(row.payload);
468
+ if (payload) {
469
+ map.set(row.id, payload);
470
+ }
471
+ }
472
+ return map;
493
473
  }
494
474
  function parsePayloadRecord(value) {
495
475
  if (!value) {
@@ -508,17 +488,6 @@ function parsePayloadRecord(value) {
508
488
  const unwrapped = unwrapStoredValue(value);
509
489
  return typeof unwrapped === 'object' ? unwrapped : undefined;
510
490
  }
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
491
  function unwrapStoredValue(value) {
523
492
  if (value && typeof value === 'object' && 'key' in value && 'payload' in value) {
524
493
  return value.payload;
@@ -575,4 +544,17 @@ function findNodeIdForPod(assignments, urls) {
575
544
  }
576
545
  return bestMatch?.nodeId;
577
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
+ }
578
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;