@volter/supercode-teams 0.2.30 → 0.2.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/server/harness-routes.mjs +17 -6
- package/server/store-core.mjs +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.32",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A machine daemon serving this machine's doors, and a local or remote Teams server for shared session discovery, OpenTelemetry ingestion and access to enrolled machines.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -117,17 +117,28 @@ async function listSessions(api,res,auth,url,requestId,search){
|
|
|
117
117
|
const query=qobj(url);
|
|
118
118
|
search?validateSearchQuery(query):validateListQuery(query);
|
|
119
119
|
const normalizedQuery=Object.fromEntries(Object.entries(query).filter(([key])=>key!=='cursor'));
|
|
120
|
+
// Every sync writes a session a new version (and its messages again), so a page reads only the versions it can
|
|
121
|
+
// show: each session's current one, and those a cursor's snapshot names; messages only for a search. Reading every
|
|
122
|
+
// version of every session made the list outgrow the Worker's memory.
|
|
123
|
+
const cursorKey=query.cursor?[cursorHash(query.cursor),auth.team_id,auth.principal_id]:null;
|
|
124
|
+
const cursorVersions="json_each(COALESCE((SELECT snapshot_json FROM cursors WHERE id=? AND team_id=? AND principal_id=? AND kind IN ('list','search')),'[]')) j";
|
|
120
125
|
const statements=[];
|
|
121
|
-
if(
|
|
126
|
+
if(cursorKey)statements.push({kind:'one',sql:'SELECT * FROM cursors WHERE id=? AND team_id=? AND principal_id=?',params:cursorKey});
|
|
122
127
|
statements.push(
|
|
123
128
|
{kind:'one',sql:'SELECT auth_epoch,journal_revision FROM teams WHERE id=?',params:[auth.team_id]},
|
|
124
129
|
{kind:'one',sql:'SELECT revoked_at,expires_at FROM credentials WHERE id=?',params:[auth.credential_id]},
|
|
125
130
|
{kind:'one',sql:'SELECT role,active FROM memberships WHERE team_id=? AND principal_id=?',params:[auth.team_id,auth.principal_id]},
|
|
126
|
-
{kind:'query',sql:'SELECT s.*,v.revision version_revision,v.descriptor_json,v.sort_at FROM sessions s JOIN session_versions v ON v.team_id=s.team_id AND v.session_id=s.id WHERE s.team_id=? AND s.deleted_at IS NULL ORDER BY v.sort_at DESC,s.id ASC',params:[auth.team_id]},
|
|
127
|
-
{kind:'query',sql:'SELECT * FROM session_grants WHERE team_id=?',params:[auth.team_id]}
|
|
128
|
-
{kind:'query',sql:'SELECT id,session_id,revision,recorded_at,search_text FROM messages WHERE team_id=?',params:[auth.team_id]}
|
|
131
|
+
{kind:'query',sql:'SELECT s.*,v.revision version_revision,v.descriptor_json,v.sort_at FROM sessions s JOIN session_versions v ON v.team_id=s.team_id AND v.session_id=s.id AND v.revision=s.revision WHERE s.team_id=? AND s.deleted_at IS NULL ORDER BY v.sort_at DESC,s.id ASC',params:[auth.team_id]},
|
|
132
|
+
{kind:'query',sql:'SELECT * FROM session_grants WHERE team_id=?',params:[auth.team_id]}
|
|
129
133
|
);
|
|
130
|
-
|
|
134
|
+
if(cursorKey)statements.push({kind:'query',sql:`SELECT s.*,v.revision version_revision,v.descriptor_json,v.sort_at FROM ${cursorVersions} JOIN session_versions v ON v.team_id=? AND v.session_id=json_extract(j.value,'$[0]') AND v.revision=json_extract(j.value,'$[1]') JOIN sessions s ON s.team_id=v.team_id AND s.id=v.session_id WHERE s.deleted_at IS NULL AND v.revision<>s.revision`,params:[...cursorKey,auth.team_id]});
|
|
135
|
+
if(search){
|
|
136
|
+
statements.push({kind:'query',sql:'SELECT m.id,m.session_id,m.revision,m.recorded_at,m.search_text FROM sessions s JOIN messages m ON m.team_id=s.team_id AND m.session_id=s.id AND m.revision=s.revision WHERE s.team_id=? AND s.deleted_at IS NULL',params:[auth.team_id]});
|
|
137
|
+
if(cursorKey)statements.push({kind:'query',sql:`SELECT m.id,m.session_id,m.revision,m.recorded_at,m.search_text FROM ${cursorVersions} JOIN messages m ON m.team_id=? AND m.session_id=json_extract(j.value,'$[0]') AND m.revision=json_extract(j.value,'$[1]') JOIN sessions s ON s.team_id=m.team_id AND s.id=m.session_id WHERE s.deleted_at IS NULL AND m.revision<>s.revision`,params:[...cursorKey,auth.team_id]});
|
|
138
|
+
}
|
|
139
|
+
const values=await api.store.snapshot(statements),offset=cursorKey?1:0,cursor=cursorKey?values[0]:null,team=values[offset],credential=values[offset+1],membership=values[offset+2],currentRows=values[offset+3],allGrants=values[offset+4];
|
|
140
|
+
let at=offset+5;const olderRows=cursorKey?values[at++]:[],allMessages=search?[...values[at++],...(cursorKey?values[at++]:[])]:[];
|
|
141
|
+
const rows=[...currentRows,...olderRows],currentById=new Map(currentRows.map(row=>[row.id,row]));
|
|
131
142
|
const boundaryNow=api.clock.nowMs();
|
|
132
143
|
if(!membership?.active||credential?.revoked_at!=null||(credential?.expires_at!=null&&credential.expires_at<=boundaryNow))fail(401,'authentication_required','authentication required');
|
|
133
144
|
let tuples;
|
|
@@ -140,7 +151,7 @@ async function listSessions(api,res,auth,url,requestId,search){
|
|
|
140
151
|
for(const message of allMessages){const key=`${message.session_id}:${message.revision}`,list=messagesByVersion.get(key)??[];list.push(message);messagesByVersion.set(key,list);}
|
|
141
152
|
const hits=[],hitTuples=[];
|
|
142
153
|
for(const [id,revision]of tuples){
|
|
143
|
-
const row=rowByVersion.get(`${id}:${revision}`),current=
|
|
154
|
+
const row=rowByVersion.get(`${id}:${revision}`),current=currentById.get(id);
|
|
144
155
|
if(!row||!current||current.canonical_id||!await canReadMerged(api,{...auth,now:boundaryNow},current))continue;
|
|
145
156
|
const view=await readerProjection(api,auth,row);
|
|
146
157
|
if(query.user&&(query.user==='me'?auth.principal_id:query.user)!==view.user?.principal_id)continue;
|
package/server/store-core.mjs
CHANGED
|
@@ -84,7 +84,9 @@ export function createStoreHandlers({ all, get, run, tx }) {
|
|
|
84
84
|
ordinals=all('SELECT ordinal FROM messages WHERE team_id=? AND session_id=? AND revision=? ORDER BY ordinal',[teamId,session.id,revision]).map(row=>row.ordinal);
|
|
85
85
|
}
|
|
86
86
|
const pageOrdinals=ordinals.slice(-tail),remaining=ordinals.slice(0,-tail);
|
|
87
|
-
|
|
87
|
+
// A page is a run of one revision's ordinals, read as a range: a hosted SQLite binds at most 100 variables, and a
|
|
88
|
+
// page of more messages than that, as an IN list, failed ("too many SQL variables").
|
|
89
|
+
const rows=pageOrdinals.length?all('SELECT * FROM messages WHERE team_id=? AND session_id=? AND revision=? AND ordinal>=? AND ordinal<=? ORDER BY ordinal',[teamId,session.id,revision,pageOrdinals[0],pageOrdinals[pageOrdinals.length-1]]).filter(row=>pageOrdinals.includes(row.ordinal)):[];
|
|
88
90
|
const version=get('SELECT descriptor_json FROM session_versions WHERE team_id=? AND session_id=? AND revision=?',[teamId,session.id,revision]);
|
|
89
91
|
if(!version)domainError(404,'not_found','session revision not found');
|
|
90
92
|
let descriptorJson=version.descriptor_json;
|