@north-light/cli 0.3.554 → 0.3.556

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.
@@ -1,3 +1,3 @@
1
- var V=Object.defineProperty;var l=(i,a)=>V(i,"name",{value:a,configurable:!0});import{DatabaseSync as W}from"node:sqlite";import{existsSync as Y,mkdirSync as P,mkdtempSync as J,renameSync as X,rmSync as k}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 re,quote as c,TABLE_ORDER as C}from"./import-sqlite.js";const se=5e3,oe=new Set(["environments","relations","schemas","charges","charge_totals","limits"]),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-")),r=process.env.CRTR_HOME;process.env.CRTR_HOME=a,P(A(a,"runtime","nodes"),{recursive:!0});try{const s=new W(i);return s.exec("PRAGMA journal_mode = DELETE;"),s.exec("PRAGMA foreign_keys = ON;"),z(s),s.exec("DELETE FROM canvas_meta; DELETE FROM message_sequence;"),s}finally{r===void 0?delete process.env.CRTR_HOME:process.env.CRTR_HOME=r,k(a,{recursive:!0,force:!0})}}l(ce,"createEmptyDatabase");async function ge(i,a=()=>{}){const r=Z();if(Y(r))throw new Error(`${r} already exists; refusing to overwrite it`);P(A(r,".."),{recursive:!0,mode:448});const s=`${r}.exporting-${process.pid}`,m=Date.now(),t=new j.Client({connectionString:i,application_name:`crtr-export-${process.pid}`,options:ee(),types:{getTypeParser:l(((f,o)=>f===20?d=>Number(d):j.types.getTypeParser(f,o)),"getTypeParser")}});await t.connect();let p;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");p=ce(s);const o=p;o.exec("PRAGMA foreign_keys = OFF;");const d=await re(t),L=[...d.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 h={};for(const[e,O]of Object.entries(ie)){const _=new Map;for(const E of(await t.query(`SELECT ${O} AS id, seq FROM ${e}`)).rows)_.set(E.id,Number(E.seq));h[e]=_}const B=h.objects;let M=0;o.exec("BEGIN");for(const e of C){const O=Date.now(),_=new Set(o.prepare(`PRAGMA table_info(${c(e)})`).all().map(n=>n.name)),E=d.get(e);if(E===void 0)throw new Error(`export: Postgres has no table ${e}`);const G=E.some(n=>n.name==="seq"),$=E.filter(n=>_.has(n.name)),K=new Set(ne[e]??[]),R=e==="objects"||e==="revisions"||e==="watches"||e==="watch_subscriptions",v=$.map(n=>n.type==="jsonb"?`${c(n.name)}::text AS ${c(n.name)}`:c(n.name));e==="watches"&&v.push("watch_id AS _watch_id"),R&&v.push("seq AS _seq");const g=$.map(n=>c(n.name)),U=o.prepare(`INSERT INTO ${c(e)} (${R?["rowid",...g]:g}) VALUES (${(R?["?",...g]:g).map(()=>"?").join(", ")})`),q=G?"seq":ae[e],T=`export_${e}`;await t.query(`DECLARE ${T} NO SCROLL CURSOR FOR SELECT ${v.join(", ")} FROM ${c(e)}${e==="canvas_meta"?` WHERE key NOT IN (${[...H].map(n=>`'${n}'`).join(", ")})`:""}${q===void 0?"":` ORDER BY ${q}`}`);let b=0;for(;;){const n=await t.query(`FETCH ${se} FROM ${T}`);if(n.rows.length===0)break;for(const N of n.rows){const D=R?[Number(N._seq)]:[];for(const u of $){let w=N[u.name];e==="revision_parents"&&(u.name==="child"||u.name==="parent")?w=x(h.revisions,"revision",e,u.name,w):e==="watch_subscriptions"&&u.name==="watch"?w=x(h.watches,"watch",e,u.name,w):K.has(u.name)&&(w=x(B,"object",e,u.name,w)),D.push(w??null)}U.run(...D)}b+=n.rows.length}await t.query(`CLOSE ${T}`),M+=b,a(`export ${e}: ${b} rows in ${Date.now()-O} 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 S=o.prepare("PRAGMA user_version").get().user_version;if(S!==I.length)throw new Error(`export: exported canvas.db is at user_version ${S}, expected ${I.length}`);return o.close(),p=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:r})]),X(s,r),{path:r,rows:M,ms:Date.now()-m,userVersion:S}}catch(f){await t.query("ROLLBACK").catch(()=>{});try{p?.close()}catch{}for(const o of["","-wal","-shm","-journal"])k(`${s}${o}`,{force:!0});throw f}finally{await t.end().catch(()=>{})}}l(ge,"exportPostgresToSqlite");function x(i,a,r,s,m){if(m==null)return null;const t=i.get(m);if(t===void 0)throw new Error(`export: ${r}.${s} references ${a} ${String(m)}, which was not exported (`);return t}l(x,"mapId");async function me(i,a){const r=[];for(const s of C){const m=Number((await i.query(`SELECT count(*) AS n FROM ${c(s)}`)).rows[0].n);let t=Number(a.prepare(`SELECT count(*) AS n FROM ${c(s)}`).get().n),p=m;s==="canvas_meta"&&(p=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)),p!==t&&r.push(`${s}: Postgres ${p} rows, SQLite ${t}`)}if(r.length>0)throw new Error(`Postgres \u2192 SQLite export verification failed:
2
- ${r.join(`
3
- `)}`)}l(me,"verify");export{ge as exportPostgresToSqlite};
1
+ var V=Object.defineProperty;var w=(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 c,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","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})}}w(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}`,p=Date.now(),t=new j.Client({connectionString:i,application_name:`crtr-export-${process.pid}`,options:ee(),types:{getTypeParser:w(((d,o)=>d===20?f=>Number(f):j.types.getTypeParser(d,o)),"getTypeParser")}});await t.connect();let m;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");m=ce(r);const o=m;o.exec("PRAGMA foreign_keys = OFF;");const f=await se(t),L=[...f.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 _ of(await t.query(`SELECT ${S} AS id, seq FROM ${e}`)).rows)h.set(_.id,Number(_.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(${c(e)})`).all().map(n=>n.name)),_=f.get(e);if(_===void 0)throw new Error(`export: Postgres has no table ${e}`);const G=_.some(n=>n.name==="seq"),O=_.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"?`${c(n.name)}::text AS ${c(n.name)}`:c(n.name));e==="watches"&&$.push("watch_id AS _watch_id"),g&&$.push("seq AS _seq");const R=O.map(n=>c(n.name)),U=o.prepare(`INSERT INTO ${c(e)} (${g?["rowid",...R]:R}) VALUES (${(g?["?",...R]:R).map(()=>"?").join(", ")})`),q=G?"seq":ae[e],b=`export_${e}`;await t.query(`DECLARE ${b} NO SCROLL CURSOR FOR SELECT ${$.join(", ")} FROM ${c(e)}${e==="canvas_meta"?` WHERE key NOT IN (${[...H].map(n=>`'${n}'`).join(", ")})`:""}${q===void 0?"":` ORDER BY ${q}`}`);let T=0;for(;;){const n=await t.query(`FETCH ${re} FROM ${b}`);if(n.rows.length===0)break;for(const k of n.rows){const N=g?[Number(k._seq)]:[];for(const u of O){let l=k[u.name];e==="revision_parents"&&(u.name==="child"||u.name==="parent")?l=x(E.revisions,"revision",e,u.name,l):e==="watch_subscriptions"&&u.name==="watch"?l=x(E.watches,"watch",e,u.name,l):K.has(u.name)&&(l=x(B,"object",e,u.name,l)),N.push(l??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 v=o.prepare("PRAGMA foreign_key_check").all();if(v.length>0)throw new Error(`export: ${v.length} foreign key violation(s) in the exported canvas.db, first: ${JSON.stringify(v[0])}`);await pe(t,o);const y=o.prepare("PRAGMA user_version").get().user_version;if(y!==I.length)throw new Error(`export: exported canvas.db is at user_version ${y}, expected ${I.length}`);return o.close(),m=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()-p,userVersion:y}}catch(d){await t.query("ROLLBACK").catch(()=>{});try{m?.close()}catch{}for(const o of["","-wal","-shm","-journal"])P(`${r}${o}`,{force:!0});throw d}finally{await t.end().catch(()=>{})}}w(Re,"exportPostgresToSqlite");function x(i,a,s,r,p){if(p==null)return null;const t=i.get(p);if(t===void 0)throw new Error(`export: ${s}.${r} references ${a} ${String(p)}, which was not exported (`);return t}w(x,"mapId");async function pe(i,a){const s=[];for(const r of C){const p=Number((await i.query(`SELECT count(*) AS n FROM ${c(r)}`)).rows[0].n);let t=Number(a.prepare(`SELECT count(*) AS n FROM ${c(r)}`).get().n),m=p;r==="canvas_meta"&&(m=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)),m!==t&&s.push(`${r}: Postgres ${m} rows, SQLite ${t}`)}if(s.length>0)throw new Error(`Postgres \u2192 SQLite export verification failed:
2
+ ${s.join(`
3
+ `)}`)}w(pe,"verify");export{Re as exportPostgresToSqlite};
@@ -5,7 +5,7 @@ export declare const IMPORT_MARK_KEY = "imported_from_sqlite";
5
5
  export declare const EXPORT_MARK_KEY = "exported_to_sqlite";
6
6
  /** Every canvas table, in dependency order (a table follows every table its foreign keys name). The import and the
7
7
  * rollback export both copy exactly this list. */
8
- export declare const TABLE_ORDER: readonly ["objects", "documents", "revisions", "revision_parents", "jobs", "identities", "custom_objects", "context_sessions", "peer_stubs", "object_kinds", "watches", "watch_subscriptions", "exposures", "rules", "edges", "unresolved_links", "nodes", "focuses", "canvas_meta", "runtime_state", "warm_pool", "sessions", "executions", "worktrees", "node_faults", "node_tokens", "node_outcome_deliveries", "deleted_isolated_nodes", "create_requests", "waits", "messages", "message_sequence", "message_causes", "deliveries", "structured_requests", "daemon_actions", "human_requests", "human_action_deliveries", "review_comments", "review_comment_events", "run_events", "idempotency_keys", "grants", "memory_reads", "app_users", "uploads", "upload_uses", "shares", "audit_rows", "grantee_usage", "provider_receipts", "provider_approvals", "usage", "crons", "cron_runs", "peer_outbox", "peer_locations", "peer_ingested", "peer_ingested_destinations"];
8
+ export declare const TABLE_ORDER: readonly ["objects", "documents", "revisions", "revision_parents", "jobs", "identities", "custom_objects", "context_sessions", "peer_stubs", "object_kinds", "watches", "watch_subscriptions", "exposures", "rules", "edges", "unresolved_links", "nodes", "focuses", "canvas_meta", "runtime_state", "warm_pool", "sessions", "executions", "worktrees", "node_faults", "node_tokens", "node_outcome_deliveries", "deleted_isolated_nodes", "create_requests", "waits", "messages", "message_sequence", "message_causes", "deliveries", "structured_requests", "daemon_actions", "human_requests", "human_action_deliveries", "review_comments", "review_comment_events", "run_events", "idempotency_keys", "grants", "memory_reads", "app_users", "uploads", "upload_uses", "shares", "audit_rows", "provider_approvals", "crons", "cron_runs", "peer_outbox", "peer_locations", "peer_ingested", "peer_ingested_destinations"];
9
9
  /** Columns holding `objects.rowid` in SQLite and `objects.id` in Postgres. */
10
10
  export declare const OBJECT_COLUMNS: Record<string, readonly string[]>;
11
11
  /** `optional`: the column may be left out of an INSERT (nullable, or it has a default or is an identity). */
@@ -1,7 +1,19 @@
1
- var X=Object.defineProperty;var p=(t,o)=>X(t,"name",{value:o,configurable:!0});import{DatabaseSync as z}from"node:sqlite";import{createHash as Z,randomBytes as ee}from"node:crypto";import{existsSync as O,mkdirSync as te,renameSync as F}from"node:fs";import{dirname as oe}from"node:path";import se from"pg";import{withExclusiveDirectoryLockAsync as re}from"../exclusive-lock.js";import{DAEMON_STARTUP_WINDOW_MS as ne}from"../../daemon/startup-policy.js";import{migrate as ae}from"./migrations.js";import{readLegacyPrivacy as ie}from"../graph/types.js";import{canvasDbPath as B,onDiskMigrationLockPath as ce}from"./paths.js";import{canvasSearchPathOptions as ue}from"./schema-version.js";const C="imported_from_sqlite",U="exported_to_sqlite",M=1e4,L=["objects","documents","revisions","revision_parents","jobs","identities","custom_objects","context_sessions","peer_stubs","object_kinds","watches","watch_subscriptions","exposures","rules","edges","unresolved_links","nodes","focuses","canvas_meta","runtime_state","warm_pool","sessions","executions","worktrees","node_faults","node_tokens","node_outcome_deliveries","deleted_isolated_nodes","create_requests","waits","messages","message_sequence","message_causes","deliveries","structured_requests","daemon_actions","human_requests","human_action_deliveries","review_comments","review_comment_events","run_events","idempotency_keys","grants","memory_reads","app_users","uploads","upload_uses","shares","audit_rows","grantee_usage","provider_receipts","provider_approvals","usage","crons","cron_runs","peer_outbox","peer_locations","peer_ingested","peer_ingested_destinations"],pe=new Set(["sqlite_sequence","sqlite_stat1","sqlite_stat4","reviews"]),de={objects:["owner"],documents:["object"],revisions:["document","author"],jobs:["object"],identities:["object"],custom_objects:["object"],context_sessions:["node"],peer_stubs:["object"],object_kinds:["owner"],watches:["watcher","object","link_app"],exposures:["node","object"],rules:["object","owner"],edges:["src","dst"],unresolved_links:["src"]},W=new Set(["daemon_owner","schema_version","schema_min_reader","ledger_migrated"]),me={canvas_meta:"key",message_sequence:"id"};function H(){return ee(16).toString("hex")}p(H,"newId");function f(t){return`"${t.replace(/"/g,'""')}"`}p(f,"quote");function fe(t){switch(t){case"bigint":return"bigint[]";case"double precision":return"float8[]";case"bytea":return"bytea[]";case"text":case"jsonb":return"text[]";default:throw new Error(`import: no array type for Postgres column type ${t}`)}}p(fe,"arrayType");async function we(t){const o=await t.query(`SELECT c.relname AS tbl, a.attname AS col, format_type(a.atttypid, a.atttypmod) AS type, a.attidentity <> '' AS identity, (NOT a.attnotnull OR a.atthasdef OR a.attidentity <> '') AS optional
1
+ var X=Object.defineProperty;var p=(s,t)=>X(s,"name",{value:t,configurable:!0});import{DatabaseSync as z}from"node:sqlite";import{createHash as Z,randomBytes as ee}from"node:crypto";import{existsSync as L,mkdirSync as te,renameSync as H}from"node:fs";import{dirname as oe}from"node:path";import se from"pg";import{withExclusiveDirectoryLockAsync as re}from"../exclusive-lock.js";import{DAEMON_STARTUP_WINDOW_MS as ne}from"../../daemon/startup-policy.js";import{migrate as ae}from"./migrations.js";import{readLegacyPrivacy as ie}from"../graph/types.js";import{LEGACY_LOCAL_PERSON as ce}from"./legacy-person.js";import{REBUILD_ALL_TOTALS as ue}from"../ledger/totals.js";import{canvasDbPath as U,onDiskMigrationLockPath as de}from"./paths.js";import{canvasSearchPathOptions as pe}from"./schema-version.js";const k="imported_from_sqlite",B="exported_to_sqlite",M=1e4,N=["objects","documents","revisions","revision_parents","jobs","identities","custom_objects","context_sessions","peer_stubs","object_kinds","watches","watch_subscriptions","exposures","rules","edges","unresolved_links","nodes","focuses","canvas_meta","runtime_state","warm_pool","sessions","executions","worktrees","node_faults","node_tokens","node_outcome_deliveries","deleted_isolated_nodes","create_requests","waits","messages","message_sequence","message_causes","deliveries","structured_requests","daemon_actions","human_requests","human_action_deliveries","review_comments","review_comment_events","run_events","idempotency_keys","grants","memory_reads","app_users","uploads","upload_uses","shares","audit_rows","provider_approvals","crons","cron_runs","peer_outbox","peer_locations","peer_ingested","peer_ingested_destinations"],me=new Set(["sqlite_sequence","sqlite_stat1","sqlite_stat4","reviews","usage","grantee_usage","provider_receipts"]),Ee={objects:["owner"],documents:["object"],revisions:["document","author"],jobs:["object"],identities:["object"],custom_objects:["object"],context_sessions:["node"],peer_stubs:["object"],object_kinds:["owner"],watches:["watcher","object","link_app"],exposures:["node","object"],rules:["object","owner"],edges:["src","dst"],unresolved_links:["src"]},W=new Set(["daemon_owner","schema_version","schema_min_reader","ledger_migrated"]),fe={canvas_meta:"key",message_sequence:"id"};function G(){return ee(16).toString("hex")}p(G,"newId");function f(s){return`"${s.replace(/"/g,'""')}"`}p(f,"quote");function we(s){switch(s){case"bigint":return"bigint[]";case"double precision":return"float8[]";case"bytea":return"bytea[]";case"text":case"jsonb":return"text[]";default:throw new Error(`import: no array type for Postgres column type ${s}`)}}p(we,"arrayType");async function le(s){const t=await s.query(`SELECT c.relname AS tbl, a.attname AS col, format_type(a.atttypid, a.atttypmod) AS type, a.attidentity <> '' AS identity, (NOT a.attnotnull OR a.atthasdef OR a.attidentity <> '') AS optional
2
2
  FROM pg_attribute a JOIN pg_class c ON c.oid = a.attrelid
3
3
  WHERE c.relnamespace = current_schema()::regnamespace AND c.relkind = 'r' AND a.attnum > 0 AND NOT a.attisdropped
4
- ORDER BY c.relname, a.attnum`),e=new Map;for(const a of o.rows){const n=e.get(a.tbl)??[];n.push({name:a.col,type:a.type,identity:a.identity,optional:a.optional}),e.set(a.tbl,n)}return e}p(we,"pgColumns");const le="\uFFFD";function Ee(t){if(!t.includes("\\u0000"))return{text:t,replaced:0};let o;try{o=JSON.parse(t)}catch{return{text:t,replaced:0}}let e=0;const a=p(c=>{const i=c.split("\0");return e+=i.length-1,i.join(le)},"clean"),n=p(c=>typeof c=="string"?a(c):Array.isArray(c)?c.map(n):c!==null&&typeof c=="object"?Object.fromEntries(Object.entries(c).map(([i,_])=>[a(i),n(_)])):c,"walk"),l=n(o);return e===0?{text:t,replaced:e}:{text:JSON.stringify(l),replaced:e}}p(Ee,"scrubNul");async function ye(t,o,e,a,n,l,c){for(let i=0;i<a.length;i++)for(const _ of e){const s=n[l.indexOf(_)][i];if(s!=null){await t.query("SAVEPOINT import_probe");try{await t.query("SELECT $1::jsonb",[s]),await t.query("RELEASE SAVEPOINT import_probe")}catch(u){return await t.query("ROLLBACK TO SAVEPOINT import_probe"),new Error(`import: ${o}.${_.name} at ${c(a[i])} is not a value jsonb accepts: ${u.message}`,{cause:u})}}}}p(ye,"describeJsonbFailure");function j(t,o,e,a,n){if(n==null)return null;const l=t.get(Number(n));if(l===void 0)throw new Error(`import: ${e}.${a} references ${o} rowid ${String(n)}, which does not exist`);return l}p(j,"lookup");async function _e(t,o,e,a,n,l){const c=t.prepare(`PRAGMA table_info(${f(e)})`).all(),i=new Set(c.map(r=>r.name)),_=p(r=>r==="seq"||r==="watch_id"||r==="subscription_id","minted"),s=a.filter(r=>i.has(r.name)||_(r.name)||!r.optional),E=!t.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?").get(e).sql.toUpperCase().includes("WITHOUT ROWID"),b=new Set(de[e]??[]),$=[];for(const r of s)if(r.name==="seq"&&!i.has("seq")){if(!E)throw new Error(`import: ${e} needs seq from rowid but has none`);$.push("rowid AS seq")}else if(r.name==="watch_id"||r.name==="subscription_id")$.push("NULL AS "+f(r.name));else if(i.has(r.name))$.push(f(r.name));else throw new Error(`import: SQLite table ${e} has no column ${r.name}`);E&&$.push("rowid AS _rowid");const y=e==="canvas_meta"?` WHERE key NOT IN (${[...W].map(r=>`'${r}'`).join(",")})`:"",h=t.prepare(`SELECT ${$.join(", ")} FROM ${f(e)}${y}${E?" ORDER BY rowid":""}`),R=s.map(r=>f(r.name)),T=s.map((r,d)=>`$${d+1}::${fe(r.type)}`),A=s.map((r,d)=>`c${d}`),V=s.map((r,d)=>r.type==="jsonb"?`c${d}::jsonb`:`c${d}`);let v=`INSERT INTO ${f(e)} (${R.join(", ")}) SELECT ${V.join(", ")} FROM unnest(${T.join(", ")}) AS u(${A.join(", ")})`;const N=me[e];if(N!==void 0){const r=s.filter(d=>d.name!==N).map(d=>`${f(d.name)} = EXCLUDED.${f(d.name)}`);v+=` ON CONFLICT (${f(N)}) DO UPDATE SET ${r.join(", ")}`}const I=e==="objects",D=s.filter(r=>r.type==="jsonb"),k=E?"_rowid":s[0].name,J=p(r=>`${E?"rowid":k} ${String(r[k])}`,"key");let x=0,S=[];const P=p(async()=>{if(S.length===0)return;const r=s.map(()=>new Array(S.length));if(S.forEach((d,g)=>{s.forEach((m,Q)=>{let w=d[m.name];if(e==="objects"&&m.name==="privacy"?w=ie(w):m.name==="watch_id"||m.name==="subscription_id"?w=H():e==="revision_parents"&&(m.name==="child"||m.name==="parent")?w=j(n.revisions,"revision",e,m.name,w):e==="watch_subscriptions"&&m.name==="watch"?w=j(n.watches,"watch",e,m.name,w):b.has(m.name)&&(w=I?null:j(n.objects,"object",e,m.name,w)),m.type==="bytea"&&w instanceof Uint8Array&&!Buffer.isBuffer(w)&&(w=Buffer.from(w.buffer,w.byteOffset,w.byteLength)),m.type==="jsonb"&&typeof w=="string"){const q=Ee(w);q.replaced>0&&(w=q.text,l.set(`${e}.${m.name}`,(l.get(`${e}.${m.name}`)??0)+q.replaced))}r[Q][g]=w??null}),e==="watches"&&n.watches.set(Number(d._rowid),r[s.findIndex(m=>m.name==="watch_id")][g])}),D.length===0)await o.query(v,r);else{await o.query("SAVEPOINT import_batch");try{await o.query(v,r)}catch(d){throw await o.query("ROLLBACK TO SAVEPOINT import_batch"),await ye(o,e,D,S,r,s,J)??d}await o.query("RELEASE SAVEPOINT import_batch")}x+=S.length,S=[]},"flush");for(const r of h.iterate())e==="objects"&&n.objects.set(Number(r._rowid),r.id),e==="revisions"&&n.revisions.set(Number(r._rowid),r.rev_id),S.push(r),S.length>=M&&await P();if(await P(),I){const r=t.prepare("SELECT id, owner FROM objects WHERE owner IS NOT NULL").all();for(let d=0;d<r.length;d+=M){const g=r.slice(d,d+M);await o.query("UPDATE objects o SET owner = v.owner FROM unnest($1::text[], $2::text[]) AS v(id, owner) WHERE o.id = v.id",[g.map(m=>m.id),g.map(m=>j(n.objects,"object","objects","owner",m.owner))])}}return x}p(_e,"copyTable");async function he(t,o){for(const[e,a]of o)for(const n of a)n.identity&&await t.query(`SELECT setval(pg_get_serial_sequence($1, $2), COALESCE((SELECT max(${f(n.name)}) FROM ${f(e)}), 1), (SELECT max(${f(n.name)}) IS NOT NULL FROM ${f(e)}))`,[f(e),n.name])}p(he,"advanceIdentities");function G(t,o,e=""){return Number(t.prepare(`SELECT count(*) AS n FROM ${f(o)}${e}`).get().n)}p(G,"sqliteCount");async function $e(t,o,e){const a=[];for(const s of L){if(s==="canvas_meta")continue;const u=G(t,s),E=Number((await o.query(`SELECT count(*) AS n FROM ${f(s)}`)).rows[0].n);u!==E&&a.push(`${s}: SQLite ${u} rows, Postgres ${E}`)}for(const s of t.prepare(`SELECT key, value FROM canvas_meta WHERE key NOT IN (${[...W].map(u=>`'${u}'`).join(",")})`).all())(await o.query("SELECT value FROM canvas_meta WHERE key = $1",[s.key])).rows[0]?.value!==s.value&&a.push(`canvas_meta ${s.key}: value differs`);const n=p(s=>{const u=Z("sha256");for(const E of s)u.update(E).update(`
5
- `);return u.digest("hex")},"hash"),l=t.prepare("SELECT id FROM objects ORDER BY rowid").all().map(s=>s.id),c=(await o.query("SELECT id FROM objects ORDER BY seq")).rows.map(s=>s.id);n(l)!==n(c)&&a.push("objects: ids in insertion order differ");const i=t.prepare("SELECT o.id AS id, p.id AS owner FROM objects o JOIN objects p ON p.rowid = o.owner ORDER BY o.rowid").all(),_=(await o.query("SELECT id, owner FROM objects WHERE owner IS NOT NULL ORDER BY seq")).rows;n(i.map(s=>`${s.id}>${s.owner}`))!==n(_.map(s=>`${s.id}>${s.owner}`))&&a.push("objects: owner links differ");for(const[s,u]of[["run_events","body_json"],["messages","body_json"],["deliveries","last_failure_json"]]){const E=Number(t.prepare(`SELECT COALESCE(sum(length(CAST(${f(u)} AS TEXT))), 0) AS n FROM ${f(s)}`).get().n),b=e.get(s).find(h=>h.name===u).type,$=b==="jsonb"?`${f(u)}::text`:f(u);if(b==="jsonb")continue;const y=Number((await o.query(`SELECT COALESCE(sum(length(${$})), 0)::bigint AS n FROM ${f(s)}`)).rows[0].n);E!==y&&a.push(`${s}.${u}: total length SQLite ${E}, Postgres ${y}`)}if(a.length>0)throw new Error(`SQLite \u2192 Postgres import verification failed (rolled back; canvas.db untouched):
4
+ ORDER BY c.relname, a.attnum`),e=new Map;for(const a of t.rows){const n=e.get(a.tbl)??[];n.push({name:a.col,type:a.type,identity:a.identity,optional:a.optional}),e.set(a.tbl,n)}return e}p(le,"pgColumns");const _e="\uFFFD";function he(s){if(!s.includes("\\u0000"))return{text:s,replaced:0};let t;try{t=JSON.parse(s)}catch{return{text:s,replaced:0}}let e=0;const a=p(d=>{const i=d.split("\0");return e+=i.length-1,i.join(_e)},"clean"),n=p(d=>typeof d=="string"?a(d):Array.isArray(d)?d.map(n):d!==null&&typeof d=="object"?Object.fromEntries(Object.entries(d).map(([i,_])=>[a(i),n(_)])):d,"walk"),c=n(t);return e===0?{text:s,replaced:e}:{text:JSON.stringify(c),replaced:e}}p(he,"scrubNul");async function ye(s,t,e,a,n,c,d){for(let i=0;i<a.length;i++)for(const _ of e){const o=n[c.indexOf(_)][i];if(o!=null){await s.query("SAVEPOINT import_probe");try{await s.query("SELECT $1::jsonb",[o]),await s.query("RELEASE SAVEPOINT import_probe")}catch(u){return await s.query("ROLLBACK TO SAVEPOINT import_probe"),new Error(`import: ${t}.${_.name} at ${d(a[i])} is not a value jsonb accepts: ${u.message}`,{cause:u})}}}}p(ye,"describeJsonbFailure");function v(s,t,e,a,n){if(n==null)return null;const c=s.get(Number(n));if(c===void 0)throw new Error(`import: ${e}.${a} references ${t} rowid ${String(n)}, which does not exist`);return c}p(v,"lookup");async function $e(s,t,e,a,n,c){const d=s.prepare(`PRAGMA table_info(${f(e)})`).all(),i=new Set(d.map(r=>r.name)),_=p(r=>r==="seq"||r==="watch_id"||r==="subscription_id","minted"),o=a.filter(r=>i.has(r.name)||_(r.name)||!r.optional),w=!s.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?").get(e).sql.toUpperCase().includes("WITHOUT ROWID"),$=new Set(Ee[e]??[]),y=[];for(const r of o)if(r.name==="seq"&&!i.has("seq")){if(!w)throw new Error(`import: ${e} needs seq from rowid but has none`);y.push("rowid AS seq")}else if(r.name==="watch_id"||r.name==="subscription_id")y.push("NULL AS "+f(r.name));else if(i.has(r.name))y.push(f(r.name));else throw new Error(`import: SQLite table ${e} has no column ${r.name}`);w&&y.push("rowid AS _rowid");const h=e==="canvas_meta"?` WHERE key NOT IN (${[...W].map(r=>`'${r}'`).join(",")})`:"",b=s.prepare(`SELECT ${y.join(", ")} FROM ${f(e)}${h}${w?" ORDER BY rowid":""}`),S=o.map(r=>f(r.name)),R=o.map((r,m)=>`$${m+1}::${we(r.type)}`),O=o.map((r,m)=>`c${m}`),A=o.map((r,m)=>r.type==="jsonb"?`c${m}::jsonb`:`c${m}`);let j=`INSERT INTO ${f(e)} (${S.join(", ")}) SELECT ${A.join(", ")} FROM unnest(${R.join(", ")}) AS u(${O.join(", ")})`;const C=fe[e];if(C!==void 0){const r=o.filter(m=>m.name!==C).map(m=>`${f(m.name)} = EXCLUDED.${f(m.name)}`);j+=` ON CONFLICT (${f(C)}) DO UPDATE SET ${r.join(", ")}`}const D=e==="objects",x=o.filter(r=>r.type==="jsonb"),I=w?"_rowid":o[0].name,J=p(r=>`${w?"rowid":I} ${String(r[I])}`,"key");let P=0,g=[];const F=p(async()=>{if(g.length===0)return;const r=o.map(()=>new Array(g.length));if(g.forEach((m,T)=>{o.forEach((E,Q)=>{let l=m[E.name];if(e==="objects"&&E.name==="privacy"?l=ie(l):E.name==="watch_id"||E.name==="subscription_id"?l=G():e==="revision_parents"&&(E.name==="child"||E.name==="parent")?l=v(n.revisions,"revision",e,E.name,l):e==="watch_subscriptions"&&E.name==="watch"?l=v(n.watches,"watch",e,E.name,l):$.has(E.name)&&(l=D?null:v(n.objects,"object",e,E.name,l)),E.type==="bytea"&&l instanceof Uint8Array&&!Buffer.isBuffer(l)&&(l=Buffer.from(l.buffer,l.byteOffset,l.byteLength)),E.type==="jsonb"&&typeof l=="string"){const q=he(l);q.replaced>0&&(l=q.text,c.set(`${e}.${E.name}`,(c.get(`${e}.${E.name}`)??0)+q.replaced))}r[Q][T]=l??null}),e==="watches"&&n.watches.set(Number(m._rowid),r[o.findIndex(E=>E.name==="watch_id")][T])}),x.length===0)await t.query(j,r);else{await t.query("SAVEPOINT import_batch");try{await t.query(j,r)}catch(m){throw await t.query("ROLLBACK TO SAVEPOINT import_batch"),await ye(t,e,x,g,r,o,J)??m}await t.query("RELEASE SAVEPOINT import_batch")}P+=g.length,g=[]},"flush");for(const r of b.iterate())e==="objects"&&n.objects.set(Number(r._rowid),r.id),e==="revisions"&&n.revisions.set(Number(r._rowid),r.rev_id),g.push(r),g.length>=M&&await F();if(await F(),D){const r=s.prepare("SELECT id, owner FROM objects WHERE owner IS NOT NULL").all();for(let m=0;m<r.length;m+=M){const T=r.slice(m,m+M);await t.query("UPDATE objects o SET owner = v.owner FROM unnest($1::text[], $2::text[]) AS v(id, owner) WHERE o.id = v.id",[T.map(E=>E.id),T.map(E=>v(n.objects,"object","objects","owner",E.owner))])}}return P}p($e,"copyTable");async function Se(s,t){for(const[e,a]of t)for(const n of a)n.identity&&await s.query(`SELECT setval(pg_get_serial_sequence($1, $2), COALESCE((SELECT max(${f(n.name)}) FROM ${f(e)}), 1), (SELECT max(${f(n.name)}) IS NOT NULL FROM ${f(e)}))`,[f(e),n.name])}p(Se,"advanceIdentities");function Y(s,t,e=""){return Number(s.prepare(`SELECT count(*) AS n FROM ${f(t)}${e}`).get().n)}p(Y,"sqliteCount");async function ge(s,t,e){const a=[];for(const o of N){if(o==="canvas_meta")continue;const u=Y(s,o),w=Number((await t.query(`SELECT count(*) AS n FROM ${f(o)}`)).rows[0].n);u!==w&&a.push(`${o}: SQLite ${u} rows, Postgres ${w}`)}for(const o of s.prepare(`SELECT key, value FROM canvas_meta WHERE key NOT IN (${[...W].map(u=>`'${u}'`).join(",")})`).all())(await t.query("SELECT value FROM canvas_meta WHERE key = $1",[o.key])).rows[0]?.value!==o.value&&a.push(`canvas_meta ${o.key}: value differs`);const n=p(o=>{const u=Z("sha256");for(const w of o)u.update(w).update(`
5
+ `);return u.digest("hex")},"hash"),c=s.prepare("SELECT id FROM objects ORDER BY rowid").all().map(o=>o.id),d=(await t.query("SELECT id FROM objects ORDER BY seq")).rows.map(o=>o.id);n(c)!==n(d)&&a.push("objects: ids in insertion order differ");const i=s.prepare("SELECT o.id AS id, p.id AS owner FROM objects o JOIN objects p ON p.rowid = o.owner ORDER BY o.rowid").all(),_=(await t.query("SELECT id, owner FROM objects WHERE owner IS NOT NULL ORDER BY seq")).rows;n(i.map(o=>`${o.id}>${o.owner}`))!==n(_.map(o=>`${o.id}>${o.owner}`))&&a.push("objects: owner links differ");for(const[o,u]of[["run_events","body_json"],["messages","body_json"],["deliveries","last_failure_json"]]){const w=Number(s.prepare(`SELECT COALESCE(sum(length(CAST(${f(u)} AS TEXT))), 0) AS n FROM ${f(o)}`).get().n),$=e.get(o).find(b=>b.name===u).type,y=$==="jsonb"?`${f(u)}::text`:f(u);if($==="jsonb")continue;const h=Number((await t.query(`SELECT COALESCE(sum(length(${y})), 0)::bigint AS n FROM ${f(o)}`)).rows[0].n);w!==h&&a.push(`${o}.${u}: total length SQLite ${w}, Postgres ${h}`)}if(a.length>0)throw new Error(`SQLite \u2192 Postgres import verification failed (rolled back; canvas.db untouched):
6
6
  ${a.join(`
7
- `)}`)}p($e,"verify");async function be(t,o,e={}){const a=e.log??(()=>{}),n=Date.now(),l=await we(o),c=t.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all().map(y=>y.name),i=c.filter(y=>!pe.has(y)&&!L.includes(y));if(i.length>0)throw new Error(`import: SQLite tables with no copy rule: ${i.join(", ")}`);const _=L.filter(y=>!l.has(y));if(_.length>0)throw new Error(`import: Postgres tables missing from the schema: ${_.join(", ")}`);const s=c.includes("reviews")?G(t,"reviews"):0;if(s>0)throw new Error(`import: the legacy reviews table still holds ${s} rows after the SQLite migration chain`);const u={objects:new Map,revisions:new Map,watches:new Map},E=[],b=new Map;let $=0;await o.query("BEGIN");try{await o.query("ALTER TABLE nodes DISABLE TRIGGER USER"),await o.query("ALTER TABLE deliveries DISABLE TRIGGER USER");for(const h of L){const R=Date.now(),T=await _e(t,o,h,l.get(h),u,b),A=Date.now()-R;E.push({table:h,rows:T,ms:A}),a(`import ${h}: ${T} rows in ${A} ms`)}await o.query("ALTER TABLE nodes ENABLE TRIGGER USER"),await o.query("ALTER TABLE deliveries ENABLE TRIGGER USER"),await he(o,l);const y=Date.now();await $e(t,o,l),$=Date.now()-y,a(`import verify: ${$} ms`),await o.query("INSERT INTO canvas_meta (key, value) VALUES ($1, $2)",[C,JSON.stringify({at:new Date().toISOString(),rows:E.reduce((h,R)=>h+R.rows,0)})]),await o.query("COMMIT")}catch(y){throw await o.query("ROLLBACK").catch(()=>{}),y}return b.size>0&&a(`import: replaced NUL (\\u0000) with U+FFFD in jsonb values: ${[...b].map(([y,h])=>`${y} ${h}`).join(", ")}`),{tables:E,verifyMs:$,totalMs:Date.now()-n,nulReplaced:Object.fromEntries(b)}}p(be,"copySqliteToPostgres");const Y=["","-wal","-shm"];function K(t=B()){if(O(`${t}.imported`)){const o=`${t}.imported.${Date.now()}`;for(const e of Y)O(`${t}.imported${e}`)&&F(`${t}.imported${e}`,`${o}${e}`)}for(const o of Y)O(`${t}${o}`)&&F(`${t}${o}`,`${t}.imported${o}`)}p(K,"renameImportedDatabase");async function Me(t,o=()=>{}){const e=B();if(!O(e))return null;const a=ce();return te(oe(a),{recursive:!0,mode:448}),re(a,async()=>{if(!O(e))return null;const n=new se.Client({connectionString:t,application_name:`crtr-import-${process.pid}`,options:ue()});await n.connect();try{const l=await n.query("SELECT key FROM canvas_meta WHERE key = ANY($1)",[[C,U]]),c=new Set(l.rows.map(s=>s.key));if(c.has(C)){if(c.has(U))throw new Error(`${e} was written by \`crtr sys db export-sqlite\` and the Postgres database predates it; importing would drop the exported writes. Stop the daemon and delete the Postgres cluster (runtime/pg/data) to import ${e} afresh, or move ${e} aside to keep using Postgres.`);return K(e),o(`import: finished the rename of ${e} (already imported)`),null}const i=new z(e);let _;try{i.exec("PRAGMA busy_timeout = 5000;"),i.exec("PRAGMA foreign_keys = ON;"),ae(i),_=await be(i,n,{log:o})}finally{i.close()}return K(e),o(`import: ${_.tables.reduce((s,u)=>s+u.rows,0)} rows in ${_.totalMs} ms; canvas.db renamed to canvas.db.imported`),_}finally{await n.end().catch(()=>{})}},{timeoutMs:ne,timeoutError:p(()=>new Error(`timed out waiting for the canvas import lock: ${a}`),"timeoutError")})}p(Me,"importSqliteCanvasIfPresent");export{U as EXPORT_MARK_KEY,C as IMPORT_MARK_KEY,de as OBJECT_COLUMNS,L as TABLE_ORDER,be as copySqliteToPostgres,Me as importSqliteCanvasIfPresent,we as pgColumns,f as quote,K as renameImportedDatabase};
7
+ `)}`)}p(ge,"verify");async function be(s,t){const e=s.prepare("SELECT grantee, month, payer, source, served_model, input_tokens, output_tokens, cached_tokens, usd_equivalent FROM usage").all();if(e.length===0)return 0;const n=(await t.query("SELECT value FROM canvas_meta WHERE key = 'person_id'")).rows[0]?.value??ce,c=p((o,u=String)=>e.map(w=>w[o]===null?null:u(w[o])),"column"),d=await t.query(`INSERT INTO charges (id, request, at, month, kind, what, units, usd, payer, funding, sub, grantee, provider)
8
+ SELECT 'legacy:' || u.grantee || ':' || u.month || ':' || u.payer || ':' || u.source || ':' || u.served_model,
9
+ NULL, u.month || '-01T00:00:00.000Z', u.month,
10
+ CASE WHEN u.source = 'model' THEN 'model' ELSE 'tool' END,
11
+ CASE WHEN u.source = 'model' THEN u.served_model ELSE substr(u.source, 10) END,
12
+ CASE WHEN u.source = 'model' THEN jsonb_build_object('input', u.input_tokens, 'output', u.output_tokens, 'cache_read', u.cached_tokens, 'cache_write', 0) END,
13
+ u.usd_equivalent,
14
+ CASE WHEN u.payer = 'app' THEN u.grantee ELSE $1::text END,
15
+ CASE WHEN u.payer = 'app' THEN 'endpoint' WHEN u.source = 'model' THEN 'legacy' ELSE 'bill' END,
16
+ $1::text, u.grantee,
17
+ CASE WHEN u.source = 'model' THEN NULL ELSE 'app:' || substr(u.source, 10) END
18
+ FROM unnest($2::text[], $3::text[], $4::text[], $5::text[], $6::text[], $7::bigint[], $8::bigint[], $9::bigint[], $10::float8[])
19
+ AS u(grantee, month, payer, source, served_model, input_tokens, output_tokens, cached_tokens, usd_equivalent)`,[n,c("grantee"),c("month"),c("payer"),c("source"),c("served_model"),c("input_tokens"),c("output_tokens"),c("cached_tokens"),c("usd_equivalent",Number)]);for(const o of ue)await t.query(o);const i=(await t.query("SELECT count(*)::int AS n, COALESCE(SUM(usd), 0)::float8 AS usd FROM charges WHERE id LIKE 'legacy:%'")).rows[0],_=e.reduce((o,u)=>o+Number(u.usd_equivalent??0),0);if(d.rowCount!==e.length||i.n!==e.length||Math.abs(i.usd-_)>1e-4)throw new Error(`import: the usage \u2192 charges copy does not match (${e.length} usage rows, ${d.rowCount} charges inserted, ${i.n} legacy charges; usd ${_} vs ${i.usd})`);return e.length}p(be,"importLegacyUsage");async function Re(s,t,e={}){const a=e.log??(()=>{}),n=Date.now(),c=await le(t),d=s.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all().map(h=>h.name),i=d.filter(h=>!me.has(h)&&!N.includes(h));if(i.length>0)throw new Error(`import: SQLite tables with no copy rule: ${i.join(", ")}`);const _=N.filter(h=>!c.has(h));if(_.length>0)throw new Error(`import: Postgres tables missing from the schema: ${_.join(", ")}`);const o=d.includes("reviews")?Y(s,"reviews"):0;if(o>0)throw new Error(`import: the legacy reviews table still holds ${o} rows after the SQLite migration chain`);const u={objects:new Map,revisions:new Map,watches:new Map},w=[],$=new Map;let y=0;await t.query("BEGIN");try{await t.query("ALTER TABLE nodes DISABLE TRIGGER USER"),await t.query("ALTER TABLE deliveries DISABLE TRIGGER USER");for(const S of N){const R=Date.now(),O=await $e(s,t,S,c.get(S),u,$),A=Date.now()-R;w.push({table:S,rows:O,ms:A}),a(`import ${S}: ${O} rows in ${A} ms`)}await t.query("ALTER TABLE nodes ENABLE TRIGGER USER"),await t.query("ALTER TABLE deliveries ENABLE TRIGGER USER"),await Se(t,c);const h=await be(s,t);h>0&&a(`import usage: ${h} rows into charges`);const b=Date.now();await ge(s,t,c),y=Date.now()-b,a(`import verify: ${y} ms`),await t.query("INSERT INTO canvas_meta (key, value) VALUES ($1, $2)",[k,JSON.stringify({at:new Date().toISOString(),rows:w.reduce((S,R)=>S+R.rows,0)})]),await t.query("COMMIT")}catch(h){throw await t.query("ROLLBACK").catch(()=>{}),h}return $.size>0&&a(`import: replaced NUL (\\u0000) with U+FFFD in jsonb values: ${[...$].map(([h,b])=>`${h} ${b}`).join(", ")}`),{tables:w,verifyMs:y,totalMs:Date.now()-n,nulReplaced:Object.fromEntries($)}}p(Re,"copySqliteToPostgres");const K=["","-wal","-shm"];function V(s=U()){if(L(`${s}.imported`)){const t=`${s}.imported.${Date.now()}`;for(const e of K)L(`${s}.imported${e}`)&&H(`${s}.imported${e}`,`${t}${e}`)}for(const t of K)L(`${s}${t}`)&&H(`${s}${t}`,`${s}.imported${t}`)}p(V,"renameImportedDatabase");async function Pe(s,t=()=>{}){const e=U();if(!L(e))return null;const a=de();return te(oe(a),{recursive:!0,mode:448}),re(a,async()=>{if(!L(e))return null;const n=new se.Client({connectionString:s,application_name:`crtr-import-${process.pid}`,options:pe()});await n.connect();try{const c=await n.query("SELECT key FROM canvas_meta WHERE key = ANY($1)",[[k,B]]),d=new Set(c.rows.map(o=>o.key));if(d.has(k)){if(d.has(B))throw new Error(`${e} was written by \`crtr sys db export-sqlite\` and the Postgres database predates it; importing would drop the exported writes. Stop the daemon and delete the Postgres cluster (runtime/pg/data) to import ${e} afresh, or move ${e} aside to keep using Postgres.`);return V(e),t(`import: finished the rename of ${e} (already imported)`),null}const i=new z(e);let _;try{i.exec("PRAGMA busy_timeout = 5000;"),i.exec("PRAGMA foreign_keys = ON;"),ae(i),_=await Re(i,n,{log:t})}finally{i.close()}return V(e),t(`import: ${_.tables.reduce((o,u)=>o+u.rows,0)} rows in ${_.totalMs} ms; canvas.db renamed to canvas.db.imported`),_}finally{await n.end().catch(()=>{})}},{timeoutMs:ne,timeoutError:p(()=>new Error(`timed out waiting for the canvas import lock: ${a}`),"timeoutError")})}p(Pe,"importSqliteCanvasIfPresent");export{B as EXPORT_MARK_KEY,k as IMPORT_MARK_KEY,Ee as OBJECT_COLUMNS,N as TABLE_ORDER,Re as copySqliteToPostgres,Pe as importSqliteCanvasIfPresent,le as pgColumns,f as quote,V as renameImportedDatabase};
@@ -29,6 +29,5 @@ export interface SchemaChainOptions {
29
29
  /** Bring the connected database to this build's schema. Takes `pg_advisory_lock` for the session and reads the version under
30
30
  * it, so processes opening together queue and a late one applies nothing. Refuses a database whose `schema_min_reader` is above
31
31
  * this build's version; opens one whose `schema_version` is above it when `schema_min_reader` allows. A database with no
32
- * `schema_version` gets `schema-pg.sql`, which runs as one multi-statement simple query, one transaction. A database at the
33
- * baseline version first runs the file's `-- <name>:begin … -- <name>:end` blocks, in file order, each idempotent (S1 deletes this). */
32
+ * `schema_version` gets `schema-pg.sql`, which runs as one multi-statement simple query, one transaction. */
34
33
  export declare function ensureSchema(client: pg.Client, options?: SchemaChainOptions): Promise<void>;
@@ -1 +1 @@
1
- var _=Object.defineProperty;var u=(a,e)=>_(a,"name",{value:e,configurable:!0});import{createHash as v}from"node:crypto";import{readdirSync as S,readFileSync as E}from"node:fs";import{dirname as T,join as d}from"node:path";import{fileURLToPath as p}from"node:url";import{CANVAS_SCHEMA_VERSION as $,readStoredVersions as f}from"./schema-version.js";const w=T(p(import.meta.url)),g=d(w,"schema-pg.sql"),k=d(w,"schema-pg"),y=7427001,h=1,A=5e3,L=12e4,q="55P03",C=/^(\d{3})-([a-z0-9][a-z0-9-]*)\.sql$/;function M(a=k){const e=[];for(const t of S(a).sort()){if(t===".gitkeep")continue;const o=C.exec(t);if(o===null)throw new Error(`schema delta directory ${a}: ${t} is not named NNN-<name>.sql`);e.push({version:Number(o[1]),name:o[2],file:d(a,t)})}return e.forEach((t,o)=>{if(t.version!==h+1+o)throw new Error(`schema delta directory ${a}: expected delta ${h+1+o} but found ${t.version}-${t.name}.sql; deltas are numbered contiguously from ${h+1}`)}),e}u(M,"listDeltas");async function l(a,e){await a.query("INSERT INTO canvas_meta (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value",[e.key,e.hash])}u(l,"recordBlock");const b=u(a=>new Promise(e=>setTimeout(e,a)),"sleep");async function D(a,e,t){const o=E(e.file,"utf8"),c=Date.now();let m=250;for(let r=1;;r++)try{if(await a.query("BEGIN"),await a.query(`SET LOCAL lock_timeout = '${t.lockTimeoutMs}ms'`),await a.query(o),(await a.query("UPDATE canvas_meta SET value = $1 WHERE key = 'schema_version' AND value = $2",[String(e.version),String(e.version-1)])).rowCount!==1)throw new Error(`schema step ${e.version} (${e.name}): schema_version was not ${e.version-1}; another writer changed the schema outside the chain`);await a.query("COMMIT"),t.emit?.("info","schema.step.applied",{version:e.version,name:e.name,ms:Date.now()-c,attempts:r});return}catch(s){if(await a.query("ROLLBACK").catch(()=>{}),s.code!==q)throw s;if(Date.now()+m-c>t.retryBudgetMs)throw new Error(`schema step ${e.version} (${e.name}) could not get its locks within ${t.retryBudgetMs} ms (${r} attempts); a long transaction holds a table it needs`,{cause:s});t.emit?.("warn","schema.step.lock_timeout",{version:e.version,name:e.name,attempt:r,retry_in_ms:m}),await b(m),m=Math.min(m*2,1e4)}}u(D,"applyStep");async function F(a,e={}){const t=e.version??$,o=M(e.deltaDir);if(e.schema!==void 0){const c=`"${e.schema.replace(/"/g,'""')}"`;await a.query(`CREATE SCHEMA IF NOT EXISTS ${c}`),await a.query(`SET search_path TO ${c}`)}await a.query("SELECT pg_advisory_lock($1)",[y]);try{const c=E(g,"utf8"),m=[...c.matchAll(/^-- (\w+):begin\n([\s\S]*?)^-- \1:end$/gm)].map(([,i,n])=>({key:`schema_block.${i}`,sql:n,hash:v("sha256").update(n).digest("hex")}));let r=await f(a);if(r!==void 0&&t<r.minReader)throw new Error(`unsupported canvas schema: this build is at version ${t} but the database requires readers at version ${r.minReader} or above (it is at ${r.version}); this build is older than the database. Select a newer runtime generation.`);let s;if(r===void 0){if(await a.query(c),r=await f(a),r===void 0)throw new Error("schema-pg.sql did not write canvas_meta.schema_version");s=r.version;for(const i of m)await l(a,i)}else if(s=r.version,s===h){const i=new Map((await a.query("SELECT key, value FROM canvas_meta WHERE key LIKE 'schema\\_block.%'")).rows.map(n=>[n.key,n.value]));for(const n of m)i.get(n.key)!==n.hash&&(await a.query(n.sql),await l(a,n))}for(const i of o)i.version<=s||(s=i.version,await D(a,i,{lockTimeoutMs:e.lockTimeoutMs??A,retryBudgetMs:e.retryBudgetMs??L,emit:e.emit}));if(s<t)throw new Error(`canvas schema version ${s} does not match this build's ${t}: no delta reaches it`)}finally{await a.query("SELECT pg_advisory_unlock($1)",[y]).catch(()=>{})}}u(F,"ensureSchema");export{h as BASELINE_VERSION,A as DEFAULT_STEP_LOCK_TIMEOUT_MS,L as DEFAULT_STEP_RETRY_BUDGET_MS,F as ensureSchema,M as listDeltas};
1
+ var w=Object.defineProperty;var i=(r,e)=>w(r,"name",{value:e,configurable:!0});import{readdirSync as _,readFileSync as d}from"node:fs";import{dirname as v,join as m}from"node:path";import{fileURLToPath as y}from"node:url";import{CANVAS_SCHEMA_VERSION as l,readStoredVersions as h}from"./schema-version.js";const E=v(y(import.meta.url)),S=m(E,"schema-pg.sql"),$=m(E,"schema-pg"),f=7427001,u=1,T=5e3,p=12e4,A="55P03",g=/^(\d{3})-([a-z0-9][a-z0-9-]*)\.sql$/;function q(r=$){const e=[];for(const t of _(r).sort()){if(t===".gitkeep")continue;const s=g.exec(t);if(s===null)throw new Error(`schema delta directory ${r}: ${t} is not named NNN-<name>.sql`);e.push({version:Number(s[1]),name:s[2],file:m(r,t)})}return e.forEach((t,s)=>{if(t.version!==u+1+s)throw new Error(`schema delta directory ${r}: expected delta ${u+1+s} but found ${t.version}-${t.name}.sql; deltas are numbered contiguously from ${u+1}`)}),e}i(q,"listDeltas");const L=i(r=>new Promise(e=>setTimeout(e,r)),"sleep");async function M(r,e,t){const s=d(e.file,"utf8"),a=Date.now();let o=250;for(let n=1;;n++)try{if(await r.query("BEGIN"),await r.query(`SET LOCAL lock_timeout = '${t.lockTimeoutMs}ms'`),await r.query(s),(await r.query("UPDATE canvas_meta SET value = $1 WHERE key = 'schema_version' AND value = $2",[String(e.version),String(e.version-1)])).rowCount!==1)throw new Error(`schema step ${e.version} (${e.name}): schema_version was not ${e.version-1}; another writer changed the schema outside the chain`);await r.query("COMMIT"),t.emit?.("info","schema.step.applied",{version:e.version,name:e.name,ms:Date.now()-a,attempts:n});return}catch(c){if(await r.query("ROLLBACK").catch(()=>{}),c.code!==A)throw c;if(Date.now()+o-a>t.retryBudgetMs)throw new Error(`schema step ${e.version} (${e.name}) could not get its locks within ${t.retryBudgetMs} ms (${n} attempts); a long transaction holds a table it needs`,{cause:c});t.emit?.("warn","schema.step.lock_timeout",{version:e.version,name:e.name,attempt:n,retry_in_ms:o}),await L(o),o=Math.min(o*2,1e4)}}i(M,"applyStep");async function O(r,e={}){const t=e.version??l,s=q(e.deltaDir);if(e.schema!==void 0){const a=`"${e.schema.replace(/"/g,'""')}"`;await r.query(`CREATE SCHEMA IF NOT EXISTS ${a}`),await r.query(`SET search_path TO ${a}`)}await r.query("SELECT pg_advisory_lock($1)",[f]);try{let a=await h(r);if(a!==void 0&&t<a.minReader)throw new Error(`unsupported canvas schema: this build is at version ${t} but the database requires readers at version ${a.minReader} or above (it is at ${a.version}); this build is older than the database. Select a newer runtime generation.`);if(a===void 0&&(await r.query(d(S,"utf8")),a=await h(r),a===void 0))throw new Error("schema-pg.sql did not write canvas_meta.schema_version");let o=a.version;for(const n of s)n.version<=o||(o=n.version,await M(r,n,{lockTimeoutMs:e.lockTimeoutMs??T,retryBudgetMs:e.retryBudgetMs??p,emit:e.emit}));if(o<t)throw new Error(`canvas schema version ${o} does not match this build's ${t}: no delta reaches it`)}finally{await r.query("SELECT pg_advisory_unlock($1)",[f]).catch(()=>{})}}i(O,"ensureSchema");export{u as BASELINE_VERSION,T as DEFAULT_STEP_LOCK_TIMEOUT_MS,p as DEFAULT_STEP_RETRY_BUDGET_MS,O as ensureSchema,q as listDeltas};