@tensorix/metahub 0.3.2 → 0.4.1

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 (180) hide show
  1. package/README.md +23 -10
  2. package/README.zh-CN.md +23 -10
  3. package/dist/cli/edge-worker-script.d.ts +4 -0
  4. package/dist/cli/edge-worker-script.d.ts.map +1 -0
  5. package/dist/cli.js +16716 -8375
  6. package/dist/cli.js.map +110 -60
  7. package/dist/core/access-policy.d.ts +82 -0
  8. package/dist/core/access-policy.d.ts.map +1 -0
  9. package/dist/core/blobs-core.d.ts +19 -0
  10. package/dist/core/blobs-core.d.ts.map +1 -1
  11. package/dist/core/blobs.d.ts.map +1 -1
  12. package/dist/core/compact.d.ts.map +1 -1
  13. package/dist/core/config.d.ts +13 -0
  14. package/dist/core/config.d.ts.map +1 -1
  15. package/dist/core/crdt.d.ts +29 -2
  16. package/dist/core/crdt.d.ts.map +1 -1
  17. package/dist/core/data-map.d.ts +105 -0
  18. package/dist/core/data-map.d.ts.map +1 -0
  19. package/dist/core/db.d.ts +1 -1
  20. package/dist/core/db.d.ts.map +1 -1
  21. package/dist/core/documents.d.ts.map +1 -1
  22. package/dist/core/errors.d.ts +2 -0
  23. package/dist/core/errors.d.ts.map +1 -1
  24. package/dist/core/grants-core.d.ts +132 -0
  25. package/dist/core/grants-core.d.ts.map +1 -0
  26. package/dist/core/guest-intent.d.ts +33 -0
  27. package/dist/core/guest-intent.d.ts.map +1 -0
  28. package/dist/core/hash.d.ts +4 -0
  29. package/dist/core/hash.d.ts.map +1 -0
  30. package/dist/core/history.d.ts.map +1 -1
  31. package/dist/core/hlc.d.ts +5 -0
  32. package/dist/core/hlc.d.ts.map +1 -1
  33. package/dist/core/ids.d.ts +1 -1
  34. package/dist/core/ids.d.ts.map +1 -1
  35. package/dist/core/inject-runtime.d.ts +8 -0
  36. package/dist/core/inject-runtime.d.ts.map +1 -0
  37. package/dist/core/intent-retention.d.ts +23 -0
  38. package/dist/core/intent-retention.d.ts.map +1 -0
  39. package/dist/core/md/inline.d.ts +6 -2
  40. package/dist/core/md/inline.d.ts.map +1 -1
  41. package/dist/core/node.d.ts +13 -0
  42. package/dist/core/node.d.ts.map +1 -1
  43. package/dist/core/properties.d.ts +8 -0
  44. package/dist/core/properties.d.ts.map +1 -1
  45. package/dist/core/records.d.ts +31 -0
  46. package/dist/core/records.d.ts.map +1 -1
  47. package/dist/core/schema-init.d.ts +19 -2
  48. package/dist/core/schema-init.d.ts.map +1 -1
  49. package/dist/core/schema.d.ts +1 -1
  50. package/dist/core/schema.d.ts.map +1 -1
  51. package/dist/core/shares.d.ts +15 -0
  52. package/dist/core/shares.d.ts.map +1 -1
  53. package/dist/core/site-channel-lifecycle.d.ts +10 -0
  54. package/dist/core/site-channel-lifecycle.d.ts.map +1 -0
  55. package/dist/core/site-channel-store.d.ts +120 -0
  56. package/dist/core/site-channel-store.d.ts.map +1 -0
  57. package/dist/core/site-channels.d.ts +65 -0
  58. package/dist/core/site-channels.d.ts.map +1 -0
  59. package/dist/core/sites-core.d.ts +100 -7
  60. package/dist/core/sites-core.d.ts.map +1 -1
  61. package/dist/core/sites.d.ts +65 -2
  62. package/dist/core/sites.d.ts.map +1 -1
  63. package/dist/core/sync/anti-abuse.d.ts +26 -0
  64. package/dist/core/sync/anti-abuse.d.ts.map +1 -0
  65. package/dist/core/sync/auth.d.ts +30 -2
  66. package/dist/core/sync/auth.d.ts.map +1 -1
  67. package/dist/core/sync/blob-routes.d.ts.map +1 -1
  68. package/dist/core/sync/cf-api.d.ts +54 -0
  69. package/dist/core/sync/cf-api.d.ts.map +1 -0
  70. package/dist/core/sync/cf-oauth.d.ts +91 -0
  71. package/dist/core/sync/cf-oauth.d.ts.map +1 -0
  72. package/dist/core/sync/client.d.ts +12 -0
  73. package/dist/core/sync/client.d.ts.map +1 -1
  74. package/dist/core/sync/data-map-db.d.ts +8 -0
  75. package/dist/core/sync/data-map-db.d.ts.map +1 -0
  76. package/dist/core/sync/devices.d.ts +65 -0
  77. package/dist/core/sync/devices.d.ts.map +1 -0
  78. package/dist/core/sync/drop-host.d.ts +40 -0
  79. package/dist/core/sync/drop-host.d.ts.map +1 -0
  80. package/dist/core/sync/drop-keys.d.ts +64 -0
  81. package/dist/core/sync/drop-keys.d.ts.map +1 -0
  82. package/dist/core/sync/drop-protocol.d.ts +94 -0
  83. package/dist/core/sync/drop-protocol.d.ts.map +1 -0
  84. package/dist/core/sync/drop-pull.d.ts +67 -0
  85. package/dist/core/sync/drop-pull.d.ts.map +1 -0
  86. package/dist/core/sync/drop-wire.d.ts +39 -0
  87. package/dist/core/sync/drop-wire.d.ts.map +1 -0
  88. package/dist/core/sync/e2ee.d.ts +3 -0
  89. package/dist/core/sync/e2ee.d.ts.map +1 -1
  90. package/dist/core/sync/edge-config.d.ts +77 -0
  91. package/dist/core/sync/edge-config.d.ts.map +1 -0
  92. package/dist/core/sync/edge-connect.d.ts +13 -0
  93. package/dist/core/sync/edge-connect.d.ts.map +1 -0
  94. package/dist/core/sync/edge-service.d.ts +100 -0
  95. package/dist/core/sync/edge-service.d.ts.map +1 -0
  96. package/dist/core/sync/grants-routes.d.ts +46 -0
  97. package/dist/core/sync/grants-routes.d.ts.map +1 -0
  98. package/dist/core/sync/guest-session.d.ts +29 -0
  99. package/dist/core/sync/guest-session.d.ts.map +1 -0
  100. package/dist/core/sync/html-escape.d.ts +2 -0
  101. package/dist/core/sync/html-escape.d.ts.map +1 -0
  102. package/dist/core/sync/http-status.d.ts +14 -0
  103. package/dist/core/sync/http-status.d.ts.map +1 -0
  104. package/dist/core/sync/http-util.d.ts +6 -0
  105. package/dist/core/sync/http-util.d.ts.map +1 -0
  106. package/dist/core/sync/pairing.d.ts +9 -9
  107. package/dist/core/sync/pairing.d.ts.map +1 -1
  108. package/dist/core/sync/partition.d.ts +77 -0
  109. package/dist/core/sync/partition.d.ts.map +1 -0
  110. package/dist/core/sync/peers-routes.d.ts.map +1 -1
  111. package/dist/core/sync/peers.d.ts +77 -0
  112. package/dist/core/sync/peers.d.ts.map +1 -1
  113. package/dist/core/sync/protocol.d.ts +2 -0
  114. package/dist/core/sync/protocol.d.ts.map +1 -1
  115. package/dist/core/sync/rate-limit.d.ts +20 -0
  116. package/dist/core/sync/rate-limit.d.ts.map +1 -0
  117. package/dist/core/sync/recovery.d.ts +9 -0
  118. package/dist/core/sync/recovery.d.ts.map +1 -0
  119. package/dist/core/sync/room-client.d.ts +50 -0
  120. package/dist/core/sync/room-client.d.ts.map +1 -0
  121. package/dist/core/sync/room-peer.d.ts +62 -0
  122. package/dist/core/sync/room-peer.d.ts.map +1 -0
  123. package/dist/core/sync/room-protocol.d.ts +130 -0
  124. package/dist/core/sync/room-protocol.d.ts.map +1 -0
  125. package/dist/core/sync/room-serve.d.ts +38 -0
  126. package/dist/core/sync/room-serve.d.ts.map +1 -0
  127. package/dist/core/sync/room-url.d.ts +4 -0
  128. package/dist/core/sync/room-url.d.ts.map +1 -0
  129. package/dist/core/sync/routes.d.ts +4 -1
  130. package/dist/core/sync/routes.d.ts.map +1 -1
  131. package/dist/core/sync/seal.d.ts +22 -0
  132. package/dist/core/sync/seal.d.ts.map +1 -0
  133. package/dist/core/sync/server.d.ts +7 -0
  134. package/dist/core/sync/server.d.ts.map +1 -1
  135. package/dist/core/sync/share-actions.d.ts +55 -6
  136. package/dist/core/sync/share-actions.d.ts.map +1 -1
  137. package/dist/core/sync/share-export.d.ts +14 -0
  138. package/dist/core/sync/share-export.d.ts.map +1 -1
  139. package/dist/core/sync/share-render.d.ts +9 -1
  140. package/dist/core/sync/share-render.d.ts.map +1 -1
  141. package/dist/core/sync/share-serve.d.ts +5 -1
  142. package/dist/core/sync/share-serve.d.ts.map +1 -1
  143. package/dist/core/sync/site-channel-reconcile.d.ts +26 -0
  144. package/dist/core/sync/site-channel-reconcile.d.ts.map +1 -0
  145. package/dist/core/sync/site-publish-recovery.d.ts +32 -0
  146. package/dist/core/sync/site-publish-recovery.d.ts.map +1 -0
  147. package/dist/core/sync/site-reachability.d.ts +13 -0
  148. package/dist/core/sync/site-reachability.d.ts.map +1 -0
  149. package/dist/core/sync/sites-routes.d.ts.map +1 -1
  150. package/dist/core/sync/sites-serve.d.ts +24 -1
  151. package/dist/core/sync/sites-serve.d.ts.map +1 -1
  152. package/dist/core/sync/storage-s3-bun.d.ts +1 -24
  153. package/dist/core/sync/storage-s3-bun.d.ts.map +1 -1
  154. package/dist/core/sync/storage-s3-sign.d.ts +43 -0
  155. package/dist/core/sync/storage-s3-sign.d.ts.map +1 -0
  156. package/dist/core/sync/storage.d.ts +59 -0
  157. package/dist/core/sync/storage.d.ts.map +1 -1
  158. package/dist/db-worker.js +206 -76
  159. package/dist/edge-worker.js +4038 -0
  160. package/dist/index.js +13715 -7064
  161. package/dist/index.js.map +88 -42
  162. package/dist/metahub-sdk.js +10 -1
  163. package/dist/mh-runtime.js +1 -1
  164. package/dist/room/do-driver.d.ts +30 -0
  165. package/dist/room/do-driver.d.ts.map +1 -0
  166. package/dist/sw.js +10 -1
  167. package/dist/webui/server/edge-routes.d.ts +3 -0
  168. package/dist/webui/server/edge-routes.d.ts.map +1 -0
  169. package/dist/webui/server/json-handler.d.ts +4 -0
  170. package/dist/webui/server/json-handler.d.ts.map +1 -0
  171. package/dist/webui/server/routes.d.ts.map +1 -1
  172. package/dist/webui/server/share-routes.d.ts.map +1 -1
  173. package/dist/webui/server/site-hosting-routes.d.ts +7 -0
  174. package/dist/webui/server/site-hosting-routes.d.ts.map +1 -0
  175. package/dist/webui.js +208 -147
  176. package/dist/workers/edge-worker.d.ts +66 -0
  177. package/dist/workers/edge-worker.d.ts.map +1 -0
  178. package/dist/workers/room.d.ts +34 -0
  179. package/dist/workers/room.d.ts.map +1 -0
  180. package/package.json +2 -2
@@ -4,6 +4,11 @@ export interface Hlc {
4
4
  counter: number;
5
5
  node: string;
6
6
  }
7
+ /** Max value of the fixed-width hex4 counter field. Beyond this the string
8
+ * form would grow to 5 hex digits (padStart pads, it never truncates) and
9
+ * break the lexicographic == causal ordering, so producers carry into millis
10
+ * instead (see nextHlc/observeHlc). */
11
+ export declare const MAX_HLC_COUNTER = 65535;
7
12
  export declare function formatHlc(h: Hlc): string;
8
13
  export declare function parseHlc(s: string): Hlc;
9
14
  /** Issue a new timestamp for a local event, advancing the persisted clock. */
@@ -1 +1 @@
1
- {"version":3,"file":"hlc.d.ts","sourceRoot":"","sources":["../../src/core/hlc.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAK5C,MAAM,WAAW,GAAG;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,CAIxC;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,GAAG,CAQvC;AAeD,8EAA8E;AAC9E,wBAAgB,OAAO,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,MAAM,CAO5E;AAED,2EAA2E;AAC3E,wBAAgB,UAAU,CACxB,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,GAAG,SAAa,GACf,IAAI,CAWN"}
1
+ {"version":3,"file":"hlc.d.ts","sourceRoot":"","sources":["../../src/core/hlc.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAK5C,MAAM,WAAW,GAAG;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;wCAGwC;AACxC,eAAO,MAAM,eAAe,QAAS,CAAC;AAYtC,wBAAgB,SAAS,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,CAOxC;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,GAAG,CAQvC;AAeD,8EAA8E;AAC9E,wBAAgB,OAAO,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,MAAM,CAS5E;AAED,2EAA2E;AAC3E,wBAAgB,UAAU,CACxB,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,GAAG,SAAa,GACf,IAAI,CAkBN"}
@@ -5,7 +5,7 @@ export declare function slugify(name: string, fallback?: string): string;
5
5
  /** id = slug(name) + "-" + random. Readable yet safe for offline multi-node creation. */
6
6
  export declare function makeId(name: string, fallback?: string): string;
7
7
  /** Entity kinds whose ids carry a type prefix. */
8
- export type Kind = "db" | "prop" | "rec" | "doc" | "blk" | "site" | "sf";
8
+ export type Kind = "db" | "prop" | "rec" | "doc" | "blk" | "site" | "sf" | "chan";
9
9
  /**
10
10
  * Typed id = "<kind>_<slug>-<rand>". `slugify` emits only [a-z0-9-] and
11
11
  * `randomSuffix` only base36, so the FIRST "_" is unambiguously the type
@@ -1 +1 @@
1
- {"version":3,"file":"ids.d.ts","sourceRoot":"","sources":["../../src/core/ids.ts"],"names":[],"mappings":"AAEA,oDAAoD;AACpD,wBAAgB,YAAY,CAAC,GAAG,SAAI,GAAG,MAAM,CAK5C;AAED,2EAA2E;AAC3E,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,SAAS,GAAG,MAAM,CAU/D;AAED,yFAAyF;AACzF,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,SAAS,GAAG,MAAM,CAE9D;AAED,kDAAkD;AAClD,MAAM,MAAM,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,IAAI,CAAC;AAYzE;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAa,GAAG,MAAM,CAE/E;AAED,4EAA4E;AAC5E,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAK9C"}
1
+ {"version":3,"file":"ids.d.ts","sourceRoot":"","sources":["../../src/core/ids.ts"],"names":[],"mappings":"AAEA,oDAAoD;AACpD,wBAAgB,YAAY,CAAC,GAAG,SAAI,GAAG,MAAM,CAK5C;AAED,2EAA2E;AAC3E,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,SAAS,GAAG,MAAM,CAU/D;AAED,yFAAyF;AACzF,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,SAAS,GAAG,MAAM,CAE9D;AAED,kDAAkD;AAClD,MAAM,MAAM,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM,CAAC;AAalF;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAa,GAAG,MAAM,CAE/E;AAED,4EAA4E;AAC5E,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAK9C"}
@@ -0,0 +1,8 @@
1
+ /** The injected tag. The closing tag is split so this constant can never
2
+ * terminate an enclosing <script> when this module's source is itself inlined
3
+ * into an HTML document (bundlers/templates that embed JS in <script> blocks
4
+ * would otherwise cut the string in half at `</script>`). */
5
+ export declare const RUNTIME_TAG: string;
6
+ /** Insert the runtime into an HTML document (right after <head>, else prepend). */
7
+ export declare function injectRuntimeTag(html: string): string;
8
+ //# sourceMappingURL=inject-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inject-runtime.d.ts","sourceRoot":"","sources":["../../src/core/inject-runtime.ts"],"names":[],"mappings":"AASA;;;8DAG8D;AAC9D,eAAO,MAAM,WAAW,QAAgD,CAAC;AAEzE,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIrD"}
@@ -0,0 +1,23 @@
1
+ import type { DbDriver } from "./driver.ts";
2
+ /** The blind inbox accepts mail for at most this long without an owner pull. */
3
+ export declare const DROP_ENVELOPE_RETENTION_MS: number;
4
+ /** Receipts outlive the inbox by a safety day so a last-moment delivery still
5
+ * has a complete retry/idempotency window on the owner. */
6
+ export declare const INTENT_REPLAY_WINDOW_MS: number;
7
+ export declare const INTENT_RECEIPT_DATASET = "intent_receipts";
8
+ interface ReceiptChange {
9
+ dataset: string;
10
+ hlc: string;
11
+ }
12
+ /** Lexicographic lower bound for every HLC minted at `now - replayWindow`. */
13
+ export declare function intentReceiptCutoffHlc(now?: number): string;
14
+ export declare function isExpiredIntentReceipt(change: ReceiptChange, now?: number): boolean;
15
+ export declare function filterExpiredIntentReceipts<T extends ReceiptChange>(changes: T[], now?: number): T[];
16
+ /** Local protocol GC. Deletion is intentionally not represented as a CRDT
17
+ * tombstone: expired receipts are also filtered at every replication boundary,
18
+ * so an old segment/snapshot cannot resurrect them. */
19
+ export declare function pruneExpiredIntentReceipts(db: DbDriver, now?: number): number;
20
+ export declare function countExpiredIntentReceipts(db: DbDriver, now?: number): number;
21
+ export declare function intentSubmissionExpired(submittedAt: number, now?: number): boolean;
22
+ export {};
23
+ //# sourceMappingURL=intent-retention.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intent-retention.d.ts","sourceRoot":"","sources":["../../src/core/intent-retention.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAI5C,gFAAgF;AAChF,eAAO,MAAM,0BAA0B,QAAc,CAAC;AAEtD;2DAC2D;AAC3D,eAAO,MAAM,uBAAuB,QAAsC,CAAC;AAE3E,eAAO,MAAM,sBAAsB,oBAAoB,CAAC;AAExD,UAAU,aAAa;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb;AAMD,8EAA8E;AAC9E,wBAAgB,sBAAsB,CAAC,GAAG,SAAa,GAAG,MAAM,CAG/D;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,aAAa,EACrB,GAAG,SAAa,GACf,OAAO,CAKT;AAED,wBAAgB,2BAA2B,CAAC,CAAC,SAAS,aAAa,EACjE,OAAO,EAAE,CAAC,EAAE,EACZ,GAAG,SAAa,GACf,CAAC,EAAE,CAEL;AAED;;uDAEuD;AACvD,wBAAgB,0BAA0B,CACxC,EAAE,EAAE,QAAQ,EACZ,GAAG,SAAa,GACf,MAAM,CAMR;AAED,wBAAgB,0BAA0B,CACxC,EAAE,EAAE,QAAQ,EACZ,GAAG,SAAa,GACf,MAAM,CAQR;AAED,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,MAAM,EACnB,GAAG,SAAa,GACf,OAAO,CAGT"}
@@ -1,15 +1,19 @@
1
1
  export interface InlineToken {
2
- kind: "code" | "image" | "strong" | "em" | "del" | "link";
2
+ kind: "code" | "image" | "strong" | "em" | "del" | "link" | "doclink";
3
3
  /** Token span in the source string (end exclusive), delimiters included. */
4
4
  start: number;
5
5
  end: number;
6
- /** Content span (code text, emphasis body, link text, image alt). */
6
+ /** Content span (code text, emphasis body, link text, image alt, doclink alias/id). */
7
7
  innerFrom: number;
8
8
  innerTo: number;
9
9
  /** link / image only. */
10
10
  url?: string;
11
11
  /** image only (may be ""). */
12
12
  alt?: string;
13
+ /** doclink only: the referenced doc/db id. */
14
+ id?: string;
15
+ /** doclink only: display text override from `[[id|alias]]`. */
16
+ alias?: string;
13
17
  }
14
18
  /** Tokenize one inline string. Returns non-overlapping tokens sorted by start. */
15
19
  export declare function tokenizeInline(text: string): InlineToken[];
@@ -1 +1 @@
1
- {"version":3,"file":"inline.d.ts","sourceRoot":"","sources":["../../../src/core/md/inline.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,IAAI,GAAG,KAAK,GAAG,MAAM,CAAC;IAC1D,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,yBAAyB;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AA8BD,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAiD1D;AAED;;8EAE8E;AAC9E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAiBtD"}
1
+ {"version":3,"file":"inline.d.ts","sourceRoot":"","sources":["../../../src/core/md/inline.ts"],"names":[],"mappings":"AAuBA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,IAAI,GAAG,KAAK,GAAG,MAAM,GAAG,SAAS,CAAC;IACtE,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,uFAAuF;IACvF,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,yBAAyB;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAmCD,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CA4D1D;AAED;;8EAE8E;AAC9E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAiBtD"}
@@ -1,4 +1,17 @@
1
1
  import type { DbDriver } from "./driver.ts";
2
2
  /** Stable per-machine node id, persisted in the meta table. */
3
3
  export declare function getNodeId(db: DbDriver): string;
4
+ /** Optional human label for THIS device (meta table, node-local — peers keep
5
+ * their own labels for us in their peers.label). Null when never set. */
6
+ export declare function getNodeLabel(db: DbDriver): string | null;
7
+ export declare function setNodeLabel(db: DbDriver, label: string | null): void;
8
+ export interface NodeInfo {
9
+ node_id: string;
10
+ label: string | null;
11
+ self: boolean;
12
+ }
13
+ /** Display roster: this device (with its own label) plus paired peers with the
14
+ * labels we gave them. Shared by GET /api/nodes and the replica's `nodes` op
15
+ * so the two can't drift. */
16
+ export declare function displayNodes(db: DbDriver): NodeInfo[];
4
17
  //# sourceMappingURL=node.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/core/node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG5C,+DAA+D;AAC/D,wBAAgB,SAAS,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,CAQ9C"}
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/core/node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG5C,+DAA+D;AAC/D,wBAAgB,SAAS,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,CAQ9C;AAED;0EAC0E;AAC1E,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAKxD;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAQrE;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;8BAE8B;AAC9B,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,QAAQ,EAAE,CAWrD"}
@@ -32,5 +32,13 @@ export declare const updateProperty: (db: DbDriver, id: string, fields: {
32
32
  position?: number;
33
33
  }) => PropertyRow;
34
34
  export declare function setPropertyWidth(db: DbDriver, id: string, width: number): PropertyRow;
35
+ export declare const renameSelectOption: (db: DbDriver, id: string, from: string, to: string) => {
36
+ property: PropertyRow;
37
+ renamed: number;
38
+ };
39
+ export declare const removeSelectOption: (db: DbDriver, id: string, name: string) => {
40
+ property: PropertyRow;
41
+ cleared: number;
42
+ };
35
43
  export declare const removeProperty: (db: DbDriver, id: string) => boolean;
36
44
  //# sourceMappingURL=properties.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"properties.d.ts","sourceRoot":"","sources":["../../src/core/properties.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAQ5C,MAAM,MAAM,QAAQ,GAChB,MAAM,GACN,QAAQ,GACR,UAAU,GACV,QAAQ,GACR,cAAc,GACd,MAAM,GACN,UAAU,GACV,KAAK,CAAC;AAEV,eAAO,MAAM,UAAU,EAAE,WAAW,CAAC,MAAM,CASzC,CAAC;AAEH,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAGD,eAAO,MAAM,aAAa,sEAAuE,CAAC;AAyBlG,eAAO,MAAM,WAAW;UAId,MAAM;UACN,QAAQ;aACL,cAAc;eACZ,MAAM;cAIP,OAAO;iBAwBnB,CAAC;AAEH,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAMxE;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,EAAE,CAU9E;AAED,eAAO,MAAM,cAAc;WAGR,MAAM;WAAS,QAAQ;aAAW,cAAc;eAAa,MAAM;iBA+BpF,CAAC;AAKH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,CAMrF;AAED,eAAO,MAAM,cAAc,uCAczB,CAAC"}
1
+ {"version":3,"file":"properties.d.ts","sourceRoot":"","sources":["../../src/core/properties.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAQ5C,MAAM,MAAM,QAAQ,GAChB,MAAM,GACN,QAAQ,GACR,UAAU,GACV,QAAQ,GACR,cAAc,GACd,MAAM,GACN,UAAU,GACV,KAAK,CAAC;AAEV,eAAO,MAAM,UAAU,EAAE,WAAW,CAAC,MAAM,CASzC,CAAC;AAEH,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAGD,eAAO,MAAM,aAAa,sEAAuE,CAAC;AAyBlG,eAAO,MAAM,WAAW;UAId,MAAM;UACN,QAAQ;aACL,cAAc;eACZ,MAAM;cAIP,OAAO;iBAwBnB,CAAC;AAEH,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAMxE;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,EAAE,CAU9E;AAED,eAAO,MAAM,cAAc;WAGR,MAAM;WAAS,QAAQ;aAAW,cAAc;eAAa,MAAM;iBA0CpF,CAAC;AAKH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,CAMrF;AAgBD,eAAO,MAAM,kBAAkB;cAKhB,WAAW;aAAW,MAAM;CAmCzC,CAAC;AAIH,eAAO,MAAM,kBAAkB;cAIhB,WAAW;aAAW,MAAM;CAgCzC,CAAC;AAEH,eAAO,MAAM,cAAc,uCAczB,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import type { DbDriver } from "./driver.ts";
2
+ import { type PropertyRow } from "./properties.ts";
2
3
  export interface RecordRow {
3
4
  id: string;
4
5
  database_id: string;
@@ -9,7 +10,36 @@ export interface RecordRow {
9
10
  * reads/writes through this. */
10
11
  cells: Record<string, unknown>;
11
12
  }
13
+ /** A property/value pair after name resolution and type coercion. Callers that
14
+ * already validated a payload can pass these to the prepared mutators without
15
+ * repeating relation resolution or coercion. */
16
+ export interface PreparedRecordCell {
17
+ prop: PropertyRow;
18
+ value: unknown;
19
+ }
20
+ /** Validate + normalize a value for a property's type. Throws on mismatch.
21
+ * Exported so grants-core delegates guest payload type checks to the ONE
22
+ * coercion implementation (spike ⑨) instead of rewriting it. */
23
+ export declare function coerce(db: DbDriver, prop: PropertyRow, value: unknown): unknown;
24
+ /**
25
+ * Match data keys (property name OR id) to properties. Duplicate property
26
+ * names are a legal state (offline peers can create them concurrently), so a
27
+ * name that matches several properties is ambiguous — callers must switch to
28
+ * the property id. Exported for grants-core (guest payload validation matches
29
+ * keys to properties with the exact same rules as the real write path).
30
+ */
31
+ export declare function resolveData(props: PropertyRow[], data: Record<string, unknown>): {
32
+ prop: PropertyRow;
33
+ value: unknown;
34
+ }[];
35
+ /** Resolve property refs and coerce each value exactly once. Input order is
36
+ * preserved because it also determines the HLC order of emitted cells. */
37
+ export declare function prepareRecordCells(db: DbDriver, databaseId: string, data: Record<string, unknown>): PreparedRecordCell[];
12
38
  export declare const backfillRecordOrderKeys: (db: DbDriver, databaseId?: string | undefined) => void;
39
+ export declare const createRecordPrepared: (db: DbDriver, database: {
40
+ id: string;
41
+ name: string;
42
+ }, cells: PreparedRecordCell[]) => RecordRow;
13
43
  export declare const createRecord: (db: DbDriver, databaseId: string, data: Record<string, unknown>) => RecordRow;
14
44
  export declare function getRecord(db: DbDriver, id: string): RecordRow | null;
15
45
  export declare function listRecords(db: DbDriver, databaseId: string, opts?: {
@@ -18,6 +48,7 @@ export declare function listRecords(db: DbDriver, databaseId: string, opts?: {
18
48
  limit?: number;
19
49
  }): RecordRow[];
20
50
  export declare const moveRecord: (db: DbDriver, id: string, targetId: string, where: "before" | "after") => RecordRow;
51
+ export declare const updateRecordPrepared: (db: DbDriver, id: string, cells: PreparedRecordCell[]) => RecordRow;
21
52
  export declare const updateRecord: (db: DbDriver, id: string, data: Record<string, unknown>) => RecordRow;
22
53
  export declare const deleteRecord: (db: DbDriver, id: string) => boolean;
23
54
  //# sourceMappingURL=records.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../../src/core/records.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAsB5C,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB;kEAC8D;IAC9D,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC;oCACgC;IAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AA8LD,eAAO,MAAM,uBAAuB,yDA4BlC,CAAC;AAEH,eAAO,MAAM,YAAY,gFAkBvB,CAAC;AAEH,wBAAgB,SAAS,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAMpE;AAOD,wBAAgB,WAAW,CACzB,EAAE,EAAE,QAAQ,EACZ,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7E,SAAS,EAAE,CAiFb;AAED,eAAO,MAAM,UAAU,sFA0CrB,CAAC;AAEH,eAAO,MAAM,YAAY,wEAavB,CAAC;AAEH,eAAO,MAAM,YAAY,uCAOvB,CAAC"}
1
+ {"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../../src/core/records.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAI5C,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAkBnE,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB;kEAC8D;IAC9D,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC;oCACgC;IAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED;;gDAEgD;AAChD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;CAChB;AA6BD;;iEAEiE;AACjE,wBAAgB,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAyC/E;AAcD;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,WAAW,EAAE,EACpB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,EAAE,CAmBzC;AAED;0EAC0E;AAC1E,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,QAAQ,EACZ,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,kBAAkB,EAAE,CAKtB;AA4ED,eAAO,MAAM,uBAAuB,yDA4BlC,CAAC;AAEH,eAAO,MAAM,oBAAoB;QAEf,MAAM;UAAQ,MAAM;4CAWpC,CAAC;AAEH,eAAO,MAAM,YAAY,gFAQvB,CAAC;AAEH,wBAAgB,SAAS,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAMpE;AAOD,wBAAgB,WAAW,CACzB,EAAE,EAAE,QAAQ,EACZ,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7E,SAAS,EAAE,CAiFb;AAED,eAAO,MAAM,UAAU,sFA0CrB,CAAC;AAEH,eAAO,MAAM,oBAAoB,sEAW/B,CAAC;AAEH,eAAO,MAAM,YAAY,wEAUvB,CAAC;AAEH,eAAO,MAAM,YAAY,uCAOvB,CAAC"}
@@ -12,8 +12,10 @@ export declare function migrateRecords(db: DbDriver): void;
12
12
  * enabled/last_sync_at/last_success_at/last_status/last_error). Idempotent —
13
13
  * guarded per column, never drops the table so existing replication cursors survive.
14
14
  */
15
- /** Add `served_base` to a `shares` table created before the column existed
16
- * (idempotent). The vestigial s3_* columns are left as-is. */
15
+ /** Add `served_base` / `grants` to a `shares` table created before the columns
16
+ * existed (idempotent). The vestigial s3_* columns are left as-is. `grants` is
17
+ * node-local like the rest of the row (never in the oplog), so unlike the
18
+ * synced sites columns there is nothing to backfill. */
17
19
  export declare function migrateShares(db: DbDriver): void;
18
20
  export declare function migratePeers(db: DbDriver): void;
19
21
  /**
@@ -105,6 +107,21 @@ export declare function migrateStoragePeerUrls(db: DbDriver): void;
105
107
  * upgrade sees the state its peers already agreed on.
106
108
  */
107
109
  export declare function migrateDatabases(db: DbDriver): void;
110
+ /**
111
+ * Add the `visibility` / `spa` / `public_grants` access columns to a legacy
112
+ * `sites` table. Idempotent — guarded per column.
113
+ *
114
+ * Re-materialization (same pattern as migrateDatabases): an OLD binary that
115
+ * pulled `sites/visibility|spa` changes from newer peers stored them in
116
+ * crdt_changes but skipped materialization (unknown column, forward-compat)
117
+ * and advanced its cursors past them. When a column is first added, backfill
118
+ * each site from its WINNING oplog change (max HLC — the LWW rule applyChange
119
+ * uses). Unlike databases.meta (a JSON-text column, where the raw change value
120
+ * is the stored form), these columns store scalars, so the JSON-encoded oplog
121
+ * value is unwrapped with json_extract (`"public"` → public, `1` → 1).
122
+ */
123
+ export declare function migrateSitesAccess(db: DbDriver): void;
124
+ export declare function migrateSiteChannels(db: DbDriver): void;
108
125
  /** Bring a freshly opened (or legacy) database to the current schema. */
109
126
  export declare function initSchema(db: DbDriver): void;
110
127
  //# sourceMappingURL=schema-init.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema-init.d.ts","sourceRoot":"","sources":["../../src/core/schema-init.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAO5C,wBAAgB,SAAS,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAO5C;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAKlD;AAeD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAuBjD;AAED;;;;GAIG;AACH;+DAC+D;AAC/D,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAGhD;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAsB/C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAGnD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAQnD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAInD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAG/C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CA4BxD;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAuDzD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAanD;AAED,yEAAyE;AACzE,wBAAgB,UAAU,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAY7C"}
1
+ {"version":3,"file":"schema-init.d.ts","sourceRoot":"","sources":["../../src/core/schema-init.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAQ5C,wBAAgB,SAAS,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAO5C;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAKlD;AAeD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAuBjD;AAED;;;;GAIG;AACH;;;yDAGyD;AACzD,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAUhD;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAsB/C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAGnD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAQnD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAInD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAO/C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAgCxD;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAuDzD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAanD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CA+BrD;AAQD,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CA0BtD;AAED,yEAAyE;AACzE,wBAAgB,UAAU,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAc7C"}
@@ -1,3 +1,3 @@
1
- export declare const CORE_SCHEMA = "\nCREATE TABLE IF NOT EXISTS meta (\n key TEXT PRIMARY KEY,\n value TEXT\n);\n\n-- CRDT oplog: one row per field assignment. Source of truth for sync.\n-- txn groups the changes of one logical mutation for history rendering\n-- (nullable: legacy rows and changes from pre-txn peers have none).\n--\n-- seq is an explicit AUTOINCREMENT id used as the replication cursor. It must\n-- be a declared INTEGER PRIMARY KEY (not the implicit rowid): VACUUM renumbers\n-- the rowids of tables that lack one, which would shift them below a peer's\n-- stored push/pull cursor and silently strand every write in the gap.\n-- AUTOINCREMENT additionally guarantees ids are never reused. The\n-- (dataset,row_id,col,hlc) tuple stays UNIQUE so applyChange's INSERT OR IGNORE\n-- dedup is unchanged. Legacy databases are rebuilt into this shape by\n-- migrateCrdtChangesSeq (schema-init.ts).\nCREATE TABLE IF NOT EXISTS crdt_changes (\n seq INTEGER PRIMARY KEY AUTOINCREMENT,\n hlc TEXT NOT NULL,\n node_id TEXT NOT NULL,\n dataset TEXT NOT NULL,\n row_id TEXT NOT NULL,\n col TEXT NOT NULL,\n value TEXT,\n txn TEXT,\n UNIQUE (dataset, row_id, col, hlc)\n);\nCREATE INDEX IF NOT EXISTS idx_changes_hlc ON crdt_changes(hlc);\n-- Serves history's \"every block ever attached to this doc\" lookup. Partial so\n-- it never indexes the large values other datasets carry (e.g. site file bodies).\nCREATE INDEX IF NOT EXISTS idx_changes_docref ON crdt_changes(value)\n WHERE dataset = 'doc_blocks' AND col = 'doc_id';\n\n-- Per-peer replication cursors (crdt_changes.seq: AUTOINCREMENT, monotonic in\n-- insertion order and stable across VACUUM, so no change is ever skipped\n-- regardless of HLC/clock skew). Outbound side of\n-- a pairing: \"token\" is the credential the *remote* issued to us (sent as a\n-- Bearer header when we sync to them). See migratePeers in db.ts for the columns\n-- added to legacy databases.\n--\n-- kind selects the transport: 'http' (POST /sync to a peer server, the classic\n-- path) or 's3' (an S3-compatible bucket used as dumb store-and-forward \u2014 see\n-- sync/storage.ts). For 's3' peers url is a synthetic key (s3://<bucket>/<prefix>)\n-- and config holds the bucket settings + credentials + master key as JSON.\n-- Like the rest of peers, config is local-only and never enters the CRDT oplog.\nCREATE TABLE IF NOT EXISTS peers (\n url TEXT PRIMARY KEY,\n pull_cursor INTEGER NOT NULL DEFAULT 0,\n push_cursor INTEGER NOT NULL DEFAULT 0,\n token TEXT,\n label TEXT,\n node_id TEXT,\n enabled INTEGER NOT NULL DEFAULT 1,\n last_sync_at INTEGER,\n last_success_at INTEGER,\n last_status TEXT,\n last_error TEXT,\n kind TEXT NOT NULL DEFAULT 'http',\n config TEXT\n);\n\n-- Per-(storage-peer, remote-node) pull progress: the key of the last oplog\n-- segment we consumed from that node's bucket prefix. HTTP peers use the single\n-- pull_cursor above; storage peers need one cursor per remote node because each\n-- node publishes its own segment stream under its own prefix. last_key is a\n-- bucket object key (lexicographically ordered = chronological), so LIST\n-- start-after resumes exactly where we left off.\nCREATE TABLE IF NOT EXISTS storage_cursors (\n peer_url TEXT NOT NULL,\n node_id TEXT NOT NULL,\n last_key TEXT,\n PRIMARY KEY (peer_url, node_id)\n);\n\n-- Inbound side of a pairing: credentials *we* issued to remote devices. A peer\n-- presents one of these on /sync; acceptsSyncToken() checks it alongside the\n-- managed master token. Revoke by deleting the row.\nCREATE TABLE IF NOT EXISTS peer_grants (\n token TEXT PRIMARY KEY,\n peer_url TEXT,\n node_id TEXT,\n created_at INTEGER\n);\n\n-- One-time pairing codes minted by \"mh config peer code\" / POST /api/pair/new.\n-- Short-lived and single-use: redeemed during the pairing handshake, then the\n-- two devices exchange durable peer_grants. used/exp gate redemption.\nCREATE TABLE IF NOT EXISTS pairing_codes (\n code TEXT PRIMARY KEY,\n exp INTEGER NOT NULL,\n used INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER\n);\n\n-- Public capability links to a doc / database / site. Like peers and blob_cache\n-- this is node-local and deliberately ABSENT from crdt.ts's DOMAIN map, so it\n-- never enters the oplog and never syncs \u2014 a share is a property of the access\n-- point that minted it, not of the workspace. slug is the unguessable capability\n-- in the URL (/share/<slug>). transport selects how it's served: 'server' (the\n-- token-exempt /share endpoint on this node) or 's3' (a presigned static export\n-- under <prefix>/shares/<slug> in a bucket). permission 'edit' is server-only:\n-- a presigned static object can't accept writes. guest_node_id attributes every\n-- edit made through an edit-share to one synthetic node (see crdt.ts withNodeId).\n-- pw_hash/pw_salt: server path stores a PBKDF2 verifier; the s3 path is E2EE so\n-- only pw_salt travels (in the link) and decryption happens in the viewer.\nCREATE TABLE IF NOT EXISTS shares (\n slug TEXT PRIMARY KEY,\n kind TEXT NOT NULL, -- 'doc' | 'database' | 'site'\n target_id TEXT NOT NULL, -- documents.id / databases.id / sites.id\n permission TEXT NOT NULL, -- 'view' | 'edit'\n transport TEXT NOT NULL, -- 'server' | 's3'\n pw_salt TEXT, -- base64; null = no password\n pw_hash TEXT, -- base64 PBKDF2 verifier (server path only)\n expires_at INTEGER, -- epoch ms; null = never\n guest_node_id TEXT, -- synthetic node id for edit shares (null for view)\n served_base TEXT, -- reachable base URL chosen at creation (link / source label)\n s3_peer_url TEXT, -- vestigial (s3 shares live in the bucket, not here)\n s3_object_prefix TEXT, -- vestigial\n s3_presign_exp INTEGER, -- vestigial\n s3_key_b64 TEXT, -- vestigial\n created_at INTEGER NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_shares_target ON shares(target_id);\n\nCREATE TABLE IF NOT EXISTS databases (\n id TEXT PRIMARY KEY,\n name TEXT,\n icon TEXT,\n meta TEXT, -- JSON: generic replicated metadata (e.g. UI flags), one LWW register\n created_hlc TEXT,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\n\nCREATE TABLE IF NOT EXISTS properties (\n id TEXT PRIMARY KEY,\n database_id TEXT,\n name TEXT,\n type TEXT,\n config TEXT,\n position REAL,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_properties_db ON properties(database_id);\n\n-- One row per record. Field cells live in the data JSON object, keyed by\n-- property id (rename-safe). Each (record,property) is still an independent\n-- CRDT register in the oplog; materialize() folds the winner into data via\n-- json_set/json_remove. Hot fields get expression indexes on data->>'propid'\n-- (see indexing.ts) so filter/sort/limit push down to SQL.\nCREATE TABLE IF NOT EXISTS records (\n id TEXT PRIMARY KEY,\n database_id TEXT,\n created_hlc TEXT,\n order_key TEXT,\n data TEXT NOT NULL DEFAULT '{}',\n __deleted INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_records_db_hlc ON records(database_id, created_hlc);\n\nCREATE TABLE IF NOT EXISTS documents (\n id TEXT PRIMARY KEY,\n title TEXT,\n body TEXT, -- materialized cache: serialized from doc_blocks\n database_id TEXT,\n parent_id TEXT,\n created_hlc TEXT,\n order_key TEXT, -- fractional index among siblings (same parent_id); ties broken by created_hlc,id\n __deleted INTEGER NOT NULL DEFAULT 0\n);\n\n-- Document body as an ordered list of blocks; each (block,field) is an\n-- independent CRDT register so concurrent edits to different blocks merge.\n-- order_key is a fractional index (ties broken by id) giving display order.\nCREATE TABLE IF NOT EXISTS doc_blocks (\n id TEXT PRIMARY KEY,\n doc_id TEXT,\n text TEXT,\n order_key TEXT,\n blank_after INTEGER NOT NULL DEFAULT 0, -- extra blank lines kept after this block (user spacing)\n __deleted INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_doc_blocks_doc ON doc_blocks(doc_id);\n\n-- A named static site (Supabase-Storage-style bucket). Served at /sites/<name>/.\nCREATE TABLE IF NOT EXISTS sites (\n id TEXT PRIMARY KEY,\n name TEXT, -- URL slug; resolved by getSiteByName\n title TEXT,\n created_hlc TEXT,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\n\n-- One row per file in a site. content holds inline utf8/base64 text, or a blob\n-- hash (see cache.ts) when encoding = 'blob'. (site_id, path) maps to a stable\n-- id so re-uploads merge as a CRDT register instead of duplicating.\nCREATE TABLE IF NOT EXISTS site_files (\n id TEXT PRIMARY KEY,\n site_id TEXT,\n path TEXT,\n content_type TEXT,\n encoding TEXT, -- 'utf8' | 'base64' | 'blob'\n content TEXT,\n created_hlc TEXT,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_site_files_site ON site_files(site_id);\n\n-- Node-local blob cache ledger (NOT synced, like peers/storage_cursors): one row\n-- per blob byte-file held in cache.ts's content-addressed store (~/.metahub/cache).\n-- Gives the bare hash-named files the metadata clearing/stats need. hash is the\n-- canonical content key (sha256 truncated to 32 hex; legacy 64-hex coexist).\n-- pending = bytes produced HERE not yet confirmed flushed to a durable anchor\n-- (bucket) \u2014 the only blobs a device must protect; never auto-evicted/cleared.\n-- pending=0 means a flushed production OR an acquired cache, both safely\n-- clearable (re-fetchable). This local fact replaces the old SYNCED blob_presence\n-- table \u2014 clearing is now purely local/offline. See blobs.ts / 22-blob-sync.\nCREATE TABLE IF NOT EXISTS blob_cache (\n hash TEXT PRIMARY KEY,\n size INTEGER NOT NULL,\n content_type TEXT,\n last_access INTEGER,\n pinned INTEGER NOT NULL DEFAULT 0, -- node-local: never auto-evicted / cleared\n pending INTEGER NOT NULL DEFAULT 1, -- produced here, not yet flushed to anchor\n anchored INTEGER NOT NULL DEFAULT 0 -- node-local: last verify confirmed a designated anchor holds it (per redundancy). Drives isClearable; reset on policy change\n);\n\n-- Synced workspace policy (single row \"default\"): which nodes are designated\n-- full-blob libraries \u2014 the durable anchor in no-bucket topologies (they pull\n-- everything and never clear). full_nodes is a JSON array of node_id; redundancy\n-- is retained but unused since clearing moved to the local pending model.\nCREATE TABLE IF NOT EXISTS blob_policy (\n id TEXT PRIMARY KEY, -- always \"default\"\n full_nodes TEXT, -- JSON array of node_id\n redundancy TEXT, -- 'all' | 'any'\n __deleted INTEGER NOT NULL DEFAULT 0\n);\n";
1
+ export declare const CORE_SCHEMA = "\nCREATE TABLE IF NOT EXISTS meta (\n key TEXT PRIMARY KEY,\n value TEXT\n);\n\n-- CRDT oplog: one row per field assignment. Source of truth for sync.\n-- txn groups the changes of one logical mutation for history rendering\n-- (nullable: legacy rows and changes from pre-txn peers have none).\n--\n-- seq is an explicit AUTOINCREMENT id used as the replication cursor. It must\n-- be a declared INTEGER PRIMARY KEY (not the implicit rowid): VACUUM renumbers\n-- the rowids of tables that lack one, which would shift them below a peer's\n-- stored push/pull cursor and silently strand every write in the gap.\n-- AUTOINCREMENT additionally guarantees ids are never reused. The\n-- (dataset,row_id,col,hlc) tuple stays UNIQUE so applyChange's INSERT OR IGNORE\n-- dedup is unchanged. Legacy databases are rebuilt into this shape by\n-- migrateCrdtChangesSeq (schema-init.ts).\nCREATE TABLE IF NOT EXISTS crdt_changes (\n seq INTEGER PRIMARY KEY AUTOINCREMENT,\n hlc TEXT NOT NULL,\n node_id TEXT NOT NULL,\n dataset TEXT NOT NULL,\n row_id TEXT NOT NULL,\n col TEXT NOT NULL,\n value TEXT,\n txn TEXT,\n UNIQUE (dataset, row_id, col, hlc)\n);\nCREATE INDEX IF NOT EXISTS idx_changes_hlc ON crdt_changes(hlc);\n-- GuestIntent idempotency probes by txn prefix on every guest write. Keep the\n-- sparse legacy/null majority out of the index.\nCREATE INDEX IF NOT EXISTS idx_changes_txn ON crdt_changes(txn)\n WHERE txn IS NOT NULL;\n-- Receipt GC is age-based and runs on every intent/sync maintenance path.\nCREATE INDEX IF NOT EXISTS idx_changes_intent_receipt_hlc ON crdt_changes(hlc)\n WHERE dataset = 'intent_receipts';\n-- migrateSiteChannels replays only the tail past its watermark on every open;\n-- partial index keeps that probe off a full-table scan (site_channels rows are\n-- a tiny minority of the oplog).\nCREATE INDEX IF NOT EXISTS idx_changes_site_channels_seq ON crdt_changes(seq)\n WHERE dataset = 'site_channels';\n-- Serves history's \"every block ever attached to this doc\" lookup. Partial so\n-- it never indexes the large values other datasets carry (e.g. site file bodies).\nCREATE INDEX IF NOT EXISTS idx_changes_docref ON crdt_changes(value)\n WHERE dataset = 'doc_blocks' AND col = 'doc_id';\n\n-- Per-peer replication cursors (crdt_changes.seq: AUTOINCREMENT, monotonic in\n-- insertion order and stable across VACUUM, so no change is ever skipped\n-- regardless of HLC/clock skew). Outbound side of\n-- a pairing: \"token\" is the credential the *remote* issued to us (sent as a\n-- Bearer header when we sync to them). See migratePeers in db.ts for the columns\n-- added to legacy databases.\n--\n-- kind selects the transport: 'http' (POST /sync to a peer server, the classic\n-- path) or 's3' (an S3-compatible bucket used as dumb store-and-forward \u2014 see\n-- sync/storage.ts). For 's3' peers url is a synthetic key (s3://<bucket>/<prefix>)\n-- and config holds the bucket settings + credentials + master key as JSON.\n-- Like the rest of peers, config is local-only and never enters the CRDT oplog.\nCREATE TABLE IF NOT EXISTS peers (\n url TEXT PRIMARY KEY,\n pull_cursor INTEGER NOT NULL DEFAULT 0,\n push_cursor INTEGER NOT NULL DEFAULT 0,\n token TEXT,\n label TEXT,\n node_id TEXT,\n enabled INTEGER NOT NULL DEFAULT 1,\n last_sync_at INTEGER,\n last_success_at INTEGER,\n last_status TEXT,\n last_error TEXT,\n kind TEXT NOT NULL DEFAULT 'http',\n config TEXT\n);\n\n-- Per-(storage-peer, remote-node) pull progress: the key of the last oplog\n-- segment we consumed from that node's bucket prefix. HTTP peers use the single\n-- pull_cursor above; storage peers need one cursor per remote node because each\n-- node publishes its own segment stream under its own prefix. last_key is a\n-- bucket object key (lexicographically ordered = chronological), so LIST\n-- start-after resumes exactly where we left off.\nCREATE TABLE IF NOT EXISTS storage_cursors (\n peer_url TEXT NOT NULL,\n node_id TEXT NOT NULL,\n last_key TEXT,\n PRIMARY KEY (peer_url, node_id)\n);\n\n-- Owner-side shadow of a room partition (sync/partition.ts): which (dataset,\n-- row_id) pairs THIS node last told a given room peer about. Node-local like\n-- peers/storage_cursors \u2014 deliberately absent from crdt.ts's DOMAIN map, never\n-- in the oplog, never synced (each device keeps its own shadow; cross-device\n-- shadow splits are healed by the room protocol's need_baseline/digest layers,\n-- not by syncing shadows). peer_key is the peers.url of a kind='room' row.\nCREATE TABLE IF NOT EXISTS room_rows (\n peer_key TEXT NOT NULL,\n dataset TEXT NOT NULL,\n row_id TEXT NOT NULL,\n PRIMARY KEY (peer_key, dataset, row_id)\n);\n\n-- Inbound side of a pairing: credentials *we* issued to remote devices. A peer\n-- presents one of these on /sync; acceptsSyncToken() checks it alongside the\n-- managed master token. Revoke by deleting the row.\nCREATE TABLE IF NOT EXISTS peer_grants (\n token TEXT PRIMARY KEY,\n peer_url TEXT,\n node_id TEXT,\n created_at INTEGER\n);\n\n-- One-time pairing codes minted by \"mh config peer code\" / POST /api/pair/new.\n-- Short-lived and single-use: redeemed during the pairing handshake, then the\n-- two devices exchange durable peer_grants. used/exp gate redemption.\nCREATE TABLE IF NOT EXISTS pairing_codes (\n code TEXT PRIMARY KEY,\n exp INTEGER NOT NULL,\n used INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER\n);\n\n-- Public capability links to a doc / database / site. Like peers and blob_cache\n-- this is node-local and deliberately ABSENT from crdt.ts's DOMAIN map, so it\n-- never enters the oplog and never syncs \u2014 a share is a property of the access\n-- point that minted it, not of the workspace. slug is the unguessable capability\n-- in the URL (/share/<slug>). transport selects how it's served: 'server' (the\n-- token-exempt /share endpoint on this node) or 's3' (a presigned static export\n-- under <prefix>/shares/<slug> in a bucket). permission 'edit' is server-only:\n-- a presigned static object can't accept writes. guest_node_id attributes every\n-- edit made through an edit-share to one synthetic node (see crdt.ts withNodeId).\n-- pw_hash/pw_salt: server path stores a PBKDF2 verifier; the s3 path is E2EE so\n-- only pw_salt travels (in the link) and decryption happens in the viewer.\nCREATE TABLE IF NOT EXISTS shares (\n slug TEXT PRIMARY KEY,\n kind TEXT NOT NULL, -- 'doc' | 'database' | 'site'\n target_id TEXT NOT NULL, -- documents.id / databases.id / sites.id\n permission TEXT NOT NULL, -- 'view' | 'edit'\n transport TEXT NOT NULL, -- 'server' | 's3'\n pw_salt TEXT, -- base64; null = no password\n pw_hash TEXT, -- base64 PBKDF2 verifier (server path only)\n expires_at INTEGER, -- epoch ms; null = never\n guest_node_id TEXT, -- synthetic node id for edit shares (null for view)\n served_base TEXT, -- reachable base URL chosen at creation (link / source label)\n s3_peer_url TEXT, -- vestigial (s3 shares live in the bucket, not here)\n s3_object_prefix TEXT, -- vestigial\n s3_presign_exp INTEGER, -- vestigial\n s3_key_b64 TEXT, -- vestigial\n created_at INTEGER NOT NULL,\n request_id TEXT, -- idempotency key for remote share creation\n grants TEXT -- serialized GrantSet for /share/<slug>/api/* (node-local like the\n -- rest of the row: revoking the share kills the grants with it)\n);\nCREATE INDEX IF NOT EXISTS idx_shares_target ON shares(target_id);\n\nCREATE TABLE IF NOT EXISTS databases (\n id TEXT PRIMARY KEY,\n name TEXT,\n icon TEXT,\n meta TEXT, -- JSON: generic replicated metadata (e.g. UI flags), one LWW register\n created_hlc TEXT,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\n\nCREATE TABLE IF NOT EXISTS properties (\n id TEXT PRIMARY KEY,\n database_id TEXT,\n name TEXT,\n type TEXT,\n config TEXT,\n position REAL,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_properties_db ON properties(database_id);\n\n-- One row per record. Field cells live in the data JSON object, keyed by\n-- property id (rename-safe). Each (record,property) is still an independent\n-- CRDT register in the oplog; materialize() folds the winner into data via\n-- json_set/json_remove. Hot fields get expression indexes on data->>'propid'\n-- (see indexing.ts) so filter/sort/limit push down to SQL.\nCREATE TABLE IF NOT EXISTS records (\n id TEXT PRIMARY KEY,\n database_id TEXT,\n created_hlc TEXT,\n order_key TEXT,\n data TEXT NOT NULL DEFAULT '{}',\n __deleted INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_records_db_hlc ON records(database_id, created_hlc);\n\nCREATE TABLE IF NOT EXISTS documents (\n id TEXT PRIMARY KEY,\n title TEXT,\n body TEXT, -- materialized cache: serialized from doc_blocks\n database_id TEXT,\n parent_id TEXT,\n created_hlc TEXT,\n order_key TEXT, -- fractional index among siblings (same parent_id); ties broken by created_hlc,id\n __deleted INTEGER NOT NULL DEFAULT 0\n);\n\n-- Document body as an ordered list of blocks; each (block,field) is an\n-- independent CRDT register so concurrent edits to different blocks merge.\n-- order_key is a fractional index (ties broken by id) giving display order.\nCREATE TABLE IF NOT EXISTS doc_blocks (\n id TEXT PRIMARY KEY,\n doc_id TEXT,\n text TEXT,\n order_key TEXT,\n blank_after INTEGER NOT NULL DEFAULT 0, -- extra blank lines kept after this block (user spacing)\n __deleted INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_doc_blocks_doc ON doc_blocks(doc_id);\n\n-- A named static site (Supabase-Storage-style bucket). Served at /sites/<name>/.\nCREATE TABLE IF NOT EXISTS sites (\n id TEXT PRIMARY KEY,\n name TEXT, -- URL slug; resolved by getSiteByName\n title TEXT,\n created_hlc TEXT,\n visibility TEXT, -- exactly 'public' = token-free serving; anything else = private (isSitePublic)\n spa INTEGER NOT NULL DEFAULT 0, -- 1 = extension-less misses fall back to index.html\n public_grants TEXT, -- serialized GrantSet for anonymous /sites/<name>/api/*; synced register,\n -- readers MUST go through parseGrantSet (default-deny on any junk)\n __deleted INTEGER NOT NULL DEFAULT 0\n);\n\n-- Synced desired-state control plane for each way a site is reachable. Access\n-- policy and hosting are separate axes; controller_node_id names the node that\n-- owns any node-local secret/cleanup work. Runtime success is NOT synced here\n-- (see node-local site_channel_observations below).\nCREATE TABLE IF NOT EXISTS site_channels (\n id TEXT PRIMARY KEY,\n site_id TEXT,\n audience TEXT, -- 'public' | 'link'\n hosting TEXT, -- 'device' | 'edge'\n controller_node_id TEXT,\n target_ref TEXT, -- serving node id (device) / capability slug (edge)\n canonical_url TEXT,\n policy_json TEXT,\n desired_state TEXT, -- 'active' | 'revoked'\n created_hlc TEXT,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_site_channels_site ON site_channels(site_id);\n\n-- What THIS node has actually observed while applying a channel's desired\n-- state. Never replicated: readiness/errors are node-relative facts.\nCREATE TABLE IF NOT EXISTS site_channel_observations (\n channel_id TEXT PRIMARY KEY,\n status TEXT NOT NULL,\n last_verified_at INTEGER,\n last_error TEXT\n);\n\n-- One row per file in a site. content holds inline utf8/base64 text, or a blob\n-- hash (see cache.ts) when encoding = 'blob'. (site_id, path) maps to a stable\n-- id so re-uploads merge as a CRDT register instead of duplicating.\nCREATE TABLE IF NOT EXISTS site_files (\n id TEXT PRIMARY KEY,\n site_id TEXT,\n path TEXT,\n content_type TEXT,\n encoding TEXT, -- 'utf8' | 'base64' | 'blob'\n content TEXT,\n created_hlc TEXT,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_site_files_site ON site_files(site_id);\n\n-- Node-local blob cache ledger (NOT synced, like peers/storage_cursors): one row\n-- per blob byte-file held in cache.ts's content-addressed store (~/.metahub/cache).\n-- Gives the bare hash-named files the metadata clearing/stats need. hash is the\n-- canonical content key (sha256 truncated to 32 hex; legacy 64-hex coexist).\n-- pending = bytes produced HERE not yet confirmed flushed to a durable anchor\n-- (bucket) \u2014 the only blobs a device must protect; never auto-evicted/cleared.\n-- pending=0 means a flushed production OR an acquired cache, both safely\n-- clearable (re-fetchable). This local fact replaces the old SYNCED blob_presence\n-- table \u2014 clearing is now purely local/offline. See blobs.ts / 22-blob-sync.\nCREATE TABLE IF NOT EXISTS blob_cache (\n hash TEXT PRIMARY KEY,\n size INTEGER NOT NULL,\n content_type TEXT,\n last_access INTEGER,\n pinned INTEGER NOT NULL DEFAULT 0, -- node-local: never auto-evicted / cleared\n pending INTEGER NOT NULL DEFAULT 1, -- produced here, not yet flushed to anchor\n anchored INTEGER NOT NULL DEFAULT 0 -- node-local: last verify confirmed a designated anchor holds it (per redundancy). Drives isClearable; reset on policy change\n);\n\n-- Node-local ledger of write-inbox envelopes the ingest isolation layer refused\n-- (drop-pull.ts): the reason is recorded here, then the envelope is deleted from\n-- the edge host (invalid mail never occupies inbox capacity). NOT synced \u2014 like\n-- peers/blob_cache, this is a fact about what THIS node saw, not workspace data.\nCREATE TABLE IF NOT EXISTS drop_rejects (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n drop_id TEXT NOT NULL,\n envelope_id TEXT,\n reason TEXT NOT NULL,\n created_at INTEGER NOT NULL\n);\n\n-- Synced workspace policy (single row \"default\"): which nodes are designated\n-- full-blob libraries \u2014 the durable anchor in no-bucket topologies (they pull\n-- everything and never clear). full_nodes is a JSON array of node_id; redundancy\n-- is retained but unused since clearing moved to the local pending model.\nCREATE TABLE IF NOT EXISTS blob_policy (\n id TEXT PRIMARY KEY, -- always \"default\"\n full_nodes TEXT, -- JSON array of node_id\n redundancy TEXT, -- 'all' | 'any'\n __deleted INTEGER NOT NULL DEFAULT 0\n);\n";
2
2
  export declare const FTS_SCHEMA = "\nCREATE VIRTUAL TABLE IF NOT EXISTS search_fts USING fts5(\n kind UNINDEXED,\n id UNINDEXED,\n database_id UNINDEXED,\n title,\n body\n);\n";
3
3
  //# sourceMappingURL=schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,43VA8OvB,CAAC;AAGF,eAAO,MAAM,UAAU,sJAQtB,CAAC"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,0ndAsTvB,CAAC;AAGF,eAAO,MAAM,UAAU,sJAQtB,CAAC"}
@@ -23,6 +23,12 @@ export interface ShareRow {
23
23
  s3_presign_exp: number | null;
24
24
  s3_key_b64: string | null;
25
25
  created_at: number;
26
+ /** Optional idempotency key supplied by a paired node. */
27
+ request_id: string | null;
28
+ /** Serialized GrantSet for /share/<slug>/api/* — node-local like the rest of
29
+ * the row, so revoking the share revokes the grants with zero dangling
30
+ * state. Read through parseGrantSet only (default-deny). */
31
+ grants: string | null;
26
32
  }
27
33
  export interface CreateShareInput {
28
34
  kind: ShareKind;
@@ -34,6 +40,9 @@ export interface CreateShareInput {
34
40
  expiresAt?: number | null;
35
41
  /** Reachable base URL recorded for the link / source label. */
36
42
  servedBase?: string | null;
43
+ /** Serialized GrantSet enabling /share/<slug>/api/* (validated + normalized here). */
44
+ grants?: string | null;
45
+ requestId?: string | null;
37
46
  }
38
47
  /**
39
48
  * Mint a server-transport share row. (Object-storage shares are NOT stored here —
@@ -42,6 +51,7 @@ export interface CreateShareInput {
42
51
  * The caller verifies that `target_id` exists.
43
52
  */
44
53
  export declare function createShare(db: DbDriver, input: CreateShareInput): ShareRow;
54
+ export declare function getShareByRequestId(db: DbDriver, requestId: string): ShareRow | null;
45
55
  export declare function getShare(db: DbDriver, slug: string): ShareRow | null;
46
56
  export declare function listShares(db: DbDriver): ShareRow[];
47
57
  /** Shares pointing at one target — used to surface existing links in the UI. */
@@ -54,6 +64,11 @@ export declare function hashSharePassword(password: string): Promise<{
54
64
  salt: string;
55
65
  hash: string;
56
66
  }>;
67
+ /** Constant-time check of `password` against a stored {salt, hash} verifier
68
+ * pair (both base64) — the portable core of verifySharePassword, reused by
69
+ * the Durable Object room's unlock flow (same PBKDF2 parameters, so the
70
+ * share row's verifier provisions the room unchanged). */
71
+ export declare function verifyPasswordVerifier(hashB64: string, saltB64: string, password: string): Promise<boolean>;
57
72
  /** Constant-time check of `password` against a share's stored verifier. A share
58
73
  * with no password always returns true (nothing to verify). */
59
74
  export declare function verifySharePassword(row: ShareRow, password: string): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"shares.d.ts","sourceRoot":"","sources":["../../src/core/shares.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAK5C,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,UAAU,GAAG,MAAM,CAAC;AACpD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,CAAC;AAC9C,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,IAAI,CAAC;AAI7C,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,eAAe,CAAC;IAC5B,SAAS,EAAE,cAAc,CAAC;IAC1B,uCAAuC;IACvC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,mFAAmF;IACnF,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,8BAA8B;IAC9B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,oFAAoF;IACpF,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,2EAA2E;IAC3E,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAG3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB;AAKD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,SAAS,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,+DAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,gBAAgB,GAAG,QAAQ,CA+B3E;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAKpE;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,QAAQ,GAAG,QAAQ,EAAE,CAInD;AAED,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,QAAQ,EAAE,CAI9E;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,GAAG,EAAE,QAAQ,EAAE,GAAG,SAAa,GAAG,OAAO,CAErE;AA+BD,yEAAyE;AACzE,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAIjG;AAED;gEACgE;AAChE,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAK3F"}
1
+ {"version":3,"file":"shares.d.ts","sourceRoot":"","sources":["../../src/core/shares.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { DbDriver } from "./driver.ts";
2
+ import { type SiteChannelRow } from "./site-channel-store.ts";
3
+ export interface RevocationRequest {
4
+ channel: SiteChannelRow;
5
+ /** True when this node owns the secret — the caller must await the
6
+ * reconciler (async, so it cannot run inside this synchronous mutator). */
7
+ needsReconcile: boolean;
8
+ }
9
+ export declare function requestChannelRevocation(db: DbDriver, id: string): RevocationRequest;
10
+ //# sourceMappingURL=site-channel-lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"site-channel-lifecycle.d.ts","sourceRoot":"","sources":["../../src/core/site-channel-lifecycle.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG5C,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,cAAc,CAAC;IACxB;gFAC4E;IAC5E,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,iBAAiB,CA6BpF"}
@@ -0,0 +1,120 @@
1
+ import type { DbDriver } from "./driver.ts";
2
+ import { type SiteRow } from "./sites-core.ts";
3
+ export type SiteChannelAudience = "public" | "link";
4
+ export type SiteChannelHosting = "device" | "edge";
5
+ export type SiteChannelDesiredState = "active" | "revoked";
6
+ export interface SiteChannelRow {
7
+ id: string;
8
+ site_id: string;
9
+ audience: string;
10
+ hosting: string;
11
+ controller_node_id: string;
12
+ target_ref: string;
13
+ canonical_url: string | null;
14
+ policy_json: string | null;
15
+ desired_state: string;
16
+ created_hlc: string;
17
+ __deleted: number;
18
+ }
19
+ export declare const SITE_CHANNEL_COLS: readonly ["id", "site_id", "audience", "hosting", "controller_node_id", "target_ref", "canonical_url", "policy_json", "desired_state", "created_hlc", "__deleted"];
20
+ export type SiteChannelObservationStatus = "provisioning" | "syncing" | "ready" | "rollback_pending" | "cleanup_pending" | "error" | "legacy_unverified" | "revoked";
21
+ export interface SiteChannelObservation {
22
+ channelId: string;
23
+ status: SiteChannelObservationStatus;
24
+ lastVerifiedAt: number | null;
25
+ lastError: string | null;
26
+ }
27
+ export type SiteChannelViewStatus = SiteChannelObservationStatus | "waiting_controller" | "unverified";
28
+ export interface SiteChannelView {
29
+ id: string;
30
+ siteId: string;
31
+ audience: SiteChannelAudience;
32
+ hosting: SiteChannelHosting;
33
+ controllerNodeId: string;
34
+ targetRef: string;
35
+ canonicalUrl: string | null;
36
+ policyJson: string | null;
37
+ desiredState: SiteChannelDesiredState;
38
+ status: SiteChannelViewStatus;
39
+ lastVerifiedAt: number | null;
40
+ lastError: string | null;
41
+ }
42
+ export declare function listSiteChannelRows(db: DbDriver, siteId?: string): SiteChannelRow[];
43
+ export declare function getSiteChannelRow(db: DbDriver, id: string): SiteChannelRow | null;
44
+ export interface PutSiteChannelInput {
45
+ id?: string;
46
+ siteId: string;
47
+ audience: SiteChannelAudience;
48
+ hosting: SiteChannelHosting;
49
+ controllerNodeId?: string;
50
+ targetRef: string;
51
+ canonicalUrl?: string | null;
52
+ policy?: unknown;
53
+ desiredState?: SiteChannelDesiredState;
54
+ }
55
+ /** Upsert one logical channel. Re-publishing to the same audience/host/target
56
+ * reactivates the existing row instead of accumulating duplicates. */
57
+ export declare const putSiteChannel: (db: DbDriver, input: PutSiteChannelInput) => SiteChannelRow;
58
+ export declare const setSiteChannelDesiredState: (db: DbDriver, id: string, desired: SiteChannelDesiredState) => SiteChannelRow;
59
+ export declare function revokePublicSiteChannels(db: DbDriver, siteId: string): number;
60
+ /** Keep the authorization snapshot attached to every live public channel in
61
+ * lockstep with the site's compatibility register. The channel copy is what a
62
+ * v2 target node actually serves, so grant edits must not wait for a republish. */
63
+ export declare function setPublicSiteChannelPolicies(db: DbDriver, siteId: string, policy: unknown): number;
64
+ /** A public device URL contains the mutable site slug. Keep the canonical
65
+ * address accurate after rename without touching stable capability URLs. */
66
+ export declare function updatePublicSiteChannelUrls(db: DbDriver, siteId: string, siteName: string): number;
67
+ export interface ApplySiteUpdateOpts {
68
+ name?: string;
69
+ title?: string;
70
+ visibility?: "public" | "private";
71
+ spa?: boolean;
72
+ }
73
+ /** All-or-nothing site update, shared by HTTP route, CLI and replica worker.
74
+ * Order inside ONE SQL transaction + change group: ① updateSite (all
75
+ * validation — not_found, duplicate rename, bad enum) ② visibility side
76
+ * effects ③ canonical-URL refresh on rename. A validation failure therefore
77
+ * rolls back completely: a replica must never sync out a revocation for an
78
+ * update the user was told did not happen. The legacy register never stores
79
+ * "public" (v2 channels are the authority); callers pass `recordPublic` to
80
+ * mint the explicit channel for their surface. */
81
+ export declare function applySiteUpdate(db: DbDriver, siteId: string, opts: ApplySiteUpdateOpts, hooks?: {
82
+ recordPublic?: (site: SiteRow) => void;
83
+ }): SiteRow;
84
+ /** All-or-nothing site delete: tombstone + channel revocations commit
85
+ * together, and a missing site revokes nothing. Callers run the (async)
86
+ * reconciler AFTER this returns — never inside the transaction. */
87
+ export declare function applySiteDelete(db: DbDriver, siteId: string): boolean;
88
+ /** Site deletion first requests teardown for every channel. Link controllers
89
+ * can then remove capability state asynchronously after the site row is gone. */
90
+ export declare function revokeAllSiteChannels(db: DbDriver, siteId: string): number;
91
+ export declare function putSiteChannelObservation(db: DbDriver, input: {
92
+ channelId: string;
93
+ status: SiteChannelObservationStatus;
94
+ lastVerifiedAt?: number | null;
95
+ lastError?: string | null;
96
+ }): SiteChannelObservation;
97
+ /** Drop a node-local observation (e.g. once a missing share row reappears).
98
+ * Observations are not synced, so this is a plain DELETE with no CRDT echo. */
99
+ export declare function clearSiteChannelObservation(db: DbDriver, channelId: string): void;
100
+ export declare function getSiteChannelObservation(db: DbDriver, channelId: string): SiteChannelObservation | null;
101
+ export declare function listSiteChannelViews(db: DbDriver, siteId?: string): SiteChannelView[];
102
+ export interface SitePublicAccessState {
103
+ /** Serve-path decision: this node may serve the site token-free right now. */
104
+ serving: boolean;
105
+ /** Management decision: public access is configured (on any node). */
106
+ configured: boolean;
107
+ /** Public rows exist but at least one is malformed. Serving fails closed on
108
+ * such rows; management surfaces must show the anomaly instead of a healthy
109
+ * "public". */
110
+ anomaly: "malformed_public_channel" | null;
111
+ }
112
+ /** The single public-access decision for every surface (serve routes, CLI,
113
+ * WebUI, grants). A site migrates from the legacy `visibility` register to v2
114
+ * channel semantics when its FIRST `audience='public'` row appears — even a
115
+ * malformed one, which then fails closed. Link rows, revoked links, and
116
+ * unknown-audience rows never flip the public decision, so creating a private
117
+ * share link cannot un-publish a legacy-public site. */
118
+ export declare function sitePublicAccessState(db: DbDriver, site: Pick<SiteRow, "id" | "visibility">): SitePublicAccessState;
119
+ export declare function publicSiteChannelOnThisNode(db: DbDriver, siteId: string): SiteChannelRow | null;
120
+ //# sourceMappingURL=site-channel-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"site-channel-store.d.ts","sourceRoot":"","sources":["../../src/core/site-channel-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ import type { ShareListItem } from "./sync/share-actions.ts";
2
+ /** One way the site is (or is about to be) reachable. */
3
+ export interface SiteChannel {
4
+ /** Present for v2 synced desired-state channels. */
5
+ id?: string;
6
+ /** anyone = token-free (visibility/public room); link = capability slug. */
7
+ audience: "anyone" | "link";
8
+ /** device = a server node must be online; room = always-on Edge DO. */
9
+ hosting: "device" | "room";
10
+ /** Ready-to-open URL; null when not yet known (e.g. unverified public). */
11
+ url: string | null;
12
+ status: "ready" | "syncing" | "unverified" | "provisioning" | "rollback_pending" | "cleanup_pending" | "waiting_controller" | "error" | "expired";
13
+ /** Human label of where it's served (device label / Edge Room / base URL). */
14
+ source: string;
15
+ slug?: string;
16
+ permission?: "view" | "edit";
17
+ hasPassword?: boolean;
18
+ expiresAt?: number | null;
19
+ desiredState?: "active" | "revoked";
20
+ controllerNodeId?: string;
21
+ }
22
+ /** Precedence-ordered one-word summary for cards/lists. Attention states first
23
+ * (something needs the user), then live states, then quiescent ones. */
24
+ export type SiteState = "rollback_pending" | "cleanup_pending" | "error" | "provisioning" | "room_live" | "device_live" | "device_syncing" | "public_unverified" | "link_only" | "expired_link" | "private";
25
+ export interface SiteChannelInput {
26
+ /** Raw synced register; only exactly "public" counts (core default-deny). */
27
+ visibility: string | null;
28
+ /** Node-local verified publishes of this site (site-publish-recovery). */
29
+ publishStates: {
30
+ targetBase: string;
31
+ url: string;
32
+ status: "ready" | "syncing";
33
+ }[];
34
+ /** Node-local unacknowledged compensating rollbacks for this site. */
35
+ pendingRollbacks: {
36
+ peerUrl: string;
37
+ targetUrl: string;
38
+ lastError: string;
39
+ }[];
40
+ /** Share listing rows already filtered to this site (local ∪ aggregated). */
41
+ shares: ShareListItem[];
42
+ /** Synced v2 desired channels + this node's optional observation. */
43
+ storedChannels?: {
44
+ id: string;
45
+ audience: "public" | "link";
46
+ hosting: "device" | "edge";
47
+ controllerNodeId: string;
48
+ targetRef: string;
49
+ canonicalUrl: string | null;
50
+ policyJson: string | null;
51
+ desiredState: "active" | "revoked";
52
+ status: "provisioning" | "syncing" | "ready" | "rollback_pending" | "cleanup_pending" | "error" | "legacy_unverified" | "revoked" | "waiting_controller" | "unverified";
53
+ lastError?: string | null;
54
+ }[];
55
+ now?: number;
56
+ }
57
+ /** Fold the scattered stores into the ordered channel list: public entries
58
+ * first, then rooms, then plain links. */
59
+ export declare function siteChannels(input: SiteChannelInput): SiteChannel[];
60
+ /** Map one aggregated share row to its channel (pure; used for both local and
61
+ * peer-fetched rows so remote shares merge into the same view). */
62
+ export declare function shareChannel(s: ShareListItem, now?: number): SiteChannel;
63
+ /** The single precedence everyone must agree on (cards, dialog, CLI). */
64
+ export declare function siteState(input: SiteChannelInput): SiteState;
65
+ //# sourceMappingURL=site-channels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"site-channels.d.ts","sourceRoot":"","sources":["../../src/core/site-channels.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D,yDAAyD;AACzD,MAAM,WAAW,WAAW;IAC1B,oDAAoD;IACpD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,4EAA4E;IAC5E,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC5B,uEAAuE;IACvE,OAAO,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC3B,2EAA2E;IAC3E,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,MAAM,EACF,OAAO,GACP,SAAS,GACT,YAAY,GACZ,cAAc,GACd,kBAAkB,GAClB,iBAAiB,GACjB,oBAAoB,GACpB,OAAO,GACP,SAAS,CAAC;IACd,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IACpC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;yEACyE;AACzE,MAAM,MAAM,SAAS,GACjB,kBAAkB,GAClB,iBAAiB,GACjB,OAAO,GACP,cAAc,GACd,WAAW,GACX,aAAa,GACb,gBAAgB,GAChB,mBAAmB,GACnB,WAAW,GACX,cAAc,GACd,SAAS,CAAC;AAEd,MAAM,WAAW,gBAAgB;IAC/B,6EAA6E;IAC7E,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,0EAA0E;IAC1E,aAAa,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,GAAG,SAAS,CAAA;KAAE,EAAE,CAAC;IAClF,sEAAsE;IACtE,gBAAgB,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9E,6EAA6E;IAC7E,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,qEAAqE;IACrE,cAAc,CAAC,EAAE;QACf,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAAC;QAC5B,OAAO,EAAE,QAAQ,GAAG,MAAM,CAAC;QAC3B,gBAAgB,EAAE,MAAM,CAAC;QACzB,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,YAAY,EAAE,QAAQ,GAAG,SAAS,CAAC;QACnC,MAAM,EACF,cAAc,GACd,SAAS,GACT,OAAO,GACP,kBAAkB,GAClB,iBAAiB,GACjB,OAAO,GACP,mBAAmB,GACnB,SAAS,GACT,oBAAoB,GACpB,YAAY,CAAC;QACjB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,EAAE,CAAC;IACJ,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;2CAC2C;AAC3C,wBAAgB,YAAY,CAAC,KAAK,EAAE,gBAAgB,GAAG,WAAW,EAAE,CA6GnE;AAED;oEACoE;AACpE,wBAAgB,YAAY,CAAC,CAAC,EAAE,aAAa,EAAE,GAAG,SAAa,GAAG,WAAW,CAsB5E;AAED,yEAAyE;AACzE,wBAAgB,SAAS,CAAC,KAAK,EAAE,gBAAgB,GAAG,SAAS,CAyB5D"}