@north-light/cli 0.3.579 → 0.3.580

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 (40) hide show
  1. package/dist/builtin-memory/internal/storage-tiers.md +2 -2
  2. package/dist/core/canvas/export-sqlite.js +1 -1
  3. package/dist/core/canvas/schema-pg/009-bodies.sql +10 -0
  4. package/dist/core/canvas/schema-version.d.ts +1 -1
  5. package/dist/core/canvas/schema-version.js +1 -1
  6. package/dist/core/feed/feed.js +3 -3
  7. package/dist/core/feed/inbox.js +1 -1
  8. package/dist/core/feed/reports.js +2 -2
  9. package/dist/core/graph/access.d.ts +1 -1
  10. package/dist/core/graph/bodies-boot-copy.d.ts +14 -0
  11. package/dist/core/graph/bodies-boot-copy.js +1 -0
  12. package/dist/core/graph/bodies.d.ts +13 -6
  13. package/dist/core/graph/bodies.js +1 -1
  14. package/dist/core/graph/documents.js +8 -8
  15. package/dist/core/graph/objects.js +11 -11
  16. package/dist/core/graph/package-docs.js +5 -5
  17. package/dist/core/graph/repo-sync/exchange.js +2 -2
  18. package/dist/core/graph/repo-sync/ref-format.d.ts +0 -1
  19. package/dist/core/graph/repo-sync/ref-format.js +2 -2
  20. package/dist/core/peer/receiver.js +1 -1
  21. package/dist/core/runtime/roadmap.js +3 -3
  22. package/dist/core/substrate/delivery/corpus.d.ts +2 -0
  23. package/dist/core/substrate/delivery/corpus.js +3 -3
  24. package/dist/core/substrate/delivery/deliver.js +3 -3
  25. package/dist/core/substrate/delivery/render-boot.js +25 -25
  26. package/dist/core/substrate/delivery/render-event.d.ts +1 -1
  27. package/dist/core/substrate/delivery/render-event.js +4 -4
  28. package/dist/core/substrate/delivery/types.d.ts +2 -2
  29. package/dist/daemon/api/document-listener.js +4 -4
  30. package/dist/daemon/api/handlers/docs.js +1 -1
  31. package/dist/daemon/api/handlers/memory.js +4 -4
  32. package/dist/daemon/api/handlers/reports.js +4 -4
  33. package/dist/daemon/boot-migrations.js +1 -1
  34. package/dist/daemon/review/finish.js +1 -1
  35. package/dist/migrations/004-canvas-documents/apply.d.ts +2 -4
  36. package/dist/migrations/004-canvas-documents/apply.js +1 -1
  37. package/dist/migrations/004-canvas-documents/index.js +2 -2
  38. package/package.json +2 -2
  39. package/packages/crouter-identity/package.json +1 -1
  40. package/runtime.lock.json +13 -13
@@ -13,7 +13,7 @@ surfaces:
13
13
 
14
14
  Everything on the canvas — documents, nodes, jobs, owners, custom objects — is an **object** in one SQLite database; see [[internal]] for the object model. Two places on disk hold the rest: the **scope root**, for configuration and prepared content that is files by nature, and the **canvas home**, for the database and per-node runtime files. This is the routing contract; precise resolver behavior lives in `src/core/scope.ts` and `src/core/canvas/paths.ts`.
15
15
 
16
- Documents are not a third place. A document is an object row in `canvas.db` plus a content-addressed body (a package document — crtr's own or a plugin's — keeps its body in its package file, read through `source_path`); who may read it comes from its privacy (`public` or `private`), the app's schema and its relation facts (`crtr doc write --owner -h`), not from a directory. Do not look for documents on disk, and do not write one as a file: use `crtr doc write`, and read with `crtr canvas read`.
16
+ Documents are not a third place. A document is an object row in `canvas.db` plus a content-addressed body, a row in the `bodies` table (a package document — crtr's own or a plugin's — is the same; its `source_path` only records where it shipped from and marks it read-only); who may read it comes from its privacy (`public` or `private`), the app's schema and its relation facts (`crtr doc write --owner -h`), not from a directory. Do not look for documents on disk, and do not write one as a file: use `crtr doc write`, and read with `crtr canvas read`.
17
17
 
18
18
  ## 1. Scope root — configuration and prepared content
19
19
 
@@ -25,7 +25,7 @@ User-wide content with no cwd dimension also belongs here: `~/.crouter/profile-d
25
25
 
26
26
  ## 2. Canvas home — the object database, per-node files, and bounded diagnostics
27
27
 
28
- `~/.crouter/canvas/` (overridable with `CRTR_HOME`) is the cwd-agnostic node-graph home. `canvas.db` is the SQLite WAL store for every object and its edges, watches, events and rules, including durable tmux-pane focus. `nodes/<node_id>/` owns `meta.json`, `context/` (the node's context directory: scripts, data, logs and page modules — files only, not a way to share writing), `messages/`, `inbox.jsonl`, `transcript.jsonl`, `session.ptr`, and `job/` state. Human ticket files (`page.json`, `page.tsx`, `page.js`, optional `reply-route.json`, `action.json`, `response.json`, `review.json`, and `branch-point.jsonl`) live under `nodes/`, the single derived ticket root. A ticket is not owned by a node: reply-bearing tickets target the bridge recorded in `reply-route.json`, while a ticket created programmatically has no bridge and outlives the process that created it. `action.json` is the frozen action binding — the declared `humanActions` name, its resolved argv and cwd, and the opaque payload — written once at creation and never rewritten, so a later config edit cannot redirect an existing request. Whichever writer wins settlement is the one that queues delivery; the attempt schedule itself is crtrd scheduling state in `canvas.db`, not a ticket file, and delivery never rewrites the ticket's terminal result. Runtime-authored document bodies are content-addressed files under `bodies/`; a package document (crtr's own or a plugin's) keeps its body in its package file, read through `source_path`. All records, revisions, delivery rules, edges and watches are rows in `canvas.db`. A repo-owned document also travels on the repo's git ref `refs/crtr/docs` (one JSON file per document), which is how collaborators receive it; the checkout's working tree never holds it.
28
+ `~/.crouter/canvas/` (overridable with `CRTR_HOME`) is the cwd-agnostic node-graph home. `canvas.db` is the SQLite WAL store for every object and its edges, watches, events and rules, including durable tmux-pane focus. `nodes/<node_id>/` owns `meta.json`, `context/` (the node's context directory: scripts, data, logs and page modules — files only, not a way to share writing), `messages/`, `inbox.jsonl`, `transcript.jsonl`, `session.ptr`, and `job/` state. Human ticket files (`page.json`, `page.tsx`, `page.js`, optional `reply-route.json`, `action.json`, `response.json`, `review.json`, and `branch-point.jsonl`) live under `nodes/`, the single derived ticket root. A ticket is not owned by a node: reply-bearing tickets target the bridge recorded in `reply-route.json`, while a ticket created programmatically has no bridge and outlives the process that created it. `action.json` is the frozen action binding — the declared `humanActions` name, its resolved argv and cwd, and the opaque payload — written once at creation and never rewritten, so a later config edit cannot redirect an existing request. Whichever writer wins settlement is the one that queues delivery; the attempt schedule itself is crtrd scheduling state in `canvas.db`, not a ticket file, and delivery never rewrites the ticket's terminal result. Document text, including a package document's, is a content-addressed row in the `bodies` table keyed by the revision's `body_hash`; the old `bodies/` files on a Mac remain only for rollback. All records, revisions, delivery rules, edges and watches are rows in `canvas.db`. A repo-owned document also travels on the repo's git ref `refs/crtr/docs` (one JSON file per document), which is how collaborators receive it; the checkout's working tree never holds it.
29
29
 
30
30
  A specification, plan or report is a document its node owns, named `<node-id>/<name>` and shared by `[[pointer]]`. It is deleted with that node unless a kept document links to it or it is given another owner (`crtr doc move --owner`).
31
31
 
@@ -1,3 +1,3 @@
1
- var V=Object.defineProperty;var d=(i,a)=>V(i,"name",{value:a,configurable:!0});import{DatabaseSync as W}from"node:sqlite";import{existsSync as Y,mkdirSync as D,mkdtempSync as J,renameSync as X,rmSync as P}from"node:fs";import{tmpdir as Q}from"node:os";import{join as A}from"node:path";import j from"pg";import{migrate as z,MIGRATIONS as I}from"./migrations.js";import{canvasDbPath as Z}from"./paths.js";import{canvasSearchPathOptions as ee}from"./schema-version.js";import{EXPORT_MARK_KEY as F,IMPORT_MARK_KEY as te,OBJECT_COLUMNS as ne,pgColumns as se,quote as p,TABLE_ORDER as C}from"./import-sqlite.js";const re=5e3,oe=new Set(["environments","relations","schemas","charges","charge_totals","limits","viewer_attachments","reconciler_leases","environment_repos","conversation_entries","conversation_parts","charge_chunks","profiles","profile_projects","profile_defaults","scope_settings","scope_bookkeeping","scope_marketplaces","scope_plugins","person_settings","model_accounts","model_account_cooldowns","model_api_keys","model_settings","model_sign_in_flows","env_vars","import_runs","import_key_map","import_manifest","import_id_map","machine_links","machine_calls","machine_credentials","import_node_files","usage","grantee_usage","provider_receipts"]),ae={exposures:"node, object, channel",rules:"object, position",edges:"type, src, dst",unresolved_links:"src, name",revision_parents:"child, parent",canvas_meta:"key",runtime_state:"key",message_causes:"message_id",peer_ingested:"key",peer_ingested_destinations:"key",message_sequence:"id",messages:"seq",review_comment_events:"seq",audit_rows:"id",peer_outbox:"id",app_users:"uid"},ie={objects:"id",revisions:"rev_id",watches:"watch_id"},H=new Set(["daemon_owner","schema_version","schema_min_reader","ledger_migrated",te,F]);function ce(i){const a=J(A(Q(),"crtr-export-")),s=process.env.CRTR_HOME;process.env.CRTR_HOME=a,D(A(a,"runtime","nodes"),{recursive:!0});try{const r=new W(i);return r.exec("PRAGMA journal_mode = DELETE;"),r.exec("PRAGMA foreign_keys = ON;"),z(r),r.exec("DELETE FROM canvas_meta; DELETE FROM message_sequence;"),r}finally{s===void 0?delete process.env.CRTR_HOME:process.env.CRTR_HOME=s,P(a,{recursive:!0,force:!0})}}d(ce,"createEmptyDatabase");async function Re(i,a=()=>{}){const s=Z();if(Y(s))throw new Error(`${s} already exists; refusing to overwrite it`);D(A(s,".."),{recursive:!0,mode:448});const r=`${s}.exporting-${process.pid}`,u=Date.now(),t=new j.Client({connectionString:i,application_name:`crtr-export-${process.pid}`,options:ee(),types:{getTypeParser:d(((f,o)=>f===20?w=>Number(w):j.types.getTypeParser(f,o)),"getTypeParser")}});await t.connect();let _;try{if((await t.query("SELECT value FROM canvas_meta WHERE key = 'schema_version'").catch(()=>({rows:[]}))).rows.length===0)throw new Error("the Postgres database has no canvas schema; nothing to export");_=ce(r);const o=_;o.exec("PRAGMA foreign_keys = OFF;");const w=await se(t),L=[...w.keys()].filter(e=>!oe.has(e)&&!C.includes(e));if(L.length>0)throw new Error(`export: Postgres tables with no export rule: ${L.join(", ")}`);await t.query("BEGIN READ ONLY ISOLATION LEVEL REPEATABLE READ");const E={};for(const[e,S]of Object.entries(ie)){const h=new Map;for(const l of(await t.query(`SELECT ${S} AS id, seq FROM ${e}`)).rows)h.set(l.id,Number(l.seq));E[e]=h}const B=E.objects;let M=0;o.exec("BEGIN");for(const e of C){const S=Date.now(),h=new Set(o.prepare(`PRAGMA table_info(${p(e)})`).all().map(n=>n.name)),l=w.get(e);if(l===void 0)throw new Error(`export: Postgres has no table ${e}`);const G=l.some(n=>n.name==="seq"),O=l.filter(n=>h.has(n.name)),K=new Set(ne[e]??[]),g=e==="objects"||e==="revisions"||e==="watches"||e==="watch_subscriptions",$=O.map(n=>n.type==="jsonb"?`${p(n.name)}::text AS ${p(n.name)}`:p(n.name));e==="watches"&&$.push("watch_id AS _watch_id"),g&&$.push("seq AS _seq");const R=O.map(n=>p(n.name)),U=o.prepare(`INSERT INTO ${p(e)} (${g?["rowid",...R]:R}) VALUES (${(g?["?",...R]:R).map(()=>"?").join(", ")})`),k=G?"seq":ae[e],b=`export_${e}`;await t.query(`DECLARE ${b} NO SCROLL CURSOR FOR SELECT ${$.join(", ")} FROM ${p(e)}${e==="canvas_meta"?` WHERE key NOT IN (${[...H].map(n=>`'${n}'`).join(", ")})`:""}${k===void 0?"":` ORDER BY ${k}`}`);let T=0;for(;;){const n=await t.query(`FETCH ${re} FROM ${b}`);if(n.rows.length===0)break;for(const q of n.rows){const N=g?[Number(q._seq)]:[];for(const c of O){let m=q[c.name];e==="revision_parents"&&(c.name==="child"||c.name==="parent")?m=x(E.revisions,"revision",e,c.name,m):e==="watch_subscriptions"&&c.name==="watch"?m=x(E.watches,"watch",e,c.name,m):K.has(c.name)?m=x(B,"object",e,c.name,m):e==="jobs"&&(c.name==="job_dir"||c.name==="log_path")&&(m=m??""),N.push(m??null)}U.run(...N)}T+=n.rows.length}await t.query(`CLOSE ${b}`),M+=T,a(`export ${e}: ${T} rows in ${Date.now()-S} ms`)}o.exec("COMMIT"),await t.query("COMMIT");const y=o.prepare("PRAGMA foreign_key_check").all();if(y.length>0)throw new Error(`export: ${y.length} foreign key violation(s) in the exported canvas.db, first: ${JSON.stringify(y[0])}`);await me(t,o);const v=o.prepare("PRAGMA user_version").get().user_version;if(v!==I.length)throw new Error(`export: exported canvas.db is at user_version ${v}, expected ${I.length}`);return o.close(),_=void 0,await t.query("INSERT INTO canvas_meta (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value",[F,JSON.stringify({at:new Date().toISOString(),path:s})]),X(r,s),{path:s,rows:M,ms:Date.now()-u,userVersion:v}}catch(f){await t.query("ROLLBACK").catch(()=>{});try{_?.close()}catch{}for(const o of["","-wal","-shm","-journal"])P(`${r}${o}`,{force:!0});throw f}finally{await t.end().catch(()=>{})}}d(Re,"exportPostgresToSqlite");function x(i,a,s,r,u){if(u==null)return null;const t=i.get(u);if(t===void 0)throw new Error(`export: ${s}.${r} references ${a} ${String(u)}, which was not exported (`);return t}d(x,"mapId");async function me(i,a){const s=[];for(const r of C){const u=Number((await i.query(`SELECT count(*) AS n FROM ${p(r)}`)).rows[0].n);let t=Number(a.prepare(`SELECT count(*) AS n FROM ${p(r)}`).get().n),_=u;r==="canvas_meta"&&(_=Number((await i.query("SELECT count(*) AS n FROM canvas_meta WHERE key <> ALL($1)",[[...H]])).rows[0].n),t=Number(a.prepare("SELECT count(*) AS n FROM canvas_meta").get().n)),_!==t&&s.push(`${r}: Postgres ${_} rows, SQLite ${t}`)}if(s.length>0)throw new Error(`Postgres \u2192 SQLite export verification failed:
1
+ var V=Object.defineProperty;var d=(i,a)=>V(i,"name",{value:a,configurable:!0});import{DatabaseSync as W}from"node:sqlite";import{existsSync as Y,mkdirSync as D,mkdtempSync as J,renameSync as X,rmSync as P}from"node:fs";import{tmpdir as Q}from"node:os";import{join as A}from"node:path";import j from"pg";import{migrate as z,MIGRATIONS as I}from"./migrations.js";import{canvasDbPath as Z}from"./paths.js";import{canvasSearchPathOptions as ee}from"./schema-version.js";import{EXPORT_MARK_KEY as F,IMPORT_MARK_KEY as te,OBJECT_COLUMNS as ne,pgColumns as se,quote as p,TABLE_ORDER as C}from"./import-sqlite.js";const oe=5e3,re=new Set(["environments","relations","schemas","charges","charge_totals","limits","viewer_attachments","reconciler_leases","environment_repos","conversation_entries","conversation_parts","charge_chunks","profiles","profile_projects","profile_defaults","scope_settings","scope_bookkeeping","scope_marketplaces","scope_plugins","person_settings","model_accounts","model_account_cooldowns","model_api_keys","model_settings","model_sign_in_flows","env_vars","import_runs","import_key_map","import_manifest","import_id_map","machine_links","machine_calls","machine_credentials","import_node_files","bodies","usage","grantee_usage","provider_receipts"]),ae={exposures:"node, object, channel",rules:"object, position",edges:"type, src, dst",unresolved_links:"src, name",revision_parents:"child, parent",canvas_meta:"key",runtime_state:"key",message_causes:"message_id",peer_ingested:"key",peer_ingested_destinations:"key",message_sequence:"id",messages:"seq",review_comment_events:"seq",audit_rows:"id",peer_outbox:"id",app_users:"uid"},ie={objects:"id",revisions:"rev_id",watches:"watch_id"},H=new Set(["daemon_owner","schema_version","schema_min_reader","ledger_migrated",te,F]);function ce(i){const a=J(A(Q(),"crtr-export-")),s=process.env.CRTR_HOME;process.env.CRTR_HOME=a,D(A(a,"runtime","nodes"),{recursive:!0});try{const o=new W(i);return o.exec("PRAGMA journal_mode = DELETE;"),o.exec("PRAGMA foreign_keys = ON;"),z(o),o.exec("DELETE FROM canvas_meta; DELETE FROM message_sequence;"),o}finally{s===void 0?delete process.env.CRTR_HOME:process.env.CRTR_HOME=s,P(a,{recursive:!0,force:!0})}}d(ce,"createEmptyDatabase");async function Re(i,a=()=>{}){const s=Z();if(Y(s))throw new Error(`${s} already exists; refusing to overwrite it`);D(A(s,".."),{recursive:!0,mode:448});const o=`${s}.exporting-${process.pid}`,u=Date.now(),t=new j.Client({connectionString:i,application_name:`crtr-export-${process.pid}`,options:ee(),types:{getTypeParser:d(((f,r)=>f===20?w=>Number(w):j.types.getTypeParser(f,r)),"getTypeParser")}});await t.connect();let _;try{if((await t.query("SELECT value FROM canvas_meta WHERE key = 'schema_version'").catch(()=>({rows:[]}))).rows.length===0)throw new Error("the Postgres database has no canvas schema; nothing to export");_=ce(o);const r=_;r.exec("PRAGMA foreign_keys = OFF;");const w=await se(t),L=[...w.keys()].filter(e=>!re.has(e)&&!C.includes(e));if(L.length>0)throw new Error(`export: Postgres tables with no export rule: ${L.join(", ")}`);await t.query("BEGIN READ ONLY ISOLATION LEVEL REPEATABLE READ");const E={};for(const[e,S]of Object.entries(ie)){const h=new Map;for(const l of(await t.query(`SELECT ${S} AS id, seq FROM ${e}`)).rows)h.set(l.id,Number(l.seq));E[e]=h}const B=E.objects;let M=0;r.exec("BEGIN");for(const e of C){const S=Date.now(),h=new Set(r.prepare(`PRAGMA table_info(${p(e)})`).all().map(n=>n.name)),l=w.get(e);if(l===void 0)throw new Error(`export: Postgres has no table ${e}`);const G=l.some(n=>n.name==="seq"),O=l.filter(n=>h.has(n.name)),K=new Set(ne[e]??[]),g=e==="objects"||e==="revisions"||e==="watches"||e==="watch_subscriptions",$=O.map(n=>n.type==="jsonb"?`${p(n.name)}::text AS ${p(n.name)}`:p(n.name));e==="watches"&&$.push("watch_id AS _watch_id"),g&&$.push("seq AS _seq");const R=O.map(n=>p(n.name)),U=r.prepare(`INSERT INTO ${p(e)} (${g?["rowid",...R]:R}) VALUES (${(g?["?",...R]:R).map(()=>"?").join(", ")})`),k=G?"seq":ae[e],b=`export_${e}`;await t.query(`DECLARE ${b} NO SCROLL CURSOR FOR SELECT ${$.join(", ")} FROM ${p(e)}${e==="canvas_meta"?` WHERE key NOT IN (${[...H].map(n=>`'${n}'`).join(", ")})`:""}${k===void 0?"":` ORDER BY ${k}`}`);let T=0;for(;;){const n=await t.query(`FETCH ${oe} FROM ${b}`);if(n.rows.length===0)break;for(const q of n.rows){const N=g?[Number(q._seq)]:[];for(const c of O){let m=q[c.name];e==="revision_parents"&&(c.name==="child"||c.name==="parent")?m=x(E.revisions,"revision",e,c.name,m):e==="watch_subscriptions"&&c.name==="watch"?m=x(E.watches,"watch",e,c.name,m):K.has(c.name)?m=x(B,"object",e,c.name,m):e==="jobs"&&(c.name==="job_dir"||c.name==="log_path")&&(m=m??""),N.push(m??null)}U.run(...N)}T+=n.rows.length}await t.query(`CLOSE ${b}`),M+=T,a(`export ${e}: ${T} rows in ${Date.now()-S} ms`)}r.exec("COMMIT"),await t.query("COMMIT");const y=r.prepare("PRAGMA foreign_key_check").all();if(y.length>0)throw new Error(`export: ${y.length} foreign key violation(s) in the exported canvas.db, first: ${JSON.stringify(y[0])}`);await me(t,r);const v=r.prepare("PRAGMA user_version").get().user_version;if(v!==I.length)throw new Error(`export: exported canvas.db is at user_version ${v}, expected ${I.length}`);return r.close(),_=void 0,await t.query("INSERT INTO canvas_meta (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value",[F,JSON.stringify({at:new Date().toISOString(),path:s})]),X(o,s),{path:s,rows:M,ms:Date.now()-u,userVersion:v}}catch(f){await t.query("ROLLBACK").catch(()=>{});try{_?.close()}catch{}for(const r of["","-wal","-shm","-journal"])P(`${o}${r}`,{force:!0});throw f}finally{await t.end().catch(()=>{})}}d(Re,"exportPostgresToSqlite");function x(i,a,s,o,u){if(u==null)return null;const t=i.get(u);if(t===void 0)throw new Error(`export: ${s}.${o} references ${a} ${String(u)}, which was not exported (`);return t}d(x,"mapId");async function me(i,a){const s=[];for(const o of C){const u=Number((await i.query(`SELECT count(*) AS n FROM ${p(o)}`)).rows[0].n);let t=Number(a.prepare(`SELECT count(*) AS n FROM ${p(o)}`).get().n),_=u;o==="canvas_meta"&&(_=Number((await i.query("SELECT count(*) AS n FROM canvas_meta WHERE key <> ALL($1)",[[...H]])).rows[0].n),t=Number(a.prepare("SELECT count(*) AS n FROM canvas_meta").get().n)),_!==t&&s.push(`${o}: Postgres ${_} rows, SQLite ${t}`)}if(s.length>0)throw new Error(`Postgres \u2192 SQLite export verification failed:
2
2
  ${s.join(`
3
3
  `)}`)}d(me,"verify");export{Re as exportPostgresToSqlite};
@@ -0,0 +1,10 @@
1
+ -- 009-bodies.sql — cycle 5 D106: document revision text in the database. One transaction, additions only: `schema_min_reader` is not
2
+ -- touched and stays 2. The previous build (version 8) opens the result but never names `bodies`: it reads the body files of revisions written before this release, and answers `no body` for a revision written after it (no file is written). A rollback keeps old documents readable, not new ones.
3
+ --
4
+ -- A revision names its text by `body_hash`, the sha256 of the text. The text is written with `INSERT ... ON CONFLICT DO NOTHING` in the same
5
+ -- transaction as the revision that names it, so a rolled-back write leaves neither. No foreign key from `revisions.body_hash`: revisions
6
+ -- written before this release name text that exists only in a Mac's body files (or, in the cloud, nowhere). Nothing deletes a row (no GC).
7
+ CREATE TABLE IF NOT EXISTS bodies (
8
+ hash text PRIMARY KEY,
9
+ text text NOT NULL
10
+ );
@@ -1,7 +1,7 @@
1
1
  /** The canvas schema version this build supports: the number of the highest delta in `schema-pg/` (the baseline
2
2
  * `schema-pg.sql` is version 1). `scripts/install-runtime.mjs` reads this module from a candidate generation to refuse
3
3
  * one below the live database's `schema_min_reader`. */
4
- export declare const CANVAS_SCHEMA_VERSION = 8;
4
+ export declare const CANVAS_SCHEMA_VERSION = 9;
5
5
  /** The Postgres schema holding the canvas tables: `CRTR_DATABASE_SCHEMA` (`crtr` in the cloud deploy). Unset or empty
6
6
  * means the role's own search_path (`public` on a Mac and on a VM). A name that is not a plain identifier is an error:
7
7
  * it is put in a `search_path` connection option. */
@@ -1 +1 @@
1
- var m=Object.defineProperty;var r=(n,e)=>m(n,"name",{value:e,configurable:!0});const A=8;function _(n=process.env){const e=n.CRTR_DATABASE_SCHEMA?.trim();if(e){if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(e))throw new Error(`CRTR_DATABASE_SCHEMA must be a plain identifier (letters, digits, underscore); got ${JSON.stringify(e)}`);return e}}r(_,"canvasDatabaseSchema");function h(n=process.env){return n.CRTR_SCHEMA_DATABASE_URL?.trim()||void 0}r(h,"canvasSchemaDatabaseUrl");function E(n=process.env){const e=_(n);return e===void 0?void 0:`-c search_path=${e}`}r(E,"canvasSearchPathOptions");async function v(n,e){const t=e===void 0?"canvas_meta":`"${e.replace(/"/g,'""')}".canvas_meta`;if((await n.query(`SELECT to_regclass('${t.replace(/'/g,"''")}') IS NOT NULL AS found`)).rows[0].found!==!0)return;const u=(await n.query(`SELECT key, value FROM ${t} WHERE key IN ('schema_version', 'schema_min_reader')`)).rows,o=r(c=>{const i=u.find(f=>f.key===c);if(i===void 0)return;const d=Number(i.value);if(!Number.isSafeInteger(d))throw new Error(`canvas_meta.${c} is not an integer: ${String(i.value)}`);return d},"value"),s=o("schema_version");return s===void 0?void 0:{version:s,minReader:o("schema_min_reader")??s}}r(v,"readStoredVersions");async function l(n,e,t){const a=await v(n,t);if(a!==void 0&&a.minReader>e)throw new Error(`refusing to select runtime with canvas schema ${e}: the live database requires readers at version ${a.minReader} or above (it is at ${a.version}); install a descendant build instead`)}r(l,"assertCandidateMayOpen");export{A as CANVAS_SCHEMA_VERSION,l as assertCandidateMayOpen,_ as canvasDatabaseSchema,h as canvasSchemaDatabaseUrl,E as canvasSearchPathOptions,v as readStoredVersions};
1
+ var m=Object.defineProperty;var r=(n,e)=>m(n,"name",{value:e,configurable:!0});const A=9;function _(n=process.env){const e=n.CRTR_DATABASE_SCHEMA?.trim();if(e){if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(e))throw new Error(`CRTR_DATABASE_SCHEMA must be a plain identifier (letters, digits, underscore); got ${JSON.stringify(e)}`);return e}}r(_,"canvasDatabaseSchema");function h(n=process.env){return n.CRTR_SCHEMA_DATABASE_URL?.trim()||void 0}r(h,"canvasSchemaDatabaseUrl");function E(n=process.env){const e=_(n);return e===void 0?void 0:`-c search_path=${e}`}r(E,"canvasSearchPathOptions");async function v(n,e){const t=e===void 0?"canvas_meta":`"${e.replace(/"/g,'""')}".canvas_meta`;if((await n.query(`SELECT to_regclass('${t.replace(/'/g,"''")}') IS NOT NULL AS found`)).rows[0].found!==!0)return;const u=(await n.query(`SELECT key, value FROM ${t} WHERE key IN ('schema_version', 'schema_min_reader')`)).rows,o=r(c=>{const i=u.find(f=>f.key===c);if(i===void 0)return;const d=Number(i.value);if(!Number.isSafeInteger(d))throw new Error(`canvas_meta.${c} is not an integer: ${String(i.value)}`);return d},"value"),s=o("schema_version");return s===void 0?void 0:{version:s,minReader:o("schema_min_reader")??s}}r(v,"readStoredVersions");async function l(n,e,t){const a=await v(n,t);if(a!==void 0&&a.minReader>e)throw new Error(`refusing to select runtime with canvas schema ${e}: the live database requires readers at version ${a.minReader} or above (it is at ${a.version}); install a descendant build instead`)}r(l,"assertCandidateMayOpen");export{A as CANVAS_SCHEMA_VERSION,l as assertCandidateMayOpen,_ as canvasDatabaseSchema,h as canvasSchemaDatabaseUrl,E as canvasSearchPathOptions,v as readStoredVersions};
@@ -1,4 +1,4 @@
1
- var O=Object.defineProperty;var i=(e,t)=>O(e,"name",{value:t,configurable:!0});import{cancelCronsOnWake as q,getNode as E}from"../canvas/index.js";import{runStructuredRequestEffects as g,settleStructuredRequestIn as R}from"../canvas/structured-requests.js";import{afterCommit as x,openDb as F,withCanvasWrite as k}from"../canvas/db.js";import{brokerRelayGeneration as M,recordBrokerRelease as W}from"../runtime/lifecycle.js";import{randomUUID as z}from"node:crypto";import{composeNodeOutcomeDocument as A}from"../runtime/outcome-document.js";import{closeNode as L}from"../runtime/close.js";import{put as H}from"../graph/bodies.js";import{writeDocument as j}from"../graph/documents.js";import{asRuntimeWriter as B}from"../permissions/ceiling.js";import{publish as T}from"../graph/events.js";import{nodeObject as v}from"../graph/objects.js";import{readerForNode as J}from"../graph/reader.js";import{endMessageSubscriptions as C}from"../graph/watches.js";import{operationIdContext as D}from"../events/operation-id.js";class b extends Error{static{i(this,"StructuredRequestFinalizationError")}state;constructor(t){super(t==="expired"?"structured_request_expired":"structured_request_settled"),this.state=t}}class h extends Error{static{i(this,"FinalizationError")}code;report;constructor(t,r){super(t),this.code=t,this.report=r}}function Y(e){const t=i((r,n=2)=>String(r).padStart(n,"0"),"pad");return`${e.getUTCFullYear()}${t(e.getUTCMonth()+1)}${t(e.getUTCDate())}T${t(e.getUTCHours())}${t(e.getUTCMinutes())}${t(e.getUTCSeconds())}${t(e.getUTCMilliseconds(),3)}`}i(Y,"compactTs");function S(e,t=80){const r=e.split(`
2
- `)[0]??"";return r.length>t?`${r.slice(0,t-1)}\u2026`:r}i(S,"firstLine");function P(e){return e.kind==="final"?{event:"report.final"}:e.result!==void 0?{event:"report.result"}:e.kind==="urgent"?{event:"report.update",level:7}:{event:"report.update",level:e.deliveryTier==="deferred"?3:e.deliveryTier==="urgent"?7:5}}i(P,"eventFor");async function U(e,t,r){const n=r.from??t,o=`reports/${Y(new Date)}-${z().slice(-8)}-${r.kind}`,u=await v(e,t),c=await v(e,n),{object:d}=await j(e,B(await J(e,n,async()=>[])),{owner:u,kind:"report",name:o,preview:S(r.body)||r.kind,privacy:"public"},r.body),s=`${t}/${o}`,{event:p,level:m}=P(r);await T(e,{event:p,object:u,actor:c,...m===void 0?{}:{level:m},attrs:{},pointer:d.id,messageBody:{kind:r.kind,label:S(r.body),bodyRef:s,...r.disposition===void 0?{}:{disposition:r.disposition}}});const l=(await e.prepare(`SELECT d.destination_id FROM deliveries d JOIN messages m ON m.message_id = d.message_id
1
+ var O=Object.defineProperty;var i=(e,t)=>O(e,"name",{value:t,configurable:!0});import{cancelCronsOnWake as q,getNode as E}from"../canvas/index.js";import{runStructuredRequestEffects as R,settleStructuredRequestIn as y}from"../canvas/structured-requests.js";import{afterCommit as x,openDb as F,withCanvasWrite as k}from"../canvas/db.js";import{brokerRelayGeneration as M,recordBrokerRelease as W}from"../runtime/lifecycle.js";import{randomUUID as z}from"node:crypto";import{composeNodeOutcomeDocument as A}from"../runtime/outcome-document.js";import{closeNode as L}from"../runtime/close.js";import{writeDocument as H}from"../graph/documents.js";import{asRuntimeWriter as j}from"../permissions/ceiling.js";import{publish as T}from"../graph/events.js";import{nodeObject as v}from"../graph/objects.js";import{readerForNode as B}from"../graph/reader.js";import{endMessageSubscriptions as C}from"../graph/watches.js";import{operationIdContext as D}from"../events/operation-id.js";class J extends Error{static{i(this,"StructuredRequestFinalizationError")}state;constructor(t){super(t==="expired"?"structured_request_expired":"structured_request_settled"),this.state=t}}class h extends Error{static{i(this,"FinalizationError")}code;report;constructor(t,r){super(t),this.code=t,this.report=r}}function b(e){const t=i((r,n=2)=>String(r).padStart(n,"0"),"pad");return`${e.getUTCFullYear()}${t(e.getUTCMonth()+1)}${t(e.getUTCDate())}T${t(e.getUTCHours())}${t(e.getUTCMinutes())}${t(e.getUTCSeconds())}${t(e.getUTCMilliseconds(),3)}`}i(b,"compactTs");function S(e,t=80){const r=e.split(`
2
+ `)[0]??"";return r.length>t?`${r.slice(0,t-1)}\u2026`:r}i(S,"firstLine");function Y(e){return e.kind==="final"?{event:"report.final"}:e.result!==void 0?{event:"report.result"}:e.kind==="urgent"?{event:"report.update",level:7}:{event:"report.update",level:e.deliveryTier==="deferred"?3:e.deliveryTier==="urgent"?7:5}}i(Y,"eventFor");async function U(e,t,r){const n=r.from??t,o=`reports/${b(new Date)}-${z().slice(-8)}-${r.kind}`,u=await v(e,t),c=await v(e,n),{object:d}=await H(e,j(await B(e,n,async()=>[])),{owner:u,kind:"report",name:o,preview:S(r.body)||r.kind,privacy:"public"},r.body),s=`${t}/${o}`,{event:p,level:m}=Y(r);await T(e,{event:p,object:u,actor:c,...m===void 0?{}:{level:m},attrs:{},pointer:d.id,messageBody:{kind:r.kind,label:S(r.body),bodyRef:s,...r.disposition===void 0?{}:{disposition:r.disposition}}});const l=(await e.prepare(`SELECT d.destination_id FROM deliveries d JOIN messages m ON m.message_id = d.message_id
3
3
  JOIN objects receiver ON receiver.id = d.destination_id
4
- WHERE m.body_ref = ? AND receiver.type = 'node' ORDER BY d.destination_id`).all(s)).map(w=>w.destination_id);return r.kind==="final"?await C(e,u,"final"):r.result?.requestMessageId&&await C(e,u,{result:r.result.requestMessageId}),{name:o,ref:s,doc:d,createdAt:d.created_at,deliveredTo:l}}i(U,"writeReport");async function G(e,t){return k(async r=>{const n=await r.prepare("SELECT status, final_report FROM nodes WHERE node_id = ?").get(e);if(n===void 0)throw new h("unknown_node");const o=t.outcome;let u;if(o?.structured!==void 0){const a=await R(r,o.structured.requestId,o.structured.declined===void 0?{intended:"completed",result:o.structured.value}:{intended:"declined",declined:o.structured.declined});if(!a.changed||a.row===null||a.row.state!=="completed"&&a.row.state!=="declined")return{written:void 0,...a.row===null?{}:{structured:a.row},structuredRefused:!0};u=a.row}else{const a=await r.prepareUncached("SELECT request_id FROM structured_requests WHERE node_id = ? AND state = 'pending'").get(e);if(a!==void 0){const f=await R(r,a.request_id,{intended:"failed",failureReason:"finalized"});if(!f.changed||f.row===null||f.row.state!=="failed")return{written:void 0,...f.row===null?{}:{structured:f.row},structuredRefused:!0};u=f.row}}if(n.final_report!==null)throw new h("already_finalized",`${e}/${n.final_report}`);if(n.status!=="active"&&n.status!=="idle"&&n.status!=="done")throw new h("finalization_forbidden");const c=await U(r,e,t),d=await E(e),s=d?.kind!=="human",p=(d?.outcome_revision??0)+1,m=t.outcome?.kind??"result",l=t.outcome?.reason??"finalized",w=t.outcome?.detailJson??null,_=c.createdAt;if(s){const a=d===null?null:await A({node:{...d,status:"done",intent:"done",terminal_reason:l},outcome:{kind:m,reason:l,at:_,revision:p,detailJson:w},finalReport:c.name,...u?.state==="completed"?{structuredResult:u.result}:{}});await r.prepare("UPDATE nodes SET status = 'done', intent = 'done', terminal_reason = ?, final_report = ?, finalized_at = ?, outcome_kind = ?, outcome_reason = ?, outcome_at = ?, outcome_revision = outcome_revision + 1, outcome_detail_json = ? WHERE node_id = ? AND final_report IS NULL").run(l,c.name,_,m,l,_,w,e),a!==null&&await T(r,{event:"outcome",object:await v(r,e),actor:null,attrs:{revision:p},messageBody:a})}else await r.prepare("UPDATE nodes SET status = 'done', intent = 'done', terminal_reason = 'finalized', final_report = ?, finalized_at = ? WHERE node_id = ? AND final_report IS NULL").run(c.name,_,e);const N=M(e);return x(()=>W(e,N)),{written:c,...u===void 0?{}:{structured:u}}})}i(G,"commitCanonicalFinal");async function $(e,t){if(!e)throw new Error(`push: empty nodeId (kind=${t.kind}) \u2014 a report must belong to a node`);if(t.kind==="final"&&t.deliveryTier!==void 0)throw new Error("push final does not accept a delivery tier");if(t.kind!=="final"&&t.outcome!==void 0)throw new Error("push outcome is final-only");if(t.kind!=="update"&&t.result!==void 0)throw new Error("a one-off result is an update report");if(H(t.body),t.kind==="final"){const n=await G(e,t);if(n.structuredRefused===!0)throw n.structured!==void 0&&g(n.structured),new b(n.structured?.state??"failed");n.structured!==void 0&&t.outcome?.structured?.skipEffects!==!0&&g(n.structured);const{written:o}=n;if(await q(e),(await E(e))?.run_id===e){const c=await(await F()).prepare("SELECT node_id FROM nodes WHERE run_id = ? AND node_id != ? ORDER BY created DESC").all(e,e);for(const d of c){const s=await E(d.node_id);s&&s.status!=="done"&&s.status!=="canceled"&&s.status!=="dead"&&await L(d.node_id,{preserveEmpty:!0})}}return{report:o.ref,reportId:o.doc.id,deliveredTo:o.deliveredTo}}const r=await k(async n=>await U(n,e,t));return{report:r.ref,reportId:r.doc.id,deliveredTo:r.deliveredTo}}i($,"pushWithContext");function y(e,t){return D.current()===void 0?D.fresh(()=>$(e,t)):$(e,t)}i(y,"push");async function ce(e,t,r){return y(e,{kind:"update",body:t,...r})}i(ce,"pushUpdate");async function le(e,t,r){return y(e,{kind:"urgent",body:t,...r})}i(le,"pushUrgent");async function fe(e,t,r){return y(e,{kind:"final",body:t,...r})}i(fe,"pushFinal");export{h as FinalizationError,b as StructuredRequestFinalizationError,y as push,fe as pushFinal,ce as pushUpdate,le as pushUrgent};
4
+ WHERE m.body_ref = ? AND receiver.type = 'node' ORDER BY d.destination_id`).all(s)).map(w=>w.destination_id);return r.kind==="final"?await C(e,u,"final"):r.result?.requestMessageId&&await C(e,u,{result:r.result.requestMessageId}),{name:o,ref:s,doc:d,createdAt:d.created_at,deliveredTo:l}}i(U,"writeReport");async function P(e,t){return k(async r=>{const n=await r.prepare("SELECT status, final_report FROM nodes WHERE node_id = ?").get(e);if(n===void 0)throw new h("unknown_node");const o=t.outcome;let u;if(o?.structured!==void 0){const a=await y(r,o.structured.requestId,o.structured.declined===void 0?{intended:"completed",result:o.structured.value}:{intended:"declined",declined:o.structured.declined});if(!a.changed||a.row===null||a.row.state!=="completed"&&a.row.state!=="declined")return{written:void 0,...a.row===null?{}:{structured:a.row},structuredRefused:!0};u=a.row}else{const a=await r.prepareUncached("SELECT request_id FROM structured_requests WHERE node_id = ? AND state = 'pending'").get(e);if(a!==void 0){const f=await y(r,a.request_id,{intended:"failed",failureReason:"finalized"});if(!f.changed||f.row===null||f.row.state!=="failed")return{written:void 0,...f.row===null?{}:{structured:f.row},structuredRefused:!0};u=f.row}}if(n.final_report!==null)throw new h("already_finalized",`${e}/${n.final_report}`);if(n.status!=="active"&&n.status!=="idle"&&n.status!=="done")throw new h("finalization_forbidden");const c=await U(r,e,t),d=await E(e),s=d?.kind!=="human",p=(d?.outcome_revision??0)+1,m=t.outcome?.kind??"result",l=t.outcome?.reason??"finalized",w=t.outcome?.detailJson??null,_=c.createdAt;if(s){const a=d===null?null:await A({node:{...d,status:"done",intent:"done",terminal_reason:l},outcome:{kind:m,reason:l,at:_,revision:p,detailJson:w},finalReport:c.name,...u?.state==="completed"?{structuredResult:u.result}:{}});await r.prepare("UPDATE nodes SET status = 'done', intent = 'done', terminal_reason = ?, final_report = ?, finalized_at = ?, outcome_kind = ?, outcome_reason = ?, outcome_at = ?, outcome_revision = outcome_revision + 1, outcome_detail_json = ? WHERE node_id = ? AND final_report IS NULL").run(l,c.name,_,m,l,_,w,e),a!==null&&await T(r,{event:"outcome",object:await v(r,e),actor:null,attrs:{revision:p},messageBody:a})}else await r.prepare("UPDATE nodes SET status = 'done', intent = 'done', terminal_reason = 'finalized', final_report = ?, finalized_at = ? WHERE node_id = ? AND final_report IS NULL").run(c.name,_,e);const N=M(e);return x(()=>W(e,N)),{written:c,...u===void 0?{}:{structured:u}}})}i(P,"commitCanonicalFinal");async function $(e,t){if(!e)throw new Error(`push: empty nodeId (kind=${t.kind}) \u2014 a report must belong to a node`);if(t.kind==="final"&&t.deliveryTier!==void 0)throw new Error("push final does not accept a delivery tier");if(t.kind!=="final"&&t.outcome!==void 0)throw new Error("push outcome is final-only");if(t.kind!=="update"&&t.result!==void 0)throw new Error("a one-off result is an update report");if(t.kind==="final"){const n=await P(e,t);if(n.structuredRefused===!0)throw n.structured!==void 0&&R(n.structured),new J(n.structured?.state??"failed");n.structured!==void 0&&t.outcome?.structured?.skipEffects!==!0&&R(n.structured);const{written:o}=n;if(await q(e),(await E(e))?.run_id===e){const c=await(await F()).prepare("SELECT node_id FROM nodes WHERE run_id = ? AND node_id != ? ORDER BY created DESC").all(e,e);for(const d of c){const s=await E(d.node_id);s&&s.status!=="done"&&s.status!=="canceled"&&s.status!=="dead"&&await L(d.node_id,{preserveEmpty:!0})}}return{report:o.ref,reportId:o.doc.id,deliveredTo:o.deliveredTo}}const r=await k(async n=>await U(n,e,t));return{report:r.ref,reportId:r.doc.id,deliveredTo:r.deliveredTo}}i($,"pushWithContext");function g(e,t){return D.current()===void 0?D.fresh(()=>$(e,t)):$(e,t)}i(g,"push");async function de(e,t,r){return g(e,{kind:"update",body:t,...r})}i(de,"pushUpdate");async function se(e,t,r){return g(e,{kind:"urgent",body:t,...r})}i(se,"pushUrgent");async function ce(e,t,r){return g(e,{kind:"final",body:t,...r})}i(ce,"pushFinal");export{h as FinalizationError,J as StructuredRequestFinalizationError,g as push,ce as pushFinal,de as pushUpdate,se as pushUrgent};
@@ -1 +1 @@
1
- var f=Object.defineProperty;var i=(n,e)=>f(n,"name",{value:e,configurable:!0});import{openDb as u}from"../canvas/db.js";import{heads as m}from"../graph/documents.js";import{getById as p}from"../graph/objects.js";import{bodyOf as l}from"../graph/bodies.js";import{validateOperationId as y}from"../events/operation-id.js";import{formatInboxCard as h,machineOfEntries as b}from"../../shared/generated-context.js";import{inlineBody as I,renderEntryBody as g,reportRefNodeId as w}from"../../shared/inbox-entry-body.js";import{mapAsync as E}from"../async-array.js";const _=/^(?!0{32}$)[0-9a-f]{32}$/;function x(n){if(typeof n!="string"||!_.test(n))throw new TypeError("entry_id must be 32 lowercase non-zero hexadecimal characters");return n}i(x,"validateInboxEntryId");function S(n){const e=n.message;return{entry_id:x(e.messageId),operation_id:y(e.operationId),ts:e.createdAt,from:e.senderNodeId,...e.senderName===null?{}:{from_name:e.senderName},tier:n.urgency,kind:e.kind,...e.bodyRef===null?{}:{ref:e.bodyRef},label:e.label,...e.bodyJson===null?{}:{data:e.bodyJson},...e.disposition===null?{}:{disposition:e.disposition}}}i(S,"nodeMailEntry");async function k(n){const e=await u(),o=await p(e,n);if(o===null||o.type!=="document"||o.deleted_at!==null)return null;const s=(await m(e,o))[0];if(s===void 0)return null;const d=await e.prepare("SELECT source_path FROM documents WHERE object = ?").get(o.id);return l(s.body_hash,d?.source_path??null)}i(k,"reportDocumentBody");async function B(n){const e=n.data?.push?.object?.id,o=w(n.ref)!==void 0&&e!==void 0?await k(e):null;return g(n,o)}i(B,"entryBody");async function N(n){return{kind:n.kind,body:await B(n),...n.ref===void 0?{}:{ref:n.ref},...n.disposition===void 0?{}:{disposition:n.disposition}}}i(N,"cardEntry");async function $(n){if(n.length===0)return[];const e=new Map;for(const t of n){const r=t.from??"system";e.has(r)||e.set(r,[]),e.get(r).push(t)}const o=[],s=[];let d=-1;for(const[t,r]of e){if(t==="human"){for(const a of r){const c=I(a);o.push({kind:"human",text:c===""?a.label:c,entries:[a]})}continue}d<0&&(d=o.length,o.push({kind:"card",text:"",entries:[]})),s.push({id:t,...b(r),entries:await E(r,N)})}return d>=0&&(o[d]={kind:"card",text:h(s),entries:n.filter(t=>(t.from??"system")!=="human")}),o}i($,"coalesce");export{$ as coalesce,S as nodeMailEntry,k as reportDocumentBody};
1
+ var c=Object.defineProperty;var i=(n,e)=>c(n,"name",{value:e,configurable:!0});import{openDb as u}from"../canvas/db.js";import{heads as m}from"../graph/documents.js";import{getById as l}from"../graph/objects.js";import{get as p}from"../graph/bodies.js";import{validateOperationId as y}from"../events/operation-id.js";import{formatInboxCard as h,machineOfEntries as b}from"../../shared/generated-context.js";import{inlineBody as g,renderEntryBody as I,reportRefNodeId as w}from"../../shared/inbox-entry-body.js";import{mapAsync as x}from"../async-array.js";const _=/^(?!0{32}$)[0-9a-f]{32}$/;function k(n){if(typeof n!="string"||!_.test(n))throw new TypeError("entry_id must be 32 lowercase non-zero hexadecimal characters");return n}i(k,"validateInboxEntryId");function z(n){const e=n.message;return{entry_id:k(e.messageId),operation_id:y(e.operationId),ts:e.createdAt,from:e.senderNodeId,...e.senderName===null?{}:{from_name:e.senderName},tier:n.urgency,kind:e.kind,...e.bodyRef===null?{}:{ref:e.bodyRef},label:e.label,...e.bodyJson===null?{}:{data:e.bodyJson},...e.disposition===null?{}:{disposition:e.disposition}}}i(z,"nodeMailEntry");async function B(n){const e=await u(),t=await l(e,n);if(t===null||t.type!=="document"||t.deleted_at!==null)return null;const d=(await m(e,t))[0];return d===void 0?null:await p(e,d.body_hash)}i(B,"reportDocumentBody");async function E(n){const e=n.data?.push?.object?.id,t=w(n.ref)!==void 0&&e!==void 0?await B(e):null;return I(n,t)}i(E,"entryBody");async function N(n){return{kind:n.kind,body:await E(n),...n.ref===void 0?{}:{ref:n.ref},...n.disposition===void 0?{}:{disposition:n.disposition}}}i(N,"cardEntry");async function C(n){if(n.length===0)return[];const e=new Map;for(const o of n){const r=o.from??"system";e.has(r)||e.set(r,[]),e.get(r).push(o)}const t=[],d=[];let s=-1;for(const[o,r]of e){if(o==="human"){for(const a of r){const f=g(a);t.push({kind:"human",text:f===""?a.label:f,entries:[a]})}continue}s<0&&(s=t.length,t.push({kind:"card",text:"",entries:[]})),d.push({id:o,...b(r),entries:await x(r,N)})}return s>=0&&(t[s]={kind:"card",text:h(d),entries:n.filter(o=>(o.from??"system")!=="human")}),t}i(C,"coalesce");export{C as coalesce,z as nodeMailEntry,B as reportDocumentBody};
@@ -1,2 +1,2 @@
1
- var f=Object.defineProperty;var o=(t,e)=>f(t,"name",{value:e,configurable:!0});import{openDb as a}from"../canvas/db.js";import{get as u}from"../graph/bodies.js";import{heads as s}from"../graph/documents.js";import{getById as d}from"../graph/objects.js";async function D(t){const e=await a(),n=await d(e,t);return n===null||n.type!=="node"?!1:await e.prepare("SELECT 1 FROM objects WHERE owner = ? AND type = 'document' AND kind = 'report' AND deleted_at IS NULL LIMIT 1").get(n.id)!==void 0}o(D,"hasReportDocuments");async function p(t){const e=await a(),n=await d(e,t);if(!(n===null||n.type!=="node"))return await e.prepare(`SELECT * FROM objects WHERE owner = ? AND type = 'document' AND kind = 'report' AND deleted_at IS NULL
2
- ORDER BY created_at DESC, name DESC LIMIT 1`).get(n.id)}o(p,"newestReport");async function N(t){const e=await p(t);return e===void 0?null:`${t}/${e.name}`}o(N,"newestReportName");async function h(t){const e=await a(),n=await p(t);if(n===void 0)return"";const r=(await s(e,n))[0];if(r===void 0)return"";try{return u(r.body_hash).trim()}catch{return""}}o(h,"newestReportBody");async function R(t){const e=t.indexOf("/");if(e<=0||!t.startsWith("reports/",e+1))return null;const n=await a(),r=await d(n,t.slice(0,e));if(r===null||r.type!=="node")return null;const i=await n.prepare("SELECT * FROM objects WHERE owner = ? AND type = 'document' AND name = ? AND deleted_at IS NULL").get(r.id,t.slice(e+1)),c=i===void 0?void 0:(await s(n,i))[0];if(c===void 0)return null;try{return u(c.body_hash)}catch{return null}}o(R,"reportBodyByName");export{D as hasReportDocuments,h as newestReportBody,N as newestReportName,R as reportBodyByName};
1
+ var f=Object.defineProperty;var a=(t,e)=>f(t,"name",{value:e,configurable:!0});import{openDb as r}from"../canvas/db.js";import{findMany as u}from"../graph/bodies.js";import{heads as c}from"../graph/documents.js";import{getById as i}from"../graph/objects.js";async function h(t){const e=await r(),n=await i(e,t);return n===null||n.type!=="node"?!1:await e.prepare("SELECT 1 FROM objects WHERE owner = ? AND type = 'document' AND kind = 'report' AND deleted_at IS NULL LIMIT 1").get(n.id)!==void 0}a(h,"hasReportDocuments");async function p(t){const e=await r(),n=await i(e,t);if(!(n===null||n.type!=="node"))return await e.prepare(`SELECT * FROM objects WHERE owner = ? AND type = 'document' AND kind = 'report' AND deleted_at IS NULL
2
+ ORDER BY created_at DESC, name DESC LIMIT 1`).get(n.id)}a(p,"newestReport");async function D(t){const e=await p(t);return e===void 0?null:`${t}/${e.name}`}a(D,"newestReportName");async function N(t){const e=await r(),n=await p(t);if(n===void 0)return"";const o=(await c(e,n))[0];return o===void 0?"":((await u(e,[o.body_hash])).get(o.body_hash)??"").trim()}a(N,"newestReportBody");async function b(t){const e=t.indexOf("/");if(e<=0||!t.startsWith("reports/",e+1))return null;const n=await r(),o=await i(n,t.slice(0,e));if(o===null||o.type!=="node")return null;const s=await n.prepare("SELECT * FROM objects WHERE owner = ? AND type = 'document' AND name = ? AND deleted_at IS NULL").get(o.id,t.slice(e+1)),d=s===void 0?void 0:(await c(n,s))[0];return d===void 0?null:(await u(n,[d.body_hash])).get(d.body_hash)??null}a(b,"reportBodyByName");export{h as hasReportDocuments,N as newestReportBody,D as newestReportName,b as reportBodyByName};
@@ -2,7 +2,7 @@ import type { CanvasDb } from '../canvas/db.js';
2
2
  import type { ObjectRow, Reader } from './types.js';
3
3
  /** The top of the object's owner chain (the object itself when it has no owner): its app, person or repo. */
4
4
  export declare function spaceOf(db: CanvasDb, object: ObjectRow): Promise<ObjectRow>;
5
- /** A shipped document: one a package reconciler owns, read from its package file (`documents.source_path` is set). Only the reconciler writes it. */
5
+ /** A shipped document: one a package reconciler owns (`documents.source_path` is set, as provenance and the shipped marker). Only the reconciler writes it. */
6
6
  export declare function isShipped(db: CanvasDb, object: ObjectRow): Promise<boolean>;
7
7
  /** Callers turn `false` into `not_found`. */
8
8
  export declare function canRead(db: CanvasDb, reader: Reader, object: ObjectRow): Promise<boolean>;
@@ -0,0 +1,14 @@
1
+ /** D106 first boot on a Mac: copy every document text still in the old `<canvas home>/bodies/<hh>/<hash>` files into the `bodies` table.
2
+ * A file is copied only when the sha256 of its text equals its name; any other is skipped and reported. Rows go in with
3
+ * `ON CONFLICT DO NOTHING` in batches of one transaction each, so an interrupted run resumes and a finished one changes nothing.
4
+ * The files stay in place for a rollback to the previous build. Cloud roles never call this: their text was never on a disk that survives. */
5
+ export interface BodiesBootCopyReport {
6
+ scanned: number;
7
+ copied: number;
8
+ mismatched: string[];
9
+ nulReplaced: Array<{
10
+ hash: string;
11
+ replaced: number;
12
+ }>;
13
+ }
14
+ export declare function copyBodyFilesToTable(root?: string): Promise<BodiesBootCopyReport>;
@@ -0,0 +1 @@
1
+ var m=Object.defineProperty;var r=(n,a)=>m(n,"name",{value:a,configurable:!0});import{existsSync as l,readdirSync as f,readFileSync as d}from"node:fs";import{join as c}from"node:path";import{openDb as u,withCanvasWrite as w}from"../canvas/db.js";import{crtrHome as y}from"../canvas/paths.js";import{hashOf as T,storable as b}from"./bodies.js";const O=200;async function E(n=c(y(),"bodies")){const a={scanned:0,copied:0,mismatched:[],nulReplaced:[]};if(!l(n))return a;const p=new Set((await(await u()).prepare("SELECT hash FROM bodies").all()).map(e=>e.hash));let i=[];const h=r(async()=>{if(i.length===0)return;const e=i;i=[],await w(async t=>{const s=t.prepare("INSERT INTO bodies (hash, text) VALUES (?, ?) ON CONFLICT (hash) DO NOTHING");for(const o of e)a.copied+=(await s.run(o.hash,o.text)).changes})},"flush");for(const e of f(n,{withFileTypes:!0}))if(!(!e.isDirectory()||!/^[0-9a-f]{2}$/.test(e.name)))for(const t of f(c(n,e.name))){if(!/^[0-9a-f]{64}$/.test(t)||(a.scanned+=1,p.has(t)))continue;const s=d(c(n,e.name,t),"utf8");if(T(s)!==t||!t.startsWith(e.name)){a.mismatched.push(t);continue}const o=b(s);o!==s&&a.nulReplaced.push({hash:t,replaced:s.split("\0").length-1}),i.push({hash:t,text:o}),i.length>=O&&await h()}return await h(),a}r(E,"copyBodyFilesToTable");export{E as copyBodyFilesToTable};
@@ -1,6 +1,13 @@
1
- /** Store a body durably (temp + fsync + rename); call before the transaction that references it. */
2
- export declare function put(body: string): string;
3
- export declare function get(hash: string): string;
4
- /** A revision's body: a package document's (one with a `source_path`) is its file's body after
5
- * the frontmatter; every other document's comes from the body store. */
6
- export declare function bodyOf(hash: string, sourcePath: string | null): string;
1
+ import type { CanvasDb } from '../canvas/db.js';
2
+ export declare function hashOf(body: string): string;
3
+ /** Postgres `text` cannot hold U+0000, so each NUL is stored as U+FFFD. The one rule for a new write and for the Mac boot copy. */
4
+ export declare function storable(text: string): string;
5
+ /** Store a body and return its hash, which is the hash of the text as stored (see `storable`). Call inside the transaction that writes the
6
+ * revision naming it, so a rolled-back write leaves neither.
7
+ * `as` is for text that arrives with the hash its revision already names (repo sync): the row is stored under that hash even when `storable` changed the text. */
8
+ export declare function put(db: CanvasDb, body: string, as?: string): Promise<string>;
9
+ export declare function get(db: CanvasDb, hash: string): Promise<string>;
10
+ /** The bodies that exist among `hashes`, in one query. A hash with no row is absent from the map. */
11
+ export declare function findMany(db: CanvasDb, hashes: readonly string[]): Promise<Map<string, string>>;
12
+ /** Several bodies in one query. A hash with no row is `no body <hash>`, as for `get`. */
13
+ export declare function getMany(db: CanvasDb, hashes: readonly string[]): Promise<Map<string, string>>;
@@ -1 +1 @@
1
- var m=Object.defineProperty;var o=(r,t)=>m(r,"name",{value:t,configurable:!0});import{createHash as p}from"node:crypto";import{closeSync as y,existsSync as s,fsyncSync as d,mkdirSync as a,openSync as l,readFileSync as c,renameSync as S,writeSync as w}from"node:fs";import{join as f}from"node:path";import{notFound as b}from"../errors.js";import{crtrHome as x}from"../canvas/paths.js";import{parseFrontmatterGeneric as $}from"../frontmatter.js";function u(r){if(!/^[0-9a-f]{64}$/.test(r))throw new Error(`invalid body hash: ${r}`);return f(x(),"bodies",r.slice(0,2),r)}o(u,"bodyPath");function j(r){const t=p("sha256").update(r,"utf8").digest("hex"),n=u(t);if(s(n))return t;a(f(n,".."),{recursive:!0});const i=`${n}.tmp-${process.pid}`,e=l(i,"w");try{w(e,r,null,"utf8"),d(e)}finally{y(e)}return S(i,n),t}o(j,"put");function h(r){try{return c(u(r),"utf8")}catch(t){throw t.code==="ENOENT"?b(`no body ${r}`):t}}o(h,"get");function k(r,t){return t===null?h(r):$(c(t,"utf8")).body}o(k,"bodyOf");export{k as bodyOf,h as get,j as put};
1
+ var c=Object.defineProperty;var r=(t,e)=>c(t,"name",{value:e,configurable:!0});import{createHash as f}from"node:crypto";import{notFound as u}from"../errors.js";const h=/^[0-9a-f]{64}$/;function s(t){if(!h.test(t))throw new Error(`invalid body hash: ${t}`);return t}r(s,"checked");function p(t){return f("sha256").update(t,"utf8").digest("hex")}r(p,"hashOf");function d(t){return t.replaceAll("\0","\uFFFD")}r(d,"storable");async function l(t,e,n){const o=d(e),a=n===void 0?p(o):s(n);return await t.prepare("INSERT INTO bodies (hash, text) VALUES (?, ?) ON CONFLICT (hash) DO NOTHING").run(a,o),a}r(l,"put");async function O(t,e){const n=await t.prepare("SELECT text FROM bodies WHERE hash = ?").get(s(e));if(n===void 0)throw u(`no body ${e}`);return n.text}r(O,"get");async function w(t,e){const n=[...new Set(e)].map(s),o=new Map;if(n.length===0)return o;const a=await t.prepare("SELECT hash, text FROM bodies WHERE hash = ANY(?::text[])").all(n);for(const i of a)o.set(i.hash,i.text);return o}r(w,"findMany");async function b(t,e){const n=await w(t,e);for(const o of e)if(!n.has(o))throw u(`no body ${o}`);return n}r(b,"getMany");export{w as findMany,O as get,b as getMany,p as hashOf,l as put,d as storable};
@@ -1,16 +1,16 @@
1
- var se=Object.defineProperty;var s=(e,n)=>se(e,"name",{value:n,configurable:!0});import{randomUUID as ce}from"node:crypto";import{CrtrError as N,notFound as R,usage as l}from"../errors.js";import{ExitCode as g}from"../../types.js";import{bodyOf as ue,get as we,put as B}from"./bodies.js";import{dropUnresolved as pe,linksOf as le,recordActedOn as O,setLinks as q,setUnresolved as de,unresolvedLinksCouldName as me}from"./edges.js";import{publish as k}from"./events.js";import{readableOrDeleted as fe,resolveName as ye}from"./names.js";import{createObject as he,getById as f}from"./objects.js";import{liveLookups as Ee,readerForNode as _e,readerForObject as ve}from"./reader.js";import{replaceRules as G,setRulesOwner as Ne}from"./rules.js";import{recordWatch as j}from"./watches.js";import{withCanvasWrite as y}from"../canvas/db.js";import{filterAsync as Re,forEachAsync as ge,mapAsync as x}from"../async-array.js";import{check as A,resolveReader as Oe,userOf as K}from"../permissions/evaluator.js";import{missingMemoryScope as Y}from"../permissions/ceiling.js";import{deleteFact as Se,parseSubject as Te,writeActsFor as De,writeFact as ke}from"../permissions/facts.js";import{PermissionError as je}from"../permissions/errors.js";function E(e){return e.class==="node"?e.node:e.class==="app"?e.app:e.class==="object"?e.object:e.person}s(E,"actorOf");async function C(e,n){const t=`user:${await K(e,n)}`;return n.class==="person"?{app:null,for:t}:{app:(await e.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(n.app.id))?.external_id??null,for:t}}s(C,"actingOf");function xe(e,n){return new N("conflict",e,g.GENERAL,n)}s(xe,"conflict");function h(e){if(e.type!=="document")throw l(`${e.type} ${e.id} is not a document`)}s(h,"assertDocument");async function V(e,n){return(await e.prepare(`WITH RECURSIVE chain(id, owner, type, depth) AS (
1
+ var se=Object.defineProperty;var s=(e,n)=>se(e,"name",{value:n,configurable:!0});import{randomUUID as ce}from"node:crypto";import{CrtrError as R,notFound as N,usage as m}from"../errors.js";import{ExitCode as g}from"../../types.js";import{get as ue,getMany as we,put as B}from"./bodies.js";import{dropUnresolved as pe,linksOf as le,recordActedOn as O,setLinks as q,setUnresolved as me,unresolvedLinksCouldName as de}from"./edges.js";import{publish as k}from"./events.js";import{readableOrDeleted as fe,resolveName as ye}from"./names.js";import{createObject as he,getById as d}from"./objects.js";import{liveLookups as Ee,readerForNode as _e,readerForObject as ve}from"./reader.js";import{replaceRules as G,setRulesOwner as Re}from"./rules.js";import{recordWatch as j}from"./watches.js";import{withCanvasWrite as y}from"../canvas/db.js";import{filterAsync as Ne,forEachAsync as ge,mapAsync as x}from"../async-array.js";import{check as A,resolveReader as Oe,userOf as K}from"../permissions/evaluator.js";import{missingMemoryScope as Y}from"../permissions/ceiling.js";import{deleteFact as Se,parseSubject as Te,writeActsFor as De,writeFact as ke}from"../permissions/facts.js";import{PermissionError as je}from"../permissions/errors.js";function E(e){return e.class==="node"?e.node:e.class==="app"?e.app:e.class==="object"?e.object:e.person}s(E,"actorOf");async function C(e,n){const t=`user:${await K(e,n)}`;return n.class==="person"?{app:null,for:t}:{app:(await e.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(n.app.id))?.external_id??null,for:t}}s(C,"actingOf");function xe(e,n){return new R("conflict",e,g.GENERAL,n)}s(xe,"conflict");function h(e){if(e.type!=="document")throw m(`${e.type} ${e.id} is not a document`)}s(h,"assertDocument");async function V(e,n){return(await e.prepare(`WITH RECURSIVE chain(id, owner, type, depth) AS (
2
2
  SELECT id, owner, type, 0 FROM objects WHERE id = ?
3
3
  UNION ALL
4
4
  SELECT o.id, o.owner, o.type, c.depth + 1 FROM objects o JOIN chain c ON o.id = c.owner WHERE c.depth < 12
5
- ) SELECT type FROM chain ORDER BY depth DESC LIMIT 1`).get(n.id))?.type??n.type}s(V,"spaceType");async function W(e,n,t){if(t!=="public"&&await V(e,n)==="repo")throw l(`a repo owns only public documents; ${n.name} cannot own a ${t} one`,{rule:"repo-public-only",next:"Write it as public, or give it another owner (your own documents can carry `if: {repo: <handle>}`)."})}s(W,"assertPublicInRepo");async function M(e,n,t,a){if(t.trim()===""||t!==t.trim())throw l(`invalid document name ${JSON.stringify(t)}`);const i=await e.prepare("SELECT id FROM objects WHERE (type = 'document' OR strpos(type, '.') > 0) AND owner = ? AND name = ? AND deleted_at IS NULL AND home_machine IS NULL").get(n.id,t);if(i&&i.id!==a?.id)throw xe(`${n.name} already has a document named ${t}`,{name:t,holder:i.id})}s(M,"assertNameFree");async function U(e,n,t){if(!await A(e,n,"read",t.id))throw R(`no document ${t.id}`)}s(U,"readable");function X(e){return new N("scope_missing",`missing scope ${e}`,g.GENERAL,{scope:e})}s(X,"scopeMissing");async function L(e,n,t){await U(e,n,t);const{source_path:a}=await v(e,t);if(a!==null)throw new N("forbidden",`${t.name} is a built-in document shipped by a package (served from ${a}); \`crtr doc\` cannot change it`,g.GENERAL,{id:t.id,next:"Edit it in the source repository of the package that ships it (for crtr's own documents, the crouter repo), not with `crtr doc`."});const i=await Y(e,n,t.id,"rw");if(i!==null)throw X(i);if(!await A(e,n,"edit",t.id))throw new N("forbidden",`you cannot change ${t.name}`,g.GENERAL,{id:t.id})}s(L,"changeable");async function z(e,n){const t=await e.prepare(`SELECT p.rev_id FROM revision_parents rp JOIN revisions p ON p.rev_id = rp.parent
5
+ ) SELECT type FROM chain ORDER BY depth DESC LIMIT 1`).get(n.id))?.type??n.type}s(V,"spaceType");async function M(e,n,t){if(t!=="public"&&await V(e,n)==="repo")throw m(`a repo owns only public documents; ${n.name} cannot own a ${t} one`,{rule:"repo-public-only",next:"Write it as public, or give it another owner (your own documents can carry `if: {repo: <handle>}`)."})}s(M,"assertPublicInRepo");async function W(e,n,t,a){if(t.trim()===""||t!==t.trim())throw m(`invalid document name ${JSON.stringify(t)}`);const i=await e.prepare("SELECT id FROM objects WHERE (type = 'document' OR strpos(type, '.') > 0) AND owner = ? AND name = ? AND deleted_at IS NULL AND home_machine IS NULL").get(n.id,t);if(i&&i.id!==a?.id)throw xe(`${n.name} already has a document named ${t}`,{name:t,holder:i.id})}s(W,"assertNameFree");async function U(e,n,t){if(!await A(e,n,"read",t.id))throw N(`no document ${t.id}`)}s(U,"readable");function X(e){return new R("scope_missing",`missing scope ${e}`,g.GENERAL,{scope:e})}s(X,"scopeMissing");async function L(e,n,t){await U(e,n,t);const{source_path:a}=await v(e,t);if(a!==null)throw new R("forbidden",`${t.name} is a built-in document shipped by a package; \`crtr doc\` cannot change it`,g.GENERAL,{id:t.id,next:"Edit it in the source repository of the package that ships it (for crtr's own documents, the crouter repo), not with `crtr doc`."});const i=await Y(e,n,t.id,"rw");if(i!==null)throw X(i);if(!await A(e,n,"edit",t.id))throw new R("forbidden",`you cannot change ${t.name}`,g.GENERAL,{id:t.id})}s(L,"changeable");async function z(e,n){const t=await e.prepare(`SELECT p.rev_id FROM revision_parents rp JOIN revisions p ON p.rev_id = rp.parent
6
6
  WHERE rp.child = ? ORDER BY p.rev_id`).all(n.rev_id);return{rev_id:n.rev_id,seq:n.seq,number:n.number,body_hash:n.body_hash,author:n.author,rationale:n.rationale,verbatim:n.verbatim===1,created_at:n.created_at,acting_app:n.acting_app,acting_for:n.acting_for,parents:t.map(a=>a.rev_id)}}s(z,"toRevision");async function I(e,n){const t=await e.prepare(`SELECT * FROM revisions r WHERE r.document = ?
7
- AND NOT EXISTS (SELECT 1 FROM revision_parents p WHERE p.parent = r.rev_id) ORDER BY r.number DESC, r.rev_id`).all(n.id);return await x(t,async a=>await z(e,a))}s(I,"heads");function Ae(e){return e.map(n=>n.rev_id).sort().join(",")}s(Ae,"headsKeyOf");async function v(e,n){const t=await e.prepare("SELECT * FROM documents WHERE object = ?").get(n.id);if(!t)throw R(`no document record for ${n.id}`);return{summary:t.summary,heads_key:t.heads_key,revision:t.revision,why_it_exists:t.why_it_exists,unlisted:t.unlisted===1,lint_ignore:t.lint_ignore_json===null?null:JSON.parse(t.lint_ignore_json),extensions:t.extensions_json===null?null:JSON.parse(t.extensions_json),source_path:t.source_path}}s(v,"documentRecord");async function S(e,n,t){const a=await e.prepare(`INSERT INTO revisions (document, rev_id, number, body_hash, author, rationale, verbatim, created_at, acting_app, acting_for)
8
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`).get(n.id,t.rev_id??ce(),t.number,t.body_hash,t.author?.id??null,t.rationale,t.verbatim?1:0,t.created_at??new Date().toISOString(),t.acting?.app??null,t.acting?.for??null),i=e.prepare("INSERT INTO revision_parents (child, parent) VALUES (?, ?)");for(const r of t.parents)await i.run(a.rev_id,r);return a}s(S,"insertRevision");function Le(e){const n=new Set;for(const t of e.matchAll(/\[\[([^[\]\n]+?)\]\]/g))n.add(t[1].trim());return[...n]}s(Le,"linksIn");async function Q(e,n,t){const a=[],i=[];for(const r of t){const o=await ye(e,n,r);"object"in o?a.push({written_as:r,dst:o.object}):"deleted"in o?a.push({written_as:r,dst:o.deleted}):"remote"in o||i.push(r)}return{links:a,unresolved:i}}s(Q,"resolveLinks");async function Z(e,n,t,a){const{links:i,unresolved:r}=await Q(e,n,Le(a));return await ie(e,await q(e,t,i)),await de(e,t,r),r}s(Z,"setRevisionLinks");async function F(e,n,t){const a=new Map;for(const{src:i,name:r}of await me(e,n.name,n.id))a.set(i,[...a.get(i)??[],r]);for(const[i,r]of a){const o=await f(e,i),u=o?.owner==null?null:await f(e,o.owner);if(!o||o.deleted_at||o.home_machine!==null||!u||u.deleted_at)continue;const w=u.type==="node"?await _e(e,u.id,t??(async()=>[]),Ee(e),!0):await ve(e,o,t),c=(await Q(e,w,r)).links.filter(p=>p.dst.id===n.id);c.length!==0&&(await q(e,o,[...await le(e,o),...c]),await pe(e,o,c.map(p=>p.written_as)))}}s(F,"linkNewlyNamed");async function Ie(e,n,t){return n.type==="node"?n.id:await e.prepare(`SELECT 1 FROM objects o JOIN objects d ON d.owner = o.id
7
+ AND NOT EXISTS (SELECT 1 FROM revision_parents p WHERE p.parent = r.rev_id) ORDER BY r.number DESC, r.rev_id`).all(n.id);return await x(t,async a=>await z(e,a))}s(I,"heads");function Ae(e){return e.map(n=>n.rev_id).sort().join(",")}s(Ae,"headsKeyOf");async function v(e,n){const t=await e.prepare("SELECT * FROM documents WHERE object = ?").get(n.id);if(!t)throw N(`no document record for ${n.id}`);return{summary:t.summary,heads_key:t.heads_key,revision:t.revision,why_it_exists:t.why_it_exists,unlisted:t.unlisted===1,lint_ignore:t.lint_ignore_json===null?null:JSON.parse(t.lint_ignore_json),extensions:t.extensions_json===null?null:JSON.parse(t.extensions_json),source_path:t.source_path}}s(v,"documentRecord");async function S(e,n,t){const a=await e.prepare(`INSERT INTO revisions (document, rev_id, number, body_hash, author, rationale, verbatim, created_at, acting_app, acting_for)
8
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`).get(n.id,t.rev_id??ce(),t.number,t.body_hash,t.author?.id??null,t.rationale,t.verbatim?1:0,t.created_at??new Date().toISOString(),t.acting?.app??null,t.acting?.for??null),i=e.prepare("INSERT INTO revision_parents (child, parent) VALUES (?, ?)");for(const r of t.parents)await i.run(a.rev_id,r);return a}s(S,"insertRevision");function Le(e){const n=new Set;for(const t of e.matchAll(/\[\[([^[\]\n]+?)\]\]/g))n.add(t[1].trim());return[...n]}s(Le,"linksIn");async function Q(e,n,t){const a=[],i=[];for(const r of t){const o=await ye(e,n,r);"object"in o?a.push({written_as:r,dst:o.object}):"deleted"in o?a.push({written_as:r,dst:o.deleted}):"remote"in o||i.push(r)}return{links:a,unresolved:i}}s(Q,"resolveLinks");async function Z(e,n,t,a){const{links:i,unresolved:r}=await Q(e,n,Le(a));return await ie(e,await q(e,t,i)),await me(e,t,r),r}s(Z,"setRevisionLinks");async function F(e,n,t){const a=new Map;for(const{src:i,name:r}of await de(e,n.name,n.id))a.set(i,[...a.get(i)??[],r]);for(const[i,r]of a){const o=await d(e,i),c=o?.owner==null?null:await d(e,o.owner);if(!o||o.deleted_at||o.home_machine!==null||!c||c.deleted_at)continue;const p=c.type==="node"?await _e(e,c.id,t??(async()=>[]),Ee(e),!0):await ve(e,o,t),u=(await Q(e,p,r)).links.filter(l=>l.dst.id===n.id);u.length!==0&&(await q(e,o,[...await le(e,o),...u]),await pe(e,o,u.map(l=>l.written_as)))}}s(F,"linkNewlyNamed");async function Ie(e,n,t){return n.type==="node"?n.id:await e.prepare(`SELECT 1 FROM objects o JOIN objects d ON d.owner = o.id
9
9
  WHERE o.name = ? AND o.type <> ? AND o.type IN ('person','app','repo','profile')
10
10
  AND d.type = 'document' AND d.name = ? AND d.deleted_at IS NULL LIMIT 1`).get(n.name,n.type,t)?`${n.type}:${n.name}`:n.name}s(Ie,"ownerPrefix");async function b(e,n,t){const a=await e.prepare(`SELECT e.written_as, o.* FROM edges e JOIN objects o ON o.id = e.dst
11
- WHERE e.type = 'links-to' AND e.src = ? AND e.written_as IS NOT NULL`).all(n.id);if(a.length===0)return t;const i=new Map;for(const r of a){if(r.deleted_at||r.written_as===r.id)continue;const o=!r.written_as.includes("/");if(o&&r.name===r.written_as)continue;const u=r.owner===null?null:await f(e,r.owner),w=u?`${await Ie(e,u,r.name)}/${r.name}`:r.name,c=o&&r.owner===n.owner?r.name:w;c!==r.written_as&&i.set(r.written_as,c)}return i.size===0?t:t.replace(/\[\[([^[\]\n]+?)\]\]/g,(r,o)=>{const u=i.get(o.trim());return u===void 0?r:`[[${u}]]`})}s(b,"withCurrentNames");async function J(e,n,t){if(t.preview!==void 0){if(t.preview.trim()==="")throw l("a document needs a preview",{field:"preview"});await e.prepare("UPDATE objects SET preview = ? WHERE id = ?").run(t.preview,n.id)}t.privacy!==void 0&&(await W(e,await f(e,n.owner),t.privacy),await e.prepare("UPDATE objects SET privacy = ? WHERE id = ?").run(t.privacy,n.id));const a=s(async(r,o)=>await e.prepareUncached(`UPDATE documents SET ${r} = ? WHERE object = ?`).run(o,n.id),"set");t.summary!==void 0&&await a("summary",t.summary),t.whyItExists!==void 0&&await a("why_it_exists",t.whyItExists),t.unlisted!==void 0&&await a("unlisted",t.unlisted?1:0),t.lintIgnore!==void 0&&await a("lint_ignore_json",t.lintIgnore===null?null:JSON.stringify(t.lintIgnore)),t.extensions!==void 0&&await a("extensions_json",t.extensions===null?null:JSON.stringify(t.extensions));const i=await f(e,n.id);return t.rules!==void 0&&await G(e,i,t.rules),i}s(J,"applyDocumentFields");async function ee(e,n,t,a="crtr"){if(n.owner.deleted_at)throw l(`${n.owner.name} is deleted`);if(n.relations?.length&&n.schemaType===void 0)throw new je(400,"schema_type_required","relations need a schemaType: they are facts about a typed resource",{field:"relations"});if(n.preview.trim()==="")throw l("a document needs a preview",{field:"preview"});await W(e,n.owner,n.privacy),await M(e,n.owner,n.name);const i=await he(e,{...n.id?{id:n.id}:{},type:"document",kind:n.kind,owner:n.owner.id,privacy:n.privacy,name:n.name,preview:n.preview,...n.schemaType!==void 0?{schema_type:n.schemaType}:{}});for(const{relation:o,subject:u}of n.relations??[])await ke(e,{resourceType:n.schemaType,resourceId:i.id,relation:o,subject:Te(u),writtenBy:a});await e.prepare("INSERT INTO documents (object, heads_key, revision) VALUES (?, '', 0)").run(i.id);const r=await J(e,i,{...n,preview:void 0,privacy:void 0,rules:n.rules??[]});return await F(e,r,t),r}s(ee,"createDocument");async function $(e,n){const t=await I(e,n),a=Ae(t),i=Math.max(0,...t.map(r=>r.number));return await e.prepare("UPDATE documents SET heads_key = ?, revision = ? WHERE object = ?").run(a,i,n.id),{heads_key:a,revision:i,headCount:t.length}}s($,"setHeads");async function nt(e,n,t,a,i){const r=B(a);return await y(async()=>{const o=E(n),u=await Y(e,n,t.owner.id,"rw",!0);if(u!==null)throw X(u);const w=await C(e,n),c=await ee(e,t,i,w.app??w.for);t.owner.type==="app"&&t.schemaType===void 0&&await De(e,c.id,await K(e,n));const p=await S(e,c,{number:1,body_hash:r,author:o,rationale:null,parents:[],acting:w});if(!await A(e,n,"edit",c.id))throw R(`no owner ${t.owner.name} you can write to`);const{heads_key:d}=await $(e,c),m=await Z(e,n,c,a);return await O(e,o,c),await j(e,o,c,"write",{readHeadsKey:d}),await k(e,{event:"created",object:c,actor:o,attrs:{revision:1},pointer:p.rev_id},i),{object:c,revision:1,unresolved:m}},{savepoint:!0})}s(nt,"writeDocument");async function $e(e,n,t){return(await e.prepare("SELECT read_heads_key FROM watches WHERE watcher = ? AND object = ?").get(E(n).id,t.id))?.read_heads_key??null}s($e,"readBase");function He(e,n){let t=e;for(const[a,{old:i,new:r}]of n.entries()){if(i==="")throw l(`block ${a+1}: a replacement needs non-empty old text`,{field:"replace"});const o=t.indexOf(i);if(o<0)throw l(`block ${a+1}: old text not found: ${JSON.stringify(i.slice(0,120))}`,{field:"replace",old:i});if(t.indexOf(i,o+1)>=0)throw l(`block ${a+1}: old text occurs more than once: ${JSON.stringify(i.slice(0,120))}`,{field:"replace",old:i});t=t.slice(0,o)+r+t.slice(o+i.length)}return t}s(He,"applyReplacements");async function at(e,n,t,a,i,r,o={},u){if(h(t),await L(e,n,t),t.deleted_at)throw l(`${t.name} is deleted`);if(r===null)throw new N("read_first",`read ${t.name} before editing it`,g.GENERAL,{id:t.id});if(i.trim()==="")throw l("an edit needs a rationale",{field:"rationale"});const w=await v(e,t);if(w.heads_key!==r)return{stale:{heads_key:w.heads_key,revision:w.revision}};const c=await I(e,t);let p;if("body"in a)p=a.body;else{if(c.length!==1)throw l(`${t.name} has ${c.length} heads and needs a merge; pass the whole merged body`,{heads:c.length});p=He(await b(e,t,we(c[0].body_hash)),a.replace)}const d=B(p);try{return await y(async()=>{const m=E(n),T=1+Math.max(0,...c.map(_=>_.number)),P=await S(e,t,{number:T,body_hash:d,author:m,rationale:i,verbatim:a.verbatim===!0,parents:c.map(_=>_.rev_id),acting:await C(e,n)}),H=P.rev_id,re=await e.prepare("UPDATE documents SET heads_key = ?, revision = ? WHERE object = ? AND heads_key = ?").run(H,T,t.id,r);if(Number(re.changes)===0){const _=await v(e,t);throw new te(_.heads_key,_.revision)}const D=await J(e,t,o),oe=await Z(e,n,D,p);return await O(e,m,D),await j(e,m,D,"write",{readHeadsKey:H}),await k(e,{event:"edited",object:D,actor:m,pointer:P.rev_id,attrs:{revision:T,from_revision:w.revision,rationale:i}},u),{revision:T,heads_key:H,unresolved:oe}},{savepoint:!0})}catch(m){if(m instanceof te)return{stale:{heads_key:m.heads_key,revision:m.revision}};throw m}}s(at,"editDocument");class te extends Error{static{s(this,"StaleEdit")}heads_key;revision;constructor(n,t){super("stale"),this.heads_key=n,this.revision=t}}async function it(e,n,t,a,i){if(h(t),n&&(await L(e,n,t),i===void 0||i.trim()===""))throw l("an edit needs a rationale",{field:"rationale"});return await y(async()=>{const r=await J(e,t,a);if(n){const o=E(n),u=await v(e,t),w=await $e(e,n,t),c=await I(e,t),p=c[0];await S(e,t,{number:1+Math.max(...c.map(m=>m.number)),body_hash:p.body_hash,author:o,rationale:i,parents:[p.rev_id],acting:await C(e,n)});const{heads_key:d}=await $(e,t);await O(e,o,r),await j(e,o,t,"write",w===u.heads_key?{readHeadsKey:d}:{})}return r},{savepoint:!0})}s(it,"setDocumentRecord");async function Ce(e,n,t,a){const i=E(n),{heads_key:r}=await v(e,t);await j(e,i,t,"read",{readHeadsKey:r}),await k(e,{event:"read",object:t,actor:i,attrs:{}},a)}s(Ce,"recordRead");async function rt(e,n,t,a){return h(t),await U(e,n,t),await y(async()=>{const i=await v(e,t),r=await x(await I(e,t),async c=>({...c,body:await b(e,t,ue(c.body_hash,i.source_path))})),o=await fe(e,await Oe(e,n),"o"),w=(await e.prepareUncached(`SELECT e.written_as AS _written_as, o.* FROM edges e JOIN objects o ON o.id = e.dst
12
- WHERE e.type = 'links-to' AND e.src = ? AND (${o.sql}) ORDER BY e.written_as`).all(t.id,...o.params)).map(({_written_as:c,...p})=>({written_as:c??"",object:p,deleted:p.deleted_at!==null}));return t.deleted_at||await Ce(e,n,t,a),{object:t,record:i,heads:r,needsMerge:r.length>1,links:w}},{savepoint:!0})}s(rt,"readDocument");async function ot(e,n,t){h(t),await U(e,n,t);const a=await e.prepare("SELECT * FROM revisions WHERE document = ? ORDER BY number DESC, created_at DESC, seq DESC").all(t.id);return await x(a,async i=>await z(e,i))}s(ot,"history");async function st(e,n,t,a,i){if(h(t),t.deleted_at)throw l(`${t.name} is deleted`);return n&&await L(e,n,t),await y(async()=>{const r=a.owner??await f(e,t.owner),o=a.name??t.name;await M(e,r,o,t),await W(e,r,t.privacy),await e.prepare("UPDATE objects SET name = ?, owner = ? WHERE id = ?").run(o,r.id,t.id);const u=await f(e,t.id);if(r.id!==t.owner&&(await Ne(e,u),n&&!await A(e,n,"edit",u.id)))throw R(`no owner ${r.name} you can move documents to`);return n&&await O(e,E(n),u),(o!==t.name||r.id!==t.owner)&&await F(e,u,i),u},{savepoint:!0})}s(st,"moveDocument");async function ct(e,n,t,a,i){return h(t),t.deleted_at?t:(n&&await L(e,n,t),await y(async()=>{const r=await ne(e,n,t,a,i);return await We(e,t),await ie(e,await ae(e,t.id),a),r},{savepoint:!0}))}s(ct,"deleteDocument");async function We(e,n){if(n.schema_type==null)return;const t=await e.prepare(`SELECT resource_type, resource_id, relation, subject_type, subject_id, subject_relation FROM relations
13
- WHERE (resource_type = ? AND resource_id = ?) OR (subject_type = ? AND subject_id = ?)`).all(n.schema_type,n.id,n.schema_type,n.id);for(const a of t)await Se(e,{resourceType:a.resource_type,resourceId:a.resource_id,relation:a.relation,subject:{type:a.subject_type,id:a.subject_id,relation:a.subject_relation}})}s(We,"deleteFactsOf");async function ut(e,n,t,a){return h(n),n.deleted_at?await y(async()=>{await M(e,await f(e,n.owner),t,n),await e.prepare("UPDATE objects SET deleted_at = NULL, name = ? WHERE id = ?").run(t,n.id);const i=await f(e,n.id);return await F(e,i,a),i},{savepoint:!0}):n}s(ut,"restoreDocument");async function ne(e,n,t,a,i){await e.prepare("UPDATE objects SET deleted_at = ? WHERE id = ?").run(i??new Date().toISOString(),t.id);const r=await f(e,t.id),o=n?E(n):null;return o&&await O(e,o,r),await k(e,{event:"deleted",object:r,actor:o,attrs:{}},a),await e.prepare("DELETE FROM watches WHERE object = ?").run(t.id),await G(e,r,[]),r}s(ne,"markDeleted");async function ae(e,n){return(await e.prepare("SELECT dst FROM edges WHERE type = 'links-to' AND src = ?").all(n)).map(({dst:t})=>t)}s(ae,"linkTargets");async function ie(e,n,t){const a=e.prepare(`SELECT 1 FROM objects d JOIN objects w ON w.id = d.owner
11
+ WHERE e.type = 'links-to' AND e.src = ? AND e.written_as IS NOT NULL`).all(n.id);if(a.length===0)return t;const i=new Map;for(const r of a){if(r.deleted_at||r.written_as===r.id)continue;const o=!r.written_as.includes("/");if(o&&r.name===r.written_as)continue;const c=r.owner===null?null:await d(e,r.owner),p=c?`${await Ie(e,c,r.name)}/${r.name}`:r.name,u=o&&r.owner===n.owner?r.name:p;u!==r.written_as&&i.set(r.written_as,u)}return i.size===0?t:t.replace(/\[\[([^[\]\n]+?)\]\]/g,(r,o)=>{const c=i.get(o.trim());return c===void 0?r:`[[${c}]]`})}s(b,"withCurrentNames");async function J(e,n,t){if(t.preview!==void 0){if(t.preview.trim()==="")throw m("a document needs a preview",{field:"preview"});await e.prepare("UPDATE objects SET preview = ? WHERE id = ?").run(t.preview,n.id)}t.privacy!==void 0&&(await M(e,await d(e,n.owner),t.privacy),await e.prepare("UPDATE objects SET privacy = ? WHERE id = ?").run(t.privacy,n.id));const a=s(async(r,o)=>await e.prepareUncached(`UPDATE documents SET ${r} = ? WHERE object = ?`).run(o,n.id),"set");t.summary!==void 0&&await a("summary",t.summary),t.whyItExists!==void 0&&await a("why_it_exists",t.whyItExists),t.unlisted!==void 0&&await a("unlisted",t.unlisted?1:0),t.lintIgnore!==void 0&&await a("lint_ignore_json",t.lintIgnore===null?null:JSON.stringify(t.lintIgnore)),t.extensions!==void 0&&await a("extensions_json",t.extensions===null?null:JSON.stringify(t.extensions));const i=await d(e,n.id);return t.rules!==void 0&&await G(e,i,t.rules),i}s(J,"applyDocumentFields");async function ee(e,n,t,a="crtr"){if(n.owner.deleted_at)throw m(`${n.owner.name} is deleted`);if(n.relations?.length&&n.schemaType===void 0)throw new je(400,"schema_type_required","relations need a schemaType: they are facts about a typed resource",{field:"relations"});if(n.preview.trim()==="")throw m("a document needs a preview",{field:"preview"});await M(e,n.owner,n.privacy),await W(e,n.owner,n.name);const i=await he(e,{...n.id?{id:n.id}:{},type:"document",kind:n.kind,owner:n.owner.id,privacy:n.privacy,name:n.name,preview:n.preview,...n.schemaType!==void 0?{schema_type:n.schemaType}:{}});for(const{relation:o,subject:c}of n.relations??[])await ke(e,{resourceType:n.schemaType,resourceId:i.id,relation:o,subject:Te(c),writtenBy:a});await e.prepare("INSERT INTO documents (object, heads_key, revision) VALUES (?, '', 0)").run(i.id);const r=await J(e,i,{...n,preview:void 0,privacy:void 0,rules:n.rules??[]});return await F(e,r,t),r}s(ee,"createDocument");async function $(e,n){const t=await I(e,n),a=Ae(t),i=Math.max(0,...t.map(r=>r.number));return await e.prepare("UPDATE documents SET heads_key = ?, revision = ? WHERE object = ?").run(a,i,n.id),{heads_key:a,revision:i,headCount:t.length}}s($,"setHeads");async function nt(e,n,t,a,i){return await y(async()=>{const r=await B(e,a),o=E(n),c=await Y(e,n,t.owner.id,"rw",!0);if(c!==null)throw X(c);const p=await C(e,n),u=await ee(e,t,i,p.app??p.for);t.owner.type==="app"&&t.schemaType===void 0&&await De(e,u.id,await K(e,n));const l=await S(e,u,{number:1,body_hash:r,author:o,rationale:null,parents:[],acting:p});if(!await A(e,n,"edit",u.id))throw N(`no owner ${t.owner.name} you can write to`);const{heads_key:w}=await $(e,u),f=await Z(e,n,u,a);return await O(e,o,u),await j(e,o,u,"write",{readHeadsKey:w}),await k(e,{event:"created",object:u,actor:o,attrs:{revision:1},pointer:l.rev_id},i),{object:u,revision:1,unresolved:f}},{savepoint:!0})}s(nt,"writeDocument");async function $e(e,n,t){return(await e.prepare("SELECT read_heads_key FROM watches WHERE watcher = ? AND object = ?").get(E(n).id,t.id))?.read_heads_key??null}s($e,"readBase");function He(e,n){let t=e;for(const[a,{old:i,new:r}]of n.entries()){if(i==="")throw m(`block ${a+1}: a replacement needs non-empty old text`,{field:"replace"});const o=t.indexOf(i);if(o<0)throw m(`block ${a+1}: old text not found: ${JSON.stringify(i.slice(0,120))}`,{field:"replace",old:i});if(t.indexOf(i,o+1)>=0)throw m(`block ${a+1}: old text occurs more than once: ${JSON.stringify(i.slice(0,120))}`,{field:"replace",old:i});t=t.slice(0,o)+r+t.slice(o+i.length)}return t}s(He,"applyReplacements");async function at(e,n,t,a,i,r,o={},c){if(h(t),await L(e,n,t),t.deleted_at)throw m(`${t.name} is deleted`);if(r===null)throw new R("read_first",`read ${t.name} before editing it`,g.GENERAL,{id:t.id});if(i.trim()==="")throw m("an edit needs a rationale",{field:"rationale"});const p=await v(e,t);if(p.heads_key!==r)return{stale:{heads_key:p.heads_key,revision:p.revision}};const u=await I(e,t);let l;if("body"in a)l=a.body;else{if(u.length!==1)throw m(`${t.name} has ${u.length} heads and needs a merge; pass the whole merged body`,{heads:u.length});l=He(await b(e,t,await ue(e,u[0].body_hash)),a.replace)}try{return await y(async()=>{const w=await B(e,l),f=E(n),T=1+Math.max(0,...u.map(_=>_.number)),P=await S(e,t,{number:T,body_hash:w,author:f,rationale:i,verbatim:a.verbatim===!0,parents:u.map(_=>_.rev_id),acting:await C(e,n)}),H=P.rev_id,re=await e.prepare("UPDATE documents SET heads_key = ?, revision = ? WHERE object = ? AND heads_key = ?").run(H,T,t.id,r);if(Number(re.changes)===0){const _=await v(e,t);throw new te(_.heads_key,_.revision)}const D=await J(e,t,o),oe=await Z(e,n,D,l);return await O(e,f,D),await j(e,f,D,"write",{readHeadsKey:H}),await k(e,{event:"edited",object:D,actor:f,pointer:P.rev_id,attrs:{revision:T,from_revision:p.revision,rationale:i}},c),{revision:T,heads_key:H,unresolved:oe}},{savepoint:!0})}catch(w){if(w instanceof te)return{stale:{heads_key:w.heads_key,revision:w.revision}};throw w}}s(at,"editDocument");class te extends Error{static{s(this,"StaleEdit")}heads_key;revision;constructor(n,t){super("stale"),this.heads_key=n,this.revision=t}}async function it(e,n,t,a,i){if(h(t),n&&(await L(e,n,t),i===void 0||i.trim()===""))throw m("an edit needs a rationale",{field:"rationale"});return await y(async()=>{const r=await J(e,t,a);if(n){const o=E(n),c=await v(e,t),p=await $e(e,n,t),u=await I(e,t),l=u[0];await S(e,t,{number:1+Math.max(...u.map(f=>f.number)),body_hash:l.body_hash,author:o,rationale:i,parents:[l.rev_id],acting:await C(e,n)});const{heads_key:w}=await $(e,t);await O(e,o,r),await j(e,o,t,"write",p===c.heads_key?{readHeadsKey:w}:{})}return r},{savepoint:!0})}s(it,"setDocumentRecord");async function Ce(e,n,t,a){const i=E(n),{heads_key:r}=await v(e,t);await j(e,i,t,"read",{readHeadsKey:r}),await k(e,{event:"read",object:t,actor:i,attrs:{}},a)}s(Ce,"recordRead");async function rt(e,n,t,a){return h(t),await U(e,n,t),await y(async()=>{const i=await v(e,t),r=await I(e,t),o=await we(e,r.map(w=>w.body_hash)),c=await x(r,async w=>({...w,body:await b(e,t,o.get(w.body_hash))})),p=await fe(e,await Oe(e,n),"o"),l=(await e.prepareUncached(`SELECT e.written_as AS _written_as, o.* FROM edges e JOIN objects o ON o.id = e.dst
12
+ WHERE e.type = 'links-to' AND e.src = ? AND (${p.sql}) ORDER BY e.written_as`).all(t.id,...p.params)).map(({_written_as:w,...f})=>({written_as:w??"",object:f,deleted:f.deleted_at!==null}));return t.deleted_at||await Ce(e,n,t,a),{object:t,record:i,heads:c,needsMerge:c.length>1,links:l}},{savepoint:!0})}s(rt,"readDocument");async function ot(e,n,t){h(t),await U(e,n,t);const a=await e.prepare("SELECT * FROM revisions WHERE document = ? ORDER BY number DESC, created_at DESC, seq DESC").all(t.id);return await x(a,async i=>await z(e,i))}s(ot,"history");async function st(e,n,t,a,i){if(h(t),t.deleted_at)throw m(`${t.name} is deleted`);return n&&await L(e,n,t),await y(async()=>{const r=a.owner??await d(e,t.owner),o=a.name??t.name;await W(e,r,o,t),await M(e,r,t.privacy),await e.prepare("UPDATE objects SET name = ?, owner = ? WHERE id = ?").run(o,r.id,t.id);const c=await d(e,t.id);if(r.id!==t.owner&&(await Re(e,c),n&&!await A(e,n,"edit",c.id)))throw N(`no owner ${r.name} you can move documents to`);return n&&await O(e,E(n),c),(o!==t.name||r.id!==t.owner)&&await F(e,c,i),c},{savepoint:!0})}s(st,"moveDocument");async function ct(e,n,t,a,i){return h(t),t.deleted_at?t:(n&&await L(e,n,t),await y(async()=>{const r=await ne(e,n,t,a,i);return await Me(e,t),await ie(e,await ae(e,t.id),a),r},{savepoint:!0}))}s(ct,"deleteDocument");async function Me(e,n){if(n.schema_type==null)return;const t=await e.prepare(`SELECT resource_type, resource_id, relation, subject_type, subject_id, subject_relation FROM relations
13
+ WHERE (resource_type = ? AND resource_id = ?) OR (subject_type = ? AND subject_id = ?)`).all(n.schema_type,n.id,n.schema_type,n.id);for(const a of t)await Se(e,{resourceType:a.resource_type,resourceId:a.resource_id,relation:a.relation,subject:{type:a.subject_type,id:a.subject_id,relation:a.subject_relation}})}s(Me,"deleteFactsOf");async function ut(e,n,t,a){return h(n),n.deleted_at?await y(async()=>{await W(e,await d(e,n.owner),t,n),await e.prepare("UPDATE objects SET deleted_at = NULL, name = ? WHERE id = ?").run(t,n.id);const i=await d(e,n.id);return await F(e,i,a),i},{savepoint:!0}):n}s(ut,"restoreDocument");async function ne(e,n,t,a,i){await e.prepare("UPDATE objects SET deleted_at = ? WHERE id = ?").run(i??new Date().toISOString(),t.id);const r=await d(e,t.id),o=n?E(n):null;return o&&await O(e,o,r),await k(e,{event:"deleted",object:r,actor:o,attrs:{}},a),await e.prepare("DELETE FROM watches WHERE object = ?").run(t.id),await G(e,r,[]),r}s(ne,"markDeleted");async function ae(e,n){return(await e.prepare("SELECT dst FROM edges WHERE type = 'links-to' AND src = ?").all(n)).map(({dst:t})=>t)}s(ae,"linkTargets");async function ie(e,n,t){const a=e.prepare(`SELECT 1 FROM objects d JOIN objects w ON w.id = d.owner
14
14
  WHERE d.id = ? AND d.type = 'document' AND d.deleted_at IS NULL AND w.deleted_at IS NOT NULL
15
15
  AND NOT EXISTS (SELECT 1 FROM edges e JOIN objects s ON s.id = e.src
16
- WHERE e.type = 'links-to' AND e.dst = d.id AND e.src != d.id AND s.deleted_at IS NULL)`),i=[...n];for(;i.length>0;){const r=i.pop();await a.get(r)!==void 0&&(await ne(e,null,await f(e,r),t),i.push(...await ae(e,r)))}}s(ie,"collectUnlinked");async function wt(e,n,t){if(n.revisions.length===0)throw l(`import of ${n.name}: no revisions`);return await y(async()=>{const a=await ee(e,n,t);await e.prepare("UPDATE objects SET created_at = ? WHERE id = ?").run(n.revisions[0].created_at,a.id);let i=null;return await ge(n.revisions,async(r,o)=>{i=(await S(e,a,{...r,number:o+1,parents:i===null?[]:[i]})).rev_id}),await $(e,a),await f(e,a.id)},{savepoint:!0})}s(wt,"importDocument");async function pt(e,n,t){if(h(n),n.privacy!=="public"&&await V(e,n)==="repo")throw l(`a repo owns only public documents; refusing revisions of ${n.name}`);return await y(async()=>{const a=e.prepare("SELECT rev_id, number, document FROM revisions WHERE rev_id = ?");let i=await Re(t,async u=>!await a.get(u.rev_id));for(;i.length>0;){const u=[];for(const w of i){const c=await x(w.parents,async d=>await a.get(d));if(c.some(d=>d===void 0)){u.push(w);continue}const p=c;if(p.some(d=>d.document!==n.id))throw l(`revision ${w.rev_id} names a parent of another document`);await S(e,n,{...w,author:null,number:1+Math.max(0,...p.map(d=>d.number)),parents:p.map(d=>d.rev_id)})}if(u.length===i.length)throw l(`revisions of ${n.name} name unknown parents: ${u.map(w=>w.rev_id).join(", ")}`);i=u}const{heads_key:r,headCount:o}=await $(e,n);return{heads_key:r,headCount:o}},{savepoint:!0})}s(pt,"insertImportedRevisions");async function lt(e,n){const t=await f(e,n);if(!t||t.type!=="document")throw R(`no document ${n}`);return t}s(lt,"documentById");export{E as actorOf,J as applyDocumentFields,He as applyReplacements,ie as collectUnlinked,ct as deleteDocument,lt as documentById,v as documentRecord,at as editDocument,I as heads,ot as history,wt as importDocument,pt as insertImportedRevisions,Le as linksIn,st as moveDocument,$e as readBase,rt as readDocument,Ce as recordRead,ut as restoreDocument,it as setDocumentRecord,Z as setRevisionLinks,b as withCurrentNames,nt as writeDocument};
16
+ WHERE e.type = 'links-to' AND e.dst = d.id AND e.src != d.id AND s.deleted_at IS NULL)`),i=[...n];for(;i.length>0;){const r=i.pop();await a.get(r)!==void 0&&(await ne(e,null,await d(e,r),t),i.push(...await ae(e,r)))}}s(ie,"collectUnlinked");async function wt(e,n,t){if(n.revisions.length===0)throw m(`import of ${n.name}: no revisions`);return await y(async()=>{const a=await ee(e,n,t);await e.prepare("UPDATE objects SET created_at = ? WHERE id = ?").run(n.revisions[0].created_at,a.id);let i=null;return await ge(n.revisions,async(r,o)=>{i=(await S(e,a,{...r,number:o+1,parents:i===null?[]:[i]})).rev_id}),await $(e,a),await d(e,a.id)},{savepoint:!0})}s(wt,"importDocument");async function pt(e,n,t){if(h(n),n.privacy!=="public"&&await V(e,n)==="repo")throw m(`a repo owns only public documents; refusing revisions of ${n.name}`);return await y(async()=>{const a=e.prepare("SELECT rev_id, number, document FROM revisions WHERE rev_id = ?");let i=await Ne(t,async c=>!await a.get(c.rev_id));for(;i.length>0;){const c=[];for(const p of i){const u=await x(p.parents,async w=>await a.get(w));if(u.some(w=>w===void 0)){c.push(p);continue}const l=u;if(l.some(w=>w.document!==n.id))throw m(`revision ${p.rev_id} names a parent of another document`);await S(e,n,{...p,author:null,number:1+Math.max(0,...l.map(w=>w.number)),parents:l.map(w=>w.rev_id)})}if(c.length===i.length)throw m(`revisions of ${n.name} name unknown parents: ${c.map(p=>p.rev_id).join(", ")}`);i=c}const{heads_key:r,headCount:o}=await $(e,n);return{heads_key:r,headCount:o}},{savepoint:!0})}s(pt,"insertImportedRevisions");async function lt(e,n){const t=await d(e,n);if(!t||t.type!=="document")throw N(`no document ${n}`);return t}s(lt,"documentById");export{E as actorOf,J as applyDocumentFields,He as applyReplacements,ie as collectUnlinked,ct as deleteDocument,lt as documentById,v as documentRecord,at as editDocument,I as heads,ot as history,wt as importDocument,pt as insertImportedRevisions,Le as linksIn,st as moveDocument,$e as readBase,rt as readDocument,Ce as recordRead,ut as restoreDocument,it as setDocumentRecord,Z as setRevisionLinks,b as withCurrentNames,nt as writeDocument};
@@ -1,17 +1,17 @@
1
- var M=Object.defineProperty;var s=(e,t)=>M(e,"name",{value:t,configurable:!0});import{CrtrError as y,notFound as H}from"../errors.js";import{ExitCode as I}from"../../types.js";import{newNodeId as W}from"../runtime/nodes.js";import{kindOf as v}from"./kinds/registry.js";import{bodyOf as k}from"./bodies.js";import{isCloudRole as L,personId as J,personIdAsync as P}from"../canvas/person.js";import{check as K,permits as O,resolveReader as b}from"../permissions/evaluator.js";import{userSubject as D,writeActsFor as T}from"../permissions/facts.js";const Y=new Set(["person","app","repo","profile"]);function x(e){return e||null}s(x,"rowFrom");async function X(e,t){return x(await e.prepare("SELECT * FROM objects WHERE id = ?").get(t))}s(X,"getById");async function z(e,t){return await v(e,t.type),await e.prepare(`INSERT INTO objects (id, type, kind, owner, privacy, name, preview, created_at, home_machine, schema_type)
2
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`).get(t.id??await W(),t.type,t.kind,t.owner,t.privacy,t.name,t.preview,new Date().toISOString(),t.home_machine??null,t.schema_type??null)}s(z,"createObject");const B=s((e,t)=>`owner:${e}:${t}`,"ownerLockKey");async function $(e,t,o,i,n,c){if(!Y.has(t))throw new Error(`ensureOwner: ${t} is not an owner type`);const d=s(async()=>await e.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
3
- WHERE i.scheme = ? AND i.external_id = ?`).get(o,i),"find"),E=await d();if(E)return E;await e.prepare("SELECT pg_advisory_xact_lock(hashtext(?))").get(B(o,i));const w=await d();if(w)return w;const p=await e.prepare(`SELECT o.id, o.type, i.scheme, i.external_id FROM objects o
1
+ var W=Object.defineProperty;var s=(e,t)=>W(e,"name",{value:t,configurable:!0});import{CrtrError as D,notFound as T}from"../errors.js";import{ExitCode as x}from"../../types.js";import{newNodeId as v}from"../runtime/nodes.js";import{kindOf as k}from"./kinds/registry.js";import{findMany as J}from"./bodies.js";import{isCloudRole as O,personId as P,personIdAsync as Y}from"../canvas/person.js";import{check as B,permits as A,resolveReader as $}from"../permissions/evaluator.js";import{userSubject as C,writeActsFor as g}from"../permissions/facts.js";const K=new Set(["person","app","repo","profile"]);function U(e){return e||null}s(U,"rowFrom");async function X(e,t){return U(await e.prepare("SELECT * FROM objects WHERE id = ?").get(t))}s(X,"getById");async function z(e,t){return await k(e,t.type),await e.prepare(`INSERT INTO objects (id, type, kind, owner, privacy, name, preview, created_at, home_machine, schema_type)
2
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`).get(t.id??await v(),t.type,t.kind,t.owner,t.privacy,t.name,t.preview,new Date().toISOString(),t.home_machine??null,t.schema_type??null)}s(z,"createObject");const G=s((e,t)=>`owner:${e}:${t}`,"ownerLockKey");async function F(e,t,o,i,n,d){if(!K.has(t))throw new Error(`ensureOwner: ${t} is not an owner type`);const p=s(async()=>await e.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
3
+ WHERE i.scheme = ? AND i.external_id = ?`).get(o,i),"find"),u=await p();if(u)return u;await e.prepare("SELECT pg_advisory_xact_lock(hashtext(?))").get(G(o,i));const f=await p();if(f)return f;const E=await e.prepare(`SELECT o.id, o.type, i.scheme, i.external_id FROM objects o
4
4
  LEFT JOIN identities i ON i.object = o.id
5
- WHERE o.name = ? AND o.type = ? AND o.home_machine IS NULL`).get(n,t);if(p)throw new y("conflict",`handle "${n}" is held by ${p.type} ${p.id} (${p.scheme}:${p.external_id}); cannot give it to ${t} ${o}:${i}`,I.GENERAL,{handle:n,holder:p.id});const N=await z(e,{type:t,kind:null,owner:t==="profile"?c?.id??null:null,privacy:"public",name:n,preview:""});return await e.prepare("INSERT INTO identities (object, scheme, external_id) VALUES (?, ?, ?)").run(N.id,o,i),t==="person"&&o==="user"?await T(e,N.id,D(i).id):(t==="profile"||t==="repo")&&await T(e,N.id,D(await P()).id),N}s($,"ensureOwner");async function se(e,t,o){if(L())return!1;const i=await e.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
5
+ WHERE o.name = ? AND o.type = ? AND o.home_machine IS NULL`).get(n,t);if(E)throw new D("conflict",`handle "${n}" is held by ${E.type} ${E.id} (${E.scheme}:${E.external_id}); cannot give it to ${t} ${o}:${i}`,x.GENERAL,{handle:n,holder:E.id});const h=await z(e,{type:t,kind:null,owner:t==="profile"?d?.id??null:null,privacy:"public",name:n,preview:""});return await e.prepare("INSERT INTO identities (object, scheme, external_id) VALUES (?, ?, ?)").run(h.id,o,i),t==="person"&&o==="user"?await g(e,h.id,C(i).id):(t==="profile"||t==="repo")&&await g(e,h.id,C(await Y()).id),h}s(F,"ensureOwner");async function ae(e,t,o){if(O())return!1;const i=await e.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
6
6
  WHERE i.scheme = 'profile' AND i.external_id = ? AND o.type = 'profile' AND o.deleted_at IS NULL`).get(t),n=await e.prepare(`SELECT o.id FROM objects o
7
- WHERE o.name = ? AND o.type = 'profile' AND o.deleted_at IS NULL AND o.home_machine IS NULL AND o.id <> ?`).get(o,i?i.id:"");if(n)throw new y("conflict",`handle "${o}" is already held by profile object ${n.id}; cannot rename profile ${t} to it`,I.GENERAL,{handle:o,holder:n.id});if(!i)return!1;const c=i;return c.name===o?!1:(await G(e,c,o),!0)}s(se,"renameProfileObject");function ae(e,t){return L()?e:t}s(ae,"profileHandle");async function ce(e,t){if(/^app:[a-z0-9][a-z0-9._-]*$/.test(t))return await $(e,"app","app",t,t.slice(4));if(/^user:[a-z0-9][a-z0-9._-]*$/.test(t))return await $(e,"person","user",t,!L()&&t===J()?"user":t.slice(5));throw new Error(`principal ${JSON.stringify(t)} is neither app:<name> nor user:<id>`)}s(ce,"ownerForGrantee");async function de(e,t,o){return x(await e.prepare("SELECT * FROM objects WHERE (type = 'document' OR strpos(type, '.') > 0) AND owner = ? AND name = ? AND deleted_at IS NULL AND home_machine IS NULL").get(t,o))}s(de,"liveDocumentNamed");async function pe(e,t){const o=await X(e,t);if(!o||o.type!=="node")throw H(`no node object ${t}`);return o}s(pe,"nodeObject");async function G(e,t,o){await e.prepare("UPDATE objects SET name = ? WHERE id = ?").run(o,t.id)}s(G,"setObjectName");async function ue(e,t,o){await e.prepare("UPDATE objects SET kind = ? WHERE id = ?").run(o,t.id)}s(ue,"setObjectKind");function A(e,t){const o=[];return e.type!==void 0&&(o.push("o.type = ?"),t.push(e.type)),e.kind!==void 0&&(o.push("o.kind = ?"),t.push(e.kind)),e.owner!==void 0&&(o.push("o.owner = ?"),t.push(e.owner.id)),e.namePrefix!==void 0&&(o.push("o.name LIKE ? ESCAPE '\\'"),t.push(`${e.namePrefix.replace(/[\\%_]/g,i=>`\\${i}`)}%`)),e.schemaType!==void 0&&(o.push("o.schema_type = ?"),t.push(e.schemaType)),e.extensions!==void 0&&(o.push("EXISTS (SELECT 1 FROM documents x WHERE x.object = o.id AND x.extensions_json::jsonb @> ?::jsonb)"),t.push(JSON.stringify(e.extensions))),o}s(A,"narrowing");function V(e,t){const o="WITH RECURSIVE sa(id, owner, depth) AS (SELECT o.id, o.owner, 0 UNION ALL SELECT p.id, p.owner, sa.depth + 1 FROM sa JOIN objects p ON p.id = sa.owner WHERE sa.depth < 12)";t.push(e.subjects.userIds,e.subjects.groupIds);const i=["o.schema_type IS NULL",`EXISTS (${o} SELECT 1 FROM sa JOIN relations r ON r.resource_type = 'crtr/object' AND r.resource_id = sa.id
7
+ WHERE o.name = ? AND o.type = 'profile' AND o.deleted_at IS NULL AND o.home_machine IS NULL AND o.id <> ?`).get(o,i?i.id:"");if(n)throw new D("conflict",`handle "${o}" is already held by profile object ${n.id}; cannot rename profile ${t} to it`,x.GENERAL,{handle:o,holder:n.id});if(!i)return!1;const d=i;return d.name===o?!1:(await V(e,d,o),!0)}s(ae,"renameProfileObject");function ce(e,t){return O()?e:t}s(ce,"profileHandle");async function de(e,t){if(/^app:[a-z0-9][a-z0-9._-]*$/.test(t))return await F(e,"app","app",t,t.slice(4));if(/^user:[a-z0-9][a-z0-9._-]*$/.test(t))return await F(e,"person","user",t,!O()&&t===P()?"user":t.slice(5));throw new Error(`principal ${JSON.stringify(t)} is neither app:<name> nor user:<id>`)}s(de,"ownerForGrantee");async function pe(e,t,o){return U(await e.prepare("SELECT * FROM objects WHERE (type = 'document' OR strpos(type, '.') > 0) AND owner = ? AND name = ? AND deleted_at IS NULL AND home_machine IS NULL").get(t,o))}s(pe,"liveDocumentNamed");async function ue(e,t){const o=await X(e,t);if(!o||o.type!=="node")throw T(`no node object ${t}`);return o}s(ue,"nodeObject");async function V(e,t,o){await e.prepare("UPDATE objects SET name = ? WHERE id = ?").run(o,t.id)}s(V,"setObjectName");async function Ee(e,t,o){await e.prepare("UPDATE objects SET kind = ? WHERE id = ?").run(o,t.id)}s(Ee,"setObjectKind");function _(e,t){const o=[];return e.type!==void 0&&(o.push("o.type = ?"),t.push(e.type)),e.kind!==void 0&&(o.push("o.kind = ?"),t.push(e.kind)),e.owner!==void 0&&(o.push("o.owner = ?"),t.push(e.owner.id)),e.namePrefix!==void 0&&(o.push("o.name LIKE ? ESCAPE '\\'"),t.push(`${e.namePrefix.replace(/[\\%_]/g,i=>`\\${i}`)}%`)),e.schemaType!==void 0&&(o.push("o.schema_type = ?"),t.push(e.schemaType)),e.extensions!==void 0&&(o.push("EXISTS (SELECT 1 FROM documents x WHERE x.object = o.id AND x.extensions_json::jsonb @> ?::jsonb)"),t.push(JSON.stringify(e.extensions))),o}s(_,"narrowing");function Q(e,t){const o="WITH RECURSIVE sa(id, owner, depth) AS (SELECT o.id, o.owner, 0 UNION ALL SELECT p.id, p.owner, sa.depth + 1 FROM sa JOIN objects p ON p.id = sa.owner WHERE sa.depth < 12)";t.push(e.subjects.userIds,e.subjects.groupIds);const i=["o.schema_type IS NULL",`EXISTS (${o} SELECT 1 FROM sa JOIN relations r ON r.resource_type = 'crtr/object' AND r.resource_id = sa.id
8
8
  AND r.relation IN ('reader', 'editor', 'messenger', 'user_of') AND r.written_by <> 'package'
9
9
  AND ((r.subject_type = 'user' AND r.subject_id = ANY(?::text[])) OR (r.subject_type = 'crtr/group' AND r.subject_relation = 'member' AND r.subject_id = ANY(?::text[]))))`];return e.userId===null?i:(t.push(e.userId),[...i,`NOT EXISTS (${o} SELECT 1 FROM sa JOIN relations r ON r.resource_type = 'crtr/object' AND r.resource_id = sa.id
10
- AND r.relation = 'acts_for' AND r.subject_type = 'user' AND r.subject_id = ?)`])}s(V,"sharedOnly");async function Ee(e,t,o,i={}){const n=["o.seq > ?"],c=[i.after_seq??0];o.privacy!==void 0&&(n.push("o.privacy = ?"),c.push(o.privacy)),o.name!==void 0&&(n.push("o.name = ?"),c.push(o.name)),n.push("o.home_machine IS NULL"),n.push(o.deleted?"o.deleted_at IS NOT NULL":"o.deleted_at IS NULL"),n.push(...A(o,c));const d=await b(e,t);o.shared&&n.push(...V(d,c));const E=await O(e,d,"read","o"),w=Math.min(i.limit??100,100);return await e.prepareUncached(`SELECT o.* FROM objects o WHERE ${n.join(" AND ")} AND (${E.sql}) ORDER BY o.seq LIMIT ${w}`).all(...c,...E.params)}s(Ee,"listObjects");async function me(e,t,o){const n=[],c=`%${o.text.replace(/[\\%_]/g,r=>`\\${r}`)}%`,d=await b(e,t);if(!o.grep){const r=[c],u=A(o,r).map(l=>` AND ${l}`).join(""),a=await O(e,d,"read","o"),m=await e.prepareUncached(`SELECT o.*, (SELECT d.summary FROM documents d WHERE d.object = o.id) AS _summary FROM objects o
10
+ AND r.relation = 'acts_for' AND r.subject_type = 'user' AND r.subject_id = ?)`])}s(Q,"sharedOnly");async function me(e,t,o,i={}){const n=["o.seq > ?"],d=[i.after_seq??0];o.privacy!==void 0&&(n.push("o.privacy = ?"),d.push(o.privacy)),o.name!==void 0&&(n.push("o.name = ?"),d.push(o.name)),n.push("o.home_machine IS NULL"),n.push(o.deleted?"o.deleted_at IS NOT NULL":"o.deleted_at IS NULL"),n.push(..._(o,d));const p=await $(e,t);o.shared&&n.push(...Q(p,d));const u=await A(e,p,"read","o"),f=Math.min(i.limit??100,100);return await e.prepareUncached(`SELECT o.* FROM objects o WHERE ${n.join(" AND ")} AND (${u.sql}) ORDER BY o.seq LIMIT ${f}`).all(...d,...u.params)}s(me,"listObjects");async function fe(e,t,o){const n=[],d=`%${o.text.replace(/[\\%_]/g,r=>`\\${r}`)}%`,p=await $(e,t);if(!o.grep){const r=[d],m=_(o,r).map(l=>` AND ${l}`).join(""),c=await A(e,p,"read","o"),a=await e.prepareUncached(`SELECT o.*, (SELECT d.summary FROM documents d WHERE d.object = o.id) AS _summary FROM objects o
11
11
  WHERE o.deleted_at IS NULL AND o.home_machine IS NULL AND (o.name ILIKE ?1 ESCAPE '\\' OR o.preview ILIKE ?1 ESCAPE '\\'
12
- OR EXISTS (SELECT 1 FROM documents d WHERE d.object = o.id AND d.summary ILIKE ?1 ESCAPE '\\'))${u}
13
- AND (${a.sql}) ORDER BY o.seq LIMIT 100`).all(...r,...a.params),f=o.text.toLowerCase();for(const l of m){const{_summary:g,...F}=l,S=F,j=S.name.toLowerCase().includes(f)?"name":S.preview.toLowerCase().includes(f)?"preview":"summary";n.push({object:S,field:j,...j==="summary"?{text:String(g)}:{}})}if(n.length>=100)return n}if(!o.body&&!o.grep)return n;const E=new Set(n.map(r=>r.object.id)),w=o.grep?(r=>u=>r.test(u))(new RegExp(o.text)):r=>r.includes(o.text),p=[],N=A(o,p).map(r=>` AND ${r}`).join(""),C=await e.prepareUncached(`SELECT o.id, r.body_hash AS body_hash, d.source_path AS source_path FROM objects o
12
+ OR EXISTS (SELECT 1 FROM documents d WHERE d.object = o.id AND d.summary ILIKE ?1 ESCAPE '\\'))${m}
13
+ AND (${c.sql}) ORDER BY o.seq LIMIT 100`).all(...r,...c.params),N=o.text.toLowerCase();for(const l of a){const{_summary:S,...H}=l,L=H,b=L.name.toLowerCase().includes(N)?"name":L.preview.toLowerCase().includes(N)?"preview":"summary";n.push({object:L,field:b,...b==="summary"?{text:String(S)}:{}})}if(n.length>=100)return n}if(!o.body&&!o.grep)return n;const u=new Set(n.map(r=>r.object.id)),f=o.grep?(r=>m=>r.test(m))(new RegExp(o.text)):r=>r.includes(o.text),E=[],h=_(o,E).map(r=>` AND ${r}`).join(""),R=await e.prepareUncached(`SELECT o.id, r.body_hash AS body_hash FROM objects o
14
14
  JOIN documents d ON d.object = o.id JOIN revisions r ON r.document = o.id
15
- WHERE o.type = 'document' AND o.deleted_at IS NULL AND NOT EXISTS (SELECT 1 FROM revision_parents p WHERE p.parent = r.rev_id)${N}
16
- ORDER BY o.seq, r.number`).all(...p),_=await O(e,d,"read","o"),U=200;let h=[];const R=s(async()=>{if(h.length===0)return!1;const r=await e.prepareUncached(`SELECT o.* FROM objects o WHERE o.id = ANY(?::text[]) AND (${_.sql})`).all(h.map(a=>a.id),..._.params),u=new Map(r.map(a=>[a.id,a]));for(const a of h){const m=u.get(a.id);if(m!==void 0&&(n.push({object:m,field:"body",line:a.line,text:a.text}),n.length>=100))return!0}return h=[],!1},"flush");for(const r of C){if(E.has(r.id))continue;let u;try{u=k(r.body_hash,r.source_path)}catch(f){if(await K(e,d,"read",r.id))throw f;continue}if(!o.grep&&!u.includes(o.text))continue;const a=u.split(`
17
- `),m=a.findIndex(w);if(!(m<0)&&(E.add(r.id),h.push({id:r.id,line:m+1,text:a[m]}),h.length>=U&&await R()))return n}return await R(),n}s(me,"searchObjects");export{z as createObject,$ as ensureOwner,X as getById,Ee as listObjects,de as liveDocumentNamed,pe as nodeObject,ce as ownerForGrantee,B as ownerLockKey,ae as profileHandle,se as renameProfileObject,me as searchObjects,ue as setObjectKind,G as setObjectName};
15
+ WHERE o.type = 'document' AND o.deleted_at IS NULL AND NOT EXISTS (SELECT 1 FROM revision_parents p WHERE p.parent = r.rev_id)${h}
16
+ ORDER BY o.seq, r.number`).all(...E),y=await A(e,p,"read","o"),M=200;let w=[];const j=s(async()=>{if(w.length===0)return!1;const r=await e.prepareUncached(`SELECT o.* FROM objects o WHERE o.id = ANY(?::text[]) AND (${y.sql})`).all(w.map(c=>c.id),...y.params),m=new Map(r.map(c=>[c.id,c]));for(const c of w){const a=m.get(c.id);if(a!==void 0&&(n.push({object:a,field:"body",line:c.line,text:c.text}),n.length>=100))return!0}return w=[],!1},"flush"),I=100;for(let r=0;r<R.length;r+=I){const m=R.slice(r,r+I).filter(a=>!u.has(a.id)),c=await J(e,m.map(a=>a.body_hash));for(const a of m){if(u.has(a.id))continue;const N=c.get(a.body_hash);if(N===void 0){if(await B(e,p,"read",a.id))throw T(`no body ${a.body_hash}`);continue}if(!o.grep&&!N.includes(o.text))continue;const l=N.split(`
17
+ `),S=l.findIndex(f);if(!(S<0)&&(u.add(a.id),w.push({id:a.id,line:S+1,text:l[S]}),w.length>=M&&await j()))return n}}return await j(),n}s(fe,"searchObjects");export{z as createObject,F as ensureOwner,X as getById,me as listObjects,pe as liveDocumentNamed,ue as nodeObject,de as ownerForGrantee,G as ownerLockKey,ce as profileHandle,ae as renameProfileObject,fe as searchObjects,Ee as setObjectKind,V as setObjectName};
@@ -1,6 +1,6 @@
1
- var S=Object.defineProperty;var u=(e,a)=>S(e,"name",{value:a,configurable:!0});import{createHash as _,randomUUID as O}from"node:crypto";import{CrtrError as g}from"../errors.js";import{emitEvent as I}from"../events/emit.js";import{quarantineBrokenPlugins as D}from"../plugin-quarantine.js";import{listInstalledPlugins as L}from"../resolver.js";import{builtinMemoryRoot as R,pluginMemoryDir as T}from"../scope.js";import{documentFields as A}from"../../migrations/004-canvas-documents/fields.js";import{applyDocumentFields as k,deleteDocument as j,documentRecord as F,heads as x,importDocument as C,linksIn as H,moveDocument as U}from"./documents.js";import{deleteShippedReader as M,writeShippedReader as W}from"../permissions/facts.js";import{setLinks as J}from"./edges.js";import{publish as b}from"./events.js";import{resolveName as P}from"./names.js";import{packageFiles as q}from"./package-files.js";import{ensureOwner as B,getById as y,setObjectKind as K}from"./objects.js";import{checkoutsForCwd as $}from"./repo-sync/repos.js";import{rulesOf as z}from"./rules.js";function G(){return[{plugin:"crtr",dir:R()},...L("user").filter(e=>e.enabled).map(e=>({plugin:e.name,dir:T(e)}))]}u(G,"packageSources");const Q=u(e=>_("sha256").update(e,"utf8").digest("hex"),"sha256");async function he(e,a){const i={created:0,updated:0,deleted:0,conflicts:[],invalid:[],quarantined:[]};if(a===void 0){i.quarantined=D();for(const n of i.quarantined)I({level:"error",event:"daemon.plugins.quarantined",fields:{plugin:n.name,file:n.file,reason:n.reason,...n.movedTo!==void 0?{moved_to:n.movedTo}:{}}})}const l=a??G(),m=u(async n=>await $(e,n),"reposFn"),o=new Set,r=[],f=new Set;for(const n of l){let s;try{if(f.has(n.plugin))throw new g("conflict",`plugin "${n.plugin}" is already provided by another package`);f.add(n.plugin),s=await B(e,"app","app",`app:${n.plugin}`,n.plugin)}catch(p){if(!(p instanceof g)||p.code!=="conflict")throw p;i.conflicts.push({plugin:n.plugin,message:p.message});continue}o.add(s.id);const{files:c,invalid:w}=q(n.dir);i.invalid.push(...w);const h=new Set;for(const p of c){const d=await Y(e,s,p,i,m);h.add(d),r.push({owner:s,id:d,body:p.body})}await v(e,s,h,i,m)}const t=await e.prepare(`SELECT DISTINCT o.owner AS id FROM objects o JOIN documents d ON d.object = o.id
2
- WHERE o.type = 'document' AND o.deleted_at IS NULL AND d.source_path IS NOT NULL`).all();for(const{id:n}of t)o.has(n)||await v(e,await y(e,n),new Set,i,m);for(const{owner:n,id:s,body:c}of r)await V(e,n,await y(e,s),c);return i}u(he,"reconcilePackageDocuments");async function V(e,a,i,l){if(i.deleted_at!==null)return;const m={class:"app",app:a,scopes:[]},o=[];for(const t of H(l)){const n=await P(e,m,t);"object"in n&&n.object.id!==i.id&&await X(e,n.object.id)&&o.push({written_as:t,dst:n.object})}const r=u(t=>JSON.stringify(t.map(n=>[n.dst,n.written_as]).sort()),"key"),f=await e.prepare("SELECT dst, written_as FROM edges WHERE type = 'links-to' AND src = ?").all(i.id);r(f)!==r(o.map(t=>({dst:t.dst.id,written_as:t.written_as})))&&await J(e,i,o)}u(V,"deriveLinks");async function X(e,a){return await e.prepare("SELECT 1 FROM documents WHERE object = ? AND source_path IS NOT NULL").get(a)!==void 0}u(X,"shippedDocument");async function Y(e,a,i,l,m){const o=A(i.frontmatter,i.body,{missingPrivacy:"public"}),r={preview:o.preview.trim()===""?i.name:o.preview,summary:o.summary,whyItExists:o.whyItExists,unlisted:o.unlisted,rules:o.rules,lintIgnore:Array.isArray(o.lintIgnore)?o.lintIgnore.filter(d=>typeof d=="string"):null,extensions:o.extensions!==null&&typeof o.extensions=="object"&&!Array.isArray(o.extensions)?o.extensions:null},f=Q(i.body);let t=await e.prepare(`SELECT o.* FROM objects o JOIN documents d ON d.object = o.id
1
+ var S=Object.defineProperty;var u=(e,a)=>S(e,"name",{value:a,configurable:!0});import{randomUUID as _}from"node:crypto";import{CrtrError as g}from"../errors.js";import{emitEvent as O}from"../events/emit.js";import{quarantineBrokenPlugins as I}from"../plugin-quarantine.js";import{listInstalledPlugins as D}from"../resolver.js";import{builtinMemoryRoot as L,pluginMemoryDir as R}from"../scope.js";import{documentFields as T}from"../../migrations/004-canvas-documents/fields.js";import{put as A}from"./bodies.js";import{applyDocumentFields as k,deleteDocument as j,documentRecord as F,heads as x,importDocument as C,linksIn as U,moveDocument as H}from"./documents.js";import{deleteShippedReader as M,writeShippedReader as W}from"../permissions/facts.js";import{setLinks as J}from"./edges.js";import{publish as b}from"./events.js";import{resolveName as P}from"./names.js";import{packageFiles as q}from"./package-files.js";import{ensureOwner as B,getById as h,setObjectKind as K}from"./objects.js";import{checkoutsForCwd as $}from"./repo-sync/repos.js";import{rulesOf as z}from"./rules.js";function G(){return[{plugin:"crtr",dir:L()},...D("user").filter(e=>e.enabled).map(e=>({plugin:e.name,dir:R(e)}))]}u(G,"packageSources");async function Ee(e,a){const i={created:0,updated:0,deleted:0,conflicts:[],invalid:[],quarantined:[]};if(a===void 0){i.quarantined=I();for(const n of i.quarantined)O({level:"error",event:"daemon.plugins.quarantined",fields:{plugin:n.name,file:n.file,reason:n.reason,...n.movedTo!==void 0?{moved_to:n.movedTo}:{}}})}const l=a??G(),m=u(async n=>await $(e,n),"reposFn"),o=new Set,r=[],f=new Set;for(const n of l){let s;try{if(f.has(n.plugin))throw new g("conflict",`plugin "${n.plugin}" is already provided by another package`);f.add(n.plugin),s=await B(e,"app","app",`app:${n.plugin}`,n.plugin)}catch(p){if(!(p instanceof g)||p.code!=="conflict")throw p;i.conflicts.push({plugin:n.plugin,message:p.message});continue}o.add(s.id);const{files:c,invalid:w}=q(n.dir);i.invalid.push(...w);const E=new Set;for(const p of c){const d=await X(e,s,p,i,m);E.add(d),r.push({owner:s,id:d,body:p.body})}await v(e,s,E,i,m)}const t=await e.prepare(`SELECT DISTINCT o.owner AS id FROM objects o JOIN documents d ON d.object = o.id
2
+ WHERE o.type = 'document' AND o.deleted_at IS NULL AND d.source_path IS NOT NULL`).all();for(const{id:n}of t)o.has(n)||await v(e,await h(e,n),new Set,i,m);for(const{owner:n,id:s,body:c}of r)await Q(e,n,await h(e,s),c);return i}u(Ee,"reconcilePackageDocuments");async function Q(e,a,i,l){if(i.deleted_at!==null)return;const m={class:"app",app:a,scopes:[]},o=[];for(const t of U(l)){const n=await P(e,m,t);"object"in n&&n.object.id!==i.id&&await V(e,n.object.id)&&o.push({written_as:t,dst:n.object})}const r=u(t=>JSON.stringify(t.map(n=>[n.dst,n.written_as]).sort()),"key"),f=await e.prepare("SELECT dst, written_as FROM edges WHERE type = 'links-to' AND src = ?").all(i.id);r(f)!==r(o.map(t=>({dst:t.dst.id,written_as:t.written_as})))&&await J(e,i,o)}u(Q,"deriveLinks");async function V(e,a){return await e.prepare("SELECT 1 FROM documents WHERE object = ? AND source_path IS NOT NULL").get(a)!==void 0}u(V,"shippedDocument");async function X(e,a,i,l,m){const o=T(i.frontmatter,i.body,{missingPrivacy:"public"}),r={preview:o.preview.trim()===""?i.name:o.preview,summary:o.summary,whyItExists:o.whyItExists,unlisted:o.unlisted,rules:o.rules,lintIgnore:Array.isArray(o.lintIgnore)?o.lintIgnore.filter(d=>typeof d=="string"):null,extensions:o.extensions!==null&&typeof o.extensions=="object"&&!Array.isArray(o.extensions)?o.extensions:null},f=await A(e,i.body);let t=await e.prepare(`SELECT o.* FROM objects o JOIN documents d ON d.object = o.id
3
3
  WHERE o.type = 'document' AND o.owner = ? AND o.deleted_at IS NULL AND d.source_path = ?`).get(a.id,i.path);if(t||(t=await e.prepare(`SELECT o.* FROM objects o JOIN documents d ON d.object = o.id
4
- WHERE o.type = 'document' AND o.owner = ? AND o.deleted_at IS NULL AND o.name = ? AND d.source_path IS NOT NULL`).get(a.id,i.name),t&&await e.prepare("UPDATE documents SET source_path = ? WHERE object = ?").run(i.path,t.id)),!t){const d=await C(e,{owner:a,name:i.name,kind:o.kind,privacy:"public",...r,revisions:[{body_hash:f,author:a,rationale:null,created_at:new Date().toISOString()}]});return await e.prepare("UPDATE documents SET source_path = ? WHERE object = ?").run(i.path,d.id),await W(e,d.id),l.created+=1,d.id}const n=t.name!==i.name||t.kind!==o.kind;t.name!==i.name&&(t=await U(e,null,t,{name:i.name})),t.kind!==o.kind&&(await K(e,t,o.kind),t=await y(e,t.id));const s=await F(e,t),[c]=await x(e,t),w=c?.body_hash!==f,h=t.preview!==r.preview||s.summary!==r.summary||s.why_it_exists!==r.whyItExists||s.unlisted!==r.unlisted||JSON.stringify(s.lint_ignore)!==JSON.stringify(r.lintIgnore)||JSON.stringify(s.extensions)!==JSON.stringify(r.extensions)||JSON.stringify((await z(e,t)).map(N))!==JSON.stringify(r.rules.map(N));if(!w&&!h&&!n)return t.id;const p=await k(e,t,r);if(w&&c){const d=O(),E=c.number+1;await e.prepare(`INSERT INTO revisions (rev_id, document, number, body_hash, author, rationale, verbatim, created_at)
5
- SELECT ?, document, ?, ?, author, rationale, verbatim, ? FROM revisions WHERE rev_id = ?`).run(d,E,f,new Date().toISOString(),c.rev_id),await e.prepare("INSERT INTO revision_parents (child, parent) SELECT ?, parent FROM revision_parents WHERE child = ?").run(d,c.rev_id),await e.prepare("DELETE FROM revisions WHERE rev_id = ?").run(c.rev_id),await e.prepare("UPDATE documents SET heads_key = ?, revision = ? WHERE object = ?").run(d,E,p.id),await b(e,{event:"edited",object:p,actor:a,pointer:d,attrs:{revision:E,from_revision:c.number,rationale:"package file changed"}},m)}return l.updated+=1,p.id}u(Y,"convergeFile");function N(e){return[e.on,e.deliver,e.match??null,e.matchFrontmatter??null,e.if??null]}u(N,"ruleKey");async function v(e,a,i,l,m){const o=await e.prepare(`SELECT o.* FROM objects o JOIN documents d ON d.object = o.id
6
- WHERE o.type = 'document' AND o.owner = ? AND o.deleted_at IS NULL AND d.source_path IS NOT NULL`).all(a.id);for(const r of o)i.has(r.id)||(await j(e,null,r,m),await M(e,r.id),l.deleted+=1)}u(v,"deleteMissing");export{G as packageSources,he as reconcilePackageDocuments};
4
+ WHERE o.type = 'document' AND o.owner = ? AND o.deleted_at IS NULL AND o.name = ? AND d.source_path IS NOT NULL`).get(a.id,i.name),t&&await e.prepare("UPDATE documents SET source_path = ? WHERE object = ?").run(i.path,t.id)),!t){const d=await C(e,{owner:a,name:i.name,kind:o.kind,privacy:"public",...r,revisions:[{body_hash:f,author:a,rationale:null,created_at:new Date().toISOString()}]});return await e.prepare("UPDATE documents SET source_path = ? WHERE object = ?").run(i.path,d.id),await W(e,d.id),l.created+=1,d.id}const n=t.name!==i.name||t.kind!==o.kind;t.name!==i.name&&(t=await H(e,null,t,{name:i.name})),t.kind!==o.kind&&(await K(e,t,o.kind),t=await h(e,t.id));const s=await F(e,t),[c]=await x(e,t),w=c?.body_hash!==f,E=t.preview!==r.preview||s.summary!==r.summary||s.why_it_exists!==r.whyItExists||s.unlisted!==r.unlisted||JSON.stringify(s.lint_ignore)!==JSON.stringify(r.lintIgnore)||JSON.stringify(s.extensions)!==JSON.stringify(r.extensions)||JSON.stringify((await z(e,t)).map(N))!==JSON.stringify(r.rules.map(N));if(!w&&!E&&!n)return t.id;const p=await k(e,t,r);if(w&&c){const d=_(),y=c.number+1;await e.prepare(`INSERT INTO revisions (rev_id, document, number, body_hash, author, rationale, verbatim, created_at)
5
+ SELECT ?, document, ?, ?, author, rationale, verbatim, ? FROM revisions WHERE rev_id = ?`).run(d,y,f,new Date().toISOString(),c.rev_id),await e.prepare("INSERT INTO revision_parents (child, parent) SELECT ?, parent FROM revision_parents WHERE child = ?").run(d,c.rev_id),await e.prepare("DELETE FROM revisions WHERE rev_id = ?").run(c.rev_id),await e.prepare("UPDATE documents SET heads_key = ?, revision = ? WHERE object = ?").run(d,y,p.id),await b(e,{event:"edited",object:p,actor:a,pointer:d,attrs:{revision:y,from_revision:c.number,rationale:"package file changed"}},m)}return l.updated+=1,p.id}u(X,"convergeFile");function N(e){return[e.on,e.deliver,e.match??null,e.matchFrontmatter??null,e.if??null]}u(N,"ruleKey");async function v(e,a,i,l,m){const o=await e.prepare(`SELECT o.* FROM objects o JOIN documents d ON d.object = o.id
6
+ WHERE o.type = 'document' AND o.owner = ? AND o.deleted_at IS NULL AND d.source_path IS NOT NULL`).all(a.id);for(const r of o)i.has(r.id)||(await j(e,null,r,m),await M(e,r.id),l.deleted+=1)}u(v,"deleteMissing");export{G as packageSources,Ee as reconcilePackageDocuments};
@@ -1,2 +1,2 @@
1
- var j=Object.defineProperty;var _=(t,i)=>j(t,"name",{value:i,configurable:!0});import{get as $,put as B}from"../bodies.js";import{setLinks as J,setUnresolved as U}from"../edges.js";import{collectUnlinked as P,deleteDocument as I,documentRecord as M,heads as x,importDocument as Y,insertImportedRevisions as z,moveDocument as b,restoreDocument as q,setDocumentRecord as F}from"../documents.js";import{CrtrError as G}from"../../errors.js";import{publish as D}from"../events.js";import{getById as k,setObjectKind as K}from"../objects.js";import{rulesOf as V}from"../rules.js";import{docPath as Q,mergeDoc as X,parseDoc as g,serializeDoc as C}from"./ref-format.js";import{withCanvasWrite as A}from"../../canvas/db.js";import{mapAsync as H}from"../../async-array.js";function L(t){if(t.owner!==null||t.type!=="repo")throw new Error(`${t.type} ${t.id} is not a space that syncs through a repo ref`)}_(L,"assertRepoRefSpace");function Z(t){const i=new Map;for(const[r,n]of t??[]){const m=g(r,n);m.ok&&i.set(m.doc.id,m.doc)}return i}_(Z,"parseMap");async function Ee(t,i,r,n=null){L(i);const m=Z(r),p=await t.prepare("SELECT * FROM objects WHERE type = 'document' AND owner = ? AND privacy = 'public' ORDER BY seq").all(i.id),l=t.prepare("SELECT rev_id, number, body_hash, author, rationale, created_at FROM revisions WHERE document = ? ORDER BY number, rev_id"),w=t.prepare("SELECT p.rev_id FROM revision_parents rp JOIN revisions p ON p.rev_id = rp.parent WHERE rp.child = ?"),a=t.prepare(`SELECT e.written_as, o.id FROM edges e JOIN objects o ON o.id = e.dst
2
- WHERE e.type = 'links-to' AND e.src = ? ORDER BY e.written_as, o.id`),s=new Map;for(const e of p){const d=await M(t,e),c=new Map((m.get(e.id)?.revisions??[]).map(o=>[o.rev_id,o])),h=new Set((await x(t,e)).map(o=>o.rev_id)),v=(await a.all(e.id)).map(o=>({written_as:o.written_as??"",dst:o.id})),E=await H(await l.all(e.id),async o=>c.get(o.rev_id)??{rev_id:o.rev_id,number:o.number,parents:(await w.all(o.rev_id)).map(R=>R.rev_id),body:$(o.body_hash),body_hash:o.body_hash,author:o.author,rationale:o.rationale,created_at:o.created_at,links:h.has(o.rev_id)?v:[]});if(E.length===0)continue;const y=(await V(t,e)).map(o=>({position:o.position,on:o.on,match:o.match??null,match_frontmatter:o.matchFrontmatter??null,if:o.if??null,deliver:o.deliver})),N={id:e.id,name:e.name,kind:e.kind??"document",privacy:"public",preview:e.preview,summary:d.summary,why_it_exists:d.why_it_exists,unlisted:d.unlisted,lint_ignore:d.lint_ignore,extensions:d.extensions,created_at:e.created_at,deleted_at:e.deleted_at,rules:y,revisions:E};s.set(Q(e.id),C(N))}for(const e of ee(r,n)){const d=r.get(e),c=s.get(e);if(c===void 0){s.set(e,d);continue}const h=g(e,d),v=g(e,c);if(!h.ok){s.set(e,d);continue}if(!v.ok)continue;const E=n?.has(e)?g(e,n.get(e)):null,y=X(E?.ok?E.doc:null,v.doc,h.doc);y&&s.set(e,C(y))}return s}_(Ee,"exportRepo");function ee(t,i){return[...t??[]].filter(([r,n])=>i?.get(r)!==n).map(([r])=>r)}_(ee,"pendingPaths");function te(t){return[...t].sort((i,r)=>i.position-r.position).map(i=>({on:i.on,deliver:i.deliver,...i.match!==null&&i.match!==void 0?{match:i.match}:{},...i.match_frontmatter!==null&&i.match_frontmatter!==void 0?{matchFrontmatter:i.match_frontmatter}:{},...i.if!==null&&i.if!==void 0?{if:i.if}:{}}))}_(te,"toRuleSpecs");const ne=_((t,i)=>JSON.stringify(t??null)===JSON.stringify(i??null),"same");function W(t,i){const r={},n=_(m=>!t||!ne(t[m],i[m]),"differs");return n("preview")&&(r.preview=i.preview),n("summary")&&(r.summary=i.summary),n("why_it_exists")&&(r.whyItExists=i.why_it_exists),n("unlisted")&&(r.unlisted=i.unlisted),n("lint_ignore")&&(r.lintIgnore=i.lint_ignore),n("extensions")&&(r.extensions=i.extensions),n("rules")&&(r.rules=te(i.rules)),r}_(W,"changedFields");async function ke(t,i,r,n,m){L(i);const p={created:0,revisions:0,deleted:0,needsMerge:[],refused:[]},l=[];for(const[a,s]of n){if(r.get(a)===s)continue;const e=g(a,s);if(!e.ok){p.refused.push({path:a,reason:e.reason});continue}const d=r.has(a)?g(a,r.get(a)):null;l.push({path:a,prior:d?.ok?d.doc:null,doc:e.doc})}for(const[a,s]of r){if(n.has(a))continue;const e=g(a,s);e.ok&&l.push({path:a,prior:e.doc,doc:null,priorId:e.doc.id})}for(const a of l)for(const s of a.doc?.revisions??[])B(s.body);const w=[];return await A(async()=>{let a=l;for(;;){const s=[];for(const e of a){const d={created:0,revisions:0,deleted:0,needsMerge:[],refused:[]};try{const c=[];await A(async()=>await de(t,i,e.prior,e.doc,e.priorId??e.doc.id,d,m,c),{savepoint:!0}),w.push(...c),p.created+=d.created,p.revisions+=d.revisions,p.deleted+=d.deleted,p.needsMerge.push(...d.needsMerge)}catch(c){se(c)?s.push({w:e,error:c}):p.refused.push({path:e.path,reason:c.message})}}if(s.length===0)break;if(s.length===a.length){for(const e of s)p.refused.push({path:e.w.path,reason:e.error.message});break}a=s.map(e=>e.w)}await ie(t,n,new Set(w),new Set(l.map(s=>s.path)))},{savepoint:!0}),p}_(ke,"importTree");async function ie(t,i,r,n){if(r.size===0)return;const m=t.prepare("INSERT INTO edges (type, src, dst, written_as, created_at) VALUES ('links-to', ?, ?, ?, ?) ON CONFLICT DO NOTHING"),p=new Date().toISOString();for(const[l,w]of i){if(!n.has(l)&&!ae(w,r))continue;const a=g(l,w);if(!a.ok||a.doc.deleted_at)continue;const s=await k(t,a.doc.id);if(!s||s.type!=="document"||s.deleted_at)continue;const e=(await x(t,s))[0],d=a.doc.revisions.find(c=>c.rev_id===e?.rev_id)?.links??[];for(const c of d){const h=await k(t,c.dst);h&&h.id!==s.id&&await m.run(s.id,h.id,c.written_as,p)}}}_(ie,"completeLinks");const re=/"dst":\s*"((?:[^"\\]|\\.)*)"/g;function ae(t,i){for(const r of t.matchAll(re))if(i.has(oe(r[1])))return!0;return!1}_(ae,"namesArrived");function oe(t){if(!t.includes("\\"))return t;try{return JSON.parse(`"${t}"`)}catch{return t}}_(oe,"decodeJsonString");function se(t){return t instanceof G&&t.code==="conflict"&&t.details?.holder!==void 0}_(se,"isNameConflict");async function de(t,i,r,n,m,p,l,w){const a=await k(t,m);if(a&&(a.type!=="document"||a.owner!==i.id))throw new Error(`id ${m} is held by ${a.type} ${a.name} outside ${i.name}`);if(!n){a&&!a.deleted_at&&(await I(t,null,a,l),p.deleted++);return}const s=t.prepare("SELECT count(*) AS n FROM revisions WHERE document = ?");let e,d=!1,c=!1,h=0,v=0;if(a){e=a,h=(await M(t,e)).revision,e.created_at!==n.created_at&&(await t.prepare("UPDATE objects SET created_at = ? WHERE id = ?").run(n.created_at,e.id),e=await k(t,e.id));const u=n.kind;(r?r.kind!==u:e.kind!==u)&&await K(t,e,u),c=!!e.deleted_at&&n.deleted_at===null&&(r===null||r.deleted_at!=null);let f=W(c?null:r,n);if(n.deleted_at&&e.deleted_at){const{rules:O,...S}=f;f=S}c&&(e=await q(t,e,n.name,l),w?.push(e.id)),Object.keys(f).length>0&&(e=await F(t,null,e,f)),n.name!==e.name&&!e.deleted_at&&(e=await b(t,null,e,{name:n.name},l))}else{const f=n.revisions.filter(O=>O.parents.length===0).sort((O,S)=>O.number-S.number||O.rev_id.localeCompare(S.rev_id))[0];if(!f)throw new Error(`${n.name}: no root revision`);e=await Y(t,{id:n.id,owner:i,name:n.name,kind:n.kind,preview:n.preview,privacy:"public",...W(null,n),revisions:[{rev_id:f.rev_id,body_hash:f.body_hash,author:null,rationale:f.rationale,created_at:f.created_at}]},l),e.created_at!==n.created_at&&(await t.prepare("UPDATE objects SET created_at = ? WHERE id = ?").run(n.created_at,e.id),e=await k(t,e.id)),d=!0,w?.push(e.id),v=1}const E=(await s.get(e.id)).n,{headCount:y}=await z(t,e,n.revisions.map(u=>({rev_id:u.rev_id,parents:u.parents,body_hash:u.body_hash,rationale:u.rationale,created_at:u.created_at})));v+=(await s.get(e.id)).n-E,p.revisions+=v;const o=(await x(t,e))[0],R=new Map(n.revisions.map(u=>[u.rev_id,u]));if(v>0||c){const u=(await H(R.get(o.rev_id)?.links??[],async f=>({written_as:f.written_as,dst:await k(t,f.dst)}))).filter(f=>f.dst!==null);await P(t,await J(t,e,u),l),await U(t,e,[])}if(y>1&&p.needsMerge.push(e.id),n.deleted_at&&!e.deleted_at){await I(t,null,e,l,n.deleted_at),p.deleted++;return}if(n.deleted_at&&e.deleted_at&&e.deleted_at!==n.deleted_at&&await t.prepare("UPDATE objects SET deleted_at = ? WHERE id = ?").run(n.deleted_at,e.id),d){p.created++,await D(t,{event:"created",object:e,actor:null,attrs:{revision:o.number},pointer:o.rev_id},l);return}if(c){p.created++,await D(t,{event:"created",object:e,actor:null,attrs:{revision:o.number},pointer:o.rev_id},l);return}if(v===0)return;const T=n.revisions.filter(u=>!r?.revisions.some(f=>f.rev_id===u.rev_id)).sort((u,f)=>f.number-u.number||f.created_at.localeCompare(u.created_at))[0]??R.get(o.rev_id);await D(t,{event:"edited",object:e,actor:null,pointer:o.rev_id,attrs:{revision:o.number,from_revision:h,rationale:T?.rationale??null,author:T?.author??null,heads:y,needs_merge:y>1}},l)}_(de,"importOne");export{Ee as exportRepo,ke as importTree,ae as namesArrived,ee as pendingPaths};
1
+ var $=Object.defineProperty;var _=(t,i)=>$(t,"name",{value:i,configurable:!0});import{getMany as B,put as J}from"../bodies.js";import{setLinks as U,setUnresolved as P}from"../edges.js";import{collectUnlinked as Y,deleteDocument as I,documentRecord as C,heads as T,importDocument as z,insertImportedRevisions as b,moveDocument as q,restoreDocument as F,setDocumentRecord as G}from"../documents.js";import{CrtrError as K}from"../../errors.js";import{publish as M}from"../events.js";import{getById as O,setObjectKind as V}from"../objects.js";import{rulesOf as Q}from"../rules.js";import{docPath as X,mergeDoc as Z,parseDoc as E,serializeDoc as A}from"./ref-format.js";import{withCanvasWrite as H}from"../../canvas/db.js";import{mapAsync as L}from"../../async-array.js";function W(t){if(t.owner!==null||t.type!=="repo")throw new Error(`${t.type} ${t.id} is not a space that syncs through a repo ref`)}_(W,"assertRepoRefSpace");function ee(t){const i=new Map;for(const[a,n]of t??[]){const m=E(a,n);m.ok&&i.set(m.doc.id,m.doc)}return i}_(ee,"parseMap");async function Ee(t,i,a,n=null){W(i);const m=ee(a),p=await t.prepare("SELECT * FROM objects WHERE type = 'document' AND owner = ? AND privacy = 'public' ORDER BY seq").all(i.id),l=t.prepare("SELECT rev_id, number, body_hash, author, rationale, created_at FROM revisions WHERE document = ? ORDER BY number, rev_id"),w=t.prepare("SELECT p.rev_id FROM revision_parents rp JOIN revisions p ON p.rev_id = rp.parent WHERE rp.child = ?"),r=t.prepare(`SELECT e.written_as, o.id FROM edges e JOIN objects o ON o.id = e.dst
2
+ WHERE e.type = 'links-to' AND e.src = ? ORDER BY e.written_as, o.id`),s=new Map;for(const e of p){const d=await C(t,e),c=new Map((m.get(e.id)?.revisions??[]).map(o=>[o.rev_id,o])),h=new Set((await T(t,e)).map(o=>o.rev_id)),v=(await r.all(e.id)).map(o=>({written_as:o.written_as??"",dst:o.id})),k=await l.all(e.id),g=await B(t,k.filter(o=>!c.has(o.rev_id)).map(o=>o.body_hash)),N=await L(k,async o=>c.get(o.rev_id)??{rev_id:o.rev_id,number:o.number,parents:(await w.all(o.rev_id)).map(x=>x.rev_id),body:g.get(o.body_hash),body_hash:o.body_hash,author:o.author,rationale:o.rationale,created_at:o.created_at,links:h.has(o.rev_id)?v:[]});if(N.length===0)continue;const y=(await Q(t,e)).map(o=>({position:o.position,on:o.on,match:o.match??null,match_frontmatter:o.matchFrontmatter??null,if:o.if??null,deliver:o.deliver})),S={id:e.id,name:e.name,kind:e.kind??"document",privacy:"public",preview:e.preview,summary:d.summary,why_it_exists:d.why_it_exists,unlisted:d.unlisted,lint_ignore:d.lint_ignore,extensions:d.extensions,created_at:e.created_at,deleted_at:e.deleted_at,rules:y,revisions:N};s.set(X(e.id),A(S))}for(const e of te(a,n)){const d=a.get(e),c=s.get(e);if(c===void 0){s.set(e,d);continue}const h=E(e,d),v=E(e,c);if(!h.ok){s.set(e,d);continue}if(!v.ok)continue;const k=n?.has(e)?E(e,n.get(e)):null,g=Z(k?.ok?k.doc:null,v.doc,h.doc);g&&s.set(e,A(g))}return s}_(Ee,"exportRepo");function te(t,i){return[...t??[]].filter(([a,n])=>i?.get(a)!==n).map(([a])=>a)}_(te,"pendingPaths");function ne(t){return[...t].sort((i,a)=>i.position-a.position).map(i=>({on:i.on,deliver:i.deliver,...i.match!==null&&i.match!==void 0?{match:i.match}:{},...i.match_frontmatter!==null&&i.match_frontmatter!==void 0?{matchFrontmatter:i.match_frontmatter}:{},...i.if!==null&&i.if!==void 0?{if:i.if}:{}}))}_(ne,"toRuleSpecs");const ie=_((t,i)=>JSON.stringify(t??null)===JSON.stringify(i??null),"same");function j(t,i){const a={},n=_(m=>!t||!ie(t[m],i[m]),"differs");return n("preview")&&(a.preview=i.preview),n("summary")&&(a.summary=i.summary),n("why_it_exists")&&(a.whyItExists=i.why_it_exists),n("unlisted")&&(a.unlisted=i.unlisted),n("lint_ignore")&&(a.lintIgnore=i.lint_ignore),n("extensions")&&(a.extensions=i.extensions),n("rules")&&(a.rules=ne(i.rules)),a}_(j,"changedFields");async function ke(t,i,a,n,m){W(i);const p={created:0,revisions:0,deleted:0,needsMerge:[],refused:[]},l=[];for(const[r,s]of n){if(a.get(r)===s)continue;const e=E(r,s);if(!e.ok){p.refused.push({path:r,reason:e.reason});continue}const d=a.has(r)?E(r,a.get(r)):null;l.push({path:r,prior:d?.ok?d.doc:null,doc:e.doc})}for(const[r,s]of a){if(n.has(r))continue;const e=E(r,s);e.ok&&l.push({path:r,prior:e.doc,doc:null,priorId:e.doc.id})}const w=[];return await H(async()=>{for(const s of l)for(const e of s.doc?.revisions??[])await J(t,e.body,e.body_hash);let r=l;for(;;){const s=[];for(const e of r){const d={created:0,revisions:0,deleted:0,needsMerge:[],refused:[]};try{const c=[];await H(async()=>await ce(t,i,e.prior,e.doc,e.priorId??e.doc.id,d,m,c),{savepoint:!0}),w.push(...c),p.created+=d.created,p.revisions+=d.revisions,p.deleted+=d.deleted,p.needsMerge.push(...d.needsMerge)}catch(c){de(c)?s.push({w:e,error:c}):p.refused.push({path:e.path,reason:c.message})}}if(s.length===0)break;if(s.length===r.length){for(const e of s)p.refused.push({path:e.w.path,reason:e.error.message});break}r=s.map(e=>e.w)}await ae(t,n,new Set(w),new Set(l.map(s=>s.path)))},{savepoint:!0}),p}_(ke,"importTree");async function ae(t,i,a,n){if(a.size===0)return;const m=t.prepare("INSERT INTO edges (type, src, dst, written_as, created_at) VALUES ('links-to', ?, ?, ?, ?) ON CONFLICT DO NOTHING"),p=new Date().toISOString();for(const[l,w]of i){if(!n.has(l)&&!se(w,a))continue;const r=E(l,w);if(!r.ok||r.doc.deleted_at)continue;const s=await O(t,r.doc.id);if(!s||s.type!=="document"||s.deleted_at)continue;const e=(await T(t,s))[0],d=r.doc.revisions.find(c=>c.rev_id===e?.rev_id)?.links??[];for(const c of d){const h=await O(t,c.dst);h&&h.id!==s.id&&await m.run(s.id,h.id,c.written_as,p)}}}_(ae,"completeLinks");const re=/"dst":\s*"((?:[^"\\]|\\.)*)"/g;function se(t,i){for(const a of t.matchAll(re))if(i.has(oe(a[1])))return!0;return!1}_(se,"namesArrived");function oe(t){if(!t.includes("\\"))return t;try{return JSON.parse(`"${t}"`)}catch{return t}}_(oe,"decodeJsonString");function de(t){return t instanceof K&&t.code==="conflict"&&t.details?.holder!==void 0}_(de,"isNameConflict");async function ce(t,i,a,n,m,p,l,w){const r=await O(t,m);if(r&&(r.type!=="document"||r.owner!==i.id))throw new Error(`id ${m} is held by ${r.type} ${r.name} outside ${i.name}`);if(!n){r&&!r.deleted_at&&(await I(t,null,r,l),p.deleted++);return}const s=t.prepare("SELECT count(*) AS n FROM revisions WHERE document = ?");let e,d=!1,c=!1,h=0,v=0;if(r){e=r,h=(await C(t,e)).revision,e.created_at!==n.created_at&&(await t.prepare("UPDATE objects SET created_at = ? WHERE id = ?").run(n.created_at,e.id),e=await O(t,e.id));const u=n.kind;(a?a.kind!==u:e.kind!==u)&&await V(t,e,u),c=!!e.deleted_at&&n.deleted_at===null&&(a===null||a.deleted_at!=null);let f=j(c?null:a,n);if(n.deleted_at&&e.deleted_at){const{rules:R,...D}=f;f=D}c&&(e=await F(t,e,n.name,l),w?.push(e.id)),Object.keys(f).length>0&&(e=await G(t,null,e,f)),n.name!==e.name&&!e.deleted_at&&(e=await q(t,null,e,{name:n.name},l))}else{const f=n.revisions.filter(R=>R.parents.length===0).sort((R,D)=>R.number-D.number||R.rev_id.localeCompare(D.rev_id))[0];if(!f)throw new Error(`${n.name}: no root revision`);e=await z(t,{id:n.id,owner:i,name:n.name,kind:n.kind,preview:n.preview,privacy:"public",...j(null,n),revisions:[{rev_id:f.rev_id,body_hash:f.body_hash,author:null,rationale:f.rationale,created_at:f.created_at}]},l),e.created_at!==n.created_at&&(await t.prepare("UPDATE objects SET created_at = ? WHERE id = ?").run(n.created_at,e.id),e=await O(t,e.id)),d=!0,w?.push(e.id),v=1}const k=(await s.get(e.id)).n,{headCount:g}=await b(t,e,n.revisions.map(u=>({rev_id:u.rev_id,parents:u.parents,body_hash:u.body_hash,rationale:u.rationale,created_at:u.created_at})));v+=(await s.get(e.id)).n-k,p.revisions+=v;const y=(await T(t,e))[0],S=new Map(n.revisions.map(u=>[u.rev_id,u]));if(v>0||c){const u=(await L(S.get(y.rev_id)?.links??[],async f=>({written_as:f.written_as,dst:await O(t,f.dst)}))).filter(f=>f.dst!==null);await Y(t,await U(t,e,u),l),await P(t,e,[])}if(g>1&&p.needsMerge.push(e.id),n.deleted_at&&!e.deleted_at){await I(t,null,e,l,n.deleted_at),p.deleted++;return}if(n.deleted_at&&e.deleted_at&&e.deleted_at!==n.deleted_at&&await t.prepare("UPDATE objects SET deleted_at = ? WHERE id = ?").run(n.deleted_at,e.id),d){p.created++,await M(t,{event:"created",object:e,actor:null,attrs:{revision:y.number},pointer:y.rev_id},l);return}if(c){p.created++,await M(t,{event:"created",object:e,actor:null,attrs:{revision:y.number},pointer:y.rev_id},l);return}if(v===0)return;const x=n.revisions.filter(u=>!a?.revisions.some(f=>f.rev_id===u.rev_id)).sort((u,f)=>f.number-u.number||f.created_at.localeCompare(u.created_at))[0]??S.get(y.rev_id);await M(t,{event:"edited",object:e,actor:null,pointer:y.rev_id,attrs:{revision:y.number,from_revision:h,rationale:x?.rationale??null,author:x?.author??null,heads:g,needs_merge:g>1}},l)}_(ce,"importOne");export{Ee as exportRepo,ke as importTree,se as namesArrived,te as pendingPaths};
@@ -41,7 +41,6 @@ export interface RefDocument {
41
41
  export declare const docPath: (id: string) => string;
42
42
  /** Every field merged three-way: all but id, created_at and revisions. */
43
43
  export declare const RECORD_FIELDS: readonly ["name", "kind", "privacy", "preview", "summary", "why_it_exists", "unlisted", "lint_ignore", "extensions", "deleted_at", "rules"];
44
- export declare function sha256(body: string): string;
45
44
  export declare function serializeDoc(doc: RefDocument): string;
46
45
  /** Parse one ref file. Refuses anything not public, a body whose hash disagrees, or an id that is not the file name. */
47
46
  export declare function parseDoc(path: string, text: string): {