@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-teams",
3
- "version": "0.2.30",
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(query.cursor)statements.push({kind:'one',sql:'SELECT * FROM cursors WHERE id=? AND team_id=? AND principal_id=?',params:[cursorHash(query.cursor),auth.team_id,auth.principal_id]});
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
- const values=await api.store.snapshot(statements),offset=query.cursor?1:0,cursor=query.cursor?values[0]:null,team=values[offset],credential=values[offset+1],membership=values[offset+2],rows=values[offset+3],allGrants=values[offset+4],allMessages=values[offset+5];
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=rows.find(candidate=>candidate.id===id&&candidate.revision===candidate.version_revision);
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;
@@ -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
- const rows=pageOrdinals.length?all(`SELECT * FROM messages WHERE team_id=? AND session_id=? AND revision=? AND ordinal IN (${pageOrdinals.map(()=>'?').join(',')}) ORDER BY ordinal`,[teamId,session.id,revision,...pageOrdinals]):[];
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;