@noy-db/hub 0.9.0-pre.0 → 0.9.0-pre.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/dist/api-H4B7ZQXP.js +18 -0
- package/dist/as/index.js +10 -10
- package/dist/at/index.js +8 -8
- package/dist/attestation/index.js +18 -18
- package/dist/{backup-7RKNZMQO.js → backup-E3GBMNDA.js} +92 -31
- package/dist/backup-E3GBMNDA.js.map +1 -0
- package/dist/blobs/index.js +12 -12
- package/dist/broker/index.js +13 -13
- package/dist/by/index.js +2 -2
- package/dist/capsule/contract.js +6 -6
- package/dist/capsule/enclave-aes/crypto.d.ts +18 -0
- package/dist/capsule/enclave-aes/index.d.ts +2 -2
- package/dist/capsule/enclave-aes/index.js +14 -8
- package/dist/capsule/enclave-aes/index.js.map +1 -1
- package/dist/capsule/index.d.ts +1 -1
- package/dist/{capsule-57BDK7LG.js → capsule-YQKQZPH7.js} +14 -8
- package/dist/cargo/index.js +35 -35
- package/dist/{chunk-AJFPAHMW.js → chunk-27NCEPKM.js} +3 -3
- package/dist/{chunk-PGC35KXE.js → chunk-2IASHJPS.js} +2 -2
- package/dist/{chunk-NYJBYVBG.js → chunk-2ODEFG6K.js} +4 -4
- package/dist/{chunk-7BMOS4JQ.js → chunk-2QH3I7QC.js} +11 -11
- package/dist/{chunk-VSUQI5Q6.js → chunk-2RH5SEY7.js} +16 -2
- package/dist/chunk-2RH5SEY7.js.map +1 -0
- package/dist/{chunk-IVMFIKNA.js → chunk-2T2TGRWE.js} +6 -6
- package/dist/{chunk-G66YXUTY.js → chunk-35CNOVQE.js} +3 -3
- package/dist/{chunk-OJU67C23.js → chunk-3EXQXOQ2.js} +4 -4
- package/dist/{chunk-BJH77UY2.js → chunk-4KYKRYRT.js} +2 -2
- package/dist/{chunk-RDOEOPOI.js → chunk-4N4OIVK3.js} +5 -5
- package/dist/{chunk-WHOKGHJ3.js → chunk-4WLI37WK.js} +7 -7
- package/dist/{chunk-RNACRT5H.js → chunk-52ZFNK46.js} +2 -2
- package/dist/{chunk-CB4467G3.js → chunk-5A3HOTP5.js} +33 -7
- package/dist/chunk-5A3HOTP5.js.map +1 -0
- package/dist/{chunk-QDHHHI5Q.js → chunk-5SMCBPJ2.js} +5 -5
- package/dist/{chunk-EMAWOGVH.js → chunk-654RE5YC.js} +3 -3
- package/dist/{chunk-FZXRLNJO.js → chunk-6BEBWHNL.js} +3 -3
- package/dist/{chunk-EUD3HQM4.js → chunk-6WEPFEMJ.js} +4 -4
- package/dist/{chunk-YFFMPZPY.js → chunk-6WIFF46H.js} +2 -2
- package/dist/{chunk-ZVSKSUGI.js → chunk-7C7OF26T.js} +5 -5
- package/dist/{chunk-QXJXG7DN.js → chunk-7IV7GVOS.js} +5 -5
- package/dist/{chunk-MQ7YWBQ5.js → chunk-7JRLCP2J.js} +2 -2
- package/dist/{chunk-QSHATAXJ.js → chunk-7ONMS7QI.js} +2 -2
- package/dist/{chunk-JBMMWYOH.js → chunk-7UHLO4ZN.js} +35 -2
- package/dist/chunk-7UHLO4ZN.js.map +1 -0
- package/dist/{chunk-6WS35E6X.js → chunk-A52BK5AY.js} +2 -2
- package/dist/{chunk-GOJQ5DZA.js → chunk-AI35LNWQ.js} +4 -4
- package/dist/chunk-AI35LNWQ.js.map +1 -0
- package/dist/{chunk-XXQ5XIYY.js → chunk-AMSSAOOB.js} +2 -2
- package/dist/{chunk-3V6LZHUO.js → chunk-AYOQZJDB.js} +2 -2
- package/dist/{chunk-DL6BZKP3.js → chunk-B3B6HV5S.js} +2 -2
- package/dist/{chunk-5XONHAFD.js → chunk-B6EWMGFX.js} +2 -2
- package/dist/{chunk-6Y5E4SJU.js → chunk-BMAXBTYH.js} +5 -5
- package/dist/{chunk-LGGIFKJP.js → chunk-BNYDOF2Q.js} +4 -4
- package/dist/{chunk-XXNWU5QZ.js → chunk-BQJ5ORU5.js} +2 -2
- package/dist/{chunk-Z3ZXVB2Y.js → chunk-BSEPI3HD.js} +12 -12
- package/dist/chunk-BSEPI3HD.js.map +1 -0
- package/dist/{chunk-BYHCKX73.js → chunk-CLOSXHR4.js} +3 -3
- package/dist/{chunk-5HNW4ZTA.js → chunk-CSAN22QG.js} +6 -6
- package/dist/{chunk-TZYTMZZY.js → chunk-DL4GCSTS.js} +10 -10
- package/dist/{chunk-EA4VDI5D.js → chunk-DYIR6RC3.js} +4 -4
- package/dist/{chunk-PUBI455O.js → chunk-EPPAEGH5.js} +2 -2
- package/dist/{chunk-2A56DSRI.js → chunk-EQ6C2ZPT.js} +17 -51
- package/dist/chunk-EQ6C2ZPT.js.map +1 -0
- package/dist/{chunk-BNFNQNEP.js → chunk-F7X7MPK4.js} +2 -2
- package/dist/{chunk-SNM7LSYG.js → chunk-FUHUGKDN.js} +4 -4
- package/dist/{chunk-72XBVN4J.js → chunk-FVKQ5QUC.js} +2 -2
- package/dist/chunk-FVKQ5QUC.js.map +1 -0
- package/dist/{chunk-D4V5JJAK.js → chunk-GHMINSER.js} +4 -4
- package/dist/{chunk-APFFJN5T.js → chunk-GLSCL74M.js} +31 -34
- package/dist/chunk-GLSCL74M.js.map +1 -0
- package/dist/{chunk-XKBTJGFP.js → chunk-GU6QXHPZ.js} +3 -3
- package/dist/chunk-GU6QXHPZ.js.map +1 -0
- package/dist/{chunk-QGFEBNHO.js → chunk-GZISWBCJ.js} +4 -4
- package/dist/{chunk-OM3SEDI4.js → chunk-HTRLQ4ZZ.js} +4 -4
- package/dist/{chunk-SWXLZ4HX.js → chunk-ICHHNI5E.js} +8 -8
- package/dist/{chunk-K673OO6B.js → chunk-ID4ZBC3X.js} +4 -4
- package/dist/{chunk-H3YM66EI.js → chunk-IF2M43G4.js} +2 -2
- package/dist/{chunk-QGSNJD3B.js → chunk-IFUYLIEY.js} +2 -2
- package/dist/{chunk-D5A3GCZ4.js → chunk-IPF5ORDI.js} +3 -3
- package/dist/{delegation-ONLM4JFI.js → chunk-JQMEXCUZ.js} +59 -16
- package/dist/chunk-JQMEXCUZ.js.map +1 -0
- package/dist/{chunk-TFWFPZRU.js → chunk-K5JNK63R.js} +11 -11
- package/dist/{chunk-TFWFPZRU.js.map → chunk-K5JNK63R.js.map} +1 -1
- package/dist/{chunk-BVF3SXD7.js → chunk-KBIS64X4.js} +5 -5
- package/dist/chunk-L4UL4IAR.js +55 -0
- package/dist/chunk-L4UL4IAR.js.map +1 -0
- package/dist/{chunk-RIZTZJX6.js → chunk-LJ744WXU.js} +2 -2
- package/dist/{chunk-XITHENBI.js → chunk-LMC5L3MM.js} +7 -7
- package/dist/{chunk-XITHENBI.js.map → chunk-LMC5L3MM.js.map} +1 -1
- package/dist/{chunk-L5A3KR7R.js → chunk-LUQBG637.js} +3 -3
- package/dist/{chunk-6QLCS7ET.js → chunk-LZN6DTFI.js} +2 -2
- package/dist/{chunk-NIXRB5BP.js → chunk-MMWX5OT5.js} +2 -2
- package/dist/{chunk-JA25QG5M.js → chunk-N6EWQ5VB.js} +622 -119
- package/dist/chunk-N6EWQ5VB.js.map +1 -0
- package/dist/{chunk-UOZOWN73.js → chunk-ND6S2DKC.js} +2 -2
- package/dist/{chunk-WFWFXDNS.js → chunk-NHEKQWFI.js} +2 -2
- package/dist/{chunk-2ZFD2UJC.js → chunk-NIUHKKMM.js} +9 -9
- package/dist/{chunk-XA34THLF.js → chunk-NK4LGBGQ.js} +4 -4
- package/dist/{chunk-JSWKVZCR.js → chunk-NUZ7ROUZ.js} +2 -2
- package/dist/{chunk-DHU2AQ6T.js → chunk-O3NGOHXQ.js} +2 -2
- package/dist/{chunk-ZBUAX4EF.js → chunk-O6VOR4NH.js} +2 -2
- package/dist/{chunk-JZ4EDEIP.js → chunk-ODBOHPZU.js} +3 -3
- package/dist/{chunk-5UUJE4FP.js → chunk-OK44MD3C.js} +2 -2
- package/dist/{chunk-ECGXA4LT.js → chunk-OR3OHKU6.js} +2 -2
- package/dist/{chunk-ZWF3EWZA.js → chunk-P6FATPXQ.js} +2 -2
- package/dist/{chunk-PCYPXLEX.js → chunk-PBEHTFUG.js} +6 -6
- package/dist/{chunk-7CT6SX4M.js → chunk-PFHDQY5L.js} +3 -3
- package/dist/{chunk-PTJY5R3O.js → chunk-PIO6NW7O.js} +2 -2
- package/dist/{chunk-TMIJAR2X.js → chunk-PREYKC76.js} +3 -3
- package/dist/{chunk-ZHQJAV6D.js → chunk-PU5ZEV65.js} +2 -2
- package/dist/{chunk-UNSQZSZP.js → chunk-PWAMX5KU.js} +2 -2
- package/dist/{chunk-GETUTTZU.js → chunk-PZN76MMV.js} +4 -4
- package/dist/{chunk-ABU6A4Z5.js → chunk-Q4DBU5RM.js} +2 -2
- package/dist/{chunk-2WJKJZSO.js → chunk-QHG7GQKM.js} +2 -2
- package/dist/{chunk-UAKY6E4N.js → chunk-QNOWNCZS.js} +2 -2
- package/dist/{chunk-JPSL5GIZ.js → chunk-QQLXKDGC.js} +3 -3
- package/dist/{chunk-45VHWE4C.js → chunk-QVUMKG3Z.js} +271 -69
- package/dist/chunk-QVUMKG3Z.js.map +1 -0
- package/dist/{chunk-OK57EATL.js → chunk-R4GLRJ55.js} +2 -2
- package/dist/{chunk-B65CC6Z6.js → chunk-RF5WDM25.js} +2 -2
- package/dist/{chunk-G46JSSVX.js → chunk-RKBCUP5D.js} +7 -7
- package/dist/{chunk-GCUMOKTN.js → chunk-S2ORTQGS.js} +3 -3
- package/dist/{chunk-MRJDLXI3.js → chunk-SVXFCCSX.js} +8 -8
- package/dist/{chunk-BUE7R3B5.js → chunk-SXPLIRQH.js} +2 -2
- package/dist/{chunk-5LMCVBMY.js → chunk-T5EXREMW.js} +5 -5
- package/dist/{chunk-PT2YCQVK.js → chunk-TBVDI3LY.js} +4 -4
- package/dist/{chunk-BMHQHH7Q.js → chunk-TLIFNRU5.js} +2 -2
- package/dist/{chunk-YKGJ44Z7.js → chunk-TU6ZJTYT.js} +3 -3
- package/dist/{chunk-FSQNGPHH.js → chunk-U277K5GR.js} +612 -175
- package/dist/chunk-U277K5GR.js.map +1 -0
- package/dist/{chunk-55P6OBB7.js → chunk-U43RSPPX.js} +2 -2
- package/dist/{chunk-K7RW7FI4.js → chunk-UADW5NOU.js} +5 -5
- package/dist/{chunk-GRS2HIV6.js → chunk-UMV5GBTJ.js} +2 -2
- package/dist/{chunk-4UIP3QNW.js → chunk-UNAMWK2Q.js} +2 -2
- package/dist/{chunk-J4KAPKBJ.js → chunk-UORWAG7K.js} +2 -2
- package/dist/{chunk-V55JASXO.js → chunk-UQRKAM2R.js} +3 -3
- package/dist/{chunk-2ELOCEXE.js → chunk-URGHKZPL.js} +1 -1
- package/dist/chunk-URGHKZPL.js.map +1 -0
- package/dist/{chunk-ZMVSKYTJ.js → chunk-VLRY2UH4.js} +10 -10
- package/dist/{chunk-SPPOUJLN.js → chunk-VZFTON37.js} +2 -2
- package/dist/{chunk-CAD7N6HQ.js → chunk-WIUH6EJ7.js} +10 -10
- package/dist/chunk-WIUH6EJ7.js.map +1 -0
- package/dist/{chunk-YQFBH5ZJ.js → chunk-WQUIJPN4.js} +3 -3
- package/dist/{chunk-4J3MNVVX.js → chunk-WRLWEYHY.js} +7 -1
- package/dist/{chunk-4J3MNVVX.js.map → chunk-WRLWEYHY.js.map} +1 -1
- package/dist/{chunk-NZULV7LK.js → chunk-WVQDY3UM.js} +4 -4
- package/dist/{chunk-3Y5JLSXS.js → chunk-X5RGG3ZW.js} +2 -2
- package/dist/{chunk-PFCTSXHU.js → chunk-X6Z2SIRF.js} +2 -2
- package/dist/{chunk-UHFGK6YA.js → chunk-XQGKS4OW.js} +3 -3
- package/dist/{chunk-L2F3CFCH.js → chunk-Y22JWHCP.js} +4 -4
- package/dist/{chunk-C53ER7AG.js → chunk-YVA6UPRN.js} +2 -2
- package/dist/{chunk-226V6XXJ.js → chunk-Z2KP6PNF.js} +6 -6
- package/dist/{chunk-G5EDYVSE.js → chunk-ZK2WT532.js} +5 -5
- package/dist/{chunk-SFCD6OU7.js → chunk-ZNVBCKMV.js} +2 -2
- package/dist/{chunk-BU2ABHFW.js → chunk-ZOFNZLAA.js} +4 -4
- package/dist/{chunk-X2RP4TO5.js → chunk-ZVJKHGE5.js} +7 -7
- package/dist/classified/index.js +5 -5
- package/dist/{classified-marker-KHDOYIJB.js → classified-marker-7HXDMYHI.js} +3 -3
- package/dist/collection-facade-V63BEBGU.js +41 -0
- package/dist/{computed-XMFTHAXV.js → computed-LZLSYOPG.js} +3 -3
- package/dist/consent/index.js +8 -8
- package/dist/cover/index.js +8 -8
- package/dist/custody/index.js +12 -12
- package/dist/{dead-filter-CXV5NS67.js → dead-filter-PZIJ4F4G.js} +10 -10
- package/dist/debug/index.js +7 -7
- package/dist/delegation-7SSGIETH.js +26 -0
- package/dist/derivations/index.js +22 -22
- package/dist/derive-IMI3BMDS.js +20 -0
- package/dist/directory/index.js +9 -9
- package/dist/{dispatch-2MQFMEGY.js → dispatch-4BUYPWMJ.js} +19 -19
- package/dist/{dispatch-OFDA4WQV.js → dispatch-KOPDDPEQ.js} +5 -5
- package/dist/executor-7SY3M6TZ.js +8 -0
- package/dist/executor-SNHW4ELF.js +35 -0
- package/dist/executor-Z3IKK7G3.js +8 -0
- package/dist/export-accessible-O7DIVFUO.js +22 -0
- package/dist/extract-partition-ELZEJKXM.js +35 -0
- package/dist/{fanout-sidecar-BO67PCPO.js → fanout-sidecar-FYDRL7F3.js} +8 -8
- package/dist/find-KWB2RYWJ.js +10 -0
- package/dist/forget/index.js +8 -8
- package/dist/guards/index.js +4 -4
- package/dist/history/index.js +15 -13
- package/dist/history/index.js.map +1 -1
- package/dist/i18n/index.js +13 -13
- package/dist/index.d.ts +3 -2
- package/dist/index.js +111 -107
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.js +5 -5
- package/dist/introspection/index.js +10 -10
- package/dist/issue-5ZHXK4HJ.js +19 -0
- package/dist/kernel/collection.d.ts +85 -6
- package/dist/kernel/constants.d.ts +7 -0
- package/dist/kernel/errors.d.ts +9 -0
- package/dist/kernel/merge-authority.d.ts +5 -1
- package/dist/kernel/noydb.d.ts +116 -21
- package/dist/kernel/rejection-seal.d.ts +13 -0
- package/dist/kernel/sync-wiring.d.ts +55 -0
- package/dist/kernel/types.d.ts +366 -8
- package/dist/kernel/vault.d.ts +39 -5
- package/dist/{ledger-MSJGWKGW.js → ledger-UFJCB6T5.js} +10 -10
- package/dist/liberate-OEOSNNFH.js +25 -0
- package/dist/{link-set-QXXSPPHL.js → link-set-KMUBFN2L.js} +10 -10
- package/dist/{managed-secret-VSK7ANQI.js → managed-secret-H5WYSJ46.js} +9 -9
- package/dist/materialized-views/index.js +25 -25
- package/dist/{migrate-cek-GEBFYBDC.js → migrate-cek-SPESQAAO.js} +2 -2
- package/dist/money/index.js +5 -5
- package/dist/noydb-QDZ4QIKO.js +100 -0
- package/dist/on/index.js +12 -12
- package/dist/overlay-views/index.js +4 -4
- package/dist/periods/index.js +11 -11
- package/dist/pod/index.js +66 -64
- package/dist/{pod-handle-YE7CT66T.js → pod-handle-GJLCOUGK.js} +8 -8
- package/dist/policy/index.js +9 -9
- package/dist/port/at/index.d.ts +1 -1
- package/dist/port/by/index.d.ts +1 -1
- package/dist/port/on/index.d.ts +1 -1
- package/dist/port/with/admission-authority.d.ts +34 -0
- package/dist/port/with/broker-strategy.d.ts +10 -0
- package/dist/port/with/rejection-courier.d.ts +37 -0
- package/dist/port/with/restore-host.d.ts +33 -0
- package/dist/port/with/service-bus.d.ts +18 -0
- package/dist/port/with/team-strategy.d.ts +2 -2
- package/dist/port/with/write-hooks.d.ts +2 -0
- package/dist/portability/index.js +8 -8
- package/dist/{post-register-QC5TEAW3.js → post-register-2AC2STHC.js} +6 -6
- package/dist/query/all/index.js +15 -15
- package/dist/query/index.js +7 -7
- package/dist/query/live/index.js +7 -7
- package/dist/query/reduce/index.js +10 -10
- package/dist/query/relate/index.js +9 -9
- package/dist/reduce/index.js +8 -8
- package/dist/register-PIORIMMR.js +30 -0
- package/dist/registry-BIMICFUH.js +23 -0
- package/dist/registry-JGWLW53D.js +35 -0
- package/dist/registry-SELCBYEQ.js +8 -0
- package/dist/registry-W327UNYH.js +8 -0
- package/dist/request-withdrawal-K3SXXODI.js +28 -0
- package/dist/restore-to-OOPKLAPU.js +66 -0
- package/dist/restore-to-OOPKLAPU.js.map +1 -0
- package/dist/{reveal-YQTRNQFH.js → reveal-UAPBG743.js} +7 -7
- package/dist/revoke-6AFVXTWF.js +24 -0
- package/dist/satellites/index.js +1 -1
- package/dist/schema-update/index.js +2 -2
- package/dist/sealed-record/index.js +11 -11
- package/dist/search/index.js +4 -4
- package/dist/{seed-JMQTRKRE.js → seed-BVYDPVIW.js} +33 -18
- package/dist/seed-BVYDPVIW.js.map +1 -0
- package/dist/sequence/index.js +8 -8
- package/dist/session/index.js +8 -8
- package/dist/shadow/index.js +2 -2
- package/dist/signer-TW5MAQJP.js +23 -0
- package/dist/snapshots/index.js +9 -9
- package/dist/{stale-SKV33PBU.js → stale-CROGJBIJ.js} +9 -9
- package/dist/storage-SO2L2Z5J.js +21 -0
- package/dist/{store-coordination-provider-Z6HKPOGM.js → store-coordination-provider-2YD2PTTP.js} +9 -9
- package/dist/sync/index.js +16 -16
- package/dist/sync/index.js.map +1 -1
- package/dist/team/index.js +15 -15
- package/dist/tiers/index.js +10 -10
- package/dist/to/index.js +2 -2
- package/dist/transactions/index.js +3 -3
- package/dist/util/index.js +1 -1
- package/dist/vault-head/index.js +7 -7
- package/dist/{verify-KWQIWLGH.js → verify-QWO5UKD6.js} +7 -7
- package/dist/walk-3KEOL7BY.js +20 -0
- package/dist/with-audit/guards/types.d.ts +2 -0
- package/dist/with-audit/tiers/index.d.ts +5 -5
- package/dist/with-commit/history/history.d.ts +16 -0
- package/dist/with-commit/history/restore-to.d.ts +40 -0
- package/dist/with-party/broker/seed.d.ts +4 -0
- package/dist/with-party/custody/strategy.d.ts +2 -2
- package/dist/with-party/team/delegation.d.ts +41 -11
- package/dist/with-party/team/index.d.ts +2 -0
- package/dist/with-party/team/keyring.d.ts +133 -3
- package/dist/with-party/team/managed-secret.d.ts +1 -1
- package/dist/with-party/team/noydb-facade.d.ts +14 -2
- package/dist/with-party/team/roster-tag.d.ts +1 -1
- package/dist/with-pod/backup.d.ts +9 -17
- package/dist/with-sync/active.d.ts +28 -1
- package/dist/with-sync/engine.d.ts +59 -2
- package/dist/with-sync/index.d.ts +1 -0
- package/dist/with-sync/reserved-mirror.d.ts +27 -4
- package/dist/with-sync/strategy.d.ts +2 -0
- package/dist/withdraw-accessible-OZU525ND.js +25 -0
- package/dist/withdraw-accessible-OZU525ND.js.map +1 -0
- package/package.json +3 -3
- package/dist/api-MSJXVYQG.js +0 -18
- package/dist/backup-7RKNZMQO.js.map +0 -1
- package/dist/chunk-2A56DSRI.js.map +0 -1
- package/dist/chunk-2ELOCEXE.js.map +0 -1
- package/dist/chunk-45VHWE4C.js.map +0 -1
- package/dist/chunk-72XBVN4J.js.map +0 -1
- package/dist/chunk-APFFJN5T.js.map +0 -1
- package/dist/chunk-CAD7N6HQ.js.map +0 -1
- package/dist/chunk-CB4467G3.js.map +0 -1
- package/dist/chunk-FSQNGPHH.js.map +0 -1
- package/dist/chunk-GOJQ5DZA.js.map +0 -1
- package/dist/chunk-JA25QG5M.js.map +0 -1
- package/dist/chunk-JBMMWYOH.js.map +0 -1
- package/dist/chunk-VSUQI5Q6.js.map +0 -1
- package/dist/chunk-XKBTJGFP.js.map +0 -1
- package/dist/chunk-Z3ZXVB2Y.js.map +0 -1
- package/dist/collection-facade-LZIF7GWD.js +0 -41
- package/dist/delegation-ONLM4JFI.js.map +0 -1
- package/dist/derive-3YSIS36W.js +0 -20
- package/dist/executor-E6YDJ7YA.js +0 -8
- package/dist/executor-NJX3YQQ4.js +0 -35
- package/dist/executor-WWM3DD4P.js +0 -8
- package/dist/export-accessible-RLHU5G4W.js +0 -22
- package/dist/extract-partition-QII2EDCS.js +0 -35
- package/dist/find-6M7GYDUZ.js +0 -10
- package/dist/issue-LPEEEMPP.js +0 -19
- package/dist/liberate-LEKDYKTC.js +0 -25
- package/dist/noydb-F2AUF6KL.js +0 -98
- package/dist/register-SN72QALV.js +0 -30
- package/dist/registry-253FF7C6.js +0 -35
- package/dist/registry-3AZWQTDT.js +0 -23
- package/dist/registry-7UEBTHWG.js +0 -8
- package/dist/registry-IVJ67NFQ.js +0 -8
- package/dist/request-withdrawal-UKVJZKX2.js +0 -28
- package/dist/revoke-G2KXJQA5.js +0 -24
- package/dist/seed-JMQTRKRE.js.map +0 -1
- package/dist/signer-IUIYR3CX.js +0 -23
- package/dist/storage-NPBYP2SM.js +0 -21
- package/dist/walk-F4OLXI5H.js +0 -20
- package/dist/withdraw-accessible-ZKPLZ66E.js +0 -25
- /package/dist/{api-MSJXVYQG.js.map → api-H4B7ZQXP.js.map} +0 -0
- /package/dist/{capsule-57BDK7LG.js.map → capsule-YQKQZPH7.js.map} +0 -0
- /package/dist/{chunk-AJFPAHMW.js.map → chunk-27NCEPKM.js.map} +0 -0
- /package/dist/{chunk-PGC35KXE.js.map → chunk-2IASHJPS.js.map} +0 -0
- /package/dist/{chunk-NYJBYVBG.js.map → chunk-2ODEFG6K.js.map} +0 -0
- /package/dist/{chunk-7BMOS4JQ.js.map → chunk-2QH3I7QC.js.map} +0 -0
- /package/dist/{chunk-IVMFIKNA.js.map → chunk-2T2TGRWE.js.map} +0 -0
- /package/dist/{chunk-G66YXUTY.js.map → chunk-35CNOVQE.js.map} +0 -0
- /package/dist/{chunk-OJU67C23.js.map → chunk-3EXQXOQ2.js.map} +0 -0
- /package/dist/{chunk-BJH77UY2.js.map → chunk-4KYKRYRT.js.map} +0 -0
- /package/dist/{chunk-RDOEOPOI.js.map → chunk-4N4OIVK3.js.map} +0 -0
- /package/dist/{chunk-WHOKGHJ3.js.map → chunk-4WLI37WK.js.map} +0 -0
- /package/dist/{chunk-RNACRT5H.js.map → chunk-52ZFNK46.js.map} +0 -0
- /package/dist/{chunk-QDHHHI5Q.js.map → chunk-5SMCBPJ2.js.map} +0 -0
- /package/dist/{chunk-EMAWOGVH.js.map → chunk-654RE5YC.js.map} +0 -0
- /package/dist/{chunk-FZXRLNJO.js.map → chunk-6BEBWHNL.js.map} +0 -0
- /package/dist/{chunk-EUD3HQM4.js.map → chunk-6WEPFEMJ.js.map} +0 -0
- /package/dist/{chunk-YFFMPZPY.js.map → chunk-6WIFF46H.js.map} +0 -0
- /package/dist/{chunk-ZVSKSUGI.js.map → chunk-7C7OF26T.js.map} +0 -0
- /package/dist/{chunk-QXJXG7DN.js.map → chunk-7IV7GVOS.js.map} +0 -0
- /package/dist/{chunk-MQ7YWBQ5.js.map → chunk-7JRLCP2J.js.map} +0 -0
- /package/dist/{chunk-QSHATAXJ.js.map → chunk-7ONMS7QI.js.map} +0 -0
- /package/dist/{chunk-6WS35E6X.js.map → chunk-A52BK5AY.js.map} +0 -0
- /package/dist/{chunk-XXQ5XIYY.js.map → chunk-AMSSAOOB.js.map} +0 -0
- /package/dist/{chunk-3V6LZHUO.js.map → chunk-AYOQZJDB.js.map} +0 -0
- /package/dist/{chunk-DL6BZKP3.js.map → chunk-B3B6HV5S.js.map} +0 -0
- /package/dist/{chunk-5XONHAFD.js.map → chunk-B6EWMGFX.js.map} +0 -0
- /package/dist/{chunk-6Y5E4SJU.js.map → chunk-BMAXBTYH.js.map} +0 -0
- /package/dist/{chunk-LGGIFKJP.js.map → chunk-BNYDOF2Q.js.map} +0 -0
- /package/dist/{chunk-XXNWU5QZ.js.map → chunk-BQJ5ORU5.js.map} +0 -0
- /package/dist/{chunk-BYHCKX73.js.map → chunk-CLOSXHR4.js.map} +0 -0
- /package/dist/{chunk-5HNW4ZTA.js.map → chunk-CSAN22QG.js.map} +0 -0
- /package/dist/{chunk-TZYTMZZY.js.map → chunk-DL4GCSTS.js.map} +0 -0
- /package/dist/{chunk-EA4VDI5D.js.map → chunk-DYIR6RC3.js.map} +0 -0
- /package/dist/{chunk-PUBI455O.js.map → chunk-EPPAEGH5.js.map} +0 -0
- /package/dist/{chunk-BNFNQNEP.js.map → chunk-F7X7MPK4.js.map} +0 -0
- /package/dist/{chunk-SNM7LSYG.js.map → chunk-FUHUGKDN.js.map} +0 -0
- /package/dist/{chunk-D4V5JJAK.js.map → chunk-GHMINSER.js.map} +0 -0
- /package/dist/{chunk-QGFEBNHO.js.map → chunk-GZISWBCJ.js.map} +0 -0
- /package/dist/{chunk-OM3SEDI4.js.map → chunk-HTRLQ4ZZ.js.map} +0 -0
- /package/dist/{chunk-SWXLZ4HX.js.map → chunk-ICHHNI5E.js.map} +0 -0
- /package/dist/{chunk-K673OO6B.js.map → chunk-ID4ZBC3X.js.map} +0 -0
- /package/dist/{chunk-H3YM66EI.js.map → chunk-IF2M43G4.js.map} +0 -0
- /package/dist/{chunk-QGSNJD3B.js.map → chunk-IFUYLIEY.js.map} +0 -0
- /package/dist/{chunk-D5A3GCZ4.js.map → chunk-IPF5ORDI.js.map} +0 -0
- /package/dist/{chunk-BVF3SXD7.js.map → chunk-KBIS64X4.js.map} +0 -0
- /package/dist/{chunk-RIZTZJX6.js.map → chunk-LJ744WXU.js.map} +0 -0
- /package/dist/{chunk-L5A3KR7R.js.map → chunk-LUQBG637.js.map} +0 -0
- /package/dist/{chunk-6QLCS7ET.js.map → chunk-LZN6DTFI.js.map} +0 -0
- /package/dist/{chunk-NIXRB5BP.js.map → chunk-MMWX5OT5.js.map} +0 -0
- /package/dist/{chunk-UOZOWN73.js.map → chunk-ND6S2DKC.js.map} +0 -0
- /package/dist/{chunk-WFWFXDNS.js.map → chunk-NHEKQWFI.js.map} +0 -0
- /package/dist/{chunk-2ZFD2UJC.js.map → chunk-NIUHKKMM.js.map} +0 -0
- /package/dist/{chunk-XA34THLF.js.map → chunk-NK4LGBGQ.js.map} +0 -0
- /package/dist/{chunk-JSWKVZCR.js.map → chunk-NUZ7ROUZ.js.map} +0 -0
- /package/dist/{chunk-DHU2AQ6T.js.map → chunk-O3NGOHXQ.js.map} +0 -0
- /package/dist/{chunk-ZBUAX4EF.js.map → chunk-O6VOR4NH.js.map} +0 -0
- /package/dist/{chunk-JZ4EDEIP.js.map → chunk-ODBOHPZU.js.map} +0 -0
- /package/dist/{chunk-5UUJE4FP.js.map → chunk-OK44MD3C.js.map} +0 -0
- /package/dist/{chunk-ECGXA4LT.js.map → chunk-OR3OHKU6.js.map} +0 -0
- /package/dist/{chunk-ZWF3EWZA.js.map → chunk-P6FATPXQ.js.map} +0 -0
- /package/dist/{chunk-PCYPXLEX.js.map → chunk-PBEHTFUG.js.map} +0 -0
- /package/dist/{chunk-7CT6SX4M.js.map → chunk-PFHDQY5L.js.map} +0 -0
- /package/dist/{chunk-PTJY5R3O.js.map → chunk-PIO6NW7O.js.map} +0 -0
- /package/dist/{chunk-TMIJAR2X.js.map → chunk-PREYKC76.js.map} +0 -0
- /package/dist/{chunk-ZHQJAV6D.js.map → chunk-PU5ZEV65.js.map} +0 -0
- /package/dist/{chunk-UNSQZSZP.js.map → chunk-PWAMX5KU.js.map} +0 -0
- /package/dist/{chunk-GETUTTZU.js.map → chunk-PZN76MMV.js.map} +0 -0
- /package/dist/{chunk-ABU6A4Z5.js.map → chunk-Q4DBU5RM.js.map} +0 -0
- /package/dist/{chunk-2WJKJZSO.js.map → chunk-QHG7GQKM.js.map} +0 -0
- /package/dist/{chunk-UAKY6E4N.js.map → chunk-QNOWNCZS.js.map} +0 -0
- /package/dist/{chunk-JPSL5GIZ.js.map → chunk-QQLXKDGC.js.map} +0 -0
- /package/dist/{chunk-OK57EATL.js.map → chunk-R4GLRJ55.js.map} +0 -0
- /package/dist/{chunk-B65CC6Z6.js.map → chunk-RF5WDM25.js.map} +0 -0
- /package/dist/{chunk-G46JSSVX.js.map → chunk-RKBCUP5D.js.map} +0 -0
- /package/dist/{chunk-GCUMOKTN.js.map → chunk-S2ORTQGS.js.map} +0 -0
- /package/dist/{chunk-MRJDLXI3.js.map → chunk-SVXFCCSX.js.map} +0 -0
- /package/dist/{chunk-BUE7R3B5.js.map → chunk-SXPLIRQH.js.map} +0 -0
- /package/dist/{chunk-5LMCVBMY.js.map → chunk-T5EXREMW.js.map} +0 -0
- /package/dist/{chunk-PT2YCQVK.js.map → chunk-TBVDI3LY.js.map} +0 -0
- /package/dist/{chunk-BMHQHH7Q.js.map → chunk-TLIFNRU5.js.map} +0 -0
- /package/dist/{chunk-YKGJ44Z7.js.map → chunk-TU6ZJTYT.js.map} +0 -0
- /package/dist/{chunk-55P6OBB7.js.map → chunk-U43RSPPX.js.map} +0 -0
- /package/dist/{chunk-K7RW7FI4.js.map → chunk-UADW5NOU.js.map} +0 -0
- /package/dist/{chunk-GRS2HIV6.js.map → chunk-UMV5GBTJ.js.map} +0 -0
- /package/dist/{chunk-4UIP3QNW.js.map → chunk-UNAMWK2Q.js.map} +0 -0
- /package/dist/{chunk-J4KAPKBJ.js.map → chunk-UORWAG7K.js.map} +0 -0
- /package/dist/{chunk-V55JASXO.js.map → chunk-UQRKAM2R.js.map} +0 -0
- /package/dist/{chunk-ZMVSKYTJ.js.map → chunk-VLRY2UH4.js.map} +0 -0
- /package/dist/{chunk-SPPOUJLN.js.map → chunk-VZFTON37.js.map} +0 -0
- /package/dist/{chunk-YQFBH5ZJ.js.map → chunk-WQUIJPN4.js.map} +0 -0
- /package/dist/{chunk-NZULV7LK.js.map → chunk-WVQDY3UM.js.map} +0 -0
- /package/dist/{chunk-3Y5JLSXS.js.map → chunk-X5RGG3ZW.js.map} +0 -0
- /package/dist/{chunk-PFCTSXHU.js.map → chunk-X6Z2SIRF.js.map} +0 -0
- /package/dist/{chunk-UHFGK6YA.js.map → chunk-XQGKS4OW.js.map} +0 -0
- /package/dist/{chunk-L2F3CFCH.js.map → chunk-Y22JWHCP.js.map} +0 -0
- /package/dist/{chunk-C53ER7AG.js.map → chunk-YVA6UPRN.js.map} +0 -0
- /package/dist/{chunk-226V6XXJ.js.map → chunk-Z2KP6PNF.js.map} +0 -0
- /package/dist/{chunk-G5EDYVSE.js.map → chunk-ZK2WT532.js.map} +0 -0
- /package/dist/{chunk-SFCD6OU7.js.map → chunk-ZNVBCKMV.js.map} +0 -0
- /package/dist/{chunk-BU2ABHFW.js.map → chunk-ZOFNZLAA.js.map} +0 -0
- /package/dist/{chunk-X2RP4TO5.js.map → chunk-ZVJKHGE5.js.map} +0 -0
- /package/dist/{classified-marker-KHDOYIJB.js.map → classified-marker-7HXDMYHI.js.map} +0 -0
- /package/dist/{collection-facade-LZIF7GWD.js.map → collection-facade-V63BEBGU.js.map} +0 -0
- /package/dist/{computed-XMFTHAXV.js.map → computed-LZLSYOPG.js.map} +0 -0
- /package/dist/{dead-filter-CXV5NS67.js.map → dead-filter-PZIJ4F4G.js.map} +0 -0
- /package/dist/{derive-3YSIS36W.js.map → delegation-7SSGIETH.js.map} +0 -0
- /package/dist/{executor-E6YDJ7YA.js.map → derive-IMI3BMDS.js.map} +0 -0
- /package/dist/{dispatch-2MQFMEGY.js.map → dispatch-4BUYPWMJ.js.map} +0 -0
- /package/dist/{dispatch-OFDA4WQV.js.map → dispatch-KOPDDPEQ.js.map} +0 -0
- /package/dist/{executor-NJX3YQQ4.js.map → executor-7SY3M6TZ.js.map} +0 -0
- /package/dist/{executor-WWM3DD4P.js.map → executor-SNHW4ELF.js.map} +0 -0
- /package/dist/{export-accessible-RLHU5G4W.js.map → executor-Z3IKK7G3.js.map} +0 -0
- /package/dist/{extract-partition-QII2EDCS.js.map → export-accessible-O7DIVFUO.js.map} +0 -0
- /package/dist/{find-6M7GYDUZ.js.map → extract-partition-ELZEJKXM.js.map} +0 -0
- /package/dist/{fanout-sidecar-BO67PCPO.js.map → fanout-sidecar-FYDRL7F3.js.map} +0 -0
- /package/dist/{issue-LPEEEMPP.js.map → find-KWB2RYWJ.js.map} +0 -0
- /package/dist/{ledger-MSJGWKGW.js.map → issue-5ZHXK4HJ.js.map} +0 -0
- /package/dist/{liberate-LEKDYKTC.js.map → ledger-UFJCB6T5.js.map} +0 -0
- /package/dist/{link-set-QXXSPPHL.js.map → liberate-OEOSNNFH.js.map} +0 -0
- /package/dist/{managed-secret-VSK7ANQI.js.map → link-set-KMUBFN2L.js.map} +0 -0
- /package/dist/{noydb-F2AUF6KL.js.map → managed-secret-H5WYSJ46.js.map} +0 -0
- /package/dist/{migrate-cek-GEBFYBDC.js.map → migrate-cek-SPESQAAO.js.map} +0 -0
- /package/dist/{register-SN72QALV.js.map → noydb-QDZ4QIKO.js.map} +0 -0
- /package/dist/{pod-handle-YE7CT66T.js.map → pod-handle-GJLCOUGK.js.map} +0 -0
- /package/dist/{post-register-QC5TEAW3.js.map → post-register-2AC2STHC.js.map} +0 -0
- /package/dist/{registry-253FF7C6.js.map → register-PIORIMMR.js.map} +0 -0
- /package/dist/{registry-3AZWQTDT.js.map → registry-BIMICFUH.js.map} +0 -0
- /package/dist/{registry-7UEBTHWG.js.map → registry-JGWLW53D.js.map} +0 -0
- /package/dist/{registry-IVJ67NFQ.js.map → registry-SELCBYEQ.js.map} +0 -0
- /package/dist/{request-withdrawal-UKVJZKX2.js.map → registry-W327UNYH.js.map} +0 -0
- /package/dist/{revoke-G2KXJQA5.js.map → request-withdrawal-K3SXXODI.js.map} +0 -0
- /package/dist/{reveal-YQTRNQFH.js.map → reveal-UAPBG743.js.map} +0 -0
- /package/dist/{signer-IUIYR3CX.js.map → revoke-6AFVXTWF.js.map} +0 -0
- /package/dist/{stale-SKV33PBU.js.map → signer-TW5MAQJP.js.map} +0 -0
- /package/dist/{storage-NPBYP2SM.js.map → stale-CROGJBIJ.js.map} +0 -0
- /package/dist/{walk-F4OLXI5H.js.map → storage-SO2L2Z5J.js.map} +0 -0
- /package/dist/{store-coordination-provider-Z6HKPOGM.js.map → store-coordination-provider-2YD2PTTP.js.map} +0 -0
- /package/dist/{verify-KWQIWLGH.js.map → verify-QWO5UKD6.js.map} +0 -0
- /package/dist/{withdraw-accessible-ZKPLZ66E.js.map → walk-3KEOL7BY.js.map} +0 -0
|
@@ -1,34 +1,38 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
REJECTIONS_COLLECTION,
|
|
3
|
+
SyncScheduler,
|
|
4
|
+
rejectionKey
|
|
5
|
+
} from "./chunk-7UHLO4ZN.js";
|
|
4
6
|
import {
|
|
5
7
|
PERIODS_COLLECTION,
|
|
6
8
|
PERIOD_ARCHIVES_COLLECTION,
|
|
7
9
|
PERIOD_FREEZES_COLLECTION,
|
|
8
10
|
PERIOD_TARGET_PURGES_COLLECTION,
|
|
9
11
|
periodExclusiveUpperBound
|
|
10
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-A52BK5AY.js";
|
|
11
13
|
import {
|
|
12
14
|
parseKeyringEnvelope
|
|
13
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-QVUMKG3Z.js";
|
|
14
16
|
import {
|
|
17
|
+
buildDeleteMarker,
|
|
15
18
|
buildRecordEnvelope,
|
|
16
19
|
decrypt,
|
|
17
20
|
derivePresenceKey,
|
|
18
21
|
derivePresenceTagKey,
|
|
19
22
|
encrypt,
|
|
23
|
+
envelopeBodyForHash,
|
|
20
24
|
envelopeBodySize,
|
|
21
25
|
hmacSignHex,
|
|
22
26
|
isDeleteMarker,
|
|
23
27
|
isTombstoneShape
|
|
24
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-WRLWEYHY.js";
|
|
25
29
|
import {
|
|
26
30
|
NOYDB_SYNC_VERSION
|
|
27
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-URGHKZPL.js";
|
|
28
32
|
import {
|
|
29
33
|
ValidationError,
|
|
30
34
|
isConflictError
|
|
31
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-2RH5SEY7.js";
|
|
32
36
|
|
|
33
37
|
// src/with-sync/presence.ts
|
|
34
38
|
var PresenceHandle = class {
|
|
@@ -287,6 +291,10 @@ function keyringEpoch(envelope) {
|
|
|
287
291
|
return void 0;
|
|
288
292
|
}
|
|
289
293
|
}
|
|
294
|
+
function keyringIdentity(envelope) {
|
|
295
|
+
const file = parseKeyringEnvelope(envelope);
|
|
296
|
+
return `${file.roster_epoch ?? ""}:${file.roster_tag.data}:${file.display_name}`;
|
|
297
|
+
}
|
|
290
298
|
function keyringSupersedes(candidate, current) {
|
|
291
299
|
if (!current) return true;
|
|
292
300
|
const c = keyringEpoch(candidate);
|
|
@@ -301,9 +309,26 @@ function versionSupersedes(candidate, current) {
|
|
|
301
309
|
}
|
|
302
310
|
var RESERVED_REPLICATION = [
|
|
303
311
|
{ collection: KEYRING_COLLECTION, supersedes: keyringSupersedes, propagateDeletes: true },
|
|
312
|
+
// core#91 — the broker seeds. `_broker/<brokerId>` is sealed under the
|
|
313
|
+
// admin `_broker` DEK (owner/admin only hold it), `_broker_member/<userId>`
|
|
314
|
+
// under that grantee's own DEK: both are ciphertext to the mover and to every
|
|
315
|
+
// other device, and both are exactly what a fresh device needs to mint its
|
|
316
|
+
// first credential. Revocation is a delete that rides the dirty log.
|
|
317
|
+
// core#97 — SECOND, right behind the roster: on a fresh device every target
|
|
318
|
+
// request before the seed has landed cannot be served by the device's own
|
|
319
|
+
// mint, so the seed travels before anything else a first pull carries.
|
|
320
|
+
{ collection: "_broker_member", supersedes: versionSupersedes, propagateDeletes: true },
|
|
321
|
+
{ collection: "_broker", supersedes: versionSupersedes, propagateDeletes: true },
|
|
304
322
|
{ collection: "_meta", idPrefix: "invite-audit-", supersedes: versionSupersedes, propagateDeletes: false },
|
|
305
323
|
{ collection: "_users", supersedes: versionSupersedes, propagateDeletes: true },
|
|
306
|
-
{ collection: "_delegations", supersedes: versionSupersedes, propagateDeletes: true }
|
|
324
|
+
{ collection: "_delegations", supersedes: versionSupersedes, propagateDeletes: true },
|
|
325
|
+
// core#107 — the arbiter's admission refusals, so the WRITER learns its
|
|
326
|
+
// offline record was rejected instead of the verdict staying on the device
|
|
327
|
+
// that made it. Sealed under the refused record's OWN collection DEK, so
|
|
328
|
+
// this mover still reads nothing (`_v` is on the envelope) and a member
|
|
329
|
+
// without that collection receives ciphertext they quietly skip.
|
|
330
|
+
// `propagateDeletes: true` — clearing a refusal is how it stops being shown.
|
|
331
|
+
{ collection: REJECTIONS_COLLECTION, supersedes: versionSupersedes, propagateDeletes: true }
|
|
307
332
|
];
|
|
308
333
|
function inScope(rule, id) {
|
|
309
334
|
return rule.idPrefix === void 0 || id.startsWith(rule.idPrefix);
|
|
@@ -324,12 +349,25 @@ async function mirrorRule(rule, from, to, vault, fromIds) {
|
|
|
324
349
|
async function pushReserved(local, remote, vault) {
|
|
325
350
|
let copied = 0;
|
|
326
351
|
let keyringsCopied = [];
|
|
352
|
+
const staleKeyrings = [];
|
|
327
353
|
for (const rule of RESERVED_REPLICATION) {
|
|
328
354
|
const ids = await mirrorRule(rule, local, remote, vault);
|
|
329
355
|
copied += ids.length;
|
|
330
|
-
if (rule.collection === KEYRING_COLLECTION)
|
|
356
|
+
if (rule.collection === KEYRING_COLLECTION) {
|
|
357
|
+
keyringsCopied = ids;
|
|
358
|
+
for (const id of await local.list(vault, KEYRING_COLLECTION)) {
|
|
359
|
+
if (ids.includes(id)) continue;
|
|
360
|
+
const [mine, theirs] = await Promise.all([local.get(vault, KEYRING_COLLECTION, id), remote.get(vault, KEYRING_COLLECTION, id)]);
|
|
361
|
+
if (mine && theirs && keyringIdentity(mine) !== keyringIdentity(theirs)) staleKeyrings.push({ id, local: mine, remote: theirs });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
331
364
|
}
|
|
332
|
-
return { copied, deleted: 0, keyringsCopied };
|
|
365
|
+
return { copied, deleted: 0, keyringsCopied, ...staleKeyrings.length > 0 && { staleKeyrings } };
|
|
366
|
+
}
|
|
367
|
+
async function pullKeyringFile(remote, local, vault, userId) {
|
|
368
|
+
const rule = RESERVED_REPLICATION[0];
|
|
369
|
+
const ids = await mirrorRule(rule, remote, local, vault, [userId]);
|
|
370
|
+
return ids.length > 0;
|
|
333
371
|
}
|
|
334
372
|
async function pullReserved(remote, local, vault, protectedIds) {
|
|
335
373
|
const localKeyrings = await local.list(vault, KEYRING_COLLECTION);
|
|
@@ -337,12 +375,14 @@ async function pullReserved(remote, local, vault, protectedIds) {
|
|
|
337
375
|
let copied = 0;
|
|
338
376
|
let deleted = 0;
|
|
339
377
|
let keyringsCopied = [];
|
|
378
|
+
let remoteInitialised = false;
|
|
340
379
|
for (const rule of RESERVED_REPLICATION) {
|
|
341
380
|
const remoteIds = (await remote.list(vault, rule.collection)).filter((id) => inScope(rule, id));
|
|
381
|
+
if (rule.collection === KEYRING_COLLECTION) remoteInitialised = remoteIds.length > 0;
|
|
342
382
|
const ids = await mirrorRule(rule, remote, local, vault, remoteIds);
|
|
343
383
|
copied += ids.length;
|
|
344
384
|
if (rule.collection === KEYRING_COLLECTION) keyringsCopied = ids;
|
|
345
|
-
if (!rule.propagateDeletes ||
|
|
385
|
+
if (!rule.propagateDeletes || !remoteInitialised) continue;
|
|
346
386
|
const remoteSet = new Set(remoteIds);
|
|
347
387
|
const protectedSet = protectedIds.get(rule.collection);
|
|
348
388
|
const localIds = rule.collection === KEYRING_COLLECTION ? localKeyrings : await local.list(vault, rule.collection);
|
|
@@ -406,8 +446,22 @@ function buildPeriodScope(option, records) {
|
|
|
406
446
|
}
|
|
407
447
|
|
|
408
448
|
// src/with-sync/engine.ts
|
|
449
|
+
var REJECTED_COLLECTION = "_sync_rejected";
|
|
450
|
+
function parseParked(env) {
|
|
451
|
+
return JSON.parse(envelopeBodyForHash(env));
|
|
452
|
+
}
|
|
409
453
|
var SyncEngine = class {
|
|
410
454
|
mergeAuthority;
|
|
455
|
+
/** core#74 — the vault's admission gate for incoming records; wired at open like the other vault seams. */
|
|
456
|
+
admission;
|
|
457
|
+
#applied = [];
|
|
458
|
+
#rejected = [];
|
|
459
|
+
/** core#108 — this device's own records, refused by its own gates on the way OUT. */
|
|
460
|
+
#pushRejected = [];
|
|
461
|
+
/** core#107 — the vault's sealing capability for replicated refusals; wired at open like `admission`. */
|
|
462
|
+
courier;
|
|
463
|
+
/** core#107 — this device's refusals replicate. Configuration, not an authority proof — see `withSync`. */
|
|
464
|
+
arbiter;
|
|
411
465
|
local;
|
|
412
466
|
remote;
|
|
413
467
|
strategy;
|
|
@@ -423,6 +477,8 @@ var SyncEngine = class {
|
|
|
423
477
|
/** #1036 — last SUCCESSFUL push/pull. Never advanced by a failed attempt. */
|
|
424
478
|
lastPush = null;
|
|
425
479
|
lastPull = null;
|
|
480
|
+
/** core#72 — the target's restore epoch this device adopted; null before any. */
|
|
481
|
+
epoch = null;
|
|
426
482
|
/**
|
|
427
483
|
* #1036 — the failure that ended the most recent attempt, cleared by the next
|
|
428
484
|
* success. Live state only: deliberately NOT persisted in `_sync/meta`, since a
|
|
@@ -490,9 +546,188 @@ var SyncEngine = class {
|
|
|
490
546
|
setPeriodPullSource(source) {
|
|
491
547
|
this.periodPullSource = source;
|
|
492
548
|
}
|
|
549
|
+
/** core#81 — the collections this keyring names; the walk set for a paged pull. Wired by the vault at open. */
|
|
550
|
+
collectionNames;
|
|
551
|
+
/** Wire the paged-pull walk set (core#81). Same injection pattern as `setCacheInvalidator`. */
|
|
552
|
+
setCollectionNames(fn) {
|
|
553
|
+
this.collectionNames = fn;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* core#81 — one chunk per page of one collection, for `pull({ paged: true })`.
|
|
557
|
+
* `listPage` when the store offers it (200 per page), else `list` + `get` in
|
|
558
|
+
* batches of the same size. Never more than one page in memory.
|
|
559
|
+
*/
|
|
560
|
+
async *pagedChunks(names) {
|
|
561
|
+
const PAGE = 200;
|
|
562
|
+
const remote = this.remote;
|
|
563
|
+
for (const coll of names) {
|
|
564
|
+
if (typeof remote.listPage === "function") {
|
|
565
|
+
let cursor;
|
|
566
|
+
do {
|
|
567
|
+
const page = await remote.listPage(this.vault, coll, cursor, PAGE);
|
|
568
|
+
const records = {};
|
|
569
|
+
for (const { id, envelope } of page.items) records[id] = envelope;
|
|
570
|
+
if (page.items.length > 0) yield [coll, records];
|
|
571
|
+
cursor = page.nextCursor ?? void 0;
|
|
572
|
+
} while (cursor !== void 0);
|
|
573
|
+
} else {
|
|
574
|
+
const ids = await remote.list(this.vault, coll);
|
|
575
|
+
for (let i = 0; i < ids.length; i += PAGE) {
|
|
576
|
+
const records = {};
|
|
577
|
+
for (const id of ids.slice(i, i + PAGE)) {
|
|
578
|
+
const env = await remote.get(this.vault, coll, id);
|
|
579
|
+
if (env) records[id] = env;
|
|
580
|
+
}
|
|
581
|
+
if (Object.keys(records).length > 0) yield [coll, records];
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
493
586
|
/** core#82 — when a pull replaces the caller's OWN keyring file, the vault reloads it in place. */
|
|
494
587
|
rosterReload;
|
|
495
588
|
/** Wire the roster-reload seam (core#82). Same injection pattern as `setCacheInvalidator`. */
|
|
589
|
+
/** core#107 — wire the vault's rejection sealing capability. */
|
|
590
|
+
setRejectionCourier(c) {
|
|
591
|
+
this.courier = c;
|
|
592
|
+
}
|
|
593
|
+
/** core#74 — wire the vault's admission gate. */
|
|
594
|
+
setAdmission(a) {
|
|
595
|
+
this.admission = a;
|
|
596
|
+
}
|
|
597
|
+
/** core#71 — after `vault.load()`: a new base. Drops the dirty log, the watermarks and the adopted epoch. */
|
|
598
|
+
async resetAfterRestore() {
|
|
599
|
+
await this.ensureLoaded();
|
|
600
|
+
this.dirty = [];
|
|
601
|
+
this.lastPush = null;
|
|
602
|
+
this.lastPull = null;
|
|
603
|
+
this.epoch = null;
|
|
604
|
+
await this.persistMeta();
|
|
605
|
+
}
|
|
606
|
+
/** core#72 — the target's epoch record, or null when the target was never replaced. */
|
|
607
|
+
async #remoteEpoch() {
|
|
608
|
+
const env = await this.remote.get(this.vault, "_sync", "epoch");
|
|
609
|
+
return env ? JSON.parse(envelopeBodyForHash(env)) : null;
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* core#72 — make the LOCAL vault authoritative on this target. Every local
|
|
613
|
+
* record goes up unconditionally, re-sealed above the target's version where
|
|
614
|
+
* the target moved on (so every peer's copy is superseded); every id the
|
|
615
|
+
* target holds that the local vault lacks gets a delete marker at
|
|
616
|
+
* `remote._v + 1` (so every peer removes it); the reserved collections are
|
|
617
|
+
* mirrored; then a new epoch is written at `_sync/epoch`. The dirty log is
|
|
618
|
+
* cleared: the target IS the local state now.
|
|
619
|
+
*/
|
|
620
|
+
async replaceRemote() {
|
|
621
|
+
await this.ensureLoaded();
|
|
622
|
+
const actor = this.rosterReload?.userId ?? "sync";
|
|
623
|
+
const [localSnapshot, remoteSnapshot] = await Promise.all([this.local.loadAll(this.vault), this.remote.loadAll(this.vault)]);
|
|
624
|
+
let replaced = 0;
|
|
625
|
+
let tombstoned = 0;
|
|
626
|
+
for (const [collection, records] of Object.entries(localSnapshot)) {
|
|
627
|
+
if (collection === REJECTED_COLLECTION) continue;
|
|
628
|
+
for (const [id, envelope] of Object.entries(records)) {
|
|
629
|
+
const current = remoteSnapshot[collection]?.[id];
|
|
630
|
+
let out = envelope;
|
|
631
|
+
if (current && current._v >= envelope._v) {
|
|
632
|
+
out = this.mergeAuthority ? await this.mergeAuthority.advance(collection, id, envelope, current._v + 1) : { ...envelope, _v: current._v + 1 };
|
|
633
|
+
await this.local.put(this.vault, collection, id, out);
|
|
634
|
+
}
|
|
635
|
+
await this.remote.put(this.vault, collection, id, out);
|
|
636
|
+
replaced++;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
for (const [collection, records] of Object.entries(remoteSnapshot)) {
|
|
640
|
+
if (collection === REJECTED_COLLECTION) continue;
|
|
641
|
+
for (const [id, envelope] of Object.entries(records)) {
|
|
642
|
+
if (localSnapshot[collection]?.[id] !== void 0) continue;
|
|
643
|
+
if (isTombstoneShape(envelope) || isDeleteMarker(envelope)) continue;
|
|
644
|
+
const marker = buildDeleteMarker({ collection, id }, envelope._v + 1, actor);
|
|
645
|
+
await this.remote.put(this.vault, collection, id, marker);
|
|
646
|
+
await this.local.put(this.vault, collection, id, marker);
|
|
647
|
+
tombstoned++;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
const mirrored = await pushReserved(this.local, this.remote, this.vault);
|
|
651
|
+
const previous = await this.#remoteEpoch();
|
|
652
|
+
const epoch = (previous?.epoch ?? 0) + 1;
|
|
653
|
+
const record = { epoch, at: (/* @__PURE__ */ new Date()).toISOString(), by: actor, replaced, tombstoned };
|
|
654
|
+
await this.remote.put(this.vault, "_sync", "epoch", buildRecordEnvelope({ collection: "_sync", id: "epoch", version: epoch }, { iv: "", data: JSON.stringify(record) }));
|
|
655
|
+
this.dirty = [];
|
|
656
|
+
this.epoch = epoch;
|
|
657
|
+
this.lastPush = (/* @__PURE__ */ new Date()).toISOString();
|
|
658
|
+
await this.persistMeta();
|
|
659
|
+
const result = { epoch, replaced, tombstoned, reserved: mirrored.copied };
|
|
660
|
+
this.emitter.emit("sync:replace", { vault: this.vault, ...result });
|
|
661
|
+
return result;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* core#72 — at the start of a pull: has the target been replaced since this
|
|
665
|
+
* device last synced? If so, park every unpushed local edit (same fate as an
|
|
666
|
+
* admission refusal, reason `restore-epoch`), drop the dirty log, adopt the
|
|
667
|
+
* epoch. The pull that follows then adopts the target: the restorer lifted
|
|
668
|
+
* its records above every peer's version and tombstoned what it removed.
|
|
669
|
+
*/
|
|
670
|
+
async #crossEpoch() {
|
|
671
|
+
const remote = await this.#remoteEpoch();
|
|
672
|
+
if (!remote || remote.epoch === this.epoch) return { epoch: remote?.epoch ?? 0, resynced: false, parked: 0 };
|
|
673
|
+
const from = this.epoch;
|
|
674
|
+
let parked = 0;
|
|
675
|
+
for (const entry of this.dirty) {
|
|
676
|
+
if (entry.action === "delete") continue;
|
|
677
|
+
const env = await this.local.get(this.vault, entry.collection, entry.id);
|
|
678
|
+
if (!env) continue;
|
|
679
|
+
const rejection = {
|
|
680
|
+
vault: this.vault,
|
|
681
|
+
collection: entry.collection,
|
|
682
|
+
id: entry.id,
|
|
683
|
+
version: env._v,
|
|
684
|
+
reason: `restore-epoch: the target was replaced (epoch ${remote.epoch}) after this edit; readmit to keep it locally, then put again to push`,
|
|
685
|
+
...env._by !== void 0 ? { by: env._by } : {},
|
|
686
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
687
|
+
};
|
|
688
|
+
const parkedRecord = { rejection, envelope: env };
|
|
689
|
+
await this.local.put(
|
|
690
|
+
this.vault,
|
|
691
|
+
REJECTED_COLLECTION,
|
|
692
|
+
`${entry.collection}::${entry.id}`,
|
|
693
|
+
buildRecordEnvelope({ collection: REJECTED_COLLECTION, id: `${entry.collection}::${entry.id}`, version: 1 }, { iv: "", data: JSON.stringify(parkedRecord) })
|
|
694
|
+
);
|
|
695
|
+
this.#rejected.push(rejection);
|
|
696
|
+
this.emitter.emit("sync:rejected", rejection);
|
|
697
|
+
await this.local.delete(this.vault, entry.collection, entry.id);
|
|
698
|
+
await this.cacheInvalidator?.(entry.collection, entry.id, "delete");
|
|
699
|
+
parked++;
|
|
700
|
+
}
|
|
701
|
+
this.dirty = [];
|
|
702
|
+
this.epoch = remote.epoch;
|
|
703
|
+
this.emitter.emit("sync:epoch", { vault: this.vault, from, to: remote.epoch, parked });
|
|
704
|
+
return { epoch: remote.epoch, resynced: true, parked };
|
|
705
|
+
}
|
|
706
|
+
/** core#74 — the parked refusals on this device, and their two fates. */
|
|
707
|
+
rejected() {
|
|
708
|
+
const key = (collection, id) => `${collection}::${id}`;
|
|
709
|
+
return {
|
|
710
|
+
list: async () => {
|
|
711
|
+
const out = [];
|
|
712
|
+
for (const id of await this.local.list(this.vault, REJECTED_COLLECTION)) {
|
|
713
|
+
const env = await this.local.get(this.vault, REJECTED_COLLECTION, id);
|
|
714
|
+
if (!env) continue;
|
|
715
|
+
out.push(parseParked(env).rejection);
|
|
716
|
+
}
|
|
717
|
+
return out;
|
|
718
|
+
},
|
|
719
|
+
readmit: async (collection, id) => {
|
|
720
|
+
const env = await this.local.get(this.vault, REJECTED_COLLECTION, key(collection, id));
|
|
721
|
+
if (!env) throw new ValidationError(`sync: no rejected record parked for "${collection}/${id}".`);
|
|
722
|
+
await this.local.put(this.vault, collection, id, parseParked(env).envelope);
|
|
723
|
+
await this.local.delete(this.vault, REJECTED_COLLECTION, key(collection, id));
|
|
724
|
+
await this.cacheInvalidator?.(collection, id, "put");
|
|
725
|
+
},
|
|
726
|
+
discard: async (collection, id) => {
|
|
727
|
+
await this.local.delete(this.vault, REJECTED_COLLECTION, key(collection, id));
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
}
|
|
496
731
|
setRosterReload(seam) {
|
|
497
732
|
this.rosterReload = seam;
|
|
498
733
|
}
|
|
@@ -531,6 +766,7 @@ var SyncEngine = class {
|
|
|
531
766
|
this.label = opts.label;
|
|
532
767
|
this.policy = opts.syncPolicy;
|
|
533
768
|
this.mergeAuthority = opts.mergeAuthority;
|
|
769
|
+
this.arbiter = opts.arbiter === true;
|
|
534
770
|
const policy = opts.syncPolicy;
|
|
535
771
|
if (policy && (policy.push.mode !== "manual" || policy.pull.mode !== "manual")) {
|
|
536
772
|
this.scheduler = new SyncScheduler(policy, {
|
|
@@ -623,11 +859,250 @@ var SyncEngine = class {
|
|
|
623
859
|
this.dirty = this.dirty.filter((d) => !(d.collection === collection && d.id === id));
|
|
624
860
|
if (this.dirty.length !== before) await this.persistMeta();
|
|
625
861
|
}
|
|
862
|
+
/**
|
|
863
|
+
* core#74/#108 — park a refused envelope under `_sync_rejected` and announce
|
|
864
|
+
* it. One helper for both fates: a record that ARRIVED and was turned away,
|
|
865
|
+
* and (core#108) this device's own record refused on the way out. The
|
|
866
|
+
* parking key is `collection::id`, so re-refusing the same record overwrites
|
|
867
|
+
* rather than accumulating.
|
|
868
|
+
*/
|
|
869
|
+
async #park(collection, id, envelope, reason, origin) {
|
|
870
|
+
const rejection = {
|
|
871
|
+
vault: this.vault,
|
|
872
|
+
collection,
|
|
873
|
+
id,
|
|
874
|
+
reason,
|
|
875
|
+
version: envelope._v,
|
|
876
|
+
...envelope._by !== void 0 ? { by: envelope._by } : {},
|
|
877
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
878
|
+
...envelope._ts !== void 0 ? { recordAt: envelope._ts } : {},
|
|
879
|
+
origin
|
|
880
|
+
};
|
|
881
|
+
await this.#parkBuilt(collection, id, envelope, rejection);
|
|
882
|
+
if (origin === "sync-apply") await this.#publishRejection(collection, id, rejection);
|
|
883
|
+
return rejection;
|
|
884
|
+
}
|
|
885
|
+
/** Store half of `#park`, also used for a refusal that arrived already made (core#107). */
|
|
886
|
+
async #parkBuilt(collection, id, envelope, rejection) {
|
|
887
|
+
const parked = { rejection, envelope };
|
|
888
|
+
await this.local.put(
|
|
889
|
+
this.vault,
|
|
890
|
+
REJECTED_COLLECTION,
|
|
891
|
+
`${collection}::${id}`,
|
|
892
|
+
buildRecordEnvelope({ collection: REJECTED_COLLECTION, id: `${collection}::${id}`, version: 1 }, { iv: "", data: JSON.stringify(parked) })
|
|
893
|
+
);
|
|
894
|
+
this.emitter.emit("sync:rejected", rejection);
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* core#107 — turn the arbiter's replicated refusals into THIS device's
|
|
898
|
+
* notification. Runs after the reserved phase has brought `_sync_rejections`.
|
|
899
|
+
*
|
|
900
|
+
* Three filters, each load-bearing:
|
|
901
|
+
* - `courier.open` returns `null` for a member who holds no DEK for that
|
|
902
|
+
* collection. They received the ciphertext like everyone else and skip it.
|
|
903
|
+
* - a refusal names a VERSION. If this device's copy has moved on, the
|
|
904
|
+
* refusal is about a record that no longer exists here and is ignored —
|
|
905
|
+
* otherwise a stale verdict would re-fire against an edit that may well
|
|
906
|
+
* pass now.
|
|
907
|
+
* - an existing parking at the same version means this was already
|
|
908
|
+
* reported; the parking record IS the dedupe, so no extra state is kept
|
|
909
|
+
* and it survives a restart.
|
|
910
|
+
*
|
|
911
|
+
* ⛔ REPORT ONLY (ruled): the local record is never deleted, tombstoned or
|
|
912
|
+
* hidden. The refusal is information; what to do about it is the writer's.
|
|
913
|
+
*/
|
|
914
|
+
async #scanReplicatedRejections() {
|
|
915
|
+
if (!this.courier) return 0;
|
|
916
|
+
let keys;
|
|
917
|
+
try {
|
|
918
|
+
keys = await this.local.list(this.vault, REJECTIONS_COLLECTION);
|
|
919
|
+
} catch {
|
|
920
|
+
return 0;
|
|
921
|
+
}
|
|
922
|
+
let seen = 0;
|
|
923
|
+
for (const parkKey of keys) {
|
|
924
|
+
const sep = parkKey.indexOf("::");
|
|
925
|
+
if (sep < 0) continue;
|
|
926
|
+
const collection = parkKey.slice(0, sep), id = parkKey.slice(sep + 2);
|
|
927
|
+
const env = await this.local.get(this.vault, REJECTIONS_COLLECTION, parkKey);
|
|
928
|
+
if (!env) continue;
|
|
929
|
+
const rejection = await this.courier.open(collection, id, env);
|
|
930
|
+
if (!rejection) continue;
|
|
931
|
+
const mine = await this.local.get(this.vault, collection, id);
|
|
932
|
+
if (!mine || mine._v !== rejection.version) continue;
|
|
933
|
+
const existing = await this.local.get(this.vault, REJECTED_COLLECTION, parkKey);
|
|
934
|
+
if (existing && parseParked(existing).rejection.version === rejection.version) continue;
|
|
935
|
+
const replicated = { ...rejection, origin: "arbiter" };
|
|
936
|
+
await this.#parkBuilt(collection, id, mine, replicated);
|
|
937
|
+
this.#rejected.push(replicated);
|
|
938
|
+
seen++;
|
|
939
|
+
}
|
|
940
|
+
return seen;
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* core#107 — the ARBITER's refusals replicate, so the writer finds out.
|
|
944
|
+
*
|
|
945
|
+
* Only `sync-apply` refusals: a `push-recheck` refusal (core#108) never left
|
|
946
|
+
* this device, so there is no other device to tell, and a `restore-epoch`
|
|
947
|
+
* parking is this device adopting a new base rather than a judgement about
|
|
948
|
+
* the record.
|
|
949
|
+
*
|
|
950
|
+
* ⛔ Best-effort by construction: a failure to publish must not fail the
|
|
951
|
+
* pull that produced it. The local parking, the `PullResult.rejected` row
|
|
952
|
+
* and the event have already happened and are this device's real answer;
|
|
953
|
+
* replication is the courtesy on top. So a courier that cannot seal (no DEK
|
|
954
|
+
* for that collection) or a store that refuses the write leaves the refusal
|
|
955
|
+
* exactly as it was before core#107.
|
|
956
|
+
*/
|
|
957
|
+
async #publishRejection(collection, id, rejection) {
|
|
958
|
+
if (!this.arbiter || !this.courier) return;
|
|
959
|
+
try {
|
|
960
|
+
const key = rejectionKey(collection, id);
|
|
961
|
+
const current = await this.local.get(this.vault, REJECTIONS_COLLECTION, key);
|
|
962
|
+
const envelope = await this.courier.seal(collection, id, rejection, (current?._v ?? 0) + 1);
|
|
963
|
+
if (envelope) await this.local.put(this.vault, REJECTIONS_COLLECTION, key, envelope);
|
|
964
|
+
} catch {
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* core#108 — re-run this device's OWN gates against a dirty record before it
|
|
969
|
+
* is pushed. A record written offline may no longer pass the rules this
|
|
970
|
+
* device now holds (a period closed while it was away); today it pushes and
|
|
971
|
+
* every OTHER device refuses it at admission, so the writer is the last to
|
|
972
|
+
* know. Judging it here means the writer's own device catches it the moment
|
|
973
|
+
* it reconnects, before anyone else sees the record.
|
|
974
|
+
*
|
|
975
|
+
* Returns `true` when the entry was WITHHELD. The entry stays dirty and the
|
|
976
|
+
* local copy is untouched, so nothing is lost and a later push carries it
|
|
977
|
+
* unchanged once the rule passes again — which is why a refusal here needs
|
|
978
|
+
* no `readmit()` and no dirty-log surgery.
|
|
979
|
+
*
|
|
980
|
+
* ⛔ `rekey` is never re-checked: core#100 rewrites the SAME content under a
|
|
981
|
+
* new key, so judging it would refuse a record for its content having been
|
|
982
|
+
* legal at write time — the same wrong answer core#74 fixed with
|
|
983
|
+
* `admit: false` for a device's own re-versioned records.
|
|
984
|
+
*/
|
|
985
|
+
async #withheldByRecheck(entry, envelope) {
|
|
986
|
+
if (!this.admission || entry.action === "rekey") return false;
|
|
987
|
+
if (isTombstoneShape(envelope) || isDeleteMarker(envelope)) return false;
|
|
988
|
+
const verdict = await this.admission.admit(entry.collection, entry.id, envelope, "push-recheck");
|
|
989
|
+
if (verdict.admitted) return false;
|
|
990
|
+
this.#pushRejected.push(await this.#park(entry.collection, entry.id, envelope, verdict.reason, "push-recheck"));
|
|
991
|
+
return true;
|
|
992
|
+
}
|
|
993
|
+
/** One dirty entry of `push()`: the CAS put, the tombstone assertion, the delete, and every conflict branch. Extracted so `push({ concurrency })` can run entries in a bounded pool (core#93). */
|
|
994
|
+
async #pushOne(i, entry, acc) {
|
|
995
|
+
try {
|
|
996
|
+
if (entry.action === "delete") {
|
|
997
|
+
await this.remote.delete(this.vault, entry.collection, entry.id);
|
|
998
|
+
acc.completed.push(i);
|
|
999
|
+
acc.pushed++;
|
|
1000
|
+
} else {
|
|
1001
|
+
const envelope = await this.local.get(this.vault, entry.collection, entry.id);
|
|
1002
|
+
if (!envelope) {
|
|
1003
|
+
acc.completed.push(i);
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
if (isTombstoneShape(envelope)) {
|
|
1007
|
+
await this.remote.put(this.vault, entry.collection, entry.id, envelope);
|
|
1008
|
+
acc.completed.push(i);
|
|
1009
|
+
acc.pushed++;
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
if (await this.#withheldByRecheck(entry, envelope)) return;
|
|
1013
|
+
try {
|
|
1014
|
+
await this.remote.put(
|
|
1015
|
+
this.vault,
|
|
1016
|
+
entry.collection,
|
|
1017
|
+
entry.id,
|
|
1018
|
+
envelope,
|
|
1019
|
+
// core#100 — a `rekey` re-encrypted the record in place: same `_v`,
|
|
1020
|
+
// new bytes; the target still at that version takes it.
|
|
1021
|
+
entry.action === "rekey" ? entry.version : entry.version - 1
|
|
1022
|
+
);
|
|
1023
|
+
acc.completed.push(i);
|
|
1024
|
+
acc.pushed++;
|
|
1025
|
+
acc.bytes += envelopeBodySize(envelope);
|
|
1026
|
+
} catch (err) {
|
|
1027
|
+
if (isConflictError(err)) {
|
|
1028
|
+
const remoteEnvelope = await this.remote.get(this.vault, entry.collection, entry.id);
|
|
1029
|
+
if (remoteEnvelope) {
|
|
1030
|
+
if (remoteEnvelope._v === envelope._v && envelopeBodyForHash(remoteEnvelope) === envelopeBodyForHash(envelope)) {
|
|
1031
|
+
acc.completed.push(i);
|
|
1032
|
+
} else if (isTombstoneShape(remoteEnvelope)) {
|
|
1033
|
+
await this.applyRemote(entry.collection, entry.id, remoteEnvelope);
|
|
1034
|
+
acc.erasures.push(this.reportErasure(entry.collection, entry.id, remoteEnvelope, envelope, "push"));
|
|
1035
|
+
acc.completed.push(i);
|
|
1036
|
+
} else if (remoteEnvelope._v === envelope._v && isDeleteMarker(remoteEnvelope) !== isDeleteMarker(envelope) && !this.conflictResolvers.get(entry.collection)) {
|
|
1037
|
+
if (isDeleteMarker(remoteEnvelope)) {
|
|
1038
|
+
await this.applyRemote(entry.collection, entry.id, remoteEnvelope);
|
|
1039
|
+
acc.completed.push(i);
|
|
1040
|
+
} else {
|
|
1041
|
+
await this.remote.put(this.vault, entry.collection, entry.id, envelope);
|
|
1042
|
+
acc.completed.push(i);
|
|
1043
|
+
acc.pushed++;
|
|
1044
|
+
}
|
|
1045
|
+
} else {
|
|
1046
|
+
const { handled, conflict } = await this.handleConflict(
|
|
1047
|
+
entry.collection,
|
|
1048
|
+
entry.id,
|
|
1049
|
+
envelope,
|
|
1050
|
+
remoteEnvelope,
|
|
1051
|
+
"push"
|
|
1052
|
+
);
|
|
1053
|
+
acc.conflicts.push(conflict);
|
|
1054
|
+
if (handled === "local") {
|
|
1055
|
+
const winner = await this.advancePastRemote(conflict.local, entry.collection, entry.id, remoteEnvelope);
|
|
1056
|
+
await this.remote.put(this.vault, entry.collection, entry.id, winner);
|
|
1057
|
+
if (winner !== conflict.local) await this.applyRemote(entry.collection, entry.id, winner, { admit: false });
|
|
1058
|
+
acc.completed.push(i);
|
|
1059
|
+
acc.pushed++;
|
|
1060
|
+
} else if (handled === "remote") {
|
|
1061
|
+
await this.applyRemote(entry.collection, entry.id, conflict.remote);
|
|
1062
|
+
acc.completed.push(i);
|
|
1063
|
+
} else if (handled === "merged" && conflict.local !== envelope) {
|
|
1064
|
+
const merged = conflict.local;
|
|
1065
|
+
await this.remote.put(this.vault, entry.collection, entry.id, merged);
|
|
1066
|
+
await this.applyRemote(entry.collection, entry.id, merged);
|
|
1067
|
+
acc.completed.push(i);
|
|
1068
|
+
acc.pushed++;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
} else {
|
|
1073
|
+
throw err;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
} catch (err) {
|
|
1078
|
+
acc.errors.push(err instanceof Error ? err : new Error(String(err)));
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* core#92 — mark every user-collection record in the local store dirty at its
|
|
1083
|
+
* current version, so the next push sends it (CAS against `_v - 1`, so a
|
|
1084
|
+
* remote that already holds a newer copy resolves through the conflict path
|
|
1085
|
+
* instead of being overwritten). Records already dirty keep their entry.
|
|
1086
|
+
*/
|
|
1087
|
+
async markAllDirty() {
|
|
1088
|
+
await this.ensureLoaded();
|
|
1089
|
+
const snapshot = await this.local.loadAll(this.vault);
|
|
1090
|
+
for (const [collection, records] of Object.entries(snapshot)) {
|
|
1091
|
+
if (collection === REJECTED_COLLECTION) continue;
|
|
1092
|
+
for (const [id, envelope] of Object.entries(records)) {
|
|
1093
|
+
if (this.dirty.some((d) => d.collection === collection && d.id === id)) continue;
|
|
1094
|
+
this.dirty.push({ vault: this.vault, collection, id, action: "put", version: envelope._v, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
await this.persistMeta();
|
|
1098
|
+
}
|
|
626
1099
|
/** Push dirty records to remote adapter. Accepts optional `PushOptions` for partial sync. */
|
|
627
1100
|
async push(options) {
|
|
628
1101
|
await this.ensureLoaded();
|
|
1102
|
+
if (options?.full) await this.markAllDirty();
|
|
629
1103
|
this.graphBatchController?.begin();
|
|
630
|
-
let
|
|
1104
|
+
let reserved = 0;
|
|
1105
|
+
this.#pushRejected = [];
|
|
631
1106
|
const conflicts = [];
|
|
632
1107
|
const erasures = [];
|
|
633
1108
|
const errors = [];
|
|
@@ -635,101 +1110,39 @@ var SyncEngine = class {
|
|
|
635
1110
|
const expanded = options?.collections ? this.pairExpander?.(options.collections) ?? options.collections : null;
|
|
636
1111
|
const filter = expanded ? /* @__PURE__ */ new Set([...expanded, ...this.reservedDictExpander?.(expanded) ?? []]) : null;
|
|
637
1112
|
const pushTotal = filter ? this.dirty.filter((d) => filter.has(d.collection)).length : this.dirty.length;
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
await this.remote.delete(this.vault, entry.collection, entry.id);
|
|
648
|
-
completed.push(i);
|
|
649
|
-
pushed++;
|
|
650
|
-
} else {
|
|
651
|
-
const envelope = await this.local.get(this.vault, entry.collection, entry.id);
|
|
652
|
-
if (!envelope) {
|
|
653
|
-
completed.push(i);
|
|
654
|
-
continue;
|
|
655
|
-
}
|
|
656
|
-
if (isTombstoneShape(envelope)) {
|
|
657
|
-
await this.remote.put(this.vault, entry.collection, entry.id, envelope);
|
|
658
|
-
completed.push(i);
|
|
659
|
-
pushed++;
|
|
660
|
-
continue;
|
|
661
|
-
}
|
|
662
|
-
try {
|
|
663
|
-
await this.remote.put(
|
|
664
|
-
this.vault,
|
|
665
|
-
entry.collection,
|
|
666
|
-
entry.id,
|
|
667
|
-
envelope,
|
|
668
|
-
entry.version - 1
|
|
669
|
-
);
|
|
670
|
-
completed.push(i);
|
|
671
|
-
pushed++;
|
|
672
|
-
pushBytes += envelopeBodySize(envelope);
|
|
673
|
-
} catch (err) {
|
|
674
|
-
if (isConflictError(err)) {
|
|
675
|
-
const remoteEnvelope = await this.remote.get(this.vault, entry.collection, entry.id);
|
|
676
|
-
if (remoteEnvelope) {
|
|
677
|
-
if (isTombstoneShape(remoteEnvelope)) {
|
|
678
|
-
await this.applyRemote(entry.collection, entry.id, remoteEnvelope);
|
|
679
|
-
erasures.push(this.reportErasure(entry.collection, entry.id, remoteEnvelope, envelope, "push"));
|
|
680
|
-
completed.push(i);
|
|
681
|
-
} else if (remoteEnvelope._v === envelope._v && isDeleteMarker(remoteEnvelope) !== isDeleteMarker(envelope) && !this.conflictResolvers.get(entry.collection)) {
|
|
682
|
-
if (isDeleteMarker(remoteEnvelope)) {
|
|
683
|
-
await this.applyRemote(entry.collection, entry.id, remoteEnvelope);
|
|
684
|
-
completed.push(i);
|
|
685
|
-
} else {
|
|
686
|
-
await this.remote.put(this.vault, entry.collection, entry.id, envelope);
|
|
687
|
-
completed.push(i);
|
|
688
|
-
pushed++;
|
|
689
|
-
}
|
|
690
|
-
} else {
|
|
691
|
-
const { handled, conflict } = await this.handleConflict(
|
|
692
|
-
entry.collection,
|
|
693
|
-
entry.id,
|
|
694
|
-
envelope,
|
|
695
|
-
remoteEnvelope,
|
|
696
|
-
"push"
|
|
697
|
-
);
|
|
698
|
-
conflicts.push(conflict);
|
|
699
|
-
if (handled === "local") {
|
|
700
|
-
const winner = await this.advancePastRemote(conflict.local, entry.collection, entry.id, remoteEnvelope);
|
|
701
|
-
await this.remote.put(this.vault, entry.collection, entry.id, winner);
|
|
702
|
-
if (winner !== conflict.local) await this.applyRemote(entry.collection, entry.id, winner);
|
|
703
|
-
completed.push(i);
|
|
704
|
-
pushed++;
|
|
705
|
-
} else if (handled === "remote") {
|
|
706
|
-
await this.applyRemote(entry.collection, entry.id, conflict.remote);
|
|
707
|
-
completed.push(i);
|
|
708
|
-
} else if (handled === "merged" && conflict.local !== envelope) {
|
|
709
|
-
const merged = conflict.local;
|
|
710
|
-
await this.remote.put(this.vault, entry.collection, entry.id, merged);
|
|
711
|
-
await this.applyRemote(entry.collection, entry.id, merged);
|
|
712
|
-
completed.push(i);
|
|
713
|
-
pushed++;
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
} else {
|
|
718
|
-
throw err;
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
} catch (err) {
|
|
723
|
-
errors.push(err instanceof Error ? err : new Error(String(err)));
|
|
1113
|
+
const acc = { pushed: 0, bytes: 0, completed, conflicts, erasures, errors };
|
|
1114
|
+
const eligible = [...this.dirty.keys()].filter((i) => !filter || filter.has(this.dirty[i].collection));
|
|
1115
|
+
const width = Math.max(1, Math.floor(options?.concurrency ?? 1));
|
|
1116
|
+
let next = 0;
|
|
1117
|
+
const worker = async () => {
|
|
1118
|
+
while (next < eligible.length) {
|
|
1119
|
+
const i = eligible[next++];
|
|
1120
|
+
this.progress({ direction: "push", phase: "records", records: acc.pushed, bytes: acc.bytes, total: { records: pushTotal } });
|
|
1121
|
+
await this.#pushOne(i, this.dirty[i], acc);
|
|
724
1122
|
}
|
|
725
|
-
}
|
|
1123
|
+
};
|
|
1124
|
+
await Promise.all(Array.from({ length: Math.min(width, eligible.length) }, () => worker()));
|
|
1125
|
+
const pushed = acc.pushed;
|
|
1126
|
+
const pushBytes = acc.bytes;
|
|
726
1127
|
for (const i of completed.sort((a, b) => b - a)) {
|
|
727
1128
|
this.dirty.splice(i, 1);
|
|
728
1129
|
}
|
|
729
1130
|
if (!filter) {
|
|
730
1131
|
this.progress({ direction: "push", phase: "reserved", records: pushed, bytes: pushBytes, total: { records: pushTotal } });
|
|
731
1132
|
try {
|
|
732
|
-
await pushReserved(this.local, this.remote, this.vault);
|
|
1133
|
+
const mirrored = await pushReserved(this.local, this.remote, this.vault);
|
|
1134
|
+
reserved = mirrored.copied;
|
|
1135
|
+
for (const stale of mirrored.staleKeyrings ?? []) {
|
|
1136
|
+
conflicts.push({
|
|
1137
|
+
vault: this.vault,
|
|
1138
|
+
collection: "_keyring",
|
|
1139
|
+
id: stale.id,
|
|
1140
|
+
local: stale.local,
|
|
1141
|
+
remote: stale.remote,
|
|
1142
|
+
localVersion: stale.local._v,
|
|
1143
|
+
remoteVersion: stale.remote._v
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
733
1146
|
} catch (err) {
|
|
734
1147
|
errors.push(err instanceof Error ? err : new Error(String(err)));
|
|
735
1148
|
}
|
|
@@ -742,7 +1155,9 @@ var SyncEngine = class {
|
|
|
742
1155
|
} finally {
|
|
743
1156
|
await this.graphBatchController?.flush();
|
|
744
1157
|
}
|
|
745
|
-
const
|
|
1158
|
+
const pushRejected = this.#pushRejected;
|
|
1159
|
+
this.#pushRejected = [];
|
|
1160
|
+
const result = { pushed, conflicts, errors, erasures, ...reserved > 0 && { reserved }, ...pushRejected.length > 0 && { rejected: pushRejected } };
|
|
746
1161
|
this.emitter.emit("sync:push", result);
|
|
747
1162
|
return result;
|
|
748
1163
|
}
|
|
@@ -750,15 +1165,19 @@ var SyncEngine = class {
|
|
|
750
1165
|
async pull(options) {
|
|
751
1166
|
await this.ensureLoaded();
|
|
752
1167
|
let pulled = 0;
|
|
1168
|
+
let reserved = 0;
|
|
1169
|
+
this.#applied = [];
|
|
1170
|
+
this.#rejected = [];
|
|
753
1171
|
const conflicts = [];
|
|
754
|
-
const erasures = [];
|
|
755
1172
|
const errors = [];
|
|
1173
|
+
let crossed = { epoch: 0, resynced: false, parked: 0 };
|
|
1174
|
+
const erasures = [];
|
|
756
1175
|
if (!options?.collections) {
|
|
757
1176
|
this.progress({ direction: "pull", phase: "reserved", records: 0, bytes: 0 });
|
|
758
1177
|
try {
|
|
759
1178
|
const protectedIds = /* @__PURE__ */ new Map();
|
|
760
1179
|
for (const d of this.dirty) {
|
|
761
|
-
if (d.action
|
|
1180
|
+
if (d.action === "delete") continue;
|
|
762
1181
|
let set = protectedIds.get(d.collection);
|
|
763
1182
|
if (!set) {
|
|
764
1183
|
set = /* @__PURE__ */ new Set();
|
|
@@ -766,12 +1185,23 @@ var SyncEngine = class {
|
|
|
766
1185
|
}
|
|
767
1186
|
set.add(d.id);
|
|
768
1187
|
}
|
|
769
|
-
const
|
|
770
|
-
|
|
1188
|
+
const mirrored = await pullReserved(this.remote, this.local, this.vault, protectedIds);
|
|
1189
|
+
reserved = mirrored.copied + mirrored.deleted;
|
|
1190
|
+
if (this.rosterReload && mirrored.keyringsCopied.includes(this.rosterReload.userId)) await this.rosterReload.reload();
|
|
1191
|
+
} catch (err) {
|
|
1192
|
+
errors.push(err instanceof Error ? err : new Error(String(err)));
|
|
1193
|
+
}
|
|
1194
|
+
try {
|
|
1195
|
+
await this.#scanReplicatedRejections();
|
|
771
1196
|
} catch (err) {
|
|
772
1197
|
errors.push(err instanceof Error ? err : new Error(String(err)));
|
|
773
1198
|
}
|
|
774
1199
|
}
|
|
1200
|
+
try {
|
|
1201
|
+
crossed = await this.#crossEpoch();
|
|
1202
|
+
} catch (err) {
|
|
1203
|
+
errors.push(err instanceof Error ? err : new Error(String(err)));
|
|
1204
|
+
}
|
|
775
1205
|
let periodScope = null;
|
|
776
1206
|
let phases = null;
|
|
777
1207
|
if (options?.periods !== void 0) {
|
|
@@ -792,7 +1222,7 @@ var SyncEngine = class {
|
|
|
792
1222
|
const remoteEnvelope = await this.remote.get(this.vault, collName, id);
|
|
793
1223
|
if (!remoteEnvelope) continue;
|
|
794
1224
|
const localEnvelope = await this.local.get(this.vault, collName, id);
|
|
795
|
-
if (!localEnvelope || remoteEnvelope
|
|
1225
|
+
if (!localEnvelope || this.#remoteSupersedes(collName, id, remoteEnvelope, localEnvelope)) {
|
|
796
1226
|
await this.applyRemote(collName, id, remoteEnvelope);
|
|
797
1227
|
pulled++;
|
|
798
1228
|
}
|
|
@@ -817,11 +1247,21 @@ var SyncEngine = class {
|
|
|
817
1247
|
const expanded = options?.collections ? this.pairExpander?.(options.collections) ?? options.collections : null;
|
|
818
1248
|
const filter = expanded ? /* @__PURE__ */ new Set([...expanded, ...this.reservedDictExpander?.(expanded) ?? []]) : null;
|
|
819
1249
|
try {
|
|
820
|
-
const remoteSnapshot = await this.remote.loadAll(this.vault);
|
|
821
1250
|
let pullTotal = 0;
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
1251
|
+
let chunks;
|
|
1252
|
+
if (options?.paged) {
|
|
1253
|
+
const known = new Set(this.collectionNames?.() ?? []);
|
|
1254
|
+
for (const name of filter ?? []) if (!name.startsWith("_")) known.add(name);
|
|
1255
|
+
const names = [...known].filter((n) => !n.startsWith("_") && (!filter || filter.has(n)));
|
|
1256
|
+
for (const name of names) pullTotal += (await this.remote.list(this.vault, name)).length;
|
|
1257
|
+
chunks = this.pagedChunks(names);
|
|
1258
|
+
} else {
|
|
1259
|
+
const remoteSnapshot = await this.remote.loadAll(this.vault);
|
|
1260
|
+
for (const [collName, records] of Object.entries(remoteSnapshot)) {
|
|
1261
|
+
if (filter && !filter.has(collName)) continue;
|
|
1262
|
+
pullTotal += Object.keys(records).length;
|
|
1263
|
+
}
|
|
1264
|
+
chunks = Object.entries(remoteSnapshot);
|
|
825
1265
|
}
|
|
826
1266
|
const pullSample = () => ({
|
|
827
1267
|
direction: "pull",
|
|
@@ -830,7 +1270,7 @@ var SyncEngine = class {
|
|
|
830
1270
|
bytes: this.pullBytes,
|
|
831
1271
|
total: { records: pullTotal }
|
|
832
1272
|
});
|
|
833
|
-
for (const [collName, records] of
|
|
1273
|
+
for await (const [collName, records] of chunks) {
|
|
834
1274
|
if (filter && !filter.has(collName)) {
|
|
835
1275
|
continue;
|
|
836
1276
|
}
|
|
@@ -877,7 +1317,7 @@ var SyncEngine = class {
|
|
|
877
1317
|
this.dirty = this.dirty.filter((d) => !(d.collection === collName && d.id === id));
|
|
878
1318
|
pulled++;
|
|
879
1319
|
}
|
|
880
|
-
} else if (remoteEnvelope
|
|
1320
|
+
} else if (this.#remoteSupersedes(collName, id, remoteEnvelope, localEnvelope)) {
|
|
881
1321
|
const isDirty = this.dirty.some((d) => d.collection === collName && d.id === id);
|
|
882
1322
|
if (isDirty) {
|
|
883
1323
|
const { handled, conflict } = await this.handleConflict(
|
|
@@ -927,7 +1367,7 @@ var SyncEngine = class {
|
|
|
927
1367
|
this.dirty = this.dirty.filter((d) => !(d.collection === collName && d.id === id));
|
|
928
1368
|
pulled++;
|
|
929
1369
|
}
|
|
930
|
-
} else if (remoteEnvelope
|
|
1370
|
+
} else if (this.#remoteSupersedes(collName, id, remoteEnvelope, localEnvelope)) {
|
|
931
1371
|
await this.applyRemote(collName, id, remoteEnvelope);
|
|
932
1372
|
this.dirty = this.dirty.filter((d) => !(d.collection === collName && d.id === id));
|
|
933
1373
|
pulled++;
|
|
@@ -950,7 +1390,22 @@ var SyncEngine = class {
|
|
|
950
1390
|
this.pullByteSink = null;
|
|
951
1391
|
await this.graphBatchController?.flush();
|
|
952
1392
|
}
|
|
953
|
-
|
|
1393
|
+
pulled = Math.max(0, pulled - this.#rejected.length);
|
|
1394
|
+
const applied = this.#applied, rejected = this.#rejected;
|
|
1395
|
+
this.#applied = [];
|
|
1396
|
+
this.#rejected = [];
|
|
1397
|
+
const result = {
|
|
1398
|
+
pulled,
|
|
1399
|
+
conflicts,
|
|
1400
|
+
errors,
|
|
1401
|
+
erasures,
|
|
1402
|
+
...reserved > 0 && { reserved },
|
|
1403
|
+
...phases !== null ? { phases } : {},
|
|
1404
|
+
...applied.length > 0 && { applied },
|
|
1405
|
+
...rejected.length > 0 && { rejected },
|
|
1406
|
+
...crossed.epoch > 0 && { epoch: crossed.epoch },
|
|
1407
|
+
...crossed.resynced && { resynced: true }
|
|
1408
|
+
};
|
|
954
1409
|
this.emitter.emit("sync:pull", result);
|
|
955
1410
|
return result;
|
|
956
1411
|
}
|
|
@@ -968,6 +1423,7 @@ var SyncEngine = class {
|
|
|
968
1423
|
async pushFiltered(predicate) {
|
|
969
1424
|
await this.ensureLoaded();
|
|
970
1425
|
let pushed = 0;
|
|
1426
|
+
this.#pushRejected = [];
|
|
971
1427
|
const conflicts = [];
|
|
972
1428
|
const erasures = [];
|
|
973
1429
|
const errors = [];
|
|
@@ -992,6 +1448,7 @@ var SyncEngine = class {
|
|
|
992
1448
|
pushed++;
|
|
993
1449
|
continue;
|
|
994
1450
|
}
|
|
1451
|
+
if (await this.#withheldByRecheck(entry, envelope)) continue;
|
|
995
1452
|
try {
|
|
996
1453
|
await this.remote.put(
|
|
997
1454
|
this.vault,
|
|
@@ -1031,7 +1488,7 @@ var SyncEngine = class {
|
|
|
1031
1488
|
if (handled === "local") {
|
|
1032
1489
|
const winner = await this.advancePastRemote(conflict.local, entry.collection, entry.id, remoteEnvelope);
|
|
1033
1490
|
await this.remote.put(this.vault, entry.collection, entry.id, winner);
|
|
1034
|
-
if (winner !== conflict.local) await this.applyRemote(entry.collection, entry.id, winner);
|
|
1491
|
+
if (winner !== conflict.local) await this.applyRemote(entry.collection, entry.id, winner, { admit: false });
|
|
1035
1492
|
completed.push(i);
|
|
1036
1493
|
pushed++;
|
|
1037
1494
|
} else if (handled === "remote") {
|
|
@@ -1060,7 +1517,9 @@ var SyncEngine = class {
|
|
|
1060
1517
|
}
|
|
1061
1518
|
this.recordOutcome("push", errors);
|
|
1062
1519
|
await this.persistMeta();
|
|
1063
|
-
const
|
|
1520
|
+
const pushRejected = this.#pushRejected;
|
|
1521
|
+
this.#pushRejected = [];
|
|
1522
|
+
const result = { pushed, conflicts, errors, erasures, ...pushRejected.length > 0 && { rejected: pushRejected } };
|
|
1064
1523
|
this.emitter.emit("sync:push", result);
|
|
1065
1524
|
return result;
|
|
1066
1525
|
}
|
|
@@ -1098,6 +1557,18 @@ var SyncEngine = class {
|
|
|
1098
1557
|
* what the caller can read. Requires a `MergeAuthority` (a vault-attached
|
|
1099
1558
|
* engine on an encrypted vault).
|
|
1100
1559
|
*/
|
|
1560
|
+
/**
|
|
1561
|
+
* core#96 (pilot-1, finding B) — bring one member's keyring file down from
|
|
1562
|
+
* this target if the target's copy supersedes ours. Called by the kernel
|
|
1563
|
+
* before an authority edit; reloads the caller's own keyring in place when
|
|
1564
|
+
* it is the one refreshed (core#82).
|
|
1565
|
+
*/
|
|
1566
|
+
async refreshKeyring(userId) {
|
|
1567
|
+
await this.ensureLoaded();
|
|
1568
|
+
const copied = await pullKeyringFile(this.remote, this.local, this.vault, userId);
|
|
1569
|
+
if (copied && this.rosterReload && userId === this.rosterReload.userId) await this.rosterReload.reload();
|
|
1570
|
+
return copied;
|
|
1571
|
+
}
|
|
1101
1572
|
async realign() {
|
|
1102
1573
|
await this.ensureLoaded();
|
|
1103
1574
|
if (!this.mergeAuthority) throw new ValidationError("realign: requires a vault-attached engine on an encrypted vault (no MergeAuthority).");
|
|
@@ -1132,6 +1603,8 @@ var SyncEngine = class {
|
|
|
1132
1603
|
dirty: this.dirty.length,
|
|
1133
1604
|
lastPush: this.lastPush,
|
|
1134
1605
|
lastPull: this.lastPull,
|
|
1606
|
+
...this.epoch !== null ? { epoch: this.epoch } : {},
|
|
1607
|
+
// core#72
|
|
1135
1608
|
...this.inFlight ? { inFlight: this.inFlight } : {},
|
|
1136
1609
|
online: this.isOnline,
|
|
1137
1610
|
...this.lastError ? { lastError: this.lastError } : {},
|
|
@@ -1215,13 +1688,41 @@ var SyncEngine = class {
|
|
|
1215
1688
|
if (!this.mergeAuthority) return { ...winner, _v: toVersion };
|
|
1216
1689
|
return this.mergeAuthority.advance(collection, id, winner, toVersion);
|
|
1217
1690
|
}
|
|
1218
|
-
|
|
1691
|
+
/**
|
|
1692
|
+
* core#100 — does the remote copy supersede ours? A higher `_v`, as always;
|
|
1693
|
+
* or the SAME `_v` with different bytes while we hold no pending write for
|
|
1694
|
+
* it: a DEK rotation re-encrypts records in place, and a replica that kept
|
|
1695
|
+
* the old ciphertext would read `TamperedError` under the delivered key.
|
|
1696
|
+
* `applyRemote` still authenticates it (`MergeAuthority`) before it lands.
|
|
1697
|
+
*/
|
|
1698
|
+
#remoteSupersedes(collection, id, remote, local) {
|
|
1699
|
+
if (remote._v > local._v) return true;
|
|
1700
|
+
if (remote._v !== local._v) return false;
|
|
1701
|
+
if (this.dirty.some((d) => d.collection === collection && d.id === id)) return false;
|
|
1702
|
+
return envelopeBodyForHash(remote) !== envelopeBodyForHash(local);
|
|
1703
|
+
}
|
|
1704
|
+
async applyRemote(collection, id, envelope, opts) {
|
|
1219
1705
|
if (this.mergeAuthority && !await this.mergeAuthority.verify(collection, id, envelope)) {
|
|
1220
1706
|
throw new ValidationError(
|
|
1221
1707
|
`sync: refusing remote envelope for "${collection}/${id}" \u2014 it does not authenticate at the identity and version it claims. The local copy is unchanged.`
|
|
1222
1708
|
);
|
|
1223
1709
|
}
|
|
1710
|
+
const isErasure = isTombstoneShape(envelope) || isDeleteMarker(envelope);
|
|
1711
|
+
if (this.admission && !isErasure && opts?.admit !== false) {
|
|
1712
|
+
const verdict = await this.admission.admit(collection, id, envelope);
|
|
1713
|
+
if (!verdict.admitted) {
|
|
1714
|
+
this.#rejected.push(await this.#park(collection, id, envelope, verdict.reason, "sync-apply"));
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
const prior = await this.local.get(this.vault, collection, id);
|
|
1224
1719
|
await this.local.put(this.vault, collection, id, envelope);
|
|
1720
|
+
this.#applied.push({
|
|
1721
|
+
collection,
|
|
1722
|
+
id,
|
|
1723
|
+
action: isErasure ? "delete" : "put",
|
|
1724
|
+
...prior && prior._v < envelope._v ? { replaced: { version: prior._v, ...prior._by !== void 0 ? { by: prior._by } : {} } } : {}
|
|
1725
|
+
});
|
|
1225
1726
|
this.pullBytes += envelopeBodySize(envelope);
|
|
1226
1727
|
if (this.pullByteSink !== null) {
|
|
1227
1728
|
this.pullByteSink.records++;
|
|
@@ -1321,6 +1822,7 @@ var SyncEngine = class {
|
|
|
1321
1822
|
this.dirty = [...meta.dirty];
|
|
1322
1823
|
this.lastPush = meta.last_push;
|
|
1323
1824
|
this.lastPull = meta.last_pull;
|
|
1825
|
+
this.epoch = meta.epoch ?? null;
|
|
1324
1826
|
}
|
|
1325
1827
|
this.loaded = true;
|
|
1326
1828
|
}
|
|
@@ -1329,7 +1831,8 @@ var SyncEngine = class {
|
|
|
1329
1831
|
_noydb_sync: NOYDB_SYNC_VERSION,
|
|
1330
1832
|
last_push: this.lastPush,
|
|
1331
1833
|
last_pull: this.lastPull,
|
|
1332
|
-
dirty: this.dirty
|
|
1834
|
+
dirty: this.dirty,
|
|
1835
|
+
...this.epoch !== null ? { epoch: this.epoch } : {}
|
|
1333
1836
|
};
|
|
1334
1837
|
const envelope = buildRecordEnvelope(
|
|
1335
1838
|
{ collection: "_sync", id: "meta", version: 1 },
|
|
@@ -1403,4 +1906,4 @@ export {
|
|
|
1403
1906
|
SyncEngine,
|
|
1404
1907
|
SyncTransaction
|
|
1405
1908
|
};
|
|
1406
|
-
//# sourceMappingURL=chunk-
|
|
1909
|
+
//# sourceMappingURL=chunk-N6EWQ5VB.js.map
|