@volter/supercode-orchestrator 0.4.147 → 0.4.149
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/board/ci.mjs +177 -108
- package/board/cli.mjs +58 -35
- package/board/dispatch.mjs +169 -43
- package/board/engine.mjs +130 -30
- package/board/notices.mjs +5 -0
- package/board/request-mail.mjs +59 -0
- package/board/store.mjs +6 -3
- package/board/tools.mjs +15 -9
- package/board/workflow.mjs +40 -32
- package/board/workspace.mjs +26 -0
- package/board/ztrack.mjs +781 -544
- package/hermes-locks.mjs +1 -20
- package/mcp/hermes-kanban-tools.json +76 -14
- package/package.json +2 -2
- package/board/rows.mjs +0 -132
package/hermes-locks.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import { spawn, spawnSync } from 'node:child_process';
|
|
|
13
13
|
import { createHash } from 'node:crypto';
|
|
14
14
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, unlinkSync, writeSync } from 'node:fs';
|
|
15
15
|
import { homedir } from 'node:os';
|
|
16
|
-
import {
|
|
16
|
+
import { join, resolve } from 'node:path';
|
|
17
17
|
|
|
18
18
|
/** Hermes's gateway kind, the `kind` of every record it writes. */
|
|
19
19
|
export const GATEWAY_KIND = 'hermes-gateway';
|
|
@@ -143,25 +143,6 @@ export function holdGatewayLock(home, { python = 'python3' } = {}) {
|
|
|
143
143
|
return holdFileLock(join(resolve(home), 'gateway.lock'), home, { python, what: `${resolve(home)} is served by`, tail: 'one runtime serves a home at a time' });
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
/**
|
|
147
|
-
* Hold the board's one dispatcher lock, `<root>/kanban/.dispatcher.lock` (Hermes's
|
|
148
|
-
* `_acquire_singleton_lock`, which every Hermes gateway takes before it dispatches). Resolves
|
|
149
|
-
* `{ release }`; rejects, naming the holder, when another dispatcher holds it.
|
|
150
|
-
*/
|
|
151
|
-
export function holdDispatcherLock(root, { python = 'python3' } = {}) {
|
|
152
|
-
const path = join(resolve(root), 'kanban', '.dispatcher.lock');
|
|
153
|
-
mkdirSync(join(resolve(root), 'kanban'), { recursive: true });
|
|
154
|
-
return holdFileLock(path, root, { python, what: `the board at ${resolve(root)} is dispatched by`, tail: 'one dispatcher serves a board' });
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Hold one board's per-tick lock, `<kanban.db>.dispatch.lock` (Hermes's `_dispatch_tick_lock`), for one tick: a
|
|
159
|
-
* one-shot `dispatch` never ticks a board beside another dispatcher. Rejects, naming the holder, when it is held.
|
|
160
|
-
*/
|
|
161
|
-
export function holdBoardTickLock(dbPath, { python = 'python3' } = {}) {
|
|
162
|
-
return holdFileLock(`${dbPath}.dispatch.lock`, dirname(dbPath), { python, what: `the board ${dbPath} is being ticked by`, tail: 'one tick at a time' });
|
|
163
|
-
}
|
|
164
|
-
|
|
165
146
|
function holdFileLock(path, home, { python, what, tail }) {
|
|
166
147
|
return new Promise((done, fail) => {
|
|
167
148
|
let child;
|
|
@@ -102,11 +102,19 @@
|
|
|
102
102
|
},
|
|
103
103
|
"commit": {
|
|
104
104
|
"type": "string",
|
|
105
|
-
"description": "Exact
|
|
105
|
+
"description": "Exact pushed branch head for a subtask, or merged deployment commit for an arc."
|
|
106
106
|
},
|
|
107
107
|
"ci_run": {
|
|
108
108
|
"type": "integer",
|
|
109
109
|
"description": "GitHub Actions deployment run id for that exact commit."
|
|
110
|
+
},
|
|
111
|
+
"branch": {
|
|
112
|
+
"type": "string",
|
|
113
|
+
"description": "Pushed worktree branch for source completion."
|
|
114
|
+
},
|
|
115
|
+
"reviewed_sha": {
|
|
116
|
+
"type": "string",
|
|
117
|
+
"description": "The full PR head SHA this reviewer ran, matching the recorded request."
|
|
110
118
|
}
|
|
111
119
|
},
|
|
112
120
|
"required": []
|
|
@@ -146,7 +154,7 @@
|
|
|
146
154
|
},
|
|
147
155
|
{
|
|
148
156
|
"name": "kanban_request_review",
|
|
149
|
-
"description": "
|
|
157
|
+
"description": "Request independent review of the opened PR at its exact pushed head. The dispatcher starts the declared reviewer when the arc tasks are closed. Deployment CI is not a review gate.",
|
|
150
158
|
"inputSchema": {
|
|
151
159
|
"type": "object",
|
|
152
160
|
"properties": {
|
|
@@ -173,11 +181,27 @@
|
|
|
173
181
|
},
|
|
174
182
|
"commit": {
|
|
175
183
|
"type": "string",
|
|
176
|
-
"description": "Exact
|
|
184
|
+
"description": "Exact pushed worktree branch head for independent review."
|
|
177
185
|
},
|
|
178
|
-
"
|
|
179
|
-
"type":
|
|
180
|
-
|
|
186
|
+
"pr": {
|
|
187
|
+
"type": [
|
|
188
|
+
"string",
|
|
189
|
+
"array"
|
|
190
|
+
],
|
|
191
|
+
"items": {
|
|
192
|
+
"type": "string"
|
|
193
|
+
},
|
|
194
|
+
"description": "Every opened PR the arc changed, one URL each (its own repository's and any other repository it changed)."
|
|
195
|
+
},
|
|
196
|
+
"pr_head": {
|
|
197
|
+
"type": [
|
|
198
|
+
"string",
|
|
199
|
+
"array"
|
|
200
|
+
],
|
|
201
|
+
"items": {
|
|
202
|
+
"type": "string"
|
|
203
|
+
},
|
|
204
|
+
"description": "The full 40-character pushed head SHA of each PR, in the same order as pr."
|
|
181
205
|
}
|
|
182
206
|
},
|
|
183
207
|
"required": [
|
|
@@ -345,10 +369,6 @@
|
|
|
345
369
|
"inputSchema": {
|
|
346
370
|
"type": "object",
|
|
347
371
|
"properties": {
|
|
348
|
-
"review_only": {
|
|
349
|
-
"type": "boolean",
|
|
350
|
-
"description": "Declare a standalone independent review job, where permitted by the home's manager policy. Its verdict is accepted without another review of the review; implementation arcs keep their independent review requirement."
|
|
351
|
-
},
|
|
352
372
|
"title": {
|
|
353
373
|
"type": "string",
|
|
354
374
|
"description": "Short task title (required)."
|
|
@@ -494,13 +514,21 @@
|
|
|
494
514
|
},
|
|
495
515
|
{
|
|
496
516
|
"name": "kanban_request_state",
|
|
497
|
-
"description": "Request a manager decision on block, pause, done or cancelled through the home workflow.
|
|
517
|
+
"description": "Request a manager decision on block, pause, done or cancelled for one or several task_ids through the home workflow. Nearby requests from the same session share one retained mail. Includes the home's rules and leaves card/run state unchanged.",
|
|
498
518
|
"inputSchema": {
|
|
499
519
|
"type": "object",
|
|
500
520
|
"properties": {
|
|
501
521
|
"task_id": {
|
|
502
522
|
"type": "string"
|
|
503
523
|
},
|
|
524
|
+
"task_ids": {
|
|
525
|
+
"type": "array",
|
|
526
|
+
"items": {
|
|
527
|
+
"type": "string"
|
|
528
|
+
},
|
|
529
|
+
"minItems": 1,
|
|
530
|
+
"description": "Cards whose state is requested together; each must be in your permitted scope."
|
|
531
|
+
},
|
|
504
532
|
"state": {
|
|
505
533
|
"type": "string",
|
|
506
534
|
"enum": [
|
|
@@ -524,15 +552,49 @@
|
|
|
524
552
|
},
|
|
525
553
|
"commit": {
|
|
526
554
|
"type": "string",
|
|
527
|
-
"description": "
|
|
555
|
+
"description": "Pushed branch head for a source verdict or subtask, or merged commit for final arc completion."
|
|
528
556
|
},
|
|
529
557
|
"ci_run": {
|
|
530
558
|
"type": "integer",
|
|
531
|
-
"description": "
|
|
559
|
+
"description": "Legacy receipt field; deployment is not a board completion requirement."
|
|
532
560
|
},
|
|
533
561
|
"deployed_sha": {
|
|
534
562
|
"type": "string",
|
|
535
|
-
"description": "
|
|
563
|
+
"description": "Legacy receipt field; deployment is not a board completion requirement."
|
|
564
|
+
},
|
|
565
|
+
"branch": {
|
|
566
|
+
"type": "string",
|
|
567
|
+
"description": "The pushed worktree branch the reviewer inspected."
|
|
568
|
+
},
|
|
569
|
+
"reviewed_sha": {
|
|
570
|
+
"type": [
|
|
571
|
+
"string",
|
|
572
|
+
"array"
|
|
573
|
+
],
|
|
574
|
+
"items": {
|
|
575
|
+
"type": "string"
|
|
576
|
+
},
|
|
577
|
+
"description": "Every requested PR head this independent reviewer inspected (full SHAs)."
|
|
578
|
+
},
|
|
579
|
+
"pr": {
|
|
580
|
+
"type": [
|
|
581
|
+
"string",
|
|
582
|
+
"array"
|
|
583
|
+
],
|
|
584
|
+
"items": {
|
|
585
|
+
"type": "string"
|
|
586
|
+
},
|
|
587
|
+
"description": "PR URLs, optional when using the recorded review candidate."
|
|
588
|
+
},
|
|
589
|
+
"pr_head": {
|
|
590
|
+
"type": [
|
|
591
|
+
"string",
|
|
592
|
+
"array"
|
|
593
|
+
],
|
|
594
|
+
"items": {
|
|
595
|
+
"type": "string"
|
|
596
|
+
},
|
|
597
|
+
"description": "Full PR head SHAs; reviewers can use reviewed_sha instead."
|
|
536
598
|
}
|
|
537
599
|
},
|
|
538
600
|
"required": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.149",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The orchestrator runtime over the Volter Harness ontology: one typed operational model whose folder is its serialization, read and written through the harness orchestration doors (docs/ORCHESTRATOR-IR.md)",
|
|
6
6
|
"exports": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@volter/supercode-harness-sdk": "^0.3.41",
|
|
28
|
-
"@volter/ztrack": "^2.4.
|
|
28
|
+
"@volter/ztrack": "^2.4.21",
|
|
29
29
|
"yaml": "^2.9.0",
|
|
30
30
|
"@volter/supercode-teams": "^0.2.145"
|
|
31
31
|
},
|
package/board/rows.mjs
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
// Normalized runtime rows. Card text remains in the document; this stores only runtime
|
|
2
|
-
// tables, run fields and small indexing metadata. No application locks or snapshot rewrites.
|
|
3
|
-
import { json } from './store.mjs';
|
|
4
|
-
const quote = s => `"${s.replaceAll('"', '""')}"`;
|
|
5
|
-
const RUNTIME = new Set(['claim_lock','claim_expires','worker_pid','last_heartbeat_at','current_run_id','consecutive_failures','last_failure_error','block_recurrences','session_id','workspace_path']);
|
|
6
|
-
const OMIT = new Set(['tasks','supercode_cards','task_links','sqlite_sequence']);
|
|
7
|
-
export class RuntimeRows {
|
|
8
|
-
constructor(db) { this.db=db; this.revision=null; this.tables=new Map(); this.runtime={}; this.clocks={}; this.sequences={}; this.cachedTasks={}; this.cachedCards={}; }
|
|
9
|
-
normalized() { return Boolean(this.db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='runtime_rows'").get()); }
|
|
10
|
-
watch(mem) {
|
|
11
|
-
mem.exec('CREATE TEMP TABLE IF NOT EXISTS _runtime_dirty (name TEXT, key TEXT)');
|
|
12
|
-
for (const {name,sql} of mem.prepare("SELECT name,sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").all()) {
|
|
13
|
-
const columns=mem.prepare(`PRAGMA table_info(${quote(name)})`).all();
|
|
14
|
-
const keys=columns.filter(c=>c.pk).sort((a,b)=>a.pk-b.pk).map(c=>c.name);
|
|
15
|
-
if (!keys.length) keys.push('rowid');
|
|
16
|
-
this.tables.set(name,{name,sql,keys,columns});
|
|
17
|
-
const key=prefix=>`json_array(${keys.map(k=>`${prefix}.${quote(k)}`).join(',')})`;
|
|
18
|
-
for(const [suffix,verb,prefix] of [['i','INSERT','NEW'],['u','UPDATE','NEW'],['d','DELETE','OLD']])
|
|
19
|
-
mem.exec(`CREATE TEMP TRIGGER IF NOT EXISTS ${quote(`_runtime_${name}_${suffix}`)} AFTER ${verb} ON main.${quote(name)} BEGIN INSERT OR IGNORE INTO _runtime_dirty VALUES ('${name}',${key(prefix)}); END`);
|
|
20
|
-
}
|
|
21
|
-
mem.exec('DELETE FROM _runtime_dirty');
|
|
22
|
-
}
|
|
23
|
-
insert(mem,name,row,key=null) {
|
|
24
|
-
if(row===null) {
|
|
25
|
-
const table=this.tables.get(name);if(!table)return;
|
|
26
|
-
mem.prepare(`DELETE FROM ${quote(name)} WHERE ${table.keys.map(k=>`${quote(k)} IS ?`).join(' AND ')}`).run(...JSON.parse(key));return;
|
|
27
|
-
}
|
|
28
|
-
const keys=Object.keys(row);
|
|
29
|
-
mem.prepare(`INSERT OR REPLACE INTO ${quote(name)} (${keys.map(quote).join(',')}) VALUES (${keys.map(()=>'?').join(',')})`).run(...keys.map(k=>row[k]??null));
|
|
30
|
-
}
|
|
31
|
-
load(mem,{fresh=false}={}) {
|
|
32
|
-
if(!this.normalized())return false;
|
|
33
|
-
if(fresh) {
|
|
34
|
-
for(const row of this.db.prepare('SELECT name,sql FROM runtime_schema').all()) mem.exec(row.sql.replace('CREATE TABLE ','CREATE TABLE IF NOT EXISTS '));
|
|
35
|
-
this.watch(mem);
|
|
36
|
-
}
|
|
37
|
-
const query=this.revision===null?'SELECT * FROM runtime_rows':'SELECT * FROM runtime_rows WHERE revision>?';
|
|
38
|
-
const rows=this.revision===null?this.db.prepare(query).all():this.db.prepare(query).all(this.revision);
|
|
39
|
-
for(const row of rows) {
|
|
40
|
-
const value=json(row.value,null);
|
|
41
|
-
if(row.name==='@runtime') { if(value)this.runtime[row.key]=value;else delete this.runtime[row.key]; }
|
|
42
|
-
else if(row.name==='@clock') { if(value)this.clocks[row.key]=value;else delete this.clocks[row.key]; }
|
|
43
|
-
else if(row.name==='@sequence') { if(value)this.sequences[row.key]=value;else delete this.sequences[row.key]; }
|
|
44
|
-
else if(row.name==='@task_cache') { if(value)this.cachedTasks[row.key]=value;else delete this.cachedTasks[row.key]; }
|
|
45
|
-
else if(row.name==='@card_cache') { if(value)this.cachedCards[row.key]=value;else delete this.cachedCards[row.key]; }
|
|
46
|
-
else this.insert(mem,row.name,value,row.key);
|
|
47
|
-
}
|
|
48
|
-
if (mem.prepare("SELECT 1 FROM sqlite_master WHERE name='sqlite_sequence'").get()) {
|
|
49
|
-
for (const [name, value] of Object.entries(this.sequences)) {
|
|
50
|
-
const sequence = mem.prepare('SELECT seq FROM sqlite_sequence WHERE name=?').get(name);
|
|
51
|
-
if (sequence) mem.prepare('UPDATE sqlite_sequence SET seq=MAX(seq,?) WHERE name=?').run(value.seq,name);
|
|
52
|
-
else mem.prepare('INSERT INTO sqlite_sequence(name,seq) VALUES(?,?)').run(name,value.seq);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
this.revision=Number(this.db.prepare("SELECT value FROM state WHERE key='rows_revision'").get()?.value??0);
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
58
|
-
changes(mem,{all=false}={}) {
|
|
59
|
-
const dirty=all?[...this.tables.values()].flatMap(t=>mem.prepare(`SELECT ${t.keys.map(quote).join(',')} FROM ${quote(t.name)}`).all().map(row=>({name:t.name,key:JSON.stringify(t.keys.map(k=>row[k]))}))):mem.prepare('SELECT DISTINCT name,key FROM _runtime_dirty').all();
|
|
60
|
-
const changes = dirty.map(({name,key})=>{
|
|
61
|
-
const table=this.tables.get(name);
|
|
62
|
-
const row=mem.prepare(`SELECT * FROM ${quote(name)} WHERE ${table.keys.map(k=>`${quote(k)} IS ?`).join(' AND ')}`).get(...JSON.parse(key))??null;
|
|
63
|
-
if(name==='tasks')return {name:'@runtime',key:JSON.parse(key)[0],value:row&&Object.fromEntries(Object.entries(row).filter(([k])=>RUNTIME.has(k))),source:{name,key,row}};
|
|
64
|
-
if(name==='supercode_cards')return {name:'@clock',key:JSON.parse(key)[0],value:row&&{workspace_root:row.workspace_root,since:row.since,after_fired:row.after_fired,next_run_at:row.next_run_at},source:{name,key,row}};
|
|
65
|
-
return {name,key,value:row,source:{name,key,row}};
|
|
66
|
-
}).filter(change=>{
|
|
67
|
-
if(all)return true;
|
|
68
|
-
const {name,key,value,source}=change;
|
|
69
|
-
if(name==='task_links') {
|
|
70
|
-
const [parent,child]=JSON.parse(key);
|
|
71
|
-
const before=json(this.cachedCards[child]?.blocked_by,[]).some(b=>b.kind==='card'&&b.id===parent);
|
|
72
|
-
return before!==Boolean(source.row);
|
|
73
|
-
}
|
|
74
|
-
const before=this.db.prepare('SELECT value FROM runtime_rows WHERE name=? AND key=?').get(name,key)?.value??null;
|
|
75
|
-
if(before!==(value===null?null:JSON.stringify(value)))return true;
|
|
76
|
-
if(name==='@runtime') {
|
|
77
|
-
const fields=row=>row&&Object.fromEntries(Object.entries(row).filter(([k])=>k!=='status'));
|
|
78
|
-
return JSON.stringify(fields(source.row))!==JSON.stringify(fields(this.cachedTasks[key]??null));
|
|
79
|
-
}
|
|
80
|
-
if(name==='@clock')return JSON.stringify(source.row)!==JSON.stringify(this.cachedCards[key]??null);
|
|
81
|
-
return false;
|
|
82
|
-
});
|
|
83
|
-
if (mem.prepare("SELECT 1 FROM sqlite_master WHERE name='sqlite_sequence'").get()) {
|
|
84
|
-
for (const row of mem.prepare('SELECT name,seq FROM sqlite_sequence').all()) {
|
|
85
|
-
if (Number(this.sequences[row.name]?.seq) !== Number(row.seq)) changes.push({name:'@sequence',key:row.name,value:row,source:{name:'sqlite_sequence',key:JSON.stringify([row.name]),row}});
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return changes;
|
|
89
|
-
}
|
|
90
|
-
mirror(changes, revision) {
|
|
91
|
-
const previous=Number(this.db.prepare("SELECT value FROM state WHERE key='read_revision'").get()?.value??-1);
|
|
92
|
-
for(const name of ['tasks','supercode_cards']) {
|
|
93
|
-
const table=this.tables.get(name); if(!table)return;
|
|
94
|
-
this.db.exec(table.sql.replace(/^CREATE TABLE (?:IF NOT EXISTS )?(?:"[^"]+"|\w+)/,`CREATE TABLE IF NOT EXISTS ${quote('read_'+name)}`));
|
|
95
|
-
const columns=this.db.prepare(`PRAGMA table_info(${quote('read_'+name)})`).all();
|
|
96
|
-
for(const c of table.columns) if(!columns.some(old=>old.name===c.name))this.db.exec(`ALTER TABLE ${quote('read_'+name)} ADD COLUMN ${quote(c.name)} ${c.type}${c.dflt_value==null?'':` DEFAULT ${c.dflt_value}`}`);
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
const rows=previous===revision-1?changes.filter(c=>['tasks','supercode_cards'].includes(c.source.name)).map(c=>c.source):this.db.prepare("SELECT name,key,value FROM runtime_rows WHERE name IN ('@task_cache','@card_cache')").all().map(r=>({name:r.name==='@task_cache'?'tasks':'supercode_cards',key:JSON.stringify([r.key]),row:json(r.value,null)}));
|
|
100
|
-
for(const {name,key,row} of rows) {
|
|
101
|
-
const table=this.tables.get(name), target=quote('read_'+name);
|
|
102
|
-
if(!row) {this.db.prepare(`DELETE FROM ${target} WHERE ${table.keys.map(k=>`${quote(k)} IS ?`).join(' AND ')}`).run(...JSON.parse(key));continue;}
|
|
103
|
-
const keys=Object.keys(row);
|
|
104
|
-
this.db.prepare(`INSERT OR REPLACE INTO ${target} (${keys.map(quote)}) VALUES (${keys.map(()=>'?')})`).run(...keys.map(k=>row[k]??null));
|
|
105
|
-
}
|
|
106
|
-
this.db.prepare("INSERT INTO state VALUES('read_revision',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value").run(String(revision));
|
|
107
|
-
}
|
|
108
|
-
write(changes) {
|
|
109
|
-
this.db.exec(`CREATE TABLE IF NOT EXISTS runtime_schema(name TEXT PRIMARY KEY,sql TEXT NOT NULL); CREATE TABLE IF NOT EXISTS runtime_rows(name TEXT,key TEXT,value TEXT,revision INTEGER NOT NULL,PRIMARY KEY(name,key)); CREATE INDEX IF NOT EXISTS runtime_rows_revision ON runtime_rows(revision); CREATE INDEX IF NOT EXISTS runtime_rows_task ON runtime_rows(name,json_extract(value,'$.task_id'),json_extract(value,'$.id')); CREATE INDEX IF NOT EXISTS runtime_rows_id ON runtime_rows(name,json_extract(value,'$.id'))`);
|
|
110
|
-
const revision=Number(this.db.prepare("SELECT value FROM state WHERE key='rows_revision'").get()?.value??0)+1;
|
|
111
|
-
const statement=this.db.prepare('INSERT INTO runtime_rows VALUES(?,?,?,?) ON CONFLICT(name,key) DO UPDATE SET value=excluded.value,revision=excluded.revision');
|
|
112
|
-
for(const {name,key,value,source} of changes) {
|
|
113
|
-
if (OMIT.has(name)) continue;
|
|
114
|
-
const encoded=value===null?null:JSON.stringify(value);
|
|
115
|
-
if (this.db.prepare('SELECT value FROM runtime_rows WHERE name=? AND key=?').get(name,key)?.value !== encoded) statement.run(name,key,encoded,revision);
|
|
116
|
-
if(name==='@runtime' || name==='@clock') {
|
|
117
|
-
const cacheName=name==='@runtime'?'@task_cache':'@card_cache';
|
|
118
|
-
const cache=name==='@runtime'?this.cachedTasks:this.cachedCards;
|
|
119
|
-
const encodedCache=source?.row?JSON.stringify(source.row):null;
|
|
120
|
-
if (this.db.prepare('SELECT value FROM runtime_rows WHERE name=? AND key=?').get(cacheName,key)?.value !== encodedCache) statement.run(cacheName,key,encodedCache,revision);
|
|
121
|
-
if(source?.row)cache[key]=source.row;else delete cache[key];
|
|
122
|
-
}
|
|
123
|
-
if(name==='@runtime') { if(value)this.runtime[key]=value;else delete this.runtime[key]; }
|
|
124
|
-
if(name==='@clock') { if(value)this.clocks[key]=value;else delete this.clocks[key]; }
|
|
125
|
-
if(name==='@sequence') { if(value)this.sequences[key]=value;else delete this.sequences[key]; }
|
|
126
|
-
}
|
|
127
|
-
for(const {name,sql} of this.tables.values())if(!OMIT.has(name))this.db.prepare('INSERT OR IGNORE INTO runtime_schema VALUES(?,?)').run(name,sql);
|
|
128
|
-
this.db.prepare("INSERT INTO state VALUES('rows_revision',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value").run(String(revision));
|
|
129
|
-
this.mirror(changes,revision);
|
|
130
|
-
this.revision=revision;
|
|
131
|
-
}
|
|
132
|
-
}
|