@north-light/cli 0.3.580 → 0.3.582

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 (237) hide show
  1. package/dist/api/client.d.ts +6 -1
  2. package/dist/api/client.js +3 -3
  3. package/dist/api/contract.d.ts +1 -1
  4. package/dist/api/contract.js +1 -1
  5. package/dist/api/dto/execution-start.d.ts +14 -0
  6. package/dist/api/dto/health.d.ts +22 -0
  7. package/dist/api/dto/imports.d.ts +142 -0
  8. package/dist/api/dto/imports.js +0 -0
  9. package/dist/api/error-codes.d.ts +5 -0
  10. package/dist/api/error-codes.js +1 -1
  11. package/dist/api/errors.js +1 -1
  12. package/dist/api/index.d.ts +1 -0
  13. package/dist/api/index.js +1 -1
  14. package/dist/api/routes.d.ts +2 -0
  15. package/dist/api/routes.js +1 -1
  16. package/dist/build-root.d.ts +11 -4
  17. package/dist/build-root.js +1 -1
  18. package/dist/cli.js +7 -7
  19. package/dist/clients/attach/session/connection.d.ts +3 -1
  20. package/dist/clients/attach/session/connection.js +1 -1
  21. package/dist/clients/attach/viewer.js +529 -529
  22. package/dist/commands/api-client.d.ts +12 -4
  23. package/dist/commands/api-client.js +3 -3
  24. package/dist/commands/sys/daemon.js +1 -1
  25. package/dist/commands/sys/desktop.d.ts +5 -0
  26. package/dist/commands/sys/desktop.js +1 -0
  27. package/dist/commands/sys/import/move.d.ts +55 -0
  28. package/dist/commands/sys/import/move.js +8 -0
  29. package/dist/commands/sys/import/resume.d.ts +1 -0
  30. package/dist/commands/sys/import/resume.js +1 -0
  31. package/dist/commands/sys/import/start.d.ts +4 -0
  32. package/dist/commands/sys/import/start.js +1 -0
  33. package/dist/commands/sys/join.d.ts +2 -0
  34. package/dist/commands/sys/join.js +1 -1
  35. package/dist/commands/sys/machine/rotate.d.ts +1 -0
  36. package/dist/commands/sys/machine/rotate.js +1 -0
  37. package/dist/commands/sys/signin.d.ts +7 -0
  38. package/dist/commands/sys/signin.js +2 -0
  39. package/dist/commands/sys.js +1 -1
  40. package/dist/core/blobs/r2.d.ts +4 -0
  41. package/dist/core/blobs/r2.js +7 -4
  42. package/dist/core/canvas/daemon-owner.d.ts +1 -1
  43. package/dist/core/canvas/executions.d.ts +1 -1
  44. package/dist/core/canvas/node-tokens.d.ts +2 -0
  45. package/dist/core/canvas/node-tokens.js +3 -3
  46. package/dist/core/canvas/pid.d.ts +6 -0
  47. package/dist/core/canvas/pid.js +5 -5
  48. package/dist/core/command-plugins/compose.js +1 -1
  49. package/dist/core/command.d.ts +17 -8
  50. package/dist/core/command.js +9 -9
  51. package/dist/core/directory/issuer.d.ts +3 -0
  52. package/dist/core/directory/issuer.js +1 -1
  53. package/dist/core/directory/person-credential.d.ts +61 -0
  54. package/dist/core/directory/person-credential.js +1 -0
  55. package/dist/core/directory/reach.d.ts +3 -0
  56. package/dist/core/directory/reach.js +1 -0
  57. package/dist/core/grants/mirror.d.ts +3 -0
  58. package/dist/core/grants/mirror.js +2 -2
  59. package/dist/core/graph/bodies-boot-copy.d.ts +16 -2
  60. package/dist/core/graph/bodies-boot-copy.js +1 -1
  61. package/dist/core/graph/bodies.d.ts +13 -4
  62. package/dist/core/graph/bodies.js +1 -1
  63. package/dist/core/graph/deletion.d.ts +4 -1
  64. package/dist/core/graph/deletion.js +3 -3
  65. package/dist/core/graph/documents.d.ts +4 -0
  66. package/dist/core/graph/documents.js +9 -9
  67. package/dist/core/graph/reader.js +2 -2
  68. package/dist/core/graph/repo-sync/exchange.js +2 -2
  69. package/dist/core/graph/repo-sync/ref-format.d.ts +7 -2
  70. package/dist/core/graph/repo-sync/ref-format.js +2 -2
  71. package/dist/core/import/bundle/blob-keys.d.ts +10 -0
  72. package/dist/core/import/bundle/blob-keys.js +1 -0
  73. package/dist/core/import/bundle/layout.d.ts +90 -0
  74. package/dist/core/import/bundle/layout.js +5 -0
  75. package/dist/core/import/export/archive.d.ts +11 -0
  76. package/dist/core/import/export/archive.js +1 -0
  77. package/dist/core/import/export/cloud.d.ts +23 -0
  78. package/dist/core/import/export/cloud.js +1 -0
  79. package/dist/core/import/export/dump.d.ts +34 -0
  80. package/dist/core/import/export/dump.js +3 -0
  81. package/dist/core/import/export/files.d.ts +22 -0
  82. package/dist/core/import/export/files.js +1 -0
  83. package/dist/core/import/export/inline.d.ts +3 -0
  84. package/dist/core/import/export/inline.js +1 -0
  85. package/dist/core/import/export/source.d.ts +2 -0
  86. package/dist/core/import/export/source.js +1 -0
  87. package/dist/core/import/export/stores.d.ts +9 -0
  88. package/dist/core/import/export/stores.js +1 -0
  89. package/dist/core/import/load/bind.d.ts +19 -0
  90. package/dist/core/import/load/bind.js +15 -0
  91. package/dist/core/import/load/blobs.d.ts +10 -0
  92. package/dist/core/import/load/blobs.js +1 -0
  93. package/dist/core/import/load/catalog.d.ts +32 -0
  94. package/dist/core/import/load/catalog.js +9 -0
  95. package/dist/core/import/load/copy.d.ts +26 -0
  96. package/dist/core/import/load/copy.js +6 -0
  97. package/dist/core/import/load/path-map.d.ts +7 -0
  98. package/dist/core/import/load/path-map.js +1 -0
  99. package/dist/core/import/load/prepare.d.ts +37 -0
  100. package/dist/core/import/load/prepare.js +10 -0
  101. package/dist/core/import/load/rewrite.d.ts +39 -0
  102. package/dist/core/import/load/rewrite.js +13 -0
  103. package/dist/core/import/load/rollback.d.ts +3 -0
  104. package/dist/core/import/load/rollback.js +6 -0
  105. package/dist/core/import/load/run.d.ts +22 -0
  106. package/dist/core/import/load/run.js +3 -0
  107. package/dist/core/import/load/stage.d.ts +15 -0
  108. package/dist/core/import/load/stage.js +1 -0
  109. package/dist/core/import/load/stores.d.ts +24 -0
  110. package/dist/core/import/load/stores.js +2 -0
  111. package/dist/core/import/load/verify.d.ts +4 -0
  112. package/dist/core/import/load/verify.js +12 -0
  113. package/dist/core/io.js +4 -4
  114. package/dist/core/northlight/previews.d.ts +22 -0
  115. package/dist/core/northlight/previews.js +1 -0
  116. package/dist/core/providers/call.js +1 -1
  117. package/dist/core/providers/device.js +1 -1
  118. package/dist/core/providers/files.d.ts +2 -1
  119. package/dist/core/providers/files.js +1 -1
  120. package/dist/core/runtime/broker/cli-relay.js +1 -1
  121. package/dist/core/runtime/broker/model-adapter.js +3 -3
  122. package/dist/core/runtime/broker-ownership.d.ts +1 -0
  123. package/dist/core/runtime/broker-ownership.js +1 -0
  124. package/dist/core/runtime/broker.js +1 -1
  125. package/dist/core/runtime/first-run-offer.d.ts +7 -2
  126. package/dist/core/runtime/first-run-offer.js +10 -2
  127. package/dist/core/runtime/front-door.js +4 -4
  128. package/dist/core/runtime/revive.d.ts +5 -2
  129. package/dist/core/runtime/revive.js +2 -2
  130. package/dist/core/runtime/runner/blaxel/runner.js +3 -3
  131. package/dist/core/runtime/runner/index.d.ts +1 -0
  132. package/dist/core/runtime/runner/index.js +1 -1
  133. package/dist/core/runtime/runner/machine/call.d.ts +8 -0
  134. package/dist/core/runtime/runner/machine/call.js +3 -0
  135. package/dist/core/runtime/runner/machine/environment.d.ts +1 -0
  136. package/dist/core/runtime/runner/machine/environment.js +1 -0
  137. package/dist/core/runtime/runner/machine/executor.d.ts +29 -0
  138. package/dist/core/runtime/runner/machine/executor.js +1 -0
  139. package/dist/core/runtime/runner/machine/machine-executor.test.d.ts +1 -0
  140. package/dist/core/runtime/runner/machine/machine-executor.test.js +4 -0
  141. package/dist/core/runtime/runner/machine/ops.d.ts +127 -0
  142. package/dist/core/runtime/runner/machine/ops.js +1 -0
  143. package/dist/core/runtime/runner/machine/runner.d.ts +12 -0
  144. package/dist/core/runtime/runner/machine/runner.js +1 -0
  145. package/dist/core/runtime/runner/types.d.ts +7 -1
  146. package/dist/core/runtime/warm-pool.d.ts +2 -0
  147. package/dist/core/runtime/warm-pool.js +1 -1
  148. package/dist/core/secrets/env-rows.d.ts +1 -0
  149. package/dist/core/secrets/env-rows.js +1 -1
  150. package/dist/core/storage/purge.d.ts +6 -0
  151. package/dist/core/storage/purge.js +4 -0
  152. package/dist/core/subscription-state.d.ts +1 -0
  153. package/dist/core/subscription-state.js +1 -1
  154. package/dist/daemon/api/bridge.js +3 -3
  155. package/dist/daemon/api/handlers/attach.js +1 -1
  156. package/dist/daemon/api/handlers/crons.js +1 -1
  157. package/dist/daemon/api/handlers/daemon.js +1 -1
  158. package/dist/daemon/api/handlers/execution-start.js +2 -2
  159. package/dist/daemon/api/handlers/files.js +1 -1
  160. package/dist/daemon/api/handlers/imports.d.ts +2 -0
  161. package/dist/daemon/api/handlers/imports.js +1 -0
  162. package/dist/daemon/api/handlers/machine-credential.d.ts +2 -0
  163. package/dist/daemon/api/handlers/machine-credential.js +1 -0
  164. package/dist/daemon/api/handlers/profiles.js +1 -1
  165. package/dist/daemon/api/handlers/providers.js +1 -1
  166. package/dist/daemon/api/handlers/runs.js +1 -1
  167. package/dist/daemon/api/import-auth.d.ts +6 -0
  168. package/dist/daemon/api/import-auth.js +1 -0
  169. package/dist/daemon/api/operations.d.ts +2 -2
  170. package/dist/daemon/api/operations.js +1 -1
  171. package/dist/daemon/api/router.d.ts +7 -1
  172. package/dist/daemon/api/router.js +1 -1
  173. package/dist/daemon/api/server.js +1 -1
  174. package/dist/daemon/boot-migrations.js +1 -1
  175. package/dist/daemon/broker-signals/bus.d.ts +3 -2
  176. package/dist/daemon/broker-signals/bus.js +1 -1
  177. package/dist/daemon/cron/armed-context.d.ts +1 -0
  178. package/dist/daemon/cron/armed-context.js +1 -1
  179. package/dist/daemon/cron/closed.d.ts +31 -0
  180. package/dist/daemon/cron/closed.js +1 -0
  181. package/dist/daemon/cron/ended.js +2 -2
  182. package/dist/daemon/cron/lease-recovery.js +1 -1
  183. package/dist/daemon/cron/machine-exec.d.ts +11 -0
  184. package/dist/daemon/cron/machine-exec.js +1 -0
  185. package/dist/daemon/cron/machine-fire.d.ts +26 -0
  186. package/dist/daemon/cron/machine-fire.js +1 -0
  187. package/dist/daemon/cron/recurrence.d.ts +14 -0
  188. package/dist/daemon/cron/recurrence.js +1 -0
  189. package/dist/daemon/cron/run-token.d.ts +7 -0
  190. package/dist/daemon/cron/run-token.js +1 -0
  191. package/dist/daemon/cron-run.js +2 -2
  192. package/dist/daemon/crtrd-cli.js +1 -1
  193. package/dist/daemon/crtrd.d.ts +3 -3
  194. package/dist/daemon/crtrd.js +14 -14
  195. package/dist/daemon/drain.d.ts +25 -0
  196. package/dist/daemon/drain.js +9 -0
  197. package/dist/daemon/due-sources.d.ts +1 -1
  198. package/dist/daemon/due-sources.js +1 -1
  199. package/dist/daemon/due.js +8 -8
  200. package/dist/daemon/fleet.js +5 -5
  201. package/dist/daemon/import-dump-run.d.ts +1 -0
  202. package/dist/daemon/import-dump-run.js +5 -0
  203. package/dist/daemon/machine-daemon.d.ts +10 -0
  204. package/dist/daemon/machine-daemon.js +1 -0
  205. package/dist/daemon/machine-link/client.d.ts +29 -0
  206. package/dist/daemon/machine-link/client.js +1 -0
  207. package/dist/daemon/machine-link/config.d.ts +45 -0
  208. package/dist/daemon/machine-link/config.js +3 -0
  209. package/dist/daemon/machine-link/credential.d.ts +7 -0
  210. package/dist/daemon/machine-link/credential.js +2 -0
  211. package/dist/daemon/machine-link/forward.d.ts +16 -0
  212. package/dist/daemon/machine-link/forward.js +1 -0
  213. package/dist/daemon/machine-link/frames.d.ts +49 -0
  214. package/dist/daemon/machine-link/frames.js +1 -0
  215. package/dist/daemon/machine-link/holder.d.ts +73 -0
  216. package/dist/daemon/machine-link/holder.js +9 -0
  217. package/dist/daemon/machine-link/move-notify.d.ts +4 -0
  218. package/dist/daemon/machine-link/move-notify.js +1 -0
  219. package/dist/daemon/manage.d.ts +19 -3
  220. package/dist/daemon/manage.js +3 -3
  221. package/dist/daemon/reconcilers/cron-lane.js +1 -1
  222. package/dist/daemon/reconcilers/delivery-lane.js +2 -2
  223. package/dist/daemon/reconcilers/import-lane.d.ts +9 -0
  224. package/dist/daemon/reconcilers/import-lane.js +1 -0
  225. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +1 -1
  226. package/dist/daemon/reconcilers/node-lifecycle/remote-wake.d.ts +3 -0
  227. package/dist/daemon/reconcilers/node-lifecycle/remote-wake.js +1 -1
  228. package/dist/daemon/reconcilers/node-lifecycle/tick.js +1 -1
  229. package/dist/daemon/reconcilers/purge-due-work.d.ts +13 -0
  230. package/dist/daemon/reconcilers/purge-due-work.js +1 -0
  231. package/dist/daemon/reconcilers/row-claim.d.ts +1 -0
  232. package/dist/daemon/reconcilers/row-claim.js +2 -2
  233. package/dist/daemon/runs/observer.js +3 -3
  234. package/package.json +2 -2
  235. package/packages/crouter-identity/package.json +1 -1
  236. package/runtime.lock.json +13 -13
  237. package/scripts/sandbox-start.mjs +185 -2
@@ -1 +1 @@
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
+ var u=Object.defineProperty;var o=(t,e)=>u(t,"name",{value:e,configurable:!0});import{createHash as h}from"node:crypto";import{notFound as c}from"../errors.js";const d=/^[0-9a-f]{64}$/;function s(t){if(!d.test(t))throw new Error(`invalid body hash: ${t}`);return t}o(s,"checked");function f(t){return h("sha256").update(t,"utf8").digest("hex")}o(f,"hashOf");function w(t){return t.replaceAll("\0","\uFFFD")}o(w,"storable");function p(t,e){if(e!==void 0&&f(t)!==s(e))throw new Error(`body does not match its hash ${e}`);const n=w(t);return{text:n,hash:f(n)}}o(p,"canonical");async function x(t,e,n){if((await t.prepare("INSERT INTO bodies (hash, text) VALUES (?, ?) ON CONFLICT (hash) DO NOTHING").run(s(e),n)).changes===1)return!0;const r=await t.prepare("SELECT text FROM bodies WHERE hash = ?").get(e);if(r===void 0||r.text!==n)throw new Error(`body ${e} already holds different text`);return!1}o(x,"store");async function O(t,e,n){const{text:r,hash:a}=p(e,n);return await x(t,a,r),a}o(O,"put");async function F(t,e){const n=await t.prepare("SELECT text FROM bodies WHERE hash = ?").get(s(e));if(n===void 0)throw c(`no body ${e}`);return n.text}o(F,"get");async function E(t,e){const n=[...new Set(e)].map(s),r=new Map;if(n.length===0)return r;const a=await t.prepare("SELECT hash, text FROM bodies WHERE hash = ANY(?::text[])").all(n);for(const i of a)r.set(i.hash,i.text);return r}o(E,"findMany");async function N(t,e){const n=await E(t,e);for(const r of e)if(!n.has(r))throw c(`no body ${r}`);return n}o(N,"getMany");export{p as canonical,E as findMany,F as get,N as getMany,f as hashOf,O as put,w as storable,x as store};
@@ -1,4 +1,4 @@
1
- import type { ObjectRow } from './types.js';
1
+ import type { ObjectRow, Reader } from './types.js';
2
2
  import { type CanvasDb } from '../canvas/db.js';
3
3
  /** Mark the node and everything it owns deleted except kept documents, which stay owned by the
4
4
  * deleted node; drop the node's watches (both ends) and its `nodes` row. One savepoint, so it runs as one
@@ -11,3 +11,6 @@ export declare function deleteNodeWithOwned(db: CanvasDb, node: ObjectRow): Prom
11
11
  * a new profile can take the name, and its identity row stays so a kept document's permission
12
12
  * string still names the profile id. Returns whether a profile object existed for `profileId`. */
13
13
  export declare function deleteProfileWithOwned(db: CanvasDb, profileId: string): Promise<boolean>;
14
+ /** The owner's undelete, inside the 30 days before the purge pass may take the row (`core/storage/purge.ts`). Only a document comes back:
15
+ * a deleted node has lost its `nodes` row and everything that cascaded from it, and a deleted profile has lost its handle. */
16
+ export declare function undeleteObject(db: CanvasDb, reader: Reader | null, object: ObjectRow): Promise<ObjectRow>;
@@ -1,7 +1,7 @@
1
- var u=Object.defineProperty;var n=(e,t)=>u(e,"name",{value:t,configurable:!0});import{hasUnreconciledWorktree as f}from"../canvas/worktrees.js";import{deleteDocument as R}from"./documents.js";import{publish as E}from"./events.js";import{getById as c}from"./objects.js";import{writeActsFor as N}from"../permissions/facts.js";import{rememberDeletedIsolated as O}from"../permissions/ceiling.js";import{withCanvasWrite as w}from"../canvas/db.js";async function A(e,t){if(t.type!=="node")throw new Error(`deleteNodeWithOwned: ${t.id} is a ${t.type}, not a node`);if(await f(t.id))throw new Error(`refusing to delete node ${t.id}: its managed worktree is not reconciled`);await w(async()=>await m(e,t),{savepoint:!0})}n(A,"deleteNodeWithOwned");async function H(e,t){const i=await e.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
1
+ var u=Object.defineProperty;var r=(e,t)=>u(e,"name",{value:t,configurable:!0});import{hasUnreconciledWorktree as f}from"../canvas/worktrees.js";import{usage as m}from"../errors.js";import{deleteDocument as O,undeleteDocument as R}from"./documents.js";import{publish as c}from"./events.js";import{getById as E}from"./objects.js";import{writeActsFor as N}from"../permissions/facts.js";import{rememberDeletedIsolated as D}from"../permissions/ceiling.js";import{withCanvasWrite as w}from"../canvas/db.js";async function j(e,t){if(t.type!=="node")throw new Error(`deleteNodeWithOwned: ${t.id} is a ${t.type}, not a node`);if(await f(t.id))throw new Error(`refusing to delete node ${t.id}: its managed worktree is not reconciled`);await w(async()=>await T(e,t),{savepoint:!0})}r(j,"deleteNodeWithOwned");async function _(e,t){const i=await e.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
2
2
  WHERE i.scheme = 'profile' AND i.external_id = ? AND o.type = 'profile' AND o.deleted_at IS NULL`).get(t);return i?(await w(async()=>{const o=await e.prepare(`UPDATE objects o SET owner = n.app FROM nodes n WHERE n.node_id = o.id AND o.owner = ? AND n.app IS NOT NULL
3
- RETURNING o.id, n.sub`).all(i.id);for(const{id:s,sub:r}of o)r&&await N(e,s,r);await p(e,i),await e.prepare("UPDATE objects SET deleted_at = ?, name = ? WHERE id = ?").run(new Date().toISOString(),`${i.name}.deleted-${i.id}`,i.id),await E(e,{event:"deleted",object:await c(e,i.id),actor:null,attrs:{}}),await e.prepare("DELETE FROM watches WHERE object = ? OR watcher = ?").run(i.id,i.id)},{savepoint:!0}),!0):!1}n(H,"deleteProfileWithOwned");async function m(e,t){const i=await p(e,t);await e.prepare("UPDATE objects SET deleted_at = ? WHERE id = ?").run(i,t.id),await E(e,{event:"deleted",object:await c(e,t.id),actor:null,attrs:{}}),await e.prepare("DELETE FROM watches WHERE object = ? OR watcher = ?").run(t.id,t.id),await O(t.id,e),await e.prepare("UPDATE nodes SET run_id = NULL WHERE run_id = ? AND node_id != ? AND isolated = 0").run(t.id,t.id),await e.prepare("DELETE FROM nodes WHERE node_id = ?").run(t.id)}n(m,"deleteInside");async function p(e,t){const i=await e.prepare(`WITH RECURSIVE sub(id) AS (
3
+ RETURNING o.id, n.sub`).all(i.id);for(const{id:s,sub:n}of o)n&&await N(e,s,n);await p(e,i),await e.prepare("UPDATE objects SET deleted_at = ?, name = ? WHERE id = ?").run(new Date().toISOString(),`${i.name}.deleted-${i.id}`,i.id),await c(e,{event:"deleted",object:await E(e,i.id),actor:null,attrs:{}}),await e.prepare("DELETE FROM watches WHERE object = ? OR watcher = ?").run(i.id,i.id)},{savepoint:!0}),!0):!1}r(_,"deleteProfileWithOwned");async function F(e,t,i){if(i.type!=="document")throw m(`${i.type} ${i.name} cannot be undeleted; only documents can`,{id:i.id});return await R(e,t,i)}r(F,"undeleteObject");async function T(e,t){const i=await p(e,t);await e.prepare("UPDATE objects SET deleted_at = ? WHERE id = ?").run(i,t.id),await c(e,{event:"deleted",object:await E(e,t.id),actor:null,attrs:{}}),await e.prepare("DELETE FROM watches WHERE object = ? OR watcher = ?").run(t.id,t.id),await D(t.id,e),await e.prepare("UPDATE nodes SET run_id = NULL WHERE run_id = ? AND node_id != ? AND isolated = 0").run(t.id,t.id),await e.prepare("DELETE FROM nodes WHERE node_id = ?").run(t.id)}r(T,"deleteInside");async function p(e,t){const i=await e.prepare(`WITH RECURSIVE sub(id) AS (
4
4
  SELECT id FROM objects WHERE owner = ?
5
5
  UNION SELECT o.id FROM objects o JOIN sub ON o.owner = sub.id)
6
6
  SELECT o.* FROM objects o JOIN sub ON sub.id = o.id WHERE o.deleted_at IS NULL`).all(t.id),o=new Set(i.map(a=>a.id)),s=e.prepare(`SELECT e.src FROM edges e JOIN objects s ON s.id = e.src
7
- WHERE e.type = 'links-to' AND e.dst = ? AND s.deleted_at IS NULL`);for(let a=!0;a;){a=!1;for(const d of i)d.type!=="document"||!o.has(d.id)||(await s.all(d.id)).some(({src:l})=>!o.has(l))&&(o.delete(d.id),a=!0)}const r=new Date().toISOString();for(const a of i)if(o.has(a.id)){if(a.type==="document"){await R(e,null,a);continue}await e.prepare("UPDATE objects SET deleted_at = ? WHERE id = ?").run(r,a.id),await E(e,{event:"deleted",object:await c(e,a.id),actor:null,attrs:{}}),await e.prepare("DELETE FROM watches WHERE object = ? OR watcher = ?").run(a.id,a.id)}return r}n(p,"deleteOwned");export{A as deleteNodeWithOwned,H as deleteProfileWithOwned};
7
+ WHERE e.type = 'links-to' AND e.dst = ? AND s.deleted_at IS NULL`);for(let a=!0;a;){a=!1;for(const d of i)d.type!=="document"||!o.has(d.id)||(await s.all(d.id)).some(({src:l})=>!o.has(l))&&(o.delete(d.id),a=!0)}const n=new Date().toISOString();for(const a of i)if(o.has(a.id)){if(a.type==="document"){await O(e,null,a);continue}await e.prepare("UPDATE objects SET deleted_at = ? WHERE id = ?").run(n,a.id),await c(e,{event:"deleted",object:await E(e,a.id),actor:null,attrs:{}}),await e.prepare("DELETE FROM watches WHERE object = ? OR watcher = ?").run(a.id,a.id)}return n}r(p,"deleteOwned");export{j as deleteNodeWithOwned,_ as deleteProfileWithOwned,F as undeleteObject};
@@ -136,6 +136,10 @@ export declare function deleteDocument(db: CanvasDb, reader: Reader | null, doc:
136
136
  /** Repo sync: bring a deleted document back under `name` (checked free first). Revisions, record
137
137
  * and links are untouched; the watches and rules deletion dropped are not recreated here. */
138
138
  export declare function restoreDocument(db: CanvasDb, doc: ObjectRow, name: string, reposFn?: ReposFn): Promise<ObjectRow>;
139
+ /** The owner's undelete: bring a deleted document back under its own name inside the 30-day window (past it the purge pass may take the
140
+ * row). Its owner must be live. Revisions, record and links are untouched; the rules, watches and facts deletion dropped are not
141
+ * recreated (as {@link restoreDocument}). */
142
+ export declare function undeleteDocument(db: CanvasDb, reader: Reader | null, doc: ObjectRow, now?: number): Promise<ObjectRow>;
139
143
  /** Delete each of `targets` that is a kept document nothing links to any more: a live document
140
144
  * whose owner is deleted (node deletion kept it because a live document linked to it) and that
141
145
  * no other live document links to. Each one deleted re-checks what it linked to, so chains of
@@ -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 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 (
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 d}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 G,setUnresolved as de,unresolvedLinksCouldName as me}from"./edges.js";import{publish as k}from"./events.js";import{readableOrDeleted as fe,resolveName as he}from"./names.js";import{createObject as ye,getById as f}from"./objects.js";import{liveLookups as _e,readerForNode as Ee,readerForObject as ve}from"./reader.js";import{replaceRules as q,setRulesOwner as Re}from"./rules.js";import{recordWatch as x}from"./watches.js";import{withCanvasWrite as h}from"../canvas/db.js";import{PURGE_RETENTION_MS as Ne}from"../storage/purge.js";import{filterAsync as ge,forEachAsync as Oe,mapAsync as L}from"../async-array.js";import{check as A,resolveReader as Se,userOf as K}from"../permissions/evaluator.js";import{missingMemoryScope as Y}from"../permissions/ceiling.js";import{deleteFact as Te,parseSubject as De,writeActsFor as je,writeFact as ke}from"../permissions/facts.js";import{PermissionError as xe}from"../permissions/errors.js";function _(e){return e.class==="node"?e.node:e.class==="app"?e.app:e.class==="object"?e.object:e.person}s(_,"actorOf");async function M(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(M,"actingOf");function Le(e,n){return new R("conflict",e,g.GENERAL,n)}s(Le,"conflict");function y(e){if(e.type!=="document")throw d(`${e.type} ${e.id} is not a document`)}s(y,"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 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
5
+ ) SELECT type FROM chain ORDER BY depth DESC LIMIT 1`).get(n.id))?.type??n.type}s(V,"spaceType");async function C(e,n,t){if(t!=="public"&&await V(e,n)==="repo")throw d(`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(C,"assertPublicInRepo");async function W(e,n,t,a){if(t.trim()===""||t!==t.trim())throw d(`invalid document name ${JSON.stringify(t)}`);const r=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(r&&r.id!==a?.id)throw Le(`${n.name} already has a document named ${t}`,{name:t,holder:r.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 S(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 r=await Y(e,n,t.id,"rw");if(r!==null)throw X(r);if(!await A(e,n,"edit",t.id))throw new R("forbidden",`you cannot change ${t.name}`,g.GENERAL,{id:t.id})}s(S,"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 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
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 L(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 T(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),r=e.prepare("INSERT INTO revision_parents (child, parent) VALUES (?, ?)");for(const i of t.parents)await r.run(a.rev_id,i);return a}s(T,"insertRevision");function Ie(e){const n=new Set;for(const t of e.matchAll(/\[\[([^[\]\n]+?)\]\]/g))n.add(t[1].trim());return[...n]}s(Ie,"linksIn");async function Q(e,n,t){const a=[],r=[];for(const i of t){const o=await he(e,n,i);"object"in o?a.push({written_as:i,dst:o.object}):"deleted"in o?a.push({written_as:i,dst:o.deleted}):"remote"in o||r.push(i)}return{links:a,unresolved:r}}s(Q,"resolveLinks");async function Z(e,n,t,a){const{links:r,unresolved:i}=await Q(e,n,Ie(a));return await ie(e,await G(e,t,r)),await de(e,t,i),i}s(Z,"setRevisionLinks");async function F(e,n,t){const a=new Map;for(const{src:r,name:i}of await me(e,n.name,n.id))a.set(r,[...a.get(r)??[],i]);for(const[r,i]of a){const o=await f(e,r),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 Ee(e,u.id,t??(async()=>[]),_e(e),!0):await ve(e,o,t),c=(await Q(e,w,i)).links.filter(m=>m.dst.id===n.id);c.length!==0&&(await G(e,o,[...await le(e,o),...c]),await pe(e,o,c.map(m=>m.written_as)))}}s(F,"linkNewlyNamed");async function $e(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
- 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 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
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($e,"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 r=new Map;for(const i of a){if(i.deleted_at||i.written_as===i.id)continue;const o=!i.written_as.includes("/");if(o&&i.name===i.written_as)continue;const u=i.owner===null?null:await f(e,i.owner),w=u?`${await $e(e,u,i.name)}/${i.name}`:i.name,c=o&&i.owner===n.owner?i.name:w;c!==i.written_as&&r.set(i.written_as,c)}return r.size===0?t:t.replace(/\[\[([^[\]\n]+?)\]\]/g,(i,o)=>{const u=r.get(o.trim());return u===void 0?i:`[[${u}]]`})}s(b,"withCurrentNames");async function J(e,n,t){if(t.preview!==void 0){if(t.preview.trim()==="")throw d("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 C(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(i,o)=>await e.prepareUncached(`UPDATE documents SET ${i} = ? 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 r=await f(e,n.id);return t.rules!==void 0&&await q(e,r,t.rules),r}s(J,"applyDocumentFields");async function ee(e,n,t,a="crtr"){if(n.owner.deleted_at)throw d(`${n.owner.name} is deleted`);if(n.relations?.length&&n.schemaType===void 0)throw new xe(400,"schema_type_required","relations need a schemaType: they are facts about a typed resource",{field:"relations"});if(n.preview.trim()==="")throw d("a document needs a preview",{field:"preview"});await C(e,n.owner,n.privacy),await W(e,n.owner,n.name);const r=await ye(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:r.id,relation:o,subject:De(u),writtenBy:a});await e.prepare("INSERT INTO documents (object, heads_key, revision) VALUES (?, '', 0)").run(r.id);const i=await J(e,r,{...n,preview:void 0,privacy:void 0,rules:n.rules??[]});return await F(e,i,t),i}s(ee,"createDocument");async function $(e,n){const t=await I(e,n),a=Ae(t),r=Math.max(0,...t.map(i=>i.number));return await e.prepare("UPDATE documents SET heads_key = ?, revision = ? WHERE object = ?").run(a,r,n.id),{heads_key:a,revision:r,headCount:t.length}}s($,"setHeads");async function rt(e,n,t,a,r){return await h(async()=>{const i=await B(e,a),o=_(n),u=await Y(e,n,t.owner.id,"rw",!0);if(u!==null)throw X(u);const w=await M(e,n),c=await ee(e,t,r,w.app??w.for);t.owner.type==="app"&&t.schemaType===void 0&&await je(e,c.id,await K(e,n));const m=await T(e,c,{number:1,body_hash:i,author:o,rationale:null,parents:[],acting:w});if(!await A(e,n,"edit",c.id))throw N(`no owner ${t.owner.name} you can write to`);const{heads_key:p}=await $(e,c),l=await Z(e,n,c,a);return await O(e,o,c),await x(e,o,c,"write",{readHeadsKey:p}),await k(e,{event:"created",object:c,actor:o,attrs:{revision:1},pointer:m.rev_id},r),{object:c,revision:1,unresolved:l}},{savepoint:!0})}s(rt,"writeDocument");async function He(e,n,t){return(await e.prepare("SELECT read_heads_key FROM watches WHERE watcher = ? AND object = ?").get(_(n).id,t.id))?.read_heads_key??null}s(He,"readBase");function Me(e,n){let t=e;for(const[a,{old:r,new:i}]of n.entries()){if(r==="")throw d(`block ${a+1}: a replacement needs non-empty old text`,{field:"replace"});const o=t.indexOf(r);if(o<0)throw d(`block ${a+1}: old text not found: ${JSON.stringify(r.slice(0,120))}`,{field:"replace",old:r});if(t.indexOf(r,o+1)>=0)throw d(`block ${a+1}: old text occurs more than once: ${JSON.stringify(r.slice(0,120))}`,{field:"replace",old:r});t=t.slice(0,o)+i+t.slice(o+r.length)}return t}s(Me,"applyReplacements");async function ot(e,n,t,a,r,i,o={},u){if(y(t),await S(e,n,t),t.deleted_at)throw d(`${t.name} is deleted`);if(i===null)throw new R("read_first",`read ${t.name} before editing it`,g.GENERAL,{id:t.id});if(r.trim()==="")throw d("an edit needs a rationale",{field:"rationale"});const w=await v(e,t);if(w.heads_key!==i)return{stale:{heads_key:w.heads_key,revision:w.revision}};const c=await I(e,t);let m;if("body"in a)m=a.body;else{if(c.length!==1)throw d(`${t.name} has ${c.length} heads and needs a merge; pass the whole merged body`,{heads:c.length});m=Me(await b(e,t,await ue(e,c[0].body_hash)),a.replace)}try{return await h(async()=>{const p=await B(e,m),l=_(n),D=1+Math.max(0,...c.map(E=>E.number)),P=await T(e,t,{number:D,body_hash:p,author:l,rationale:r,verbatim:a.verbatim===!0,parents:c.map(E=>E.rev_id),acting:await M(e,n)}),H=P.rev_id,re=await e.prepare("UPDATE documents SET heads_key = ?, revision = ? WHERE object = ? AND heads_key = ?").run(H,D,t.id,i);if(Number(re.changes)===0){const E=await v(e,t);throw new te(E.heads_key,E.revision)}const j=await J(e,t,o),oe=await Z(e,n,j,m);return await O(e,l,j),await x(e,l,j,"write",{readHeadsKey:H}),await k(e,{event:"edited",object:j,actor:l,pointer:P.rev_id,attrs:{revision:D,from_revision:w.revision,rationale:r}},u),{revision:D,heads_key:H,unresolved:oe}},{savepoint:!0})}catch(p){if(p instanceof te)return{stale:{heads_key:p.heads_key,revision:p.revision}};throw p}}s(ot,"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 st(e,n,t,a,r){if(y(t),n&&(await S(e,n,t),r===void 0||r.trim()===""))throw d("an edit needs a rationale",{field:"rationale"});return await h(async()=>{const i=await J(e,t,a);if(n){const o=_(n),u=await v(e,t),w=await He(e,n,t),c=await I(e,t),m=c[0];await T(e,t,{number:1+Math.max(...c.map(l=>l.number)),body_hash:m.body_hash,author:o,rationale:r,parents:[m.rev_id],acting:await M(e,n)});const{heads_key:p}=await $(e,t);await O(e,o,i),await x(e,o,t,"write",w===u.heads_key?{readHeadsKey:p}:{})}return i},{savepoint:!0})}s(st,"setDocumentRecord");async function Ce(e,n,t,a){const r=_(n),{heads_key:i}=await v(e,t);await x(e,r,t,"read",{readHeadsKey:i}),await k(e,{event:"read",object:t,actor:r,attrs:{}},a)}s(Ce,"recordRead");async function ct(e,n,t,a){return y(t),await U(e,n,t),await h(async()=>{const r=await v(e,t),i=await I(e,t),o=await we(e,i.map(p=>p.body_hash)),u=await L(i,async p=>({...p,body:await b(e,t,o.get(p.body_hash))})),w=await fe(e,await Se(e,n),"o"),m=(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 (${w.sql}) ORDER BY e.written_as`).all(t.id,...w.params)).map(({_written_as:p,...l})=>({written_as:p??"",object:l,deleted:l.deleted_at!==null}));return t.deleted_at||await Ce(e,n,t,a),{object:t,record:r,heads:u,needsMerge:u.length>1,links:m}},{savepoint:!0})}s(ct,"readDocument");async function ut(e,n,t){y(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 L(a,async r=>await z(e,r))}s(ut,"history");async function wt(e,n,t,a,r){if(y(t),t.deleted_at)throw d(`${t.name} is deleted`);return n&&await S(e,n,t),await h(async()=>{const i=a.owner??await f(e,t.owner),o=a.name??t.name;await W(e,i,o,t),await C(e,i,t.privacy),await e.prepare("UPDATE objects SET name = ?, owner = ? WHERE id = ?").run(o,i.id,t.id);const u=await f(e,t.id);if(i.id!==t.owner&&(await Re(e,u),n&&!await A(e,n,"edit",u.id)))throw N(`no owner ${i.name} you can move documents to`);return n&&await O(e,_(n),u),(o!==t.name||i.id!==t.owner)&&await F(e,u,r),u},{savepoint:!0})}s(wt,"moveDocument");async function pt(e,n,t,a,r){return y(t),t.deleted_at?t:(n&&await S(e,n,t),await h(async()=>{const i=await ne(e,n,t,a,r);return await We(e,t),await ie(e,await ae(e,t.id),a),i},{savepoint:!0}))}s(pt,"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 Te(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 Ue(e,n,t,a){return y(n),n.deleted_at?await h(async()=>{await W(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 r=await f(e,n.id);return await F(e,r,a),r},{savepoint:!0}):n}s(Ue,"restoreDocument");async function lt(e,n,t,a=Date.now()){if(y(t),!t.deleted_at)return t;if(Date.parse(t.deleted_at)<a-Ne)throw d(`${t.name} was deleted over 30 days ago and can no longer be undeleted`,{id:t.id,deleted_at:t.deleted_at});const r=t.owner===null?null:await f(e,t.owner);if(r===null||r.deleted_at)throw d(`${t.name} cannot be undeleted while its owner is deleted`,{id:t.id});return await h(async()=>{const i=await Ue(e,t,t.name);return n&&await S(e,n,i),i},{savepoint:!0})}s(lt,"undeleteDocument");async function ne(e,n,t,a,r){await e.prepare("UPDATE objects SET deleted_at = ? WHERE id = ?").run(r??new Date().toISOString(),t.id);const i=await f(e,t.id),o=n?_(n):null;return o&&await O(e,o,i),await k(e,{event:"deleted",object:i,actor:o,attrs:{}},a),await e.prepare("DELETE FROM watches WHERE object = ?").run(t.id),await q(e,i,[]),i}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 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};
16
+ WHERE e.type = 'links-to' AND e.dst = d.id AND e.src != d.id AND s.deleted_at IS NULL)`),r=[...n];for(;r.length>0;){const i=r.pop();await a.get(i)!==void 0&&(await ne(e,null,await f(e,i),t),r.push(...await ae(e,i)))}}s(ie,"collectUnlinked");async function dt(e,n,t){if(n.revisions.length===0)throw d(`import of ${n.name}: no revisions`);return await h(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 r=null;return await Oe(n.revisions,async(i,o)=>{r=(await T(e,a,{...i,number:o+1,parents:r===null?[]:[r]})).rev_id}),await $(e,a),await f(e,a.id)},{savepoint:!0})}s(dt,"importDocument");async function mt(e,n,t){if(y(n),n.privacy!=="public"&&await V(e,n)==="repo")throw d(`a repo owns only public documents; refusing revisions of ${n.name}`);return await h(async()=>{const a=e.prepare("SELECT rev_id, number, document, body_hash FROM revisions WHERE rev_id = ?"),r=e.prepare("SELECT parent FROM revision_parents WHERE child = ?");let i=await ge(t,async w=>{const c=await a.get(w.rev_id);if(c===void 0)return!0;const m=(await r.all(w.rev_id)).map(p=>p.parent).sort().join(",");if(c.document!==n.id||c.body_hash!==w.body_hash||m!==[...w.parents].sort().join(","))throw d(`revision ${w.rev_id} already exists with different content (document, body hash or parents); refusing to overwrite it`);return!1});for(;i.length>0;){const w=[];for(const c of i){const m=await L(c.parents,async l=>await a.get(l));if(m.some(l=>l===void 0)){w.push(c);continue}const p=m;if(p.some(l=>l.document!==n.id))throw d(`revision ${c.rev_id} names a parent of another document`);await T(e,n,{...c,author:null,number:1+Math.max(0,...p.map(l=>l.number)),parents:p.map(l=>l.rev_id)})}if(w.length===i.length)throw d(`revisions of ${n.name} name unknown parents: ${w.map(c=>c.rev_id).join(", ")}`);i=w}const{heads_key:o,headCount:u}=await $(e,n);return{heads_key:o,headCount:u}},{savepoint:!0})}s(mt,"insertImportedRevisions");async function ft(e,n){const t=await f(e,n);if(!t||t.type!=="document")throw N(`no document ${n}`);return t}s(ft,"documentById");export{_ as actorOf,J as applyDocumentFields,Me as applyReplacements,ie as collectUnlinked,pt as deleteDocument,ft as documentById,v as documentRecord,ot as editDocument,I as heads,ut as history,dt as importDocument,mt as insertImportedRevisions,Ie as linksIn,wt as moveDocument,He as readBase,ct as readDocument,Ce as recordRead,Ue as restoreDocument,st as setDocumentRecord,Z as setRevisionLinks,lt as undeleteDocument,b as withCurrentNames,rt as writeDocument};
@@ -1,3 +1,3 @@
1
- var b=Object.defineProperty;var p=(o,e)=>b(o,"name",{value:e,configurable:!0});import{coveredByAny as d,isSandboxedNode as C}from"../../shared/env.js";import{existsSync as S}from"node:fs";import{CrtrError as L}from"../errors.js";import{getCachedProfile as M}from"../scoped-state/person-store.js";import{parseScope as R}from"@north-light/identity";import{activeGrant as O,grantFor as v}from"../grants/mirror.js";import{ApiError as $}from"../../api/errors.js";import{pluginAppToken as A}from"../secrets.js";import{loadExactProfileManifest as H}from"../profiles/manifest.js";import{ensureOwner as y,getById as T,nodeObject as W,ownerForGrantee as D,profileHandle as P}from"./objects.js";import{personId as B}from"../canvas/person.js";import{warmCheckoutIdentities as F}from"./repo-sync/repos.js";async function N(o,e,n,r){const t=e===null?[]:r.projectPaths(e),a=await n(o),l=new Set(a.map(s=>s.repo.id)),c=[];for(const s of t){if(!S(s))continue;let i;try{i=await n(s)}catch(u){if(u instanceof L&&u.code==="git-lookup-failed")continue;throw u}for(const u of i)l.has(u.repo.id)||(l.add(u.repo.id),c.push(u))}return{repos:a.map(s=>s.repo),checkouts:a,projectRepos:c.map(s=>s.repo),projectCheckouts:c}}p(N,"viewRepos");async function ne(o,e){const n=await o.prepare("SELECT cwd, profile_id FROM nodes WHERE node_id = ?").get(e);if(n!==void 0&&(await F(n.cwd),n.profile_id))for(const r of w(o).projectPaths(n.profile_id))S(r)&&await F(r)}p(ne,"warmNodeRepos");async function ae(o,e,n){if(e.class==="anonymous")throw new Error("a publish token cannot read as a principal");if(e.class==="person")return{class:"person",person:await j(o)};if(e.class==="app")return{class:"app",app:await E(o,e.app),scopes:e.scopes,...e.sub===void 0?{}:{sub:e.sub}};if(e.class==="machine")throw new Error("a machine token cannot read as a principal");if(e.class==="forwarded"){if(e.actor.kind==="person")return{class:"person",person:await j(o)};const c=await v(e.actor.app,e.sub);if(!c)throw new $(403,"app_not_granted",`${e.actor.app} holds no active grant for ${e.sub} on this machine`);const s=e.actor.node_scopes;return{class:"app",app:await E(o,e.actor.app),place:e.actor.place,launched:s,scopes:s===null?c.resolved:s.filter(i=>d(c.resolved,i))}}if(e.class==="cron"){const c=e.grant?(await O(e.grant))?.resolved??[]:[],s=e.scopes;return{class:"app",app:await E(o,e.app),scopes:s===null?c:s.filter(i=>d(c,i)),launched:s,place:{run:e.origin_run??"",isolated:e.isolated}}}const r=w(o),t=await W(o,e.node),a=await o.prepare("SELECT cwd, profile_id, app FROM nodes WHERE node_id = ?").get(e.node),l=a.app===null?null:await r.object(a.app);if(!l)throw new Error(`node ${e.node} has no app`);return{class:"node",node:t,app:l,scopes:await x(e.app,e.sub,e.grant,e.scopes,r),launched:e.scopes,cwd:a.cwd,...await N(a.cwd,a.profile_id,n,r),profile:a.profile_id===null?null:await r.profile(a.profile_id,l)}}p(ae,"readerFor");async function J(o,e,n,r=w(o),t=!1){const a=await o.prepare("SELECT o.* FROM objects o WHERE o.id = ? AND o.type = 'node'").get(e),l=await o.prepare(`SELECT (SELECT external_id FROM identities WHERE object = nodes.app) AS grantee,
2
- sub, grant_id, scopes_json, cwd, profile_id, app FROM nodes WHERE node_id = ?`).get(e);if(!a||!l)throw new Error(`no node ${e}`);const{grantee:c,sub:s,grant_id:i,scopes_json:u,cwd:f,profile_id:g,app:h}=l,m=h===null?null:await r.object(h);if(!m)throw new Error(`node ${e} has no app`);const _=u===null?null:JSON.parse(u);return{class:"node",node:a,app:m,scopes:await x(c,s??"",i,_,r,t),launched:_,cwd:f,...await N(f,g,n,r),profile:g===null?null:await r.profile(g,m)}}p(J,"readerForNode");async function re(o,e,n=async()=>[],r=w(o)){const t=await o.prepare("SELECT envelope_json FROM peer_stubs WHERE object = ?").get(e.id);if(t!==void 0)return await k(o,e,t.envelope_json===null?null:JSON.parse(t.envelope_json),r);if(e.type==="node")return await J(o,e.id,n,r,!0);const a=e.owner===null?null:await r.object(e.owner);if(!a)throw new Error(`watcher ${e.id} has no owner`);const c=(a.type==="app"?a:a.type==="profile"&&a.owner!==null?await r.object(a.owner):null)??await o.prepare("SELECT o.* FROM identities i JOIN objects o ON o.id = i.object WHERE i.external_id = 'app:terminal' AND i.scheme = 'app'").get()??await o.prepare("SELECT * FROM objects WHERE type = 'person' AND name = 'user' AND home_machine IS NULL").get();if(!c)throw new Error("no terminal app or person object");const s=(await o.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(c.id))?.external_id??"app:terminal",i=await o.prepare("SELECT resolved_scopes_json FROM grants WHERE grantee = ? AND status = 'active'").all(s),u=a.type==="person"?null:i.length===0?r.pluginScopes(s):i.length===1?JSON.parse(i[0].resolved_scopes_json).filter(f=>f.mode==="always").map(f=>R(f.scope)):null;return{class:"object",object:e,node:e,owner:a,app:c,scopes:u,repos:a.type==="repo"?[a]:[]}}p(re,"readerForObject");async function se(o,e,n){const r=(await o.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(e.id))?.external_id;if(r===void 0)return null;const t=await o.prepare("SELECT resolved_scopes_json FROM grants WHERE grantee = ? AND status = 'active'").all(r);return t.length===0?n.pluginScopes(r):t.length>1?null:JSON.parse(t[0].resolved_scopes_json).filter(a=>a.mode==="always").map(a=>R(a.scope))}p(se,"appScopesNow");async function k(o,e,n,r){const t=await j(o);if(n!==null&&n.app===null)return{class:"object",object:e,node:e,owner:t,app:t,scopes:null,repos:[]};const a=n?.app==null?null:await E(o,n.app),l=n?.app==null?null:await r.grantFor(n.app,n.sub),c=n===null||l===null?null:n.node_scopes===null?l.resolved:n.node_scopes.filter(i=>d(l.resolved,i)),s=a??(e.owner===null?t:await r.object(e.owner)??t);return{class:"object",object:e,node:e,owner:s,app:s,scopes:c,launched:n?.node_scopes??null,repos:[],...n?.place==null?{}:{place:n.place}}}p(k,"readerForStub");function w(o){return{grantById:O,grantFor:v,pluginScopes:p(e=>A(e.replace(/^app:/,""))?.scopes??null,"pluginScopes"),object:p(async e=>await T(o,e),"object"),profile:p(async(e,n)=>await G(o,e,n),"profile"),projectPaths:p(e=>C()?[]:M(e)?.manifest.projects.map(n=>n.path)??[],"projectPaths")}}p(w,"liveLookups");function pe(o){const e=w(o),n=new Map,r=p((t,a)=>(n.has(t)||n.set(t,a()),n.get(t)),"memo");return{grantById:p(t=>r(`g\0${t}`,()=>e.grantById(t)),"grantById"),grantFor:p((t,a)=>r(`a\0${t}\0${a}`,()=>e.grantFor(t,a)),"grantFor"),pluginScopes:p(t=>r(`t\0${t}`,()=>e.pluginScopes(t)),"pluginScopes"),object:p(async t=>await r(`o\0${t}`,async()=>await e.object(t)),"object"),profile:p(async(t,a)=>await r(`p\0${t}\0${a.id}`,async()=>await e.profile(t,a)),"profile"),projectPaths:p(t=>r(`j\0${t}`,()=>e.projectPaths(t)),"projectPaths")}}p(pe,"publishLookups");async function x(o,e,n,r,t,a=!1){const l=n?await t.grantById(n):await t.grantFor(o,e),c=l&&l.grantee===o?l.resolved:null;if(c===null){if(a)return null;throw new Error(`the grant of ${o} is not active`)}return r===null?c:r.filter(s=>d(c,s))}p(x,"nodeScopes");async function j(o){return await D(o,B())}p(j,"personObject");async function E(o,e){return await y(o,"app","app",e,e.replace(/^app:/,""))}p(E,"appObject");async function G(o,e,n){return await o.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
1
+ var b=Object.defineProperty;var p=(o,e)=>b(o,"name",{value:e,configurable:!0});import{coveredByAny as d,isSandboxedNode as C}from"../../shared/env.js";import{existsSync as S}from"node:fs";import{CrtrError as L}from"../errors.js";import{getCachedProfile as $}from"../scoped-state/person-store.js";import{parseScope as R}from"@north-light/identity";import{activeGrant as O,grantFor as v}from"../grants/mirror.js";import{ApiError as M}from"../../api/errors.js";import{pluginAppToken as A}from"../secrets.js";import{loadExactProfileManifest as H}from"../profiles/manifest.js";import{ensureOwner as y,getById as T,nodeObject as W,ownerForGrantee as D,profileHandle as P}from"./objects.js";import{personId as B}from"../canvas/person.js";import{warmCheckoutIdentities as F}from"./repo-sync/repos.js";async function N(o,e,n,r){const t=e===null?[]:r.projectPaths(e),a=await n(o),l=new Set(a.map(s=>s.repo.id)),c=[];for(const s of t){if(!S(s))continue;let i;try{i=await n(s)}catch(u){if(u instanceof L&&u.code==="git-lookup-failed")continue;throw u}for(const u of i)l.has(u.repo.id)||(l.add(u.repo.id),c.push(u))}return{repos:a.map(s=>s.repo),checkouts:a,projectRepos:c.map(s=>s.repo),projectCheckouts:c}}p(N,"viewRepos");async function ne(o,e){const n=await o.prepare("SELECT cwd, profile_id FROM nodes WHERE node_id = ?").get(e);if(n!==void 0&&(await F(n.cwd),n.profile_id))for(const r of w(o).projectPaths(n.profile_id))S(r)&&await F(r)}p(ne,"warmNodeRepos");async function ae(o,e,n){if(e.class==="anonymous")throw new Error("a publish token cannot read as a principal");if(e.class==="person")return{class:"person",person:await j(o)};if(e.class==="app")return{class:"app",app:await E(o,e.app),scopes:e.scopes,...e.sub===void 0?{}:{sub:e.sub}};if(e.class==="machine"||e.class==="import")throw new Error(`a ${e.class} token cannot read as a principal`);if(e.class==="forwarded"){if(e.actor.kind==="person")return{class:"person",person:await j(o)};const c=await v(e.actor.app,e.sub);if(!c)throw new M(403,"app_not_granted",`${e.actor.app} holds no active grant for ${e.sub} on this machine`);const s=e.actor.node_scopes;return{class:"app",app:await E(o,e.actor.app),place:e.actor.place,launched:s,scopes:s===null?c.resolved:s.filter(i=>d(c.resolved,i))}}if(e.class==="cron"){const c=e.grant?(await O(e.grant))?.resolved??[]:[],s=e.scopes;return{class:"app",app:await E(o,e.app),scopes:s===null?c:s.filter(i=>d(c,i)),launched:s,place:{run:e.origin_run??"",isolated:e.isolated}}}const r=w(o),t=await W(o,e.node),a=await o.prepare("SELECT cwd, profile_id, app FROM nodes WHERE node_id = ?").get(e.node),l=a.app===null?null:await r.object(a.app);if(!l)throw new Error(`node ${e.node} has no app`);return{class:"node",node:t,app:l,scopes:await x(e.app,e.sub,e.grant,e.scopes,r),launched:e.scopes,cwd:a.cwd,...await N(a.cwd,a.profile_id,n,r),profile:a.profile_id===null?null:await r.profile(a.profile_id,l)}}p(ae,"readerFor");async function J(o,e,n,r=w(o),t=!1){const a=await o.prepare("SELECT o.* FROM objects o WHERE o.id = ? AND o.type = 'node'").get(e),l=await o.prepare(`SELECT (SELECT external_id FROM identities WHERE object = nodes.app) AS grantee,
2
+ sub, grant_id, scopes_json, cwd, profile_id, app FROM nodes WHERE node_id = ?`).get(e);if(!a||!l)throw new Error(`no node ${e}`);const{grantee:c,sub:s,grant_id:i,scopes_json:u,cwd:f,profile_id:g,app:h}=l,m=h===null?null:await r.object(h);if(!m)throw new Error(`node ${e} has no app`);const _=u===null?null:JSON.parse(u);return{class:"node",node:a,app:m,scopes:await x(c,s??"",i,_,r,t),launched:_,cwd:f,...await N(f,g,n,r),profile:g===null?null:await r.profile(g,m)}}p(J,"readerForNode");async function re(o,e,n=async()=>[],r=w(o)){const t=await o.prepare("SELECT envelope_json FROM peer_stubs WHERE object = ?").get(e.id);if(t!==void 0)return await k(o,e,t.envelope_json===null?null:JSON.parse(t.envelope_json),r);if(e.type==="node")return await J(o,e.id,n,r,!0);const a=e.owner===null?null:await r.object(e.owner);if(!a)throw new Error(`watcher ${e.id} has no owner`);const c=(a.type==="app"?a:a.type==="profile"&&a.owner!==null?await r.object(a.owner):null)??await o.prepare("SELECT o.* FROM identities i JOIN objects o ON o.id = i.object WHERE i.external_id = 'app:terminal' AND i.scheme = 'app'").get()??await o.prepare("SELECT * FROM objects WHERE type = 'person' AND name = 'user' AND home_machine IS NULL").get();if(!c)throw new Error("no terminal app or person object");const s=(await o.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(c.id))?.external_id??"app:terminal",i=await o.prepare("SELECT resolved_scopes_json FROM grants WHERE grantee = ? AND status = 'active'").all(s),u=a.type==="person"?null:i.length===0?r.pluginScopes(s):i.length===1?JSON.parse(i[0].resolved_scopes_json).filter(f=>f.mode==="always").map(f=>R(f.scope)):null;return{class:"object",object:e,node:e,owner:a,app:c,scopes:u,repos:a.type==="repo"?[a]:[]}}p(re,"readerForObject");async function se(o,e,n){const r=(await o.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(e.id))?.external_id;if(r===void 0)return null;const t=await o.prepare("SELECT resolved_scopes_json FROM grants WHERE grantee = ? AND status = 'active'").all(r);return t.length===0?n.pluginScopes(r):t.length>1?null:JSON.parse(t[0].resolved_scopes_json).filter(a=>a.mode==="always").map(a=>R(a.scope))}p(se,"appScopesNow");async function k(o,e,n,r){const t=await j(o);if(n!==null&&n.app===null)return{class:"object",object:e,node:e,owner:t,app:t,scopes:null,repos:[]};const a=n?.app==null?null:await E(o,n.app),l=n?.app==null?null:await r.grantFor(n.app,n.sub),c=n===null||l===null?null:n.node_scopes===null?l.resolved:n.node_scopes.filter(i=>d(l.resolved,i)),s=a??(e.owner===null?t:await r.object(e.owner)??t);return{class:"object",object:e,node:e,owner:s,app:s,scopes:c,launched:n?.node_scopes??null,repos:[],...n?.place==null?{}:{place:n.place}}}p(k,"readerForStub");function w(o){return{grantById:O,grantFor:v,pluginScopes:p(e=>A(e.replace(/^app:/,""))?.scopes??null,"pluginScopes"),object:p(async e=>await T(o,e),"object"),profile:p(async(e,n)=>await G(o,e,n),"profile"),projectPaths:p(e=>C()?[]:$(e)?.manifest.projects.map(n=>n.path)??[],"projectPaths")}}p(w,"liveLookups");function pe(o){const e=w(o),n=new Map,r=p((t,a)=>(n.has(t)||n.set(t,a()),n.get(t)),"memo");return{grantById:p(t=>r(`g\0${t}`,()=>e.grantById(t)),"grantById"),grantFor:p((t,a)=>r(`a\0${t}\0${a}`,()=>e.grantFor(t,a)),"grantFor"),pluginScopes:p(t=>r(`t\0${t}`,()=>e.pluginScopes(t)),"pluginScopes"),object:p(async t=>await r(`o\0${t}`,async()=>await e.object(t)),"object"),profile:p(async(t,a)=>await r(`p\0${t}\0${a.id}`,async()=>await e.profile(t,a)),"profile"),projectPaths:p(t=>r(`j\0${t}`,()=>e.projectPaths(t)),"projectPaths")}}p(pe,"publishLookups");async function x(o,e,n,r,t,a=!1){const l=n?await t.grantById(n):await t.grantFor(o,e),c=l&&l.grantee===o?l.resolved:null;if(c===null){if(a)return null;throw new Error(`the grant of ${o} is not active`)}return r===null?c:r.filter(s=>d(c,s))}p(x,"nodeScopes");async function j(o){return await D(o,B())}p(j,"personObject");async function E(o,e){return await y(o,"app","app",e,e.replace(/^app:/,""))}p(E,"appObject");async function G(o,e,n){return await o.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
3
3
  WHERE i.scheme = 'profile' AND i.external_id = ?`).get(e)??await y(o,"profile","profile",e,P(e,H(e).manifest.name),n)}p(G,"profileObject");export{se as appScopesNow,w as liveLookups,G as profileObject,pe as publishLookups,ae as readerFor,J as readerForNode,re as readerForObject,N as viewRepos,ne as warmNodeRepos};
@@ -1,2 +1,2 @@
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};
1
+ var W=Object.defineProperty;var h=(t,i)=>W(t,"name",{value:i,configurable:!0});import{getMany as j,hashOf as B,put as J}from"../bodies.js";import{setLinks as U,setUnresolved as P}from"../edges.js";import{collectUnlinked as Y,deleteDocument as C,documentRecord as I,heads as N,importDocument as z,insertImportedRevisions as q,moveDocument as F,restoreDocument as G,setDocumentRecord as K}from"../documents.js";import{CrtrError as V}from"../../errors.js";import{publish as T}from"../events.js";import{getById as x,setObjectKind as Q}from"../objects.js";import{rulesOf as X}from"../rules.js";import{docPath as Z,mergeDoc as ee,parseDoc as k,RevisionConflict as te,serializeDoc as A}from"./ref-format.js";import{withCanvasWrite as $}from"../../canvas/db.js";import{mapAsync as b}from"../../async-array.js";function H(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`)}h(H,"assertRepoRefSpace");function ne(t){const i=new Map;for(const[o,n]of t??[]){const m=k(o,n);m.ok&&i.set(m.doc.id,m.doc)}return i}h(ne,"parseMap");async function Oe(t,i,o,n=null){H(i);const m=ne(o),p=await t.prepare("SELECT * FROM objects WHERE type = 'document' AND owner = ? AND privacy = 'public' ORDER BY seq").all(i.id),f=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 c=await I(t,e),u=new Map((m.get(e.id)?.revisions??[]).map(r=>[r.rev_id,r])),_=new Set((await N(t,e)).map(r=>r.rev_id)),y=(await a.all(e.id)).map(r=>({written_as:r.written_as??"",dst:r.id})),O=await f.all(e.id),g=h(r=>{const d=u.get(r.rev_id);return d!==void 0&&d.body_hash===r.body_hash?d:void 0},"verbatim"),S=await j(t,O.filter(r=>g(r)===void 0).map(r=>r.body_hash)),v=await b(O,async r=>{const d=g(r);if(d!==void 0)return d;const l=S.get(r.body_hash);if(B(l)!==r.body_hash)throw new Error(`body ${r.body_hash} of revision ${r.rev_id} does not hash to its key; refusing to export it`);const E=u.get(r.rev_id);return E!==void 0?{...E,body:l,body_hash:r.body_hash}:{rev_id:r.rev_id,number:r.number,parents:(await w.all(r.rev_id)).map(R=>R.rev_id),body:l,body_hash:r.body_hash,author:r.author,rationale:r.rationale,created_at:r.created_at,links:_.has(r.rev_id)?y:[]}});if(v.length===0)continue;const D=(await X(t,e)).map(r=>({position:r.position,on:r.on,match:r.match??null,match_frontmatter:r.matchFrontmatter??null,if:r.if??null,deliver:r.deliver})),M={id:e.id,name:e.name,kind:e.kind??"document",privacy:"public",preview:e.preview,summary:c.summary,why_it_exists:c.why_it_exists,unlisted:c.unlisted,lint_ignore:c.lint_ignore,extensions:c.extensions,created_at:e.created_at,deleted_at:e.deleted_at,rules:D,revisions:v};s.set(Z(e.id),A(M))}for(const e of ie(o,n)){const c=o.get(e),u=s.get(e);if(u===void 0){s.set(e,c);continue}const _=k(e,c),y=k(e,u);if(!_.ok){s.set(e,c);continue}if(!y.ok)continue;const O=n?.has(e)?k(e,n.get(e)):null;let g;try{g=ee(O?.ok?O.doc:null,y.doc,_.doc)}catch(S){if(!(S instanceof te))throw S;s.set(e,c);continue}g&&s.set(e,A(g))}return s}h(Oe,"exportRepo");function ie(t,i){return[...t??[]].filter(([o,n])=>i?.get(o)!==n).map(([o])=>o)}h(ie,"pendingPaths");function re(t){return[...t].sort((i,o)=>i.position-o.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}:{}}))}h(re,"toRuleSpecs");const oe=h((t,i)=>JSON.stringify(t??null)===JSON.stringify(i??null),"same");function L(t,i){const o={},n=h(m=>!t||!oe(t[m],i[m]),"differs");return n("preview")&&(o.preview=i.preview),n("summary")&&(o.summary=i.summary),n("why_it_exists")&&(o.whyItExists=i.why_it_exists),n("unlisted")&&(o.unlisted=i.unlisted),n("lint_ignore")&&(o.lintIgnore=i.lint_ignore),n("extensions")&&(o.extensions=i.extensions),n("rules")&&(o.rules=re(i.rules)),o}h(L,"changedFields");async function Re(t,i,o,n,m){H(i);const p={created:0,revisions:0,deleted:0,needsMerge:[],refused:[]},f=[];for(const[a,s]of n){if(o.get(a)===s)continue;const e=k(a,s);if(!e.ok){p.refused.push({path:a,reason:e.reason});continue}const c=o.has(a)?k(a,o.get(a)):null;f.push({path:a,prior:c?.ok?c.doc:null,doc:e.doc})}for(const[a,s]of o){if(n.has(a))continue;const e=k(a,s);e.ok&&f.push({path:a,prior:e.doc,doc:null,priorId:e.doc.id})}const w=[];return await $(async()=>{for(const s of f)for(const e of s.doc?.revisions??[])await J(t,e.body,e.body_hash);let a=f;for(;;){const s=[];for(const e of a){const c={created:0,revisions:0,deleted:0,needsMerge:[],refused:[]};try{const u=[];await $(async()=>await ue(t,i,e.prior,e.doc,e.priorId??e.doc.id,c,m,u),{savepoint:!0}),w.push(...u),p.created+=c.created,p.revisions+=c.revisions,p.deleted+=c.deleted,p.needsMerge.push(...c.needsMerge)}catch(u){le(u)?s.push({w:e,error:u}):p.refused.push({path:e.path,reason:u.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 ae(t,n,new Set(w),new Set(f.map(s=>s.path)))},{savepoint:!0}),p}h(Re,"importTree");async function ae(t,i,o,n){if(o.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[f,w]of i){if(!n.has(f)&&!de(w,o))continue;const a=k(f,w);if(!a.ok||a.doc.deleted_at)continue;const s=await x(t,a.doc.id);if(!s||s.type!=="document"||s.deleted_at)continue;const e=(await N(t,s))[0],c=a.doc.revisions.find(u=>u.rev_id===e?.rev_id)?.links??[];for(const u of c){const _=await x(t,u.dst);_&&_.id!==s.id&&await m.run(s.id,_.id,u.written_as,p)}}}h(ae,"completeLinks");const se=/"dst":\s*"((?:[^"\\]|\\.)*)"/g;function de(t,i){for(const o of t.matchAll(se))if(i.has(ce(o[1])))return!0;return!1}h(de,"namesArrived");function ce(t){if(!t.includes("\\"))return t;try{return JSON.parse(`"${t}"`)}catch{return t}}h(ce,"decodeJsonString");function le(t){return t instanceof V&&t.code==="conflict"&&t.details?.holder!==void 0}h(le,"isNameConflict");async function ue(t,i,o,n,m,p,f,w){const a=await x(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 C(t,null,a,f),p.deleted++);return}const s=t.prepare("SELECT count(*) AS n FROM revisions WHERE document = ?");let e,c=!1,u=!1,_=0,y=0;if(a){e=a,_=(await I(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 x(t,e.id));const d=n.kind;(o?o.kind!==d:e.kind!==d)&&await Q(t,e,d),u=!!e.deleted_at&&n.deleted_at===null&&(o===null||o.deleted_at!=null);let l=L(u?null:o,n);if(n.deleted_at&&e.deleted_at){const{rules:E,...R}=l;l=R}u&&(e=await G(t,e,n.name,f),w?.push(e.id)),Object.keys(l).length>0&&(e=await K(t,null,e,l)),n.name!==e.name&&!e.deleted_at&&(e=await F(t,null,e,{name:n.name},f))}else{const l=n.revisions.filter(E=>E.parents.length===0).sort((E,R)=>E.number-R.number||E.rev_id.localeCompare(R.rev_id))[0];if(!l)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",...L(null,n),revisions:[{rev_id:l.rev_id,body_hash:l.body_hash,author:null,rationale:l.rationale,created_at:l.created_at}]},f),e.created_at!==n.created_at&&(await t.prepare("UPDATE objects SET created_at = ? WHERE id = ?").run(n.created_at,e.id),e=await x(t,e.id)),c=!0,w?.push(e.id),y=1}const O=(await s.get(e.id)).n,{headCount:g}=await q(t,e,n.revisions.map(d=>({rev_id:d.rev_id,parents:d.parents,body_hash:d.body_hash,rationale:d.rationale,created_at:d.created_at})));y+=(await s.get(e.id)).n-O,p.revisions+=y;const v=(await N(t,e))[0],D=new Map(n.revisions.map(d=>[d.rev_id,d]));if(y>0||u){const d=(await b(D.get(v.rev_id)?.links??[],async l=>({written_as:l.written_as,dst:await x(t,l.dst)}))).filter(l=>l.dst!==null);await Y(t,await U(t,e,d),f),await P(t,e,[])}if(g>1&&p.needsMerge.push(e.id),n.deleted_at&&!e.deleted_at){await C(t,null,e,f,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),c){p.created++,await T(t,{event:"created",object:e,actor:null,attrs:{revision:v.number},pointer:v.rev_id},f);return}if(u){p.created++,await T(t,{event:"created",object:e,actor:null,attrs:{revision:v.number},pointer:v.rev_id},f);return}if(y===0)return;const r=n.revisions.filter(d=>!o?.revisions.some(l=>l.rev_id===d.rev_id)).sort((d,l)=>l.number-d.number||l.created_at.localeCompare(d.created_at))[0]??D.get(v.rev_id);await T(t,{event:"edited",object:e,actor:null,pointer:v.rev_id,attrs:{revision:v.number,from_revision:_,rationale:r?.rationale??null,author:r?.author??null,heads:g,needs_merge:g>1}},f)}h(ue,"importOne");export{Oe as exportRepo,Re as importTree,de as namesArrived,ie as pendingPaths};
@@ -42,7 +42,9 @@ 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
44
  export declare function serializeDoc(doc: RefDocument): string;
45
- /** Parse one ref file. Refuses anything not public, a body whose hash disagrees, or an id that is not the file name. */
45
+ /** Parse one ref file. Refuses anything not public, a body whose hash disagrees, or an id that is not the file name.
46
+ * Each body is checked against the hash it arrived with, then held as `canonical` makes it (NUL → U+FFFD, hash of that text), so every consumer of a
47
+ * parsed file — prior export, import, merge — sees the one representation the database stores. */
46
48
  export declare function parseDoc(path: string, text: string): {
47
49
  ok: true;
48
50
  doc: RefDocument;
@@ -50,8 +52,11 @@ export declare function parseDoc(path: string, text: string): {
50
52
  ok: false;
51
53
  reason: string;
52
54
  };
55
+ /** Two sides name one revision id with different content: a revision-id collision, never merged or overwritten. */
56
+ export declare class RevisionConflict extends Error {
57
+ }
53
58
  /**
54
- * Merge one document. Revisions: union by rev_id (first copy kept verbatim). Fields: three-way
59
+ * Merge one document. Revisions: union by rev_id (first copy kept verbatim; the same id with another body hash or parents throws `RevisionConflict`). Fields: three-way
55
60
  * against base, remote wins when both changed — except that clearing `deleted_at` (a restoration) beats a tombstone. A side missing the file while base had it deleted
56
61
  * it: the result is marked deleted (at `now`) and keeps every revision.
57
62
  */
@@ -1,2 +1,2 @@
1
- var $=Object.defineProperty;var a=(n,t)=>$(n,"name",{value:t,configurable:!0});import{hashOf as x}from"../bodies.js";const D=a(n=>`docs/${n}.json`,"docPath"),N=["name","kind","privacy","preview","summary","why_it_exists","unlisted","lint_ignore","extensions","deleted_at","rules"];function _(n){if(Array.isArray(n))return n.map(_);if(n&&typeof n=="object"){const t={};for(const r of Object.keys(n).sort())t[r]=_(n[r]);return t}return n}a(_,"sortKeys");const g=a(n=>JSON.stringify(_(n??null)),"stable");function b(n,t){return n.number-t.number||(n.rev_id<t.rev_id?-1:n.rev_id>t.rev_id?1:0)}a(b,"byRevision");function R(n){const t={...n,rules:[...n.rules].sort((r,o)=>r.position-o.position),revisions:[...n.revisions].map(r=>({...r,parents:[...r.parents].sort()})).sort(b)};return`${JSON.stringify(_(t),null,2)}
2
- `}a(R,"serializeDoc");const j=new Set(["boot","workspace-open","file-read","document-read","command","pre-command","slash-command"]),E=new Set(["name","preview","content"]),p=a(n=>typeof n=="string","isStr"),w=a(n=>n===null||typeof n=="string","isStrOrNull");function A(n,t){let r;try{r=JSON.parse(t)}catch(e){return{ok:!1,reason:`not JSON: ${e.message}`}}if(!r||typeof r!="object"||Array.isArray(r))return{ok:!1,reason:"not an object"};const o=r;if(o.privacy!=="public")return{ok:!1,reason:`privacy is ${String(o.privacy)}; a repo holds only public documents`};if(!p(o.id)||D(o.id)!==n)return{ok:!1,reason:`id ${String(o.id)} does not match file ${n}`};for(const e of["name","kind","preview","created_at"])if(!p(o[e]))return{ok:!1,reason:`${e} is not a string`};for(const e of["summary","why_it_exists","deleted_at"])if(!w(o[e]))return{ok:!1,reason:`${e} is not a string or null`};if(typeof o.unlisted!="boolean")return{ok:!1,reason:"unlisted is not a boolean"};if(!Array.isArray(o.rules))return{ok:!1,reason:"rules is not an array"};for(const e of o.rules)if(!e||typeof e.position!="number"||!j.has(e.on)||!E.has(e.deliver))return{ok:!1,reason:"malformed rule"};if(!Array.isArray(o.revisions)||o.revisions.length===0)return{ok:!1,reason:"no revisions"};const f=new Set;for(const e of o.revisions){if(!e||!p(e.rev_id)||typeof e.number!="number"||!p(e.body)||!p(e.body_hash)||!p(e.created_at)||!w(e.author)||!w(e.rationale)||!Array.isArray(e.parents)||!e.parents.every(p)||!Array.isArray(e.links))return{ok:!1,reason:"malformed revision"};if(f.has(e.rev_id))return{ok:!1,reason:`revision ${e.rev_id} appears twice`};if(f.add(e.rev_id),x(e.body)!==e.body_hash)return{ok:!1,reason:`revision ${e.rev_id} body does not match body_hash`}}return{ok:!0,doc:o}}a(A,"parseDoc");function O(...n){const t=new Map;for(const r of n)for(const o of r?.revisions??[])t.has(o.rev_id)||t.set(o.rev_id,o);return[...t.values()].sort(b)}a(O,"unionRevisions");function I(n,t,r,o=new Date().toISOString()){if(!t&&!r)return null;if(!t||!r){const e=t??r;return n?{...e,deleted_at:e.deleted_at??o,revisions:O(r,t,n)}:e}const f={...r,revisions:O(r,t,n)};for(const e of N){const c=g(t[e]),m=g(r[e]);if(c===m)continue;const h=n?g(n[e]):void 0;if(e==="deleted_at"&&n?.deleted_at!=null&&t.deleted_at===null!=(r.deleted_at===null)){f.deleted_at=null,f.rules=(t.deleted_at===null?t:r).rules;continue}h===m&&(f[e]=t[e])}return f}a(I,"mergeDoc");function C(n,t,r,o=new Date().toISOString()){const f=new Map,e=[],c=new Map,m=[...new Set([...n.keys(),...t.keys(),...r.keys()])].sort();for(const i of m){const s=[];let l=null;for(const u of[n,t,r]){const y=u.get(i);if(y===void 0){s.push(null);continue}const v=A(i,y);v.ok?s.push(v.doc):(l??=v.reason,s.push(null))}if(l!==null){e.push({path:i,reason:l});const u=t.get(i)??r.get(i);u!==void 0&&f.set(i,u);continue}const d=I(s[0],s[1],s[2],o);d&&c.set(i,d)}const h=a(i=>{const s=r.get(i);if(s===void 0)return null;const l=A(i,s);return l.ok&&l.doc.deleted_at===null?l.doc.name:null},"remoteNameOf"),k=new Map;for(const[i,s]of c)s.deleted_at===null&&k.set(s.name,[...k.get(s.name)??[],i]);const S=[];for(const[i,s]of k){if(s.length<2)continue;const l=s.find(d=>h(d)===i)??[...s].sort((d,u)=>c.get(d).id.localeCompare(c.get(u).id))[0];for(const d of s){if(d===l)continue;const u=c.get(d),y=`${i}-${u.id.slice(0,8)}`;c.set(d,{...u,name:y}),S.push({id:u.id,from:i,to:y})}}for(const[i,s]of c)f.set(i,R(s));return{tree:new Map([...f].sort(([i],[s])=>i.localeCompare(s))),renamed:S,refused:e}}a(C,"mergeTrees");export{N as RECORD_FIELDS,D as docPath,I as mergeDoc,C as mergeTrees,A as parseDoc,R as serializeDoc};
1
+ var O=Object.defineProperty;var f=(n,t)=>O(n,"name",{value:t,configurable:!0});import{canonical as D,hashOf as N}from"../bodies.js";const R=f(n=>`docs/${n}.json`,"docPath"),j=["name","kind","privacy","preview","summary","why_it_exists","unlisted","lint_ignore","extensions","deleted_at","rules"];function m(n){if(Array.isArray(n))return n.map(m);if(n&&typeof n=="object"){const t={};for(const o of Object.keys(n).sort())t[o]=m(n[o]);return t}return n}f(m,"sortKeys");const g=f(n=>JSON.stringify(m(n??null)),"stable");function S(n,t){return n.number-t.number||(n.rev_id<t.rev_id?-1:n.rev_id>t.rev_id?1:0)}f(S,"byRevision");function E(n){const t={...n,rules:[...n.rules].sort((o,r)=>o.position-r.position),revisions:[...n.revisions].map(o=>({...o,parents:[...o.parents].sort()})).sort(S)};return`${JSON.stringify(m(t),null,2)}
2
+ `}f(E,"serializeDoc");const I=new Set(["boot","workspace-open","file-read","document-read","command","pre-command","slash-command"]),M=new Set(["name","preview","content"]),y=f(n=>typeof n=="string","isStr"),b=f(n=>n===null||typeof n=="string","isStrOrNull");function x(n,t){let o;try{o=JSON.parse(t)}catch(e){return{ok:!1,reason:`not JSON: ${e.message}`}}if(!o||typeof o!="object"||Array.isArray(o))return{ok:!1,reason:"not an object"};const r=o;if(r.privacy!=="public")return{ok:!1,reason:`privacy is ${String(r.privacy)}; a repo holds only public documents`};if(!y(r.id)||R(r.id)!==n)return{ok:!1,reason:`id ${String(r.id)} does not match file ${n}`};for(const e of["name","kind","preview","created_at"])if(!y(r[e]))return{ok:!1,reason:`${e} is not a string`};for(const e of["summary","why_it_exists","deleted_at"])if(!b(r[e]))return{ok:!1,reason:`${e} is not a string or null`};if(typeof r.unlisted!="boolean")return{ok:!1,reason:"unlisted is not a boolean"};if(!Array.isArray(r.rules))return{ok:!1,reason:"rules is not an array"};for(const e of r.rules)if(!e||typeof e.position!="number"||!I.has(e.on)||!M.has(e.deliver))return{ok:!1,reason:"malformed rule"};if(!Array.isArray(r.revisions)||r.revisions.length===0)return{ok:!1,reason:"no revisions"};const a=new Set;for(const e of r.revisions){if(!e||!y(e.rev_id)||typeof e.number!="number"||!y(e.body)||!y(e.body_hash)||!y(e.created_at)||!b(e.author)||!b(e.rationale)||!Array.isArray(e.parents)||!e.parents.every(y)||!Array.isArray(e.links))return{ok:!1,reason:"malformed revision"};if(a.has(e.rev_id))return{ok:!1,reason:`revision ${e.rev_id} appears twice`};if(a.add(e.rev_id),N(e.body)!==e.body_hash)return{ok:!1,reason:`revision ${e.rev_id} body does not match body_hash`}}const d=r.revisions.map(e=>{const{text:p,hash:h}=D(e.body,e.body_hash);return p===e.body?e:{...e,body:p,body_hash:h}});return{ok:!0,doc:{...r,revisions:d}}}f(x,"parseDoc");class C extends Error{static{f(this,"RevisionConflict")}}const $=f(n=>[...n.parents].sort().join(","),"parentsKey");function A(...n){const t=new Map;for(const o of n)for(const r of o?.revisions??[]){const a=t.get(r.rev_id);if(!a)t.set(r.rev_id,r);else if(a.body_hash!==r.body_hash||$(a)!==$(r))throw new C(`revision ${r.rev_id} is named with different content (body hash or parents) on two sides`)}return[...t.values()].sort(S)}f(A,"unionRevisions");function J(n,t,o,r=new Date().toISOString()){if(!t&&!o)return null;if(!t||!o){const d=t??o;return n?{...d,deleted_at:d.deleted_at??r,revisions:A(o,t,n)}:d}const a={...o,revisions:A(o,t,n)};for(const d of j){const e=g(t[d]),p=g(o[d]);if(e===p)continue;const h=n?g(n[d]):void 0;if(d==="deleted_at"&&n?.deleted_at!=null&&t.deleted_at===null!=(o.deleted_at===null)){a.deleted_at=null,a.rules=(t.deleted_at===null?t:o).rules;continue}h===p&&(a[d]=t[d])}return a}f(J,"mergeDoc");function K(n,t,o,r=new Date().toISOString()){const a=new Map,d=[],e=new Map,p=[...new Set([...n.keys(),...t.keys(),...o.keys()])].sort();for(const i of p){const s=[];let l=null;for(const u of[n,t,o]){const _=u.get(i);if(_===void 0){s.push(null);continue}const k=x(i,_);k.ok?s.push(k.doc):(l??=k.reason,s.push(null))}if(l!==null){d.push({path:i,reason:l});const u=t.get(i)??o.get(i);u!==void 0&&a.set(i,u);continue}const c=J(s[0],s[1],s[2],r);c&&e.set(i,c)}const h=f(i=>{const s=o.get(i);if(s===void 0)return null;const l=x(i,s);return l.ok&&l.doc.deleted_at===null?l.doc.name:null},"remoteNameOf"),v=new Map;for(const[i,s]of e)s.deleted_at===null&&v.set(s.name,[...v.get(s.name)??[],i]);const w=[];for(const[i,s]of v){if(s.length<2)continue;const l=s.find(c=>h(c)===i)??[...s].sort((c,u)=>e.get(c).id.localeCompare(e.get(u).id))[0];for(const c of s){if(c===l)continue;const u=e.get(c),_=`${i}-${u.id.slice(0,8)}`;e.set(c,{...u,name:_}),w.push({id:u.id,from:i,to:_})}}for(const[i,s]of e)a.set(i,E(s));return{tree:new Map([...a].sort(([i],[s])=>i.localeCompare(s))),renamed:w,refused:d}}f(K,"mergeTrees");export{j as RECORD_FIELDS,C as RevisionConflict,R as docPath,J as mergeDoc,K as mergeTrees,x as parseDoc,E as serializeDoc};
@@ -0,0 +1,10 @@
1
+ export declare const runBlobPrefix: (run: string) => string;
2
+ export declare const runBlobKey: (run: string, key: string) => string;
3
+ /** `node`: the owner is a node id of the bundle; `request`: a human request id of the bundle. */
4
+ export interface ParsedBlobKey {
5
+ kind: 'conversation' | 'attachment' | 'job' | 'event' | 'request';
6
+ ownerTable: 'nodes' | 'human_requests';
7
+ owner: string;
8
+ }
9
+ /** The key's kind and owner, or null when it is none of the keys cycle 5 reads (or has an empty, `.` or `..` segment). */
10
+ export declare function parseBlobKey(key: string): ParsedBlobKey | null;
@@ -0,0 +1 @@
1
+ var r=Object.defineProperty;var n=(t,e)=>r(t,"name",{value:e,configurable:!0});const u=n(t=>`imports/${t}/blobs/`,"runBlobPrefix"),c=n((t,e)=>`${u(t)}${e}`,"runBlobKey"),a=[["conversation",/^conversations\/([^/]+)\/[^/]+\/[^/]+$/],["attachment",/^attachments\/([^/]+)\/[0-9a-f]{64}$/],["job",/^jobs\/([^/]+)\/[^/]+\/output(?:\/\d{8})?$/],["event",/^events\/([^/]+)\/\d{16}-\d{16}-[0-9a-f]{32}\.jsonl\.zst$/],["request",/^requests\/([^/]+)\/.+$/]];function i(t){if(t.split("/").some(e=>e===""||e==="."||e===".."))return null;for(const[e,s]of a){const o=s.exec(t);if(o!==null)return{kind:e,ownerTable:e==="request"?"human_requests":"nodes",owner:o[1]}}return null}n(i,"parseBlobKey");export{i as parseBlobKey,c as runBlobKey,u as runBlobPrefix};
@@ -0,0 +1,90 @@
1
+ /** `bodies` (revision text, design-fleet D106) is copied like any table but dumped apart: its 667 MB do not fit one row file (`encodeRows` builds one string), so its rows
2
+ * travel as the part files named in `source.bodyParts` (see `rowKeys`), each written and read a line at a time (`RowFileWriter`, `rowLines`).
3
+ * Every row is canonical: `hash` is the sha256 of `text`, which holds no NUL. The exporter dumps only such rows and the lane refuses a bundle with any other (`stage`). */
4
+ export declare const BODIES_TABLE = "bodies";
5
+ /** Tables whose rows are dumped, staged and (after rewrites) copied, in no particular order: the lane computes the copy
6
+ * order from the target's foreign-key catalog. Each row file holds the table's rows in primary-key order (identity-ordered
7
+ * tables in source `seq` order). */
8
+ export declare const COPIED_TABLES: readonly ["objects", "documents", "revisions", "revision_parents", "identities", "jobs", "custom_objects", "context_sessions", "environments", "object_kinds", "edges", "watches", "watch_subscriptions", "exposures", "rules", "unresolved_links", "nodes", "sessions", "worktrees", "node_faults", "waits", "structured_requests", "memory_reads", "deleted_isolated_nodes", "node_outcome_deliveries", "run_events", "messages", "message_causes", "deliveries", "crons", "cron_runs", "daemon_actions", "human_requests", "human_action_deliveries", "review_comments", "review_comment_events", "audit_rows", "app_users", "idempotency_keys", "uploads", "upload_uses", "shares", "provider_approvals", "relations", "charges", "limits", "conversation_parts", "conversation_entries", "profiles", "bodies"];
9
+ /** Dumped and staged so rewrite 2 can map them, never inserted into the target (`grants`: the directory writes them). */
10
+ export declare const STAGED_ONLY_TABLES: readonly ["grants"];
11
+ /** Every table the exporter dumps. */
12
+ export declare const DUMPED_TABLES: readonly string[];
13
+ /** Tables with an identity primary key, tracked in `import_key_map` (source key → target key). */
14
+ export declare const KEY_MAPPED_TABLES: {
15
+ readonly audit_rows: "id";
16
+ readonly review_comment_events: "seq";
17
+ readonly app_users: "uid";
18
+ };
19
+ /** Tables the target numbers by `seq`/identity in source order (the staged `seq` is not carried over). */
20
+ export declare const SEQ_TABLES: readonly ["objects", "revisions", "watches", "watch_subscriptions", "sessions", "waits", "structured_requests", "deliveries", "run_events"];
21
+ export declare const bundleKeys: {
22
+ readonly prefix: (run: string) => string;
23
+ readonly rows: (run: string, table: string) => string;
24
+ readonly source: (run: string) => string;
25
+ readonly manifest: (run: string) => string;
26
+ readonly nodeFiles: (run: string, node: string) => string;
27
+ readonly bodyRows: (run: string, part: string) => string;
28
+ readonly request: (requestId: string, relative: string) => string;
29
+ };
30
+ /** The blob keys of a table's row files: one file, or for `bodies` its parts. */
31
+ export declare function rowKeys(run: string, table: string, source: Pick<SourceJson, 'bodyParts'>): string[];
32
+ /** `imports/<run>/source.json`. */
33
+ export interface SourceJson {
34
+ installId: string;
35
+ kind: 'machine' | 'hosted';
36
+ /** The source's person (`user:<id>`, or the local person on a runtime that never joined). */
37
+ person: string;
38
+ release: string;
39
+ schemaVersion: number;
40
+ /** Rows per dumped table. The lane refuses a row file whose line count differs. */
41
+ counts: Record<string, number>;
42
+ /** The source's machine environment object id, when it has one (`environments.object`). */
43
+ environmentObject?: string;
44
+ /** Hosted sources: every directory the exporter archived for a running node (`nodeFiles` holds its entries as `<from minus leading slash>/<relative path>`).
45
+ * The lane picks the sandbox directory each one lands in (`sandboxPathMap`); the exporter does not. */
46
+ pathPrefixes?: PathPrefix[];
47
+ /** The part files of the `bodies` rows (`imports/<run>/rows/bodies/<part>.jsonl.zst`): the pre-pause dump's parts, then the pause dump's. `counts.bodies` is their total. */
48
+ bodyParts?: string[];
49
+ /** Shipped documents (`documents.source_path` set): document object id → hash of the body text the exporter read from its file; that text is a `bodies` row in the bundle.
50
+ * The lane uses it for a shipped document the target has no copy of, which becomes an ordinary document (I23). */
51
+ shippedBodies?: Record<string, string>;
52
+ }
53
+ /** One archived directory of a running node: `app` is its grantee (`app:<slug>`), `role` says which directory it is. */
54
+ export interface PathPrefix {
55
+ node: string;
56
+ app: string;
57
+ role: 'context' | 'worktree' | 'cwd';
58
+ from: string;
59
+ }
60
+ /** Rewrite 4: source path prefix → target path prefix, applied to the rows of running nodes of a hosted source. */
61
+ export interface PathMapEntry {
62
+ from: string;
63
+ to: string;
64
+ }
65
+ export type PathMap = readonly PathMapEntry[];
66
+ /** Rewrite one path through the map: the longest matching prefix wins; a path under no prefix is returned unchanged. */
67
+ export declare function mapPath(map: PathMap, path: string): string;
68
+ export type RowObject = Record<string, unknown>;
69
+ /** Row-file codec: one JSON object per line, newline-terminated, zstd-compressed. */
70
+ export declare function encodeRows(rows: Iterable<RowObject>): Buffer;
71
+ export declare function decodeRows(data: Buffer | Uint8Array): RowObject[];
72
+ /** One JSON object per line, newline-terminated: the line `RowFileWriter.write` takes. */
73
+ export declare function rowLine(row: RowObject): string;
74
+ /** Writes a row file as a zstd stream, a line at a time, so no string larger than one line exists (`encodeRows` builds the whole file as one string, past V8's
75
+ * ~512 MiB string limit for a large file). `bytes` is the size of the JSON written so far, before compression. */
76
+ export declare class RowFileWriter {
77
+ private readonly zst;
78
+ private readonly finished;
79
+ private written;
80
+ constructor(path: string);
81
+ get bytes(): number;
82
+ /** Write one line (`rowLine`), waiting for the compressor when it is full. */
83
+ write(line: string, size?: number): Promise<void>;
84
+ /** Finish the file; resolves when it is on disk. */
85
+ close(): Promise<void>;
86
+ destroy(): void;
87
+ }
88
+ /** The lines of a zstd row file, decompressed as a stream: each yielded string is one line (without its newline), so memory is one line plus a chunk. A file whose last
89
+ * line is not newline-terminated, or that is not valid zstd, throws when the reader reaches it. */
90
+ export declare function rowLines(data: Buffer | Uint8Array): AsyncGenerator<string>;
@@ -0,0 +1,5 @@
1
+ var a=Object.defineProperty;var r=(s,e)=>a(s,"name",{value:e,configurable:!0});import{once as d}from"node:events";import{createWriteStream as u}from"node:fs";import{pipeline as f}from"node:stream/promises";import{createZstdCompress as m,createZstdDecompress as p,zstdCompressSync as l,zstdDecompressSync as _}from"node:zlib";const h="bodies",w=["objects","documents","revisions","revision_parents","identities","jobs","custom_objects","context_sessions","environments","object_kinds","edges","watches","watch_subscriptions","exposures","rules","unresolved_links","nodes","sessions","worktrees","node_faults","waits","structured_requests","memory_reads","deleted_isolated_nodes","node_outcome_deliveries","run_events","messages","message_causes","deliveries","crons","cron_runs","daemon_actions","human_requests","human_action_deliveries","review_comments","review_comment_events","audit_rows","app_users","idempotency_keys","uploads","upload_uses","shares","provider_approvals","relations","charges","limits","conversation_parts","conversation_entries","profiles","bodies"],x=["grants"],E=[...w,...x],z={audit_rows:"id",review_comment_events:"seq",app_users:"uid"},$=["objects","revisions","watches","watch_subscriptions","sessions","waits","structured_requests","deliveries","run_events"],c={prefix:r(s=>`imports/${s}/`,"prefix"),rows:r((s,e)=>`imports/${s}/rows/${e}.jsonl.zst`,"rows"),source:r(s=>`imports/${s}/source.json`,"source"),manifest:r(s=>`imports/${s}/manifest.jsonl.zst`,"manifest"),nodeFiles:r((s,e)=>`imports/${s}/node-files/${e}.tar.zst`,"nodeFiles"),bodyRows:r((s,e)=>`imports/${s}/rows/bodies/${e}.jsonl.zst`,"bodyRows"),request:r((s,e)=>`requests/${s}/${e}`,"request")};function B(s,e,t){return e===h?(t.bodyParts??[]).map(o=>c.bodyRows(s,o)):[c.rows(s,e)]}r(B,"rowKeys");function L(s,e){let t;for(const o of s)(e===o.from||e.startsWith(o.from.endsWith("/")?o.from:`${o.from}/`))&&(t===void 0||o.from.length>t.from.length)&&(t=o);return t===void 0?e:t.to+e.slice(t.from.length)}r(L,"mapPath");function j(s){let e="";for(const t of s)e+=`${JSON.stringify(t)}
2
+ `;return l(Buffer.from(e,"utf8"))}r(j,"encodeRows");function A(s){const e=_(s).toString("utf8");if(e==="")return[];if(!e.endsWith(`
3
+ `))throw new Error("row file is truncated: the last line is not newline-terminated");return e.slice(0,-1).split(`
4
+ `).map(t=>JSON.parse(t))}r(A,"decodeRows");function D(s){return`${JSON.stringify(s)}
5
+ `}r(D,"rowLine");class O{static{r(this,"RowFileWriter")}zst=m();finished;written=0;constructor(e){this.finished=f(this.zst,u(e)),this.finished.catch(()=>{})}get bytes(){return this.written}async write(e,t=Buffer.byteLength(e,"utf8")){this.written+=t,this.zst.write(e,"utf8")||await Promise.race([d(this.zst,"drain"),this.finished])}async close(){this.zst.end(),await this.finished}destroy(){this.zst.destroy()}}async function*P(s){const e=p({chunkSize:1048576});e.end(s);let t=[];for await(const o of e){let n=0;for(let i=o.indexOf(10,n);i!==-1;i=o.indexOf(10,n))t.push(o.subarray(n,i)),yield(t.length===1?t[0]:Buffer.concat(t)).toString("utf8"),t=[],n=i+1;n<o.length&&t.push(o.subarray(n))}if(t.length>0)throw new Error("row file is truncated: the last line is not newline-terminated")}r(P,"rowLines");export{h as BODIES_TABLE,w as COPIED_TABLES,E as DUMPED_TABLES,z as KEY_MAPPED_TABLES,O as RowFileWriter,$ as SEQ_TABLES,x as STAGED_ONLY_TABLES,c as bundleKeys,A as decodeRows,j as encodeRows,L as mapPath,B as rowKeys,D as rowLine,P as rowLines};
@@ -0,0 +1,11 @@
1
+ import type { PathPrefix } from '../bundle/layout.js';
2
+ /** One node's archive and the directories in it: context dir, worktree, and a cwd outside both. The lane picks each directory's sandbox target. */
3
+ export declare function nodeArchive(node: {
4
+ node: string;
5
+ app: string;
6
+ cwd: string;
7
+ worktree: string | null;
8
+ }, contextDir?: string): Promise<{
9
+ data: Buffer;
10
+ prefixes: PathPrefix[];
11
+ }>;
@@ -0,0 +1 @@
1
+ var a=Object.defineProperty;var s=(r,c)=>a(r,"name",{value:c,configurable:!0});import{existsSync as n}from"node:fs";import{join as l}from"node:path";import{zstdCompressSync as u}from"node:zlib";import*as w from"tar";import{nodeAgentDir as h}from"../../canvas/paths.js";async function S(r,c=l(h(r.node,r.app),"context")){const e={node:r.node,app:r.app},t=[{...e,role:"context",from:c}];r.worktree!==null&&t.push({...e,role:"worktree",from:r.worktree});const f=t.some(o=>r.cwd===o.from||r.cwd.startsWith(`${o.from}/`));r.cwd!==""&&!f&&t.push({...e,role:"cwd",from:r.cwd});const i=[],p=w.create({cwd:"/",portable:!0,filter:s((o,m)=>!("isSymbolicLink"in m&&m.isSymbolicLink()),"filter")},t.filter(o=>n(o.from)).map(o=>o.from.replace(/^\//,"")));for await(const o of p)i.push(o);return{data:u(Buffer.concat(i)),prefixes:t}}s(S,"nodeArchive");export{S as nodeArchive};
@@ -0,0 +1,23 @@
1
+ import type { ImportRunDTO, ImportStartRequest, ImportStartedDTO, ImportStoreNameDTO, ImportUploadUrlsDTO } from '../../../api/dto/imports.js';
2
+ /** The dump directory's file holding the opened file stores, and the order they are uploaded in. */
3
+ export declare const STORE_FILE = "stores.json";
4
+ export declare const STORE_ORDER: readonly ImportStoreNameDTO[];
5
+ export declare class ImportCloud {
6
+ private readonly apiUrl;
7
+ private readonly token;
8
+ /** `token` is a fixed import token, or a getter for a person bearer (5-minute tokens, so a long import asks per call). */
9
+ constructor(apiUrl: string, token: string | (() => Promise<string>));
10
+ private call;
11
+ start(request: ImportStartRequest): Promise<ImportStartedDTO>;
12
+ uploadUrls(run: string, keys: string[]): Promise<ImportUploadUrlsDTO>;
13
+ putStore(run: string, name: ImportStoreNameDTO, body: unknown): Promise<void>;
14
+ load(run: string): Promise<ImportRunDTO>;
15
+ get(run: string): Promise<ImportRunDTO>;
16
+ /** `rotate` replaces a credential already issued for this run's machine (without it a second call answers 409 `credential_exists`). */
17
+ machineCredential(run: string, rotate?: boolean): Promise<{
18
+ environment_id: string;
19
+ credential: string;
20
+ }>;
21
+ /** PUT bytes to a signed URL. */
22
+ put(url: string, data: Buffer): Promise<void>;
23
+ }
@@ -0,0 +1 @@
1
+ var c=Object.defineProperty;var o=(i,t)=>c(i,"name",{value:t,configurable:!0});import{general as l}from"../../errors.js";const d="stores.json",h=["auth","credentials","env","settings","plugins"];class ${static{o(this,"ImportCloud")}apiUrl;token;constructor(t,e){this.apiUrl=t,this.token=e}async call(t,e,s){const r=await fetch(new URL(e,this.apiUrl),{method:t,headers:{authorization:`Bearer ${typeof this.token=="string"?this.token:await this.token()}`,...s===void 0?{}:{"content-type":"application/json"}},...s===void 0?{}:{body:JSON.stringify(s)}}),a=await r.text();if(!r.ok){let n;try{n=JSON.parse(a).error?.code}catch{n=void 0}throw l(`${t} ${e} answered ${r.status}: ${a.slice(0,500)}`,{status:r.status,...typeof n=="string"?{code:n}:{}})}return a===""?void 0:JSON.parse(a)}start(t){return this.call("POST","/v1/imports",t)}uploadUrls(t,e){return this.call("POST",`/v1/imports/${t}/upload-urls`,{keys:e})}putStore(t,e,s){return this.call("PUT",`/v1/imports/${t}/stores/${e}`,s)}load(t){return this.call("POST",`/v1/imports/${t}/load`,{})}get(t){return this.call("GET",`/v1/imports/${t}`)}machineCredential(t,e=!1){return this.call("POST",`/v1/imports/${t}/machine-credential${e?"?rotate=1":""}`,{})}async put(t,e){const s=await fetch(t,{method:"PUT",body:new Uint8Array(e)});if(!s.ok)throw l(`upload answered ${s.status}: ${(await s.text()).slice(0,300)}`)}}export{$ as ImportCloud,d as STORE_FILE,h as STORE_ORDER};
@@ -0,0 +1,34 @@
1
+ import { type SourceJson } from '../bundle/layout.js';
2
+ export interface DumpRequestRef {
3
+ id: string;
4
+ dir: string;
5
+ file: string | null;
6
+ }
7
+ export interface DumpRunningNode {
8
+ node: string;
9
+ app: string;
10
+ cwd: string;
11
+ worktree: string | null;
12
+ }
13
+ export interface DumpResult {
14
+ source: SourceJson;
15
+ /** Human requests (page and review) with the folder and reviewed file their rows name. */
16
+ requests: DumpRequestRef[];
17
+ /** Nodes with status active or idle, for the hosted node-file archives. */
18
+ runningNodes: DumpRunningNode[];
19
+ /** `body_ref` files that could not be read and stayed as references. */
20
+ bodiesMissing: number;
21
+ bodiesInlined: number;
22
+ }
23
+ /** A `bodies` part file holds about this much JSON (the encoded lines, before compression); a row that alone exceeds it is a part of its own. */
24
+ export declare const PART_JSON_BYTES: number;
25
+ /** A body longer than this many UTF-8 bytes fails the export. The largest body on Silas's Mac is 5.2 MB. JSON escapes one character into at most six (`\u0001`), so a body at
26
+ * the ceiling is a line of at most 192 MiB, well inside V8's ~512 MiB string limit. */
27
+ export declare const BODY_MAX_BYTES: number;
28
+ /** The pre-pause dump of `bodies` (design-import cutover step 2): every row, while nodes still run. Rows are never updated or deleted, so the pause dump
29
+ * needs only the rows whose hash is not in the list this leaves in `<out>/bodies-pre.json`. */
30
+ export declare function dumpBodiesPre(out: string): Promise<{
31
+ parts: string[];
32
+ count: number;
33
+ }>;
34
+ export declare function dumpRows(out: string, kind: SourceJson['kind']): Promise<DumpResult>;