@noy-db/hub 0.7.0-pre.8 → 0.7.0
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 +1379 -0
- package/README.md +7 -6
- package/dist/api-TXFOVOXX.js +19 -0
- package/dist/as/index.js +14 -14
- package/dist/at/index.js +9 -10
- package/dist/attestation/index.js +18 -19
- package/dist/attestation/index.js.map +1 -1
- package/dist/{backup-SZBDQQSL.js → backup-IJTB4S4M.js} +14 -15
- package/dist/{backup-SZBDQQSL.js.map → backup-IJTB4S4M.js.map} +1 -1
- package/dist/blobs/index.js +13 -14
- package/dist/blobs/index.js.map +1 -1
- package/dist/broker/index.js +12 -13
- package/dist/broker/index.js.map +1 -1
- package/dist/by/index.js +2 -3
- package/dist/cargo/index.js +33 -34
- package/dist/{chunk-X6TBQKHW.js → chunk-2577DKXB.js} +2 -2
- package/dist/chunk-2C3AQB46.js +1652 -0
- package/dist/chunk-2C3AQB46.js.map +1 -0
- package/dist/{chunk-L3FHEWA3.js → chunk-2N4GY74U.js} +6 -6
- package/dist/{chunk-626TSARM.js → chunk-2Y4IMKA6.js} +4 -4
- package/dist/{chunk-7CWII4BL.js → chunk-3RCJJBNR.js} +10 -10
- package/dist/{chunk-M7UC2QPT.js → chunk-453F654R.js} +2 -2
- package/dist/{chunk-2QNKXGUS.js → chunk-4GVM6QZA.js} +2 -2
- package/dist/{chunk-AB5P32RE.js → chunk-4OZNXTO4.js} +2 -2
- package/dist/{chunk-L3HDEHGM.js → chunk-5VCRVKOZ.js} +4 -4
- package/dist/{chunk-6AQIFBSP.js → chunk-5WDXHNSW.js} +5 -5
- package/dist/chunk-66TQOPFM.js +37 -0
- package/dist/chunk-66TQOPFM.js.map +1 -0
- package/dist/{chunk-BOFRKAUS.js → chunk-6GMZ7HEE.js} +6 -6
- package/dist/{chunk-PRHCMEUK.js → chunk-6GOE5PGN.js} +168 -1328
- package/dist/chunk-6GOE5PGN.js.map +1 -0
- package/dist/{chunk-XBFEKTIY.js → chunk-6I2RQCET.js} +4 -4
- package/dist/{chunk-PUJTKLPL.js → chunk-6X6SEMEE.js} +6 -4
- package/dist/{chunk-PUJTKLPL.js.map → chunk-6X6SEMEE.js.map} +1 -1
- package/dist/{chunk-UCUALOSM.js → chunk-6YXCPL4T.js} +2 -2
- package/dist/{chunk-BYBB4ABD.js → chunk-77K4PG6P.js} +8 -8
- package/dist/{chunk-S2VG3ABP.js → chunk-7MG2LHO5.js} +3 -3
- package/dist/{chunk-GU3LQYW2.js → chunk-7MG7SJT7.js} +1 -1
- package/dist/chunk-7MG7SJT7.js.map +1 -0
- package/dist/{chunk-JBIV4FLR.js → chunk-7O3Y4PPD.js} +5 -1
- package/dist/chunk-7O3Y4PPD.js.map +1 -0
- package/dist/{chunk-EO2KGKEJ.js → chunk-7V4TP3AF.js} +4 -4
- package/dist/{chunk-LFDMKZIP.js → chunk-ANATMFIO.js} +5 -5
- package/dist/{chunk-T3PHCEN4.js → chunk-ANSIZYEG.js} +3 -3
- package/dist/{chunk-ZKRRBRSZ.js → chunk-AO42KJO6.js} +7 -7
- package/dist/{chunk-VFIWDPXZ.js → chunk-BLN54W3L.js} +3 -3
- package/dist/{chunk-ZQ2UUHSL.js → chunk-BM5TWITZ.js} +7 -7
- package/dist/{chunk-WLFABTZ6.js → chunk-BOET4NA4.js} +3 -3
- package/dist/{chunk-FOG7YEJP.js → chunk-BOWNCWZT.js} +3 -3
- package/dist/{chunk-7XODKGTW.js → chunk-BSFNRLK2.js} +5 -5
- package/dist/{chunk-YPSOHDNC.js → chunk-BUE2D7PX.js} +26 -3
- package/dist/chunk-BUE2D7PX.js.map +1 -0
- package/dist/{chunk-J4MN6XIU.js → chunk-BZ3534YM.js} +2 -2
- package/dist/chunk-CARABLSQ.js +176 -0
- package/dist/chunk-CARABLSQ.js.map +1 -0
- package/dist/{chunk-4MZEZ37W.js → chunk-CHJBLCOG.js} +10 -8
- package/dist/{chunk-4MZEZ37W.js.map → chunk-CHJBLCOG.js.map} +1 -1
- package/dist/{chunk-IF56DYJK.js → chunk-CPWQB3N5.js} +7 -7
- package/dist/{chunk-SHILAQH5.js → chunk-DGOZ4UZD.js} +2 -2
- package/dist/{chunk-BWWUXE3L.js → chunk-ELNXJMGB.js} +6 -6
- package/dist/{chunk-44C2URL7.js → chunk-EQEYOJBM.js} +3 -3
- package/dist/{chunk-Y3XWRDAM.js → chunk-EYBQEIUY.js} +3 -3
- package/dist/{chunk-Z7ZXZL6Y.js → chunk-FAHMLERK.js} +2 -2
- package/dist/chunk-FFWI7UH7.js +53 -0
- package/dist/chunk-FFWI7UH7.js.map +1 -0
- package/dist/{chunk-EXDIHWFO.js → chunk-FMZY5Q6L.js} +3 -3
- package/dist/{chunk-H6WXKZL7.js → chunk-FXBIP2WD.js} +6 -6
- package/dist/{chunk-ZTYG36DH.js → chunk-G56KBWFY.js} +67 -22
- package/dist/chunk-G56KBWFY.js.map +1 -0
- package/dist/{chunk-672U6STZ.js → chunk-GPEQUXHL.js} +4 -4
- package/dist/{chunk-Z2PWOSCJ.js → chunk-GY4WYNNG.js} +2 -2
- package/dist/{chunk-RDHUZ674.js → chunk-HDOU6I2Y.js} +80 -8
- package/dist/chunk-HDOU6I2Y.js.map +1 -0
- package/dist/{chunk-OMHHGOG4.js → chunk-J6FXAEUG.js} +15 -13
- package/dist/chunk-J6FXAEUG.js.map +1 -0
- package/dist/{chunk-4A4TYISP.js → chunk-JBDFMYKQ.js} +2 -2
- package/dist/{chunk-POS53FG6.js → chunk-JMGDHKQF.js} +2 -2
- package/dist/{chunk-Q7GEDVHC.js → chunk-K6LHRFBL.js} +7 -7
- package/dist/{chunk-4JDIRUTT.js → chunk-K7NTTA6P.js} +2 -2
- package/dist/{chunk-26W5VETY.js → chunk-KG7TZKZG.js} +5 -5
- package/dist/{chunk-RJ7RIIX6.js → chunk-KJIMGIWO.js} +8 -8
- package/dist/{chunk-F7ZFRPGQ.js → chunk-KTFRCC5C.js} +2 -2
- package/dist/{chunk-HSGEIMJS.js → chunk-KUJDV5QH.js} +5 -5
- package/dist/{chunk-XWNLKG6R.js → chunk-KUX3OB6D.js} +7 -5
- package/dist/{chunk-XWNLKG6R.js.map → chunk-KUX3OB6D.js.map} +1 -1
- package/dist/{chunk-UPJMBMCR.js → chunk-L742JPKZ.js} +6 -6
- package/dist/{chunk-4TZUFNXL.js → chunk-L7CIOOBW.js} +2 -2
- package/dist/{chunk-7LYUJNU3.js → chunk-LWBCUYZW.js} +2 -2
- package/dist/{chunk-JHLJQUXU.js → chunk-MUM7IANK.js} +3 -3
- package/dist/{chunk-2GXEQUEX.js → chunk-MVMXZYXT.js} +2 -2
- package/dist/{chunk-XLPXGN66.js → chunk-NA2NIJZI.js} +8 -8
- package/dist/{chunk-S6YB5ZRP.js → chunk-NKHRDYOR.js} +4 -4
- package/dist/{chunk-XJ3FEKEY.js → chunk-NQKIUNW5.js} +2 -2
- package/dist/{chunk-6NRVFD55.js → chunk-PT43Y56J.js} +6 -6
- package/dist/{chunk-5ACAVJUI.js → chunk-Q2PU57HC.js} +2 -2
- package/dist/{chunk-E6YA6UAP.js → chunk-Q7HZX3IF.js} +2 -2
- package/dist/{chunk-4ZPE6SKS.js → chunk-QIBVIOXO.js} +3 -3
- package/dist/{chunk-WLLUM35R.js → chunk-QIZPZMUI.js} +2 -2
- package/dist/{chunk-P6ASQOZR.js → chunk-REDOOY2Q.js} +3 -3
- package/dist/chunk-REDOOY2Q.js.map +1 -0
- package/dist/{chunk-LKFKXS5K.js → chunk-RMF67KCG.js} +2 -2
- package/dist/{chunk-YCSM3GZU.js → chunk-RMS3NEA3.js} +2 -2
- package/dist/{chunk-4FPEWQKF.js → chunk-RRNRNANG.js} +27 -17
- package/dist/chunk-RRNRNANG.js.map +1 -0
- package/dist/chunk-RTBJ5IOO.js +87 -0
- package/dist/chunk-RTBJ5IOO.js.map +1 -0
- package/dist/chunk-RXLYKVTF.js +17 -0
- package/dist/chunk-RXLYKVTF.js.map +1 -0
- package/dist/{chunk-OPXR4PB2.js → chunk-SFAWCTEM.js} +2 -2
- package/dist/{chunk-3QARDAYW.js → chunk-SKGMN35F.js} +2 -2
- package/dist/{chunk-W5LHQXZU.js → chunk-SL3JAAKN.js} +2 -2
- package/dist/chunk-SMH6SRX7.js +87 -0
- package/dist/chunk-SMH6SRX7.js.map +1 -0
- package/dist/{chunk-JJZIIJSA.js → chunk-SPXXX5E4.js} +12 -82
- package/dist/chunk-SPXXX5E4.js.map +1 -0
- package/dist/{chunk-UALOYGCP.js → chunk-SPYWXKJN.js} +3 -3
- package/dist/{chunk-QU3YUP2X.js → chunk-SZJ3DLZA.js} +2 -2
- package/dist/chunk-TFMM5DBB.js +20 -0
- package/dist/chunk-TFMM5DBB.js.map +1 -0
- package/dist/{chunk-225O5CXQ.js → chunk-TFR7DSOV.js} +2 -2
- package/dist/{chunk-X67FCULK.js → chunk-TRGTKX5E.js} +3 -3
- package/dist/{chunk-JVLSU7S2.js → chunk-TUFM4UZP.js} +2 -2
- package/dist/{chunk-VVAVTSTR.js → chunk-U4FOQDY4.js} +5 -172
- package/dist/chunk-U4FOQDY4.js.map +1 -0
- package/dist/{chunk-HTGRQZFB.js → chunk-UGTBXF5U.js} +3 -3
- package/dist/{chunk-THNJ3IKU.js → chunk-ULUMAZ5S.js} +2 -2
- package/dist/{chunk-XDVVBETV.js → chunk-UXVWEX5L.js} +2 -2
- package/dist/{chunk-FKFIEEY6.js → chunk-UXWB3FPF.js} +10 -10
- package/dist/{chunk-NO272T7Q.js → chunk-V4PXLYWM.js} +2 -31
- package/dist/chunk-V4PXLYWM.js.map +1 -0
- package/dist/{chunk-CBRS2NEK.js → chunk-VTFVLCUH.js} +2 -2
- package/dist/{chunk-TOV6VMHF.js → chunk-VXANTWBJ.js} +3 -3
- package/dist/{chunk-VSLUSXVK.js → chunk-VXGSWL6L.js} +6 -6
- package/dist/{chunk-KHKKD62N.js → chunk-VYYM3MEN.js} +2 -16
- package/dist/chunk-VYYM3MEN.js.map +1 -0
- package/dist/{chunk-HQINBNHM.js → chunk-WEQEYLCS.js} +7 -7
- package/dist/{chunk-U6GCYG2T.js → chunk-X2DLENDQ.js} +3 -3
- package/dist/{chunk-XVU7J43V.js → chunk-XLMEZIAJ.js} +2 -2
- package/dist/{chunk-ILSR22WM.js → chunk-XTXOCYOJ.js} +5 -5
- package/dist/{chunk-KDJGJR7F.js → chunk-YSRUYAVU.js} +2 -2
- package/dist/{chunk-NLKLXXTO.js → chunk-YYP7WSEB.js} +2 -2
- package/dist/{chunk-4G25SDQL.js → chunk-ZN4FD4PE.js} +4 -4
- package/dist/{chunk-XSN63FUS.js → chunk-ZORTV6NH.js} +2 -2
- package/dist/{chunk-S3I7VKID.js → chunk-ZVO4QVPD.js} +5 -5
- package/dist/classified/index.js +5 -6
- package/dist/{classified-marker-KL4YXUXD.js → classified-marker-LI2SF2E4.js} +3 -5
- package/dist/{classified-marker-KL4YXUXD.js.map → classified-marker-LI2SF2E4.js.map} +1 -1
- package/dist/collection-facade-4GRREZ6C.js +45 -0
- package/dist/computed-VPOKQK4N.js +10 -0
- package/dist/consent/index.js +9 -10
- package/dist/consent/index.js.map +1 -1
- package/dist/cover/index.js +9 -10
- package/dist/crdt/index.js +0 -1
- package/dist/crdt/index.js.map +1 -1
- package/dist/custody/index.js +12 -13
- package/dist/{dead-filter-ZGRFZKYE.js → dead-filter-TSWZQRR5.js} +2 -3
- package/dist/{dead-filter-ZGRFZKYE.js.map → dead-filter-TSWZQRR5.js.map} +1 -1
- package/dist/debug/index.js +1 -2
- package/dist/debug/index.js.map +1 -1
- package/dist/{delegation-2DO3VI5E.js → delegation-OVIFFPG4.js} +11 -12
- package/dist/{delegation-2DO3VI5E.js.map → delegation-OVIFFPG4.js.map} +1 -1
- package/dist/derivations/index.js +30 -16
- package/dist/derive-6O5SWQKN.js +21 -0
- package/dist/directory/index.js +10 -11
- package/dist/dispatch-S73TK3IY.js +302 -0
- package/dist/dispatch-S73TK3IY.js.map +1 -0
- package/dist/{dispatch-M62IAL2S.js → dispatch-WC6X2M6C.js} +5 -7
- package/dist/{dispatch-M62IAL2S.js.map → dispatch-WC6X2M6C.js.map} +1 -1
- package/dist/{enclave-4P4KH5ZW.js → enclave-KJOTOH7X.js} +9 -10
- package/dist/executor-5NIMLSKZ.js +8 -0
- package/dist/executor-AQVXV5GZ.js +8 -0
- package/dist/executor-PUAO6AKJ.js +29 -0
- package/dist/export-accessible-72EQNBMR.js +22 -0
- package/dist/extract-partition-PUYNU3UF.js +37 -0
- package/dist/{fanout-sidecar-BRQHWTZQ.js → fanout-sidecar-WUFXNYP4.js} +9 -10
- package/dist/{fanout-sidecar-BRQHWTZQ.js.map → fanout-sidecar-WUFXNYP4.js.map} +1 -1
- package/dist/{fence-watcher-ZVSMLKQD.js → fence-watcher-U2PW6JU5.js} +1 -3
- package/dist/{fence-watcher-ZVSMLKQD.js.map → fence-watcher-U2PW6JU5.js.map} +1 -1
- package/dist/find-2ABFCZX4.js +10 -0
- package/dist/forget/index.js +9 -10
- package/dist/forget/index.js.map +1 -1
- package/dist/guards/index.js +4 -5
- package/dist/history/index.js +13 -14
- package/dist/history/index.js.map +1 -1
- package/dist/i18n/index.js +14 -15
- package/dist/i18n/index.js.map +1 -1
- package/dist/index.d.ts +20 -4
- package/dist/index.js +122 -115
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.js +2 -3
- package/dist/indexing/index.js.map +1 -1
- package/dist/introspection/index.js +13 -13
- package/dist/issue-SIDORTSZ.js +18 -0
- package/dist/{json-schema-I22JCYCG.js → json-schema-V3M5MUCM.js} +1 -3
- package/dist/{json-schema-I22JCYCG.js.map → json-schema-V3M5MUCM.js.map} +1 -1
- package/dist/kernel/collection-config.d.ts +34 -0
- package/dist/kernel/collection.d.ts +11 -6
- package/dist/kernel/errors.d.ts +45 -0
- package/dist/kernel/match-pairs.d.ts +30 -0
- package/dist/kernel/types.d.ts +66 -0
- package/dist/lazy/index.js +3 -3
- package/dist/{ledger-4X5ZH43X.js → ledger-F33ZVMTK.js} +11 -12
- package/dist/liberate-LCDPNAG2.js +24 -0
- package/dist/link-set-FKNWAYKS.js +30 -0
- package/dist/{managed-secret-CXALLEUB.js → managed-secret-PQYKSR6T.js} +10 -11
- package/dist/materialized-views/index.js +21 -21
- package/dist/{migrate-cek-LBRKYUZZ.js → migrate-cek-5ENH64AA.js} +2 -3
- package/dist/{migrate-cek-LBRKYUZZ.js.map → migrate-cek-5ENH64AA.js.map} +1 -1
- package/dist/money/index.js +4 -5
- package/dist/noydb-DUXZPKSN.js +90 -0
- package/dist/overlay-views/index.js +4 -5
- package/dist/periods/index.js +12 -13
- package/dist/pod/index.js +64 -60
- package/dist/{pod-handle-DHEYBTJ2.js → pod-handle-Q736KYON.js} +10 -11
- package/dist/{pod-handle-DHEYBTJ2.js.map → pod-handle-Q736KYON.js.map} +1 -1
- package/dist/policy/index.js +10 -11
- package/dist/port/to/index.d.ts +40 -0
- package/dist/portability/index.js +8 -9
- package/dist/portability/index.js.map +1 -1
- package/dist/{post-register-4DXRYGTU.js → post-register-LNE2FE2A.js} +6 -7
- package/dist/{post-register-4DXRYGTU.js.map → post-register-LNE2FE2A.js.map} +1 -1
- package/dist/{purge-scope-FGNNLWHD.js → purge-scope-4HEFPDOW.js} +1 -3
- package/dist/{purge-scope-FGNNLWHD.js.map → purge-scope-4HEFPDOW.js.map} +1 -1
- package/dist/query/index.js +3 -4
- package/dist/{read-only-facade-CHCKSEPD.js → read-only-facade-BIGO37U3.js} +1 -2
- package/dist/reduce/index.js +4 -5
- package/dist/reduce/index.js.map +1 -1
- package/dist/register-I2DZXGAR.js +31 -0
- package/dist/registry-23WF3TGH.js +8 -0
- package/dist/registry-4AYVUNXK.js +8 -0
- package/dist/registry-VSSQPIGF.js +18 -0
- package/dist/registry-YLEWMGAC.js +32 -0
- package/dist/request-withdrawal-KFQHEZ25.js +30 -0
- package/dist/{reveal-X2QMWOVX.js → reveal-67RDQRIN.js} +6 -7
- package/dist/{reveal-X2QMWOVX.js.map → reveal-67RDQRIN.js.map} +1 -1
- package/dist/revoke-DQDZAZZM.js +23 -0
- package/dist/satellites/index.js +1 -2
- package/dist/schema-update/index.js +2 -3
- package/dist/sealed-record/index.js +12 -13
- package/dist/sealed-record/index.js.map +1 -1
- package/dist/search/index.js +4 -5
- package/dist/{seed-IX5NFSH5.js → seed-G42JYN56.js} +12 -13
- package/dist/{seed-IX5NFSH5.js.map → seed-G42JYN56.js.map} +1 -1
- package/dist/sequence/index.js +9 -10
- package/dist/session/index.js +9 -10
- package/dist/session/index.js.map +1 -1
- package/dist/shadow/index.js +2 -3
- package/dist/shadow/index.js.map +1 -1
- package/dist/share-link/index.js +0 -1
- package/dist/share-link/index.js.map +1 -1
- package/dist/signer-WAMDZHEN.js +24 -0
- package/dist/snapshots/index.js +10 -11
- package/dist/snapshots/index.js.map +1 -1
- package/dist/{stale-5I36LPVK.js → stale-DDL7WQEZ.js} +10 -11
- package/dist/storage-FLR73HHF.js +22 -0
- package/dist/store/index.js +0 -1
- package/dist/{store-coordination-provider-OLPNRMFO.js → store-coordination-provider-MEQ2MJQG.js} +10 -11
- package/dist/{store-coordination-provider-OLPNRMFO.js.map → store-coordination-provider-MEQ2MJQG.js.map} +1 -1
- package/dist/sync/index.js +10 -11
- package/dist/sync/index.js.map +1 -1
- package/dist/team/index.js +15 -16
- package/dist/tiers/index.js +11 -12
- package/dist/to/index.js +2 -3
- package/dist/to/index.js.map +1 -1
- package/dist/transactions/index.js +3 -4
- package/dist/transactions/index.js.map +1 -1
- package/dist/{ulid-VQW6ERQN.js → ulid-4MKXG325.js} +1 -2
- package/dist/util/index.js +1 -2
- package/dist/util/index.js.map +1 -1
- package/dist/{vault-diff-FE5J35NJ.js → vault-diff-C6WUJFNP.js} +1 -2
- package/dist/vault-head/index.js +8 -9
- package/dist/vault-head/index.js.map +1 -1
- package/dist/{verify-WYOFLSNA.js → verify-KOV2G5D6.js} +5 -6
- package/dist/{verify-WYOFLSNA.js.map → verify-KOV2G5D6.js.map} +1 -1
- package/dist/walk-3UQBRMDV.js +21 -0
- package/dist/with-formula/derivations/dispatch.d.ts +16 -1
- package/dist/with-formula/derivations/registry.d.ts +20 -0
- package/dist/with-formula/derivations/trigger-match.d.ts +104 -0
- package/dist/with-formula/derivations/types.d.ts +61 -0
- package/dist/with-party/team/roster-epoch.d.ts +71 -0
- package/dist/with-party/team/roster-tag.d.ts +1 -1
- package/dist/with-shape/introspection/behaviors.d.ts +7 -0
- package/dist/with-shape/introspection/describe.d.ts +10 -1
- package/dist/with-shape/introspection/field-meta.d.ts +86 -0
- package/dist/withdraw-accessible-EL5GXZBS.js +27 -0
- package/package.json +8 -3
- package/dist/api-OAZLPNVV.js +0 -20
- package/dist/chunk-4FPEWQKF.js.map +0 -1
- package/dist/chunk-5263OEM3.js +0 -344
- package/dist/chunk-5263OEM3.js.map +0 -1
- package/dist/chunk-GU3LQYW2.js.map +0 -1
- package/dist/chunk-JBIV4FLR.js.map +0 -1
- package/dist/chunk-JJZIIJSA.js.map +0 -1
- package/dist/chunk-KHKKD62N.js.map +0 -1
- package/dist/chunk-NO272T7Q.js.map +0 -1
- package/dist/chunk-OMHHGOG4.js.map +0 -1
- package/dist/chunk-P6ASQOZR.js.map +0 -1
- package/dist/chunk-PRHCMEUK.js.map +0 -1
- package/dist/chunk-PZ5AY32C.js +0 -10
- package/dist/chunk-RDHUZ674.js.map +0 -1
- package/dist/chunk-VAG47QHV.js +0 -81
- package/dist/chunk-VAG47QHV.js.map +0 -1
- package/dist/chunk-VVAVTSTR.js.map +0 -1
- package/dist/chunk-YPSOHDNC.js.map +0 -1
- package/dist/chunk-ZTYG36DH.js.map +0 -1
- package/dist/collection-facade-6IY2LW6W.js +0 -45
- package/dist/computed-ASBNTLOK.js +0 -11
- package/dist/derive-TCTS7ULE.js +0 -22
- package/dist/dispatch-FNCCONFK.js +0 -194
- package/dist/dispatch-FNCCONFK.js.map +0 -1
- package/dist/executor-AFHQYRAU.js +0 -9
- package/dist/executor-CDIMJVE2.js +0 -29
- package/dist/executor-YYDW6XQI.js +0 -9
- package/dist/export-accessible-WADNWX35.js +0 -23
- package/dist/extract-partition-SHQYQSWE.js +0 -38
- package/dist/find-SZBL2HPB.js +0 -11
- package/dist/issue-2DGG7NKE.js +0 -19
- package/dist/liberate-7NEPRE52.js +0 -25
- package/dist/link-set-WHTYVTOV.js +0 -31
- package/dist/noydb-BQ3ZTN2Z.js +0 -86
- package/dist/register-GLCOH73C.js +0 -32
- package/dist/registry-GWGHZWGP.js +0 -19
- package/dist/registry-IWFMXCHX.js +0 -9
- package/dist/registry-Q3FG3OAX.js +0 -9
- package/dist/registry-T346IZCF.js +0 -18
- package/dist/request-withdrawal-2RERJECX.js +0 -31
- package/dist/revoke-4NRSCPNW.js +0 -24
- package/dist/signer-HJHTGX7X.js +0 -25
- package/dist/storage-ZYYAB3J3.js +0 -23
- package/dist/walk-LTV7LOCN.js +0 -21
- package/dist/withdraw-accessible-V4XCPH7U.js +0 -28
- package/dist/withdraw-accessible-V4XCPH7U.js.map +0 -1
- package/dist/zod-HAJM7LMS.js +0 -14763
- package/dist/zod-HAJM7LMS.js.map +0 -1
- /package/dist/{api-OAZLPNVV.js.map → api-TXFOVOXX.js.map} +0 -0
- /package/dist/{chunk-X6TBQKHW.js.map → chunk-2577DKXB.js.map} +0 -0
- /package/dist/{chunk-L3FHEWA3.js.map → chunk-2N4GY74U.js.map} +0 -0
- /package/dist/{chunk-626TSARM.js.map → chunk-2Y4IMKA6.js.map} +0 -0
- /package/dist/{chunk-7CWII4BL.js.map → chunk-3RCJJBNR.js.map} +0 -0
- /package/dist/{chunk-M7UC2QPT.js.map → chunk-453F654R.js.map} +0 -0
- /package/dist/{chunk-2QNKXGUS.js.map → chunk-4GVM6QZA.js.map} +0 -0
- /package/dist/{chunk-AB5P32RE.js.map → chunk-4OZNXTO4.js.map} +0 -0
- /package/dist/{chunk-L3HDEHGM.js.map → chunk-5VCRVKOZ.js.map} +0 -0
- /package/dist/{chunk-6AQIFBSP.js.map → chunk-5WDXHNSW.js.map} +0 -0
- /package/dist/{chunk-BOFRKAUS.js.map → chunk-6GMZ7HEE.js.map} +0 -0
- /package/dist/{chunk-XBFEKTIY.js.map → chunk-6I2RQCET.js.map} +0 -0
- /package/dist/{chunk-UCUALOSM.js.map → chunk-6YXCPL4T.js.map} +0 -0
- /package/dist/{chunk-BYBB4ABD.js.map → chunk-77K4PG6P.js.map} +0 -0
- /package/dist/{chunk-S2VG3ABP.js.map → chunk-7MG2LHO5.js.map} +0 -0
- /package/dist/{chunk-EO2KGKEJ.js.map → chunk-7V4TP3AF.js.map} +0 -0
- /package/dist/{chunk-LFDMKZIP.js.map → chunk-ANATMFIO.js.map} +0 -0
- /package/dist/{chunk-T3PHCEN4.js.map → chunk-ANSIZYEG.js.map} +0 -0
- /package/dist/{chunk-ZKRRBRSZ.js.map → chunk-AO42KJO6.js.map} +0 -0
- /package/dist/{chunk-VFIWDPXZ.js.map → chunk-BLN54W3L.js.map} +0 -0
- /package/dist/{chunk-ZQ2UUHSL.js.map → chunk-BM5TWITZ.js.map} +0 -0
- /package/dist/{chunk-WLFABTZ6.js.map → chunk-BOET4NA4.js.map} +0 -0
- /package/dist/{chunk-FOG7YEJP.js.map → chunk-BOWNCWZT.js.map} +0 -0
- /package/dist/{chunk-7XODKGTW.js.map → chunk-BSFNRLK2.js.map} +0 -0
- /package/dist/{chunk-J4MN6XIU.js.map → chunk-BZ3534YM.js.map} +0 -0
- /package/dist/{chunk-IF56DYJK.js.map → chunk-CPWQB3N5.js.map} +0 -0
- /package/dist/{chunk-SHILAQH5.js.map → chunk-DGOZ4UZD.js.map} +0 -0
- /package/dist/{chunk-BWWUXE3L.js.map → chunk-ELNXJMGB.js.map} +0 -0
- /package/dist/{chunk-44C2URL7.js.map → chunk-EQEYOJBM.js.map} +0 -0
- /package/dist/{chunk-Y3XWRDAM.js.map → chunk-EYBQEIUY.js.map} +0 -0
- /package/dist/{chunk-Z7ZXZL6Y.js.map → chunk-FAHMLERK.js.map} +0 -0
- /package/dist/{chunk-EXDIHWFO.js.map → chunk-FMZY5Q6L.js.map} +0 -0
- /package/dist/{chunk-H6WXKZL7.js.map → chunk-FXBIP2WD.js.map} +0 -0
- /package/dist/{chunk-672U6STZ.js.map → chunk-GPEQUXHL.js.map} +0 -0
- /package/dist/{chunk-Z2PWOSCJ.js.map → chunk-GY4WYNNG.js.map} +0 -0
- /package/dist/{chunk-4A4TYISP.js.map → chunk-JBDFMYKQ.js.map} +0 -0
- /package/dist/{chunk-POS53FG6.js.map → chunk-JMGDHKQF.js.map} +0 -0
- /package/dist/{chunk-Q7GEDVHC.js.map → chunk-K6LHRFBL.js.map} +0 -0
- /package/dist/{chunk-4JDIRUTT.js.map → chunk-K7NTTA6P.js.map} +0 -0
- /package/dist/{chunk-26W5VETY.js.map → chunk-KG7TZKZG.js.map} +0 -0
- /package/dist/{chunk-RJ7RIIX6.js.map → chunk-KJIMGIWO.js.map} +0 -0
- /package/dist/{chunk-F7ZFRPGQ.js.map → chunk-KTFRCC5C.js.map} +0 -0
- /package/dist/{chunk-HSGEIMJS.js.map → chunk-KUJDV5QH.js.map} +0 -0
- /package/dist/{chunk-UPJMBMCR.js.map → chunk-L742JPKZ.js.map} +0 -0
- /package/dist/{chunk-4TZUFNXL.js.map → chunk-L7CIOOBW.js.map} +0 -0
- /package/dist/{chunk-7LYUJNU3.js.map → chunk-LWBCUYZW.js.map} +0 -0
- /package/dist/{chunk-JHLJQUXU.js.map → chunk-MUM7IANK.js.map} +0 -0
- /package/dist/{chunk-2GXEQUEX.js.map → chunk-MVMXZYXT.js.map} +0 -0
- /package/dist/{chunk-XLPXGN66.js.map → chunk-NA2NIJZI.js.map} +0 -0
- /package/dist/{chunk-S6YB5ZRP.js.map → chunk-NKHRDYOR.js.map} +0 -0
- /package/dist/{chunk-XJ3FEKEY.js.map → chunk-NQKIUNW5.js.map} +0 -0
- /package/dist/{chunk-6NRVFD55.js.map → chunk-PT43Y56J.js.map} +0 -0
- /package/dist/{chunk-5ACAVJUI.js.map → chunk-Q2PU57HC.js.map} +0 -0
- /package/dist/{chunk-E6YA6UAP.js.map → chunk-Q7HZX3IF.js.map} +0 -0
- /package/dist/{chunk-4ZPE6SKS.js.map → chunk-QIBVIOXO.js.map} +0 -0
- /package/dist/{chunk-WLLUM35R.js.map → chunk-QIZPZMUI.js.map} +0 -0
- /package/dist/{chunk-LKFKXS5K.js.map → chunk-RMF67KCG.js.map} +0 -0
- /package/dist/{chunk-YCSM3GZU.js.map → chunk-RMS3NEA3.js.map} +0 -0
- /package/dist/{chunk-OPXR4PB2.js.map → chunk-SFAWCTEM.js.map} +0 -0
- /package/dist/{chunk-3QARDAYW.js.map → chunk-SKGMN35F.js.map} +0 -0
- /package/dist/{chunk-W5LHQXZU.js.map → chunk-SL3JAAKN.js.map} +0 -0
- /package/dist/{chunk-UALOYGCP.js.map → chunk-SPYWXKJN.js.map} +0 -0
- /package/dist/{chunk-QU3YUP2X.js.map → chunk-SZJ3DLZA.js.map} +0 -0
- /package/dist/{chunk-225O5CXQ.js.map → chunk-TFR7DSOV.js.map} +0 -0
- /package/dist/{chunk-X67FCULK.js.map → chunk-TRGTKX5E.js.map} +0 -0
- /package/dist/{chunk-JVLSU7S2.js.map → chunk-TUFM4UZP.js.map} +0 -0
- /package/dist/{chunk-HTGRQZFB.js.map → chunk-UGTBXF5U.js.map} +0 -0
- /package/dist/{chunk-THNJ3IKU.js.map → chunk-ULUMAZ5S.js.map} +0 -0
- /package/dist/{chunk-XDVVBETV.js.map → chunk-UXVWEX5L.js.map} +0 -0
- /package/dist/{chunk-FKFIEEY6.js.map → chunk-UXWB3FPF.js.map} +0 -0
- /package/dist/{chunk-CBRS2NEK.js.map → chunk-VTFVLCUH.js.map} +0 -0
- /package/dist/{chunk-TOV6VMHF.js.map → chunk-VXANTWBJ.js.map} +0 -0
- /package/dist/{chunk-VSLUSXVK.js.map → chunk-VXGSWL6L.js.map} +0 -0
- /package/dist/{chunk-HQINBNHM.js.map → chunk-WEQEYLCS.js.map} +0 -0
- /package/dist/{chunk-U6GCYG2T.js.map → chunk-X2DLENDQ.js.map} +0 -0
- /package/dist/{chunk-XVU7J43V.js.map → chunk-XLMEZIAJ.js.map} +0 -0
- /package/dist/{chunk-ILSR22WM.js.map → chunk-XTXOCYOJ.js.map} +0 -0
- /package/dist/{chunk-KDJGJR7F.js.map → chunk-YSRUYAVU.js.map} +0 -0
- /package/dist/{chunk-NLKLXXTO.js.map → chunk-YYP7WSEB.js.map} +0 -0
- /package/dist/{chunk-4G25SDQL.js.map → chunk-ZN4FD4PE.js.map} +0 -0
- /package/dist/{chunk-XSN63FUS.js.map → chunk-ZORTV6NH.js.map} +0 -0
- /package/dist/{chunk-S3I7VKID.js.map → chunk-ZVO4QVPD.js.map} +0 -0
- /package/dist/{chunk-PZ5AY32C.js.map → collection-facade-4GRREZ6C.js.map} +0 -0
- /package/dist/{collection-facade-6IY2LW6W.js.map → computed-VPOKQK4N.js.map} +0 -0
- /package/dist/{computed-ASBNTLOK.js.map → derive-6O5SWQKN.js.map} +0 -0
- /package/dist/{derive-TCTS7ULE.js.map → enclave-KJOTOH7X.js.map} +0 -0
- /package/dist/{enclave-4P4KH5ZW.js.map → executor-5NIMLSKZ.js.map} +0 -0
- /package/dist/{executor-AFHQYRAU.js.map → executor-AQVXV5GZ.js.map} +0 -0
- /package/dist/{executor-CDIMJVE2.js.map → executor-PUAO6AKJ.js.map} +0 -0
- /package/dist/{executor-YYDW6XQI.js.map → export-accessible-72EQNBMR.js.map} +0 -0
- /package/dist/{export-accessible-WADNWX35.js.map → extract-partition-PUYNU3UF.js.map} +0 -0
- /package/dist/{extract-partition-SHQYQSWE.js.map → find-2ABFCZX4.js.map} +0 -0
- /package/dist/{find-SZBL2HPB.js.map → issue-SIDORTSZ.js.map} +0 -0
- /package/dist/{issue-2DGG7NKE.js.map → ledger-F33ZVMTK.js.map} +0 -0
- /package/dist/{ledger-4X5ZH43X.js.map → liberate-LCDPNAG2.js.map} +0 -0
- /package/dist/{liberate-7NEPRE52.js.map → link-set-FKNWAYKS.js.map} +0 -0
- /package/dist/{link-set-WHTYVTOV.js.map → managed-secret-PQYKSR6T.js.map} +0 -0
- /package/dist/{managed-secret-CXALLEUB.js.map → noydb-DUXZPKSN.js.map} +0 -0
- /package/dist/{noydb-BQ3ZTN2Z.js.map → read-only-facade-BIGO37U3.js.map} +0 -0
- /package/dist/{read-only-facade-CHCKSEPD.js.map → register-I2DZXGAR.js.map} +0 -0
- /package/dist/{register-GLCOH73C.js.map → registry-23WF3TGH.js.map} +0 -0
- /package/dist/{registry-GWGHZWGP.js.map → registry-4AYVUNXK.js.map} +0 -0
- /package/dist/{registry-IWFMXCHX.js.map → registry-VSSQPIGF.js.map} +0 -0
- /package/dist/{registry-Q3FG3OAX.js.map → registry-YLEWMGAC.js.map} +0 -0
- /package/dist/{registry-T346IZCF.js.map → request-withdrawal-KFQHEZ25.js.map} +0 -0
- /package/dist/{request-withdrawal-2RERJECX.js.map → revoke-DQDZAZZM.js.map} +0 -0
- /package/dist/{revoke-4NRSCPNW.js.map → signer-WAMDZHEN.js.map} +0 -0
- /package/dist/{signer-HJHTGX7X.js.map → stale-DDL7WQEZ.js.map} +0 -0
- /package/dist/{stale-5I36LPVK.js.map → storage-FLR73HHF.js.map} +0 -0
- /package/dist/{storage-ZYYAB3J3.js.map → ulid-4MKXG325.js.map} +0 -0
- /package/dist/{ulid-VQW6ERQN.js.map → vault-diff-C6WUJFNP.js.map} +0 -0
- /package/dist/{vault-diff-FE5J35NJ.js.map → walk-3UQBRMDV.js.map} +0 -0
- /package/dist/{walk-LTV7LOCN.js.map → withdraw-accessible-EL5GXZBS.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/share-link/share-link.ts"],"sourcesContent":["/**\n * Portal share-link grammar + parse/build helper (#806).\n *\n * ONE canonical link grammar addresses `vault / period / collection /\n * record` across all three portal surfaces — the LIFF permalink path\n * (`https://liff.line.me/{liffId}/r/…`), the installed-PWA same-origin\n * path (`https://{portal-origin}/r/…`), and the vendor backend console.\n * The link SHAPE is identical everywhere; only the origin prefix\n * differs, and callers own the prefix.\n *\n * ## Grammar (frozen — see `__tests__/share-link-surface.golden.json`)\n *\n * ```\n * share-link = [ base ] \"/r/\" vault \"/\" collection \"/\" record\n * [ \"?\" query ] [ \"#\" fragment ]\n * vault = ULID ; 26 uppercase Crockford base32 chars\n * collection = segment ; opaque, non-empty after decode\n * record = segment ; opaque, non-empty after decode\n * query = pair *( \"&\" pair ) ; keys unique; ONLY these two keys\n * pair = \"period=\" component ; opaque period id\n * / \"v=\" version ; record version\n * version = \"0\" / %x31-39 *DIGIT ; base-10 integer, ≤ 2^53 − 1\n * fragment = \"g=\" component ; single-use grant token (opaque)\n * ```\n *\n * `segment` / `component` are strict-canonical `encodeURIComponent`\n * output: decoding then re-encoding MUST reproduce the raw text exactly\n * (uppercase-hex escapes, `+` is a literal plus never a space, `%2F`\n * inside a segment never creates an extra segment). Path segments\n * additionally may not decode to `.` or `..`. Links produced by\n * {@link buildShareLink} always satisfy this; hand-written links using\n * only unreserved characters (`inv-001`, ULIDs, …) do too.\n *\n * ## Transport rule for the grant token\n *\n * The optional single-use grant token travels ONLY in the URL fragment\n * (`#g={token}`) — never in the path or query — because fragments are\n * not sent to servers, so the token never reaches server logs, CDNs, or\n * LINE's infrastructure. Same rule `on-magic-link` follows. The token is\n * fully OPAQUE here: no validation beyond non-empty, no decoding of its\n * semantics — grant semantics live in `on-magic-link` (not imported).\n *\n * ## Fail closed\n *\n * {@link parseShareLink} throws a typed {@link ShareLinkParseError} for\n * ANYTHING that is not exactly the grammar above — unknown prefix,\n * missing/extra/empty/malformed segments, a non-ULID vault handle,\n * duplicate or unknown query keys, malformed fragments. There is never a\n * default-vault (or any other) fallback; the app maps the error to a 404.\n *\n * Zero dependencies on the hub floor: pure string/`URL` code, no crypto,\n * no store — LIFF shells, PWAs, and klum-db can import this subpath\n * without dragging anything else. (The one internal import is the pure,\n * leaf ULID shape check shared with the pod format.)\n */\n\nimport { isULID } from '../with-pod/ulid.js'\n\n/** Machine-readable {@link ShareLinkParseError} discriminant. */\nexport type ShareLinkErrorCode =\n | 'MALFORMED_URL'\n | 'UNKNOWN_PREFIX'\n | 'MALFORMED_PATH'\n | 'MALFORMED_SEGMENT'\n | 'INVALID_VAULT_HANDLE'\n | 'MALFORMED_QUERY'\n | 'DUPLICATE_QUERY_KEY'\n | 'UNKNOWN_QUERY_KEY'\n | 'INVALID_VERSION'\n | 'MALFORMED_FRAGMENT'\n | 'INVALID_PARTS'\n\n/**\n * Typed failure for the share-link grammar. Thrown by\n * {@link parseShareLink} for any input outside the grammar (fail closed —\n * map to an app-level 404, never a default-vault fallback), and by\n * {@link buildShareLink} with code `'INVALID_PARTS'` when the caller\n * hands it parts that could not round-trip.\n *\n * Standalone (`extends Error`, not `NoydbError`) so this subpath stays\n * import-light; the `code` field follows the same machine-readable\n * convention as the kernel error classes.\n */\nexport class ShareLinkParseError extends Error {\n /** Machine-readable error code. Stable across library versions. */\n readonly code: ShareLinkErrorCode\n\n constructor(code: ShareLinkErrorCode, message: string) {\n super(message)\n this.name = 'ShareLinkParseError'\n this.code = code\n }\n}\n\n/**\n * The logical address a share link carries. Input to\n * {@link buildShareLink}; {@link parseShareLink} returns the same shape\n * (as the readonly {@link ShareLink} alias) with optional fields OMITTED\n * (not `undefined`) when absent from the link.\n */\nexport interface ShareLinkParts {\n /** Vault handle — a 26-character uppercase Crockford-base32 ULID. */\n vaultHandle: string\n /** Collection name — opaque, non-empty. */\n collection: string\n /** Record id — opaque, non-empty. */\n recordId: string\n /** Optional period id (`?period=`) — opaque, non-empty. */\n period?: string\n /** Optional record version (`?v=`) — non-negative safe integer. */\n version?: number\n /**\n * Optional single-use grant token (`#g=`) — opaque, non-empty. Carried\n * ONLY in the URL fragment; semantics belong to `on-magic-link`.\n */\n grantToken?: string\n}\n\n/** A successfully parsed share link. See {@link ShareLinkParts}. */\nexport type ShareLink = Readonly<ShareLinkParts>\n\n// Dummy base for parsing bare `/r/…` paths with the WHATWG URL parser.\n// `.invalid` is reserved (RFC 2606) — it can never collide with a real\n// origin, and the host is discarded anyway (only path/query/fragment\n// matter to the grammar).\nconst DUMMY_BASE = 'https://share-link.invalid'\n\n/**\n * Canonical encoding of a QUERY value. `encodeURIComponent`, plus `'`\n * → `%27`: the WHATWG URL parser percent-encodes the apostrophe inside\n * the query component of special (http/https) URLs — if we emitted a\n * raw `'`, any URL-object round-trip (browser address bar, `new URL`)\n * would rewrite the link into a form the strict parser then rejects.\n * Path and fragment components don't need this — every character the\n * URL parser escapes there is already escaped by `encodeURIComponent`.\n */\nfunction encodeQueryValue(value: string): string {\n return encodeURIComponent(value).replace(/'/g, '%27')\n}\n\n/**\n * Strict-canonical decode: `raw` must be exactly what the given\n * canonical `encode` produces for the decoded text — no `+`-for-space,\n * no lowercase hex escapes, no unencoded reserved characters,\n * non-empty. `redact` keeps the raw text out of the error message\n * (grant tokens).\n */\nfunction decodeCanonical(\n raw: string,\n code: ShareLinkErrorCode,\n what: string,\n { redact = false, encode = encodeURIComponent }: {\n redact?: boolean\n encode?: (value: string) => string\n } = {},\n): string {\n const shown = redact ? '(redacted)' : `\"${raw}\"`\n let decoded: string\n try {\n decoded = decodeURIComponent(raw)\n } catch {\n throw new ShareLinkParseError(code, `${what}: malformed percent-encoding in ${shown}`)\n }\n if (decoded === '') {\n throw new ShareLinkParseError(code, `${what}: empty`)\n }\n if (encode(decoded) !== raw) {\n throw new ShareLinkParseError(\n code,\n `${what}: ${shown} is not canonical encodeURIComponent output`,\n )\n }\n return decoded\n}\n\n/** Path-segment decode — canonical, plus the dot-segment refusal. */\nfunction decodePathSegment(raw: string, what: string): string {\n const decoded = decodeCanonical(raw, 'MALFORMED_SEGMENT', what)\n if (decoded === '.' || decoded === '..') {\n throw new ShareLinkParseError('MALFORMED_SEGMENT', `${what}: \"${decoded}\" is a dot segment`)\n }\n return decoded\n}\n\n/** `v=` value → non-negative safe integer. Canonical base-10 only. */\nfunction parseVersion(raw: string): number {\n if (!/^(?:0|[1-9][0-9]*)$/.test(raw)) {\n throw new ShareLinkParseError(\n 'INVALID_VERSION',\n `version \"${raw}\" is not a canonical base-10 integer`,\n )\n }\n const version = Number(raw)\n if (!Number.isSafeInteger(version)) {\n throw new ShareLinkParseError('INVALID_VERSION', `version \"${raw}\" exceeds 2^53 - 1`)\n }\n return version\n}\n\n/** Resolve string | URL input to a URL object, fail closed. */\nfunction toUrl(input: string | URL): URL {\n if (input instanceof URL) {\n if (input.protocol !== 'http:' && input.protocol !== 'https:') {\n throw new ShareLinkParseError(\n 'UNKNOWN_PREFIX',\n `unsupported protocol \"${input.protocol}\" — share links are http(s) URLs or bare /r/ paths`,\n )\n }\n return input\n }\n if (input.startsWith('/')) {\n try {\n return new URL(input, DUMMY_BASE)\n } catch {\n throw new ShareLinkParseError('MALFORMED_URL', 'unparseable share path')\n }\n }\n if (/^https?:\\/\\//i.test(input)) {\n try {\n return new URL(input)\n } catch {\n throw new ShareLinkParseError('MALFORMED_URL', 'unparseable share URL')\n }\n }\n throw new ShareLinkParseError(\n 'UNKNOWN_PREFIX',\n 'input is neither a \"/\"-rooted share path nor an http(s) URL',\n )\n}\n\n/**\n * Parse a share link — full URL, `URL` instance, or bare `/r/…` path.\n *\n * A LIFF permalink prefix is tolerated: when the path does not start\n * with `/r/` directly, the share path is taken from the FIRST `/r/`\n * segment boundary (`https://liff.line.me/{liffId}/r/…` → `/r/…`). No\n * fuzzy matching beyond that single rule.\n *\n * Fail closed: anything outside the grammar throws\n * {@link ShareLinkParseError}. Never a default-vault fallback.\n */\nexport function parseShareLink(input: string | URL): ShareLink {\n const url = toUrl(input)\n\n let pathname = url.pathname\n if (!pathname.startsWith('/r/')) {\n // LIFF permalink tolerance — only after the direct parse failed. An\n // indexOf hit is always a whole `r` segment (the match is slash-\n // delimited on both sides), so this cannot fire mid-segment.\n const idx = pathname.indexOf('/r/')\n if (idx === -1) {\n throw new ShareLinkParseError(\n 'UNKNOWN_PREFIX',\n `path \"${pathname}\" has no /r/ share prefix`,\n )\n }\n pathname = pathname.slice(idx)\n }\n\n // \"/r/{vault}/{collection}/{record}\" splits into exactly 5 parts:\n // ['', 'r', vault, collection, record].\n const rawSegments = pathname.split('/')\n if (rawSegments.length !== 5) {\n throw new ShareLinkParseError(\n 'MALFORMED_PATH',\n `expected /r/{vaultHandle}/{collection}/{recordId} — got ${rawSegments.length - 2} segment(s) after /r/`,\n )\n }\n\n const vaultHandle = decodePathSegment(rawSegments[2]!, 'vault handle')\n if (!isULID(vaultHandle)) {\n throw new ShareLinkParseError(\n 'INVALID_VAULT_HANDLE',\n `vault handle \"${vaultHandle}\" is not a ULID`,\n )\n }\n const collection = decodePathSegment(rawSegments[3]!, 'collection')\n const recordId = decodePathSegment(rawSegments[4]!, 'record id')\n\n // Query — manual parse of url.search (NOT URLSearchParams, whose\n // form-urlencoded rules decode \"+\" as a space and hide duplicates).\n let period: string | undefined\n let version: number | undefined\n if (url.search !== '') {\n const seen = new Set<string>()\n for (const pair of url.search.slice(1).split('&')) {\n const eq = pair.indexOf('=')\n if (eq <= 0) {\n throw new ShareLinkParseError('MALFORMED_QUERY', `query pair \"${pair}\" is not key=value`)\n }\n const key = pair.slice(0, eq)\n const rawValue = pair.slice(eq + 1)\n if (key !== 'period' && key !== 'v') {\n throw new ShareLinkParseError('UNKNOWN_QUERY_KEY', `unknown query key \"${key}\"`)\n }\n if (seen.has(key)) {\n throw new ShareLinkParseError('DUPLICATE_QUERY_KEY', `duplicate query key \"${key}\"`)\n }\n seen.add(key)\n if (key === 'period') {\n period = decodeCanonical(rawValue, 'MALFORMED_QUERY', 'period', {\n encode: encodeQueryValue,\n })\n } else {\n version = parseVersion(rawValue)\n }\n }\n }\n\n // Fragment — the ONLY place a grant token may appear.\n let grantToken: string | undefined\n if (url.hash !== '' && url.hash !== '#') {\n const fragment = url.hash.slice(1)\n if (!fragment.startsWith('g=')) {\n throw new ShareLinkParseError(\n 'MALFORMED_FRAGMENT',\n 'fragment is not a g={token} grant carrier',\n )\n }\n grantToken = decodeCanonical(fragment.slice(2), 'MALFORMED_FRAGMENT', 'grant token', {\n redact: true,\n })\n }\n\n return {\n vaultHandle,\n collection,\n recordId,\n ...(period !== undefined ? { period } : {}),\n ...(version !== undefined ? { version } : {}),\n ...(grantToken !== undefined ? { grantToken } : {}),\n }\n}\n\n/** Build-side part validation. Returns the (narrowed) string value. */\nfunction requirePart(value: unknown, what: string, dotSegmentRefused = false): string {\n if (typeof value !== 'string' || value === '') {\n throw new ShareLinkParseError('INVALID_PARTS', `${what} must be a non-empty string`)\n }\n if (dotSegmentRefused && (value === '.' || value === '..')) {\n throw new ShareLinkParseError('INVALID_PARTS', `${what}: \"${value}\" is a dot segment`)\n }\n return value\n}\n\n/**\n * Build a RELATIVE share link (`/r/…`) from typed parts. Callers prefix\n * their surface origin — the LIFF permalink base, the PWA origin, or the\n * vendor console — either themselves or via `base`, which is prepended\n * VERBATIM (no validation, no joining logic; pass it without a trailing\n * slash, e.g. `https://liff.line.me/{liffId}`).\n *\n * The grant token, when present, is emitted ONLY as the `#g=` fragment.\n * Invalid parts throw {@link ShareLinkParseError} with code\n * `'INVALID_PARTS'` — every built link is guaranteed to round-trip\n * through {@link parseShareLink}.\n */\nexport function buildShareLink(parts: ShareLinkParts, base?: string): string {\n const { vaultHandle, collection, recordId, period, version, grantToken } = parts\n\n if (typeof vaultHandle !== 'string' || !isULID(vaultHandle)) {\n throw new ShareLinkParseError('INVALID_PARTS', 'vaultHandle is not a ULID')\n }\n const path = `/r/${vaultHandle}/${encodeURIComponent(\n requirePart(collection, 'collection', true),\n )}/${encodeURIComponent(requirePart(recordId, 'recordId', true))}`\n\n const query: string[] = []\n if (period !== undefined) {\n query.push(`period=${encodeQueryValue(requirePart(period, 'period'))}`)\n }\n if (version !== undefined) {\n if (typeof version !== 'number' || !Number.isSafeInteger(version) || version < 0) {\n throw new ShareLinkParseError('INVALID_PARTS', 'version must be a non-negative safe integer')\n }\n query.push(`v=${version}`)\n }\n\n const fragment =\n grantToken !== undefined\n ? `#g=${encodeURIComponent(requirePart(grantToken, 'grantToken'))}`\n : ''\n\n return (base ?? '') + path + (query.length > 0 ? `?${query.join('&')}` : '') + fragment\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/share-link/share-link.ts"],"sourcesContent":["/**\n * Portal share-link grammar + parse/build helper (#806).\n *\n * ONE canonical link grammar addresses `vault / period / collection /\n * record` across all three portal surfaces — the LIFF permalink path\n * (`https://liff.line.me/{liffId}/r/…`), the installed-PWA same-origin\n * path (`https://{portal-origin}/r/…`), and the vendor backend console.\n * The link SHAPE is identical everywhere; only the origin prefix\n * differs, and callers own the prefix.\n *\n * ## Grammar (frozen — see `__tests__/share-link-surface.golden.json`)\n *\n * ```\n * share-link = [ base ] \"/r/\" vault \"/\" collection \"/\" record\n * [ \"?\" query ] [ \"#\" fragment ]\n * vault = ULID ; 26 uppercase Crockford base32 chars\n * collection = segment ; opaque, non-empty after decode\n * record = segment ; opaque, non-empty after decode\n * query = pair *( \"&\" pair ) ; keys unique; ONLY these two keys\n * pair = \"period=\" component ; opaque period id\n * / \"v=\" version ; record version\n * version = \"0\" / %x31-39 *DIGIT ; base-10 integer, ≤ 2^53 − 1\n * fragment = \"g=\" component ; single-use grant token (opaque)\n * ```\n *\n * `segment` / `component` are strict-canonical `encodeURIComponent`\n * output: decoding then re-encoding MUST reproduce the raw text exactly\n * (uppercase-hex escapes, `+` is a literal plus never a space, `%2F`\n * inside a segment never creates an extra segment). Path segments\n * additionally may not decode to `.` or `..`. Links produced by\n * {@link buildShareLink} always satisfy this; hand-written links using\n * only unreserved characters (`inv-001`, ULIDs, …) do too.\n *\n * ## Transport rule for the grant token\n *\n * The optional single-use grant token travels ONLY in the URL fragment\n * (`#g={token}`) — never in the path or query — because fragments are\n * not sent to servers, so the token never reaches server logs, CDNs, or\n * LINE's infrastructure. Same rule `on-magic-link` follows. The token is\n * fully OPAQUE here: no validation beyond non-empty, no decoding of its\n * semantics — grant semantics live in `on-magic-link` (not imported).\n *\n * ## Fail closed\n *\n * {@link parseShareLink} throws a typed {@link ShareLinkParseError} for\n * ANYTHING that is not exactly the grammar above — unknown prefix,\n * missing/extra/empty/malformed segments, a non-ULID vault handle,\n * duplicate or unknown query keys, malformed fragments. There is never a\n * default-vault (or any other) fallback; the app maps the error to a 404.\n *\n * Zero dependencies on the hub floor: pure string/`URL` code, no crypto,\n * no store — LIFF shells, PWAs, and klum-db can import this subpath\n * without dragging anything else. (The one internal import is the pure,\n * leaf ULID shape check shared with the pod format.)\n */\n\nimport { isULID } from '../with-pod/ulid.js'\n\n/** Machine-readable {@link ShareLinkParseError} discriminant. */\nexport type ShareLinkErrorCode =\n | 'MALFORMED_URL'\n | 'UNKNOWN_PREFIX'\n | 'MALFORMED_PATH'\n | 'MALFORMED_SEGMENT'\n | 'INVALID_VAULT_HANDLE'\n | 'MALFORMED_QUERY'\n | 'DUPLICATE_QUERY_KEY'\n | 'UNKNOWN_QUERY_KEY'\n | 'INVALID_VERSION'\n | 'MALFORMED_FRAGMENT'\n | 'INVALID_PARTS'\n\n/**\n * Typed failure for the share-link grammar. Thrown by\n * {@link parseShareLink} for any input outside the grammar (fail closed —\n * map to an app-level 404, never a default-vault fallback), and by\n * {@link buildShareLink} with code `'INVALID_PARTS'` when the caller\n * hands it parts that could not round-trip.\n *\n * Standalone (`extends Error`, not `NoydbError`) so this subpath stays\n * import-light; the `code` field follows the same machine-readable\n * convention as the kernel error classes.\n */\nexport class ShareLinkParseError extends Error {\n /** Machine-readable error code. Stable across library versions. */\n readonly code: ShareLinkErrorCode\n\n constructor(code: ShareLinkErrorCode, message: string) {\n super(message)\n this.name = 'ShareLinkParseError'\n this.code = code\n }\n}\n\n/**\n * The logical address a share link carries. Input to\n * {@link buildShareLink}; {@link parseShareLink} returns the same shape\n * (as the readonly {@link ShareLink} alias) with optional fields OMITTED\n * (not `undefined`) when absent from the link.\n */\nexport interface ShareLinkParts {\n /** Vault handle — a 26-character uppercase Crockford-base32 ULID. */\n vaultHandle: string\n /** Collection name — opaque, non-empty. */\n collection: string\n /** Record id — opaque, non-empty. */\n recordId: string\n /** Optional period id (`?period=`) — opaque, non-empty. */\n period?: string\n /** Optional record version (`?v=`) — non-negative safe integer. */\n version?: number\n /**\n * Optional single-use grant token (`#g=`) — opaque, non-empty. Carried\n * ONLY in the URL fragment; semantics belong to `on-magic-link`.\n */\n grantToken?: string\n}\n\n/** A successfully parsed share link. See {@link ShareLinkParts}. */\nexport type ShareLink = Readonly<ShareLinkParts>\n\n// Dummy base for parsing bare `/r/…` paths with the WHATWG URL parser.\n// `.invalid` is reserved (RFC 2606) — it can never collide with a real\n// origin, and the host is discarded anyway (only path/query/fragment\n// matter to the grammar).\nconst DUMMY_BASE = 'https://share-link.invalid'\n\n/**\n * Canonical encoding of a QUERY value. `encodeURIComponent`, plus `'`\n * → `%27`: the WHATWG URL parser percent-encodes the apostrophe inside\n * the query component of special (http/https) URLs — if we emitted a\n * raw `'`, any URL-object round-trip (browser address bar, `new URL`)\n * would rewrite the link into a form the strict parser then rejects.\n * Path and fragment components don't need this — every character the\n * URL parser escapes there is already escaped by `encodeURIComponent`.\n */\nfunction encodeQueryValue(value: string): string {\n return encodeURIComponent(value).replace(/'/g, '%27')\n}\n\n/**\n * Strict-canonical decode: `raw` must be exactly what the given\n * canonical `encode` produces for the decoded text — no `+`-for-space,\n * no lowercase hex escapes, no unencoded reserved characters,\n * non-empty. `redact` keeps the raw text out of the error message\n * (grant tokens).\n */\nfunction decodeCanonical(\n raw: string,\n code: ShareLinkErrorCode,\n what: string,\n { redact = false, encode = encodeURIComponent }: {\n redact?: boolean\n encode?: (value: string) => string\n } = {},\n): string {\n const shown = redact ? '(redacted)' : `\"${raw}\"`\n let decoded: string\n try {\n decoded = decodeURIComponent(raw)\n } catch {\n throw new ShareLinkParseError(code, `${what}: malformed percent-encoding in ${shown}`)\n }\n if (decoded === '') {\n throw new ShareLinkParseError(code, `${what}: empty`)\n }\n if (encode(decoded) !== raw) {\n throw new ShareLinkParseError(\n code,\n `${what}: ${shown} is not canonical encodeURIComponent output`,\n )\n }\n return decoded\n}\n\n/** Path-segment decode — canonical, plus the dot-segment refusal. */\nfunction decodePathSegment(raw: string, what: string): string {\n const decoded = decodeCanonical(raw, 'MALFORMED_SEGMENT', what)\n if (decoded === '.' || decoded === '..') {\n throw new ShareLinkParseError('MALFORMED_SEGMENT', `${what}: \"${decoded}\" is a dot segment`)\n }\n return decoded\n}\n\n/** `v=` value → non-negative safe integer. Canonical base-10 only. */\nfunction parseVersion(raw: string): number {\n if (!/^(?:0|[1-9][0-9]*)$/.test(raw)) {\n throw new ShareLinkParseError(\n 'INVALID_VERSION',\n `version \"${raw}\" is not a canonical base-10 integer`,\n )\n }\n const version = Number(raw)\n if (!Number.isSafeInteger(version)) {\n throw new ShareLinkParseError('INVALID_VERSION', `version \"${raw}\" exceeds 2^53 - 1`)\n }\n return version\n}\n\n/** Resolve string | URL input to a URL object, fail closed. */\nfunction toUrl(input: string | URL): URL {\n if (input instanceof URL) {\n if (input.protocol !== 'http:' && input.protocol !== 'https:') {\n throw new ShareLinkParseError(\n 'UNKNOWN_PREFIX',\n `unsupported protocol \"${input.protocol}\" — share links are http(s) URLs or bare /r/ paths`,\n )\n }\n return input\n }\n if (input.startsWith('/')) {\n try {\n return new URL(input, DUMMY_BASE)\n } catch {\n throw new ShareLinkParseError('MALFORMED_URL', 'unparseable share path')\n }\n }\n if (/^https?:\\/\\//i.test(input)) {\n try {\n return new URL(input)\n } catch {\n throw new ShareLinkParseError('MALFORMED_URL', 'unparseable share URL')\n }\n }\n throw new ShareLinkParseError(\n 'UNKNOWN_PREFIX',\n 'input is neither a \"/\"-rooted share path nor an http(s) URL',\n )\n}\n\n/**\n * Parse a share link — full URL, `URL` instance, or bare `/r/…` path.\n *\n * A LIFF permalink prefix is tolerated: when the path does not start\n * with `/r/` directly, the share path is taken from the FIRST `/r/`\n * segment boundary (`https://liff.line.me/{liffId}/r/…` → `/r/…`). No\n * fuzzy matching beyond that single rule.\n *\n * Fail closed: anything outside the grammar throws\n * {@link ShareLinkParseError}. Never a default-vault fallback.\n */\nexport function parseShareLink(input: string | URL): ShareLink {\n const url = toUrl(input)\n\n let pathname = url.pathname\n if (!pathname.startsWith('/r/')) {\n // LIFF permalink tolerance — only after the direct parse failed. An\n // indexOf hit is always a whole `r` segment (the match is slash-\n // delimited on both sides), so this cannot fire mid-segment.\n const idx = pathname.indexOf('/r/')\n if (idx === -1) {\n throw new ShareLinkParseError(\n 'UNKNOWN_PREFIX',\n `path \"${pathname}\" has no /r/ share prefix`,\n )\n }\n pathname = pathname.slice(idx)\n }\n\n // \"/r/{vault}/{collection}/{record}\" splits into exactly 5 parts:\n // ['', 'r', vault, collection, record].\n const rawSegments = pathname.split('/')\n if (rawSegments.length !== 5) {\n throw new ShareLinkParseError(\n 'MALFORMED_PATH',\n `expected /r/{vaultHandle}/{collection}/{recordId} — got ${rawSegments.length - 2} segment(s) after /r/`,\n )\n }\n\n const vaultHandle = decodePathSegment(rawSegments[2]!, 'vault handle')\n if (!isULID(vaultHandle)) {\n throw new ShareLinkParseError(\n 'INVALID_VAULT_HANDLE',\n `vault handle \"${vaultHandle}\" is not a ULID`,\n )\n }\n const collection = decodePathSegment(rawSegments[3]!, 'collection')\n const recordId = decodePathSegment(rawSegments[4]!, 'record id')\n\n // Query — manual parse of url.search (NOT URLSearchParams, whose\n // form-urlencoded rules decode \"+\" as a space and hide duplicates).\n let period: string | undefined\n let version: number | undefined\n if (url.search !== '') {\n const seen = new Set<string>()\n for (const pair of url.search.slice(1).split('&')) {\n const eq = pair.indexOf('=')\n if (eq <= 0) {\n throw new ShareLinkParseError('MALFORMED_QUERY', `query pair \"${pair}\" is not key=value`)\n }\n const key = pair.slice(0, eq)\n const rawValue = pair.slice(eq + 1)\n if (key !== 'period' && key !== 'v') {\n throw new ShareLinkParseError('UNKNOWN_QUERY_KEY', `unknown query key \"${key}\"`)\n }\n if (seen.has(key)) {\n throw new ShareLinkParseError('DUPLICATE_QUERY_KEY', `duplicate query key \"${key}\"`)\n }\n seen.add(key)\n if (key === 'period') {\n period = decodeCanonical(rawValue, 'MALFORMED_QUERY', 'period', {\n encode: encodeQueryValue,\n })\n } else {\n version = parseVersion(rawValue)\n }\n }\n }\n\n // Fragment — the ONLY place a grant token may appear.\n let grantToken: string | undefined\n if (url.hash !== '' && url.hash !== '#') {\n const fragment = url.hash.slice(1)\n if (!fragment.startsWith('g=')) {\n throw new ShareLinkParseError(\n 'MALFORMED_FRAGMENT',\n 'fragment is not a g={token} grant carrier',\n )\n }\n grantToken = decodeCanonical(fragment.slice(2), 'MALFORMED_FRAGMENT', 'grant token', {\n redact: true,\n })\n }\n\n return {\n vaultHandle,\n collection,\n recordId,\n ...(period !== undefined ? { period } : {}),\n ...(version !== undefined ? { version } : {}),\n ...(grantToken !== undefined ? { grantToken } : {}),\n }\n}\n\n/** Build-side part validation. Returns the (narrowed) string value. */\nfunction requirePart(value: unknown, what: string, dotSegmentRefused = false): string {\n if (typeof value !== 'string' || value === '') {\n throw new ShareLinkParseError('INVALID_PARTS', `${what} must be a non-empty string`)\n }\n if (dotSegmentRefused && (value === '.' || value === '..')) {\n throw new ShareLinkParseError('INVALID_PARTS', `${what}: \"${value}\" is a dot segment`)\n }\n return value\n}\n\n/**\n * Build a RELATIVE share link (`/r/…`) from typed parts. Callers prefix\n * their surface origin — the LIFF permalink base, the PWA origin, or the\n * vendor console — either themselves or via `base`, which is prepended\n * VERBATIM (no validation, no joining logic; pass it without a trailing\n * slash, e.g. `https://liff.line.me/{liffId}`).\n *\n * The grant token, when present, is emitted ONLY as the `#g=` fragment.\n * Invalid parts throw {@link ShareLinkParseError} with code\n * `'INVALID_PARTS'` — every built link is guaranteed to round-trip\n * through {@link parseShareLink}.\n */\nexport function buildShareLink(parts: ShareLinkParts, base?: string): string {\n const { vaultHandle, collection, recordId, period, version, grantToken } = parts\n\n if (typeof vaultHandle !== 'string' || !isULID(vaultHandle)) {\n throw new ShareLinkParseError('INVALID_PARTS', 'vaultHandle is not a ULID')\n }\n const path = `/r/${vaultHandle}/${encodeURIComponent(\n requirePart(collection, 'collection', true),\n )}/${encodeURIComponent(requirePart(recordId, 'recordId', true))}`\n\n const query: string[] = []\n if (period !== undefined) {\n query.push(`period=${encodeQueryValue(requirePart(period, 'period'))}`)\n }\n if (version !== undefined) {\n if (typeof version !== 'number' || !Number.isSafeInteger(version) || version < 0) {\n throw new ShareLinkParseError('INVALID_PARTS', 'version must be a non-negative safe integer')\n }\n query.push(`v=${version}`)\n }\n\n const fragment =\n grantToken !== undefined\n ? `#g=${encodeURIComponent(requirePart(grantToken, 'grantToken'))}`\n : ''\n\n return (base ?? '') + path + (query.length > 0 ? `?${query.join('&')}` : '') + fragment\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ATTESTATIONS_COLLECTION,
|
|
3
|
+
REVOKED_RECORD_ID,
|
|
4
|
+
SIGNER_RECORD_ID,
|
|
5
|
+
loadOrCreateSigner,
|
|
6
|
+
loadSigner
|
|
7
|
+
} from "./chunk-ANATMFIO.js";
|
|
8
|
+
import "./chunk-77K4PG6P.js";
|
|
9
|
+
import "./chunk-SZJ3DLZA.js";
|
|
10
|
+
import "./chunk-BOWNCWZT.js";
|
|
11
|
+
import "./chunk-YSRUYAVU.js";
|
|
12
|
+
import "./chunk-RMF67KCG.js";
|
|
13
|
+
import "./chunk-LP7BEXCT.js";
|
|
14
|
+
import "./chunk-SL3JAAKN.js";
|
|
15
|
+
import "./chunk-7MG7SJT7.js";
|
|
16
|
+
import "./chunk-7O3Y4PPD.js";
|
|
17
|
+
export {
|
|
18
|
+
ATTESTATIONS_COLLECTION,
|
|
19
|
+
REVOKED_RECORD_ID,
|
|
20
|
+
SIGNER_RECORD_ID,
|
|
21
|
+
loadOrCreateSigner,
|
|
22
|
+
loadSigner
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=signer-WAMDZHEN.js.map
|
package/dist/snapshots/index.js
CHANGED
|
@@ -4,20 +4,19 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
readPod,
|
|
6
6
|
writePod
|
|
7
|
-
} from "../chunk-
|
|
8
|
-
import "../chunk-
|
|
9
|
-
import "../chunk-
|
|
10
|
-
import "../chunk-
|
|
11
|
-
import "../chunk-
|
|
12
|
-
import "../chunk-
|
|
13
|
-
import "../chunk-
|
|
7
|
+
} from "../chunk-5WDXHNSW.js";
|
|
8
|
+
import "../chunk-EQEYOJBM.js";
|
|
9
|
+
import "../chunk-77K4PG6P.js";
|
|
10
|
+
import "../chunk-SZJ3DLZA.js";
|
|
11
|
+
import "../chunk-BOWNCWZT.js";
|
|
12
|
+
import "../chunk-YSRUYAVU.js";
|
|
13
|
+
import "../chunk-RMF67KCG.js";
|
|
14
14
|
import "../chunk-LP7BEXCT.js";
|
|
15
|
-
import "../chunk-
|
|
16
|
-
import "../chunk-
|
|
15
|
+
import "../chunk-SL3JAAKN.js";
|
|
16
|
+
import "../chunk-7MG7SJT7.js";
|
|
17
17
|
import {
|
|
18
18
|
SnapshotNotFoundError
|
|
19
|
-
} from "../chunk-
|
|
20
|
-
import "../chunk-PZ5AY32C.js";
|
|
19
|
+
} from "../chunk-7O3Y4PPD.js";
|
|
21
20
|
|
|
22
21
|
// src/with-fork/snapshots/engine.ts
|
|
23
22
|
var SnapshotEngine = class {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/with-fork/snapshots/engine.ts","../../src/with-fork/snapshots/active.ts"],"sourcesContent":["import { writePod, readPod } from '../../with-pod/pod.js'\nimport { SnapshotNotFoundError } from '../../kernel/errors.js'\nimport type { NoydbPodStore } from '../../kernel/types.js'\nimport type { Vault } from '../../kernel/vault.js'\nimport type { SnapshotMeta, RetentionPolicy, SnapshotIndex } from './strategy.js'\n\nexport class SnapshotEngine {\n constructor(\n private readonly store: NoydbPodStore,\n private readonly retention: RetentionPolicy,\n ) {}\n\n private indexKey(vaultName: string): string {\n return `${vaultName}__index`\n }\n\n private snapKey(vaultName: string, n: number): string {\n return `${vaultName}__snap_${n.toString().padStart(6, '0')}`\n }\n\n private autoKey(vaultName: string): string {\n return `${vaultName}__auto`\n }\n\n private async readIndex(\n vaultName: string,\n ): Promise<{ index: SnapshotIndex; indexVersion: string | null }> {\n const result = await this.store.readBundle(this.indexKey(vaultName))\n if (!result) return { index: { snapshots: [], nextCounter: 1 }, indexVersion: null }\n const text = new TextDecoder().decode(result.bytes)\n return { index: JSON.parse(text) as SnapshotIndex, indexVersion: result.version }\n }\n\n private async writeIndex(\n vaultName: string,\n index: SnapshotIndex,\n expectedVersion: string | null,\n ): Promise<void> {\n const bytes = new TextEncoder().encode(JSON.stringify(index))\n await this.store.writeBundle(this.indexKey(vaultName), bytes, expectedVersion)\n }\n\n async snapshot(\n vault: Vault,\n by: string,\n opts?: { label?: string; note?: string },\n ): Promise<SnapshotMeta> {\n const bytes = await writePod(vault, {})\n const { index, indexVersion } = await this.readIndex(vault.name)\n const key = this.snapKey(vault.name, index.nextCounter)\n\n // Write blob first. If the subsequent index write fails, the next snapshot()\n // call will re-derive the same key (counter not persisted) and overwrite this blob.\n // This is an accepted v1 trade-off: failure window is narrow and requires a store\n // error between the two writes. A retry produces a fresh snapshot at the same key.\n await this.store.writeBundle(key, bytes, null)\n\n const meta: SnapshotMeta = {\n version: key,\n ...(opts?.label !== undefined ? { label: opts.label } : {}),\n ...(opts?.note !== undefined ? { note: opts.note } : {}),\n exportedAt: new Date().toISOString(),\n exportedBy: by,\n size: bytes.length,\n integrity: 'verified',\n }\n\n const newIndex: SnapshotIndex = {\n snapshots: [...index.snapshots, meta],\n nextCounter: index.nextCounter + 1,\n // Preserve the rolling auto slot — on-demand snapshots never touch it.\n ...(index.auto ? { auto: index.auto } : {}),\n }\n const toDelete = this.applyRetention(newIndex)\n await this.writeIndex(vault.name, newIndex, indexVersion)\n\n for (const k of toDelete) {\n await this.store.deleteBundle(k)\n }\n\n return meta\n }\n\n /**\n * Rolling auto-snapshot. Overwrites the single fixed `<vault>__auto` key and\n * stores its meta in `index.auto`, separate from the immutable `snapshots`\n * pool — retention never prunes it. Used by the cadence scheduler.\n */\n async autoSnapshot(\n vault: Vault,\n by: string,\n opts?: { label?: string; note?: string },\n ): Promise<SnapshotMeta> {\n const bytes = await writePod(vault, {})\n const { index, indexVersion } = await this.readIndex(vault.name)\n const key = this.autoKey(vault.name)\n\n // Unconditional overwrite of the rolling slot.\n await this.store.writeBundle(key, bytes, null)\n\n const meta: SnapshotMeta = {\n version: key,\n label: opts?.label ?? 'auto',\n ...(opts?.note !== undefined ? { note: opts.note } : {}),\n exportedAt: new Date().toISOString(),\n exportedBy: by,\n size: bytes.length,\n integrity: 'verified',\n auto: true,\n }\n\n index.auto = meta\n await this.writeIndex(vault.name, index, indexVersion)\n return meta\n }\n\n async listSnapshots(vaultId: string): Promise<SnapshotMeta[]> {\n const { index } = await this.readIndex(vaultId)\n const immutable = [...index.snapshots].reverse()\n return index.auto ? [index.auto, ...immutable] : immutable\n }\n\n async restoreSnapshot(vault: Vault, version: string): Promise<void> {\n if (!version.startsWith(`${vault.name}__`)) throw new SnapshotNotFoundError(version)\n const result = await this.store.readBundle(version)\n if (!result) throw new SnapshotNotFoundError(version)\n const { dumpJson } = await readPod(result.bytes)\n await vault.load(dumpJson)\n }\n\n /**\n * Applies the configured retention policy to `index`, mutating `index.snapshots`\n * in place and returning the blob keys that should be deleted from the store.\n * Called by `snapshot()` before the index is written.\n *\n * @internal — public for direct testing only\n */\n applyRetention(index: SnapshotIndex): string[] {\n const prune = this.retention.prune ?? true\n if (!prune) return []\n\n const toDelete: string[] = []\n let remaining = index.snapshots.slice()\n\n if (this.retention.keepLast !== undefined && remaining.length > this.retention.keepLast) {\n const excess = remaining.splice(0, remaining.length - this.retention.keepLast)\n toDelete.push(...excess.map(m => m.version))\n }\n\n if (this.retention.maxAgeDays !== undefined) {\n const cutoffMs = this.retention.maxAgeDays * 86_400_000\n const now = Date.now()\n const fresh = remaining.filter(m => now - new Date(m.exportedAt).getTime() <= cutoffMs)\n toDelete.push(...remaining.filter(m => !fresh.includes(m)).map(m => m.version))\n remaining = fresh\n }\n\n index.snapshots = remaining\n return toDelete\n }\n}\n","import { SnapshotEngine } from './engine.js'\nimport type { SnapshotsStrategy, RetentionPolicy } from './strategy.js'\nimport type { SnapshotPolicy } from './policy.js'\nimport type { NoydbPodStore } from '../../kernel/types.js'\nimport type { Vault } from '../../kernel/vault.js'\n\nexport interface WithSnapshotsOptions {\n /** Bundle store where snapshot blobs and the sidecar index are written. */\n store: NoydbPodStore\n /**\n * Declarative retention policy. Enforced eagerly after each on-demand `snapshot()`.\n * Defaults to no retention (all on-demand snapshots kept forever). Never affects\n * the rolling auto-snapshot.\n */\n retention?: RetentionPolicy\n /**\n * Automatic-snapshot cadence. Default `mode:'manual'` ⇒ no timers; snapshots\n * stay on-demand. Set `mode:'debounce'`/`'interval'` to enable auto-snapshots\n * to the rolling `<vault>__auto` key.\n */\n snapshotPolicy?: SnapshotPolicy\n}\n\nexport function withSnapshots(opts: WithSnapshotsOptions): SnapshotsStrategy {\n const engine = new SnapshotEngine(opts.store, opts.retention ?? {})\n return {\n snapshot(vault, by, snapOpts) {\n return engine.snapshot(vault as Vault, by, snapOpts)\n },\n listSnapshots(vaultId) {\n return engine.listSnapshots(vaultId)\n },\n restoreSnapshot(vault, version) {\n return engine.restoreSnapshot(vault as Vault, version)\n },\n autoSnapshot(vault, by, snapOpts) {\n return engine.autoSnapshot(vault as Vault, by, snapOpts)\n },\n ...(opts.snapshotPolicy ? { policy: opts.snapshotPolicy } : {}),\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/with-fork/snapshots/engine.ts","../../src/with-fork/snapshots/active.ts"],"sourcesContent":["import { writePod, readPod } from '../../with-pod/pod.js'\nimport { SnapshotNotFoundError } from '../../kernel/errors.js'\nimport type { NoydbPodStore } from '../../kernel/types.js'\nimport type { Vault } from '../../kernel/vault.js'\nimport type { SnapshotMeta, RetentionPolicy, SnapshotIndex } from './strategy.js'\n\nexport class SnapshotEngine {\n constructor(\n private readonly store: NoydbPodStore,\n private readonly retention: RetentionPolicy,\n ) {}\n\n private indexKey(vaultName: string): string {\n return `${vaultName}__index`\n }\n\n private snapKey(vaultName: string, n: number): string {\n return `${vaultName}__snap_${n.toString().padStart(6, '0')}`\n }\n\n private autoKey(vaultName: string): string {\n return `${vaultName}__auto`\n }\n\n private async readIndex(\n vaultName: string,\n ): Promise<{ index: SnapshotIndex; indexVersion: string | null }> {\n const result = await this.store.readBundle(this.indexKey(vaultName))\n if (!result) return { index: { snapshots: [], nextCounter: 1 }, indexVersion: null }\n const text = new TextDecoder().decode(result.bytes)\n return { index: JSON.parse(text) as SnapshotIndex, indexVersion: result.version }\n }\n\n private async writeIndex(\n vaultName: string,\n index: SnapshotIndex,\n expectedVersion: string | null,\n ): Promise<void> {\n const bytes = new TextEncoder().encode(JSON.stringify(index))\n await this.store.writeBundle(this.indexKey(vaultName), bytes, expectedVersion)\n }\n\n async snapshot(\n vault: Vault,\n by: string,\n opts?: { label?: string; note?: string },\n ): Promise<SnapshotMeta> {\n const bytes = await writePod(vault, {})\n const { index, indexVersion } = await this.readIndex(vault.name)\n const key = this.snapKey(vault.name, index.nextCounter)\n\n // Write blob first. If the subsequent index write fails, the next snapshot()\n // call will re-derive the same key (counter not persisted) and overwrite this blob.\n // This is an accepted v1 trade-off: failure window is narrow and requires a store\n // error between the two writes. A retry produces a fresh snapshot at the same key.\n await this.store.writeBundle(key, bytes, null)\n\n const meta: SnapshotMeta = {\n version: key,\n ...(opts?.label !== undefined ? { label: opts.label } : {}),\n ...(opts?.note !== undefined ? { note: opts.note } : {}),\n exportedAt: new Date().toISOString(),\n exportedBy: by,\n size: bytes.length,\n integrity: 'verified',\n }\n\n const newIndex: SnapshotIndex = {\n snapshots: [...index.snapshots, meta],\n nextCounter: index.nextCounter + 1,\n // Preserve the rolling auto slot — on-demand snapshots never touch it.\n ...(index.auto ? { auto: index.auto } : {}),\n }\n const toDelete = this.applyRetention(newIndex)\n await this.writeIndex(vault.name, newIndex, indexVersion)\n\n for (const k of toDelete) {\n await this.store.deleteBundle(k)\n }\n\n return meta\n }\n\n /**\n * Rolling auto-snapshot. Overwrites the single fixed `<vault>__auto` key and\n * stores its meta in `index.auto`, separate from the immutable `snapshots`\n * pool — retention never prunes it. Used by the cadence scheduler.\n */\n async autoSnapshot(\n vault: Vault,\n by: string,\n opts?: { label?: string; note?: string },\n ): Promise<SnapshotMeta> {\n const bytes = await writePod(vault, {})\n const { index, indexVersion } = await this.readIndex(vault.name)\n const key = this.autoKey(vault.name)\n\n // Unconditional overwrite of the rolling slot.\n await this.store.writeBundle(key, bytes, null)\n\n const meta: SnapshotMeta = {\n version: key,\n label: opts?.label ?? 'auto',\n ...(opts?.note !== undefined ? { note: opts.note } : {}),\n exportedAt: new Date().toISOString(),\n exportedBy: by,\n size: bytes.length,\n integrity: 'verified',\n auto: true,\n }\n\n index.auto = meta\n await this.writeIndex(vault.name, index, indexVersion)\n return meta\n }\n\n async listSnapshots(vaultId: string): Promise<SnapshotMeta[]> {\n const { index } = await this.readIndex(vaultId)\n const immutable = [...index.snapshots].reverse()\n return index.auto ? [index.auto, ...immutable] : immutable\n }\n\n async restoreSnapshot(vault: Vault, version: string): Promise<void> {\n if (!version.startsWith(`${vault.name}__`)) throw new SnapshotNotFoundError(version)\n const result = await this.store.readBundle(version)\n if (!result) throw new SnapshotNotFoundError(version)\n const { dumpJson } = await readPod(result.bytes)\n await vault.load(dumpJson)\n }\n\n /**\n * Applies the configured retention policy to `index`, mutating `index.snapshots`\n * in place and returning the blob keys that should be deleted from the store.\n * Called by `snapshot()` before the index is written.\n *\n * @internal — public for direct testing only\n */\n applyRetention(index: SnapshotIndex): string[] {\n const prune = this.retention.prune ?? true\n if (!prune) return []\n\n const toDelete: string[] = []\n let remaining = index.snapshots.slice()\n\n if (this.retention.keepLast !== undefined && remaining.length > this.retention.keepLast) {\n const excess = remaining.splice(0, remaining.length - this.retention.keepLast)\n toDelete.push(...excess.map(m => m.version))\n }\n\n if (this.retention.maxAgeDays !== undefined) {\n const cutoffMs = this.retention.maxAgeDays * 86_400_000\n const now = Date.now()\n const fresh = remaining.filter(m => now - new Date(m.exportedAt).getTime() <= cutoffMs)\n toDelete.push(...remaining.filter(m => !fresh.includes(m)).map(m => m.version))\n remaining = fresh\n }\n\n index.snapshots = remaining\n return toDelete\n }\n}\n","import { SnapshotEngine } from './engine.js'\nimport type { SnapshotsStrategy, RetentionPolicy } from './strategy.js'\nimport type { SnapshotPolicy } from './policy.js'\nimport type { NoydbPodStore } from '../../kernel/types.js'\nimport type { Vault } from '../../kernel/vault.js'\n\nexport interface WithSnapshotsOptions {\n /** Bundle store where snapshot blobs and the sidecar index are written. */\n store: NoydbPodStore\n /**\n * Declarative retention policy. Enforced eagerly after each on-demand `snapshot()`.\n * Defaults to no retention (all on-demand snapshots kept forever). Never affects\n * the rolling auto-snapshot.\n */\n retention?: RetentionPolicy\n /**\n * Automatic-snapshot cadence. Default `mode:'manual'` ⇒ no timers; snapshots\n * stay on-demand. Set `mode:'debounce'`/`'interval'` to enable auto-snapshots\n * to the rolling `<vault>__auto` key.\n */\n snapshotPolicy?: SnapshotPolicy\n}\n\nexport function withSnapshots(opts: WithSnapshotsOptions): SnapshotsStrategy {\n const engine = new SnapshotEngine(opts.store, opts.retention ?? {})\n return {\n snapshot(vault, by, snapOpts) {\n return engine.snapshot(vault as Vault, by, snapOpts)\n },\n listSnapshots(vaultId) {\n return engine.listSnapshots(vaultId)\n },\n restoreSnapshot(vault, version) {\n return engine.restoreSnapshot(vault as Vault, version)\n },\n autoSnapshot(vault, by, snapOpts) {\n return engine.autoSnapshot(vault as Vault, by, snapOpts)\n },\n ...(opts.snapshotPolicy ? { policy: opts.snapshotPolicy } : {}),\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -6,18 +6,17 @@ import {
|
|
|
6
6
|
isMVStale,
|
|
7
7
|
markMVStale,
|
|
8
8
|
resolveStaleMVOnRead
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-FMZY5Q6L.js";
|
|
10
10
|
import "./chunk-5D2ALSM5.js";
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-77K4PG6P.js";
|
|
12
|
+
import "./chunk-SZJ3DLZA.js";
|
|
13
|
+
import "./chunk-BOWNCWZT.js";
|
|
14
|
+
import "./chunk-YSRUYAVU.js";
|
|
15
|
+
import "./chunk-RMF67KCG.js";
|
|
16
16
|
import "./chunk-LP7BEXCT.js";
|
|
17
|
-
import "./chunk-
|
|
18
|
-
import "./chunk-
|
|
19
|
-
import "./chunk-
|
|
20
|
-
import "./chunk-PZ5AY32C.js";
|
|
17
|
+
import "./chunk-SL3JAAKN.js";
|
|
18
|
+
import "./chunk-7MG7SJT7.js";
|
|
19
|
+
import "./chunk-7O3Y4PPD.js";
|
|
21
20
|
export {
|
|
22
21
|
MV_STALE_COLLECTION,
|
|
23
22
|
clearMVStale,
|
|
@@ -27,4 +26,4 @@ export {
|
|
|
27
26
|
markMVStale,
|
|
28
27
|
resolveStaleMVOnRead
|
|
29
28
|
};
|
|
30
|
-
//# sourceMappingURL=stale-
|
|
29
|
+
//# sourceMappingURL=stale-DDL7WQEZ.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SCHEMAS_COLLECTION,
|
|
3
|
+
loadPersistedSchema,
|
|
4
|
+
loadPersistedSchemaEntry,
|
|
5
|
+
savePersistedSchema
|
|
6
|
+
} from "./chunk-2Y4IMKA6.js";
|
|
7
|
+
import "./chunk-77K4PG6P.js";
|
|
8
|
+
import "./chunk-SZJ3DLZA.js";
|
|
9
|
+
import "./chunk-BOWNCWZT.js";
|
|
10
|
+
import "./chunk-YSRUYAVU.js";
|
|
11
|
+
import "./chunk-RMF67KCG.js";
|
|
12
|
+
import "./chunk-LP7BEXCT.js";
|
|
13
|
+
import "./chunk-SL3JAAKN.js";
|
|
14
|
+
import "./chunk-7MG7SJT7.js";
|
|
15
|
+
import "./chunk-7O3Y4PPD.js";
|
|
16
|
+
export {
|
|
17
|
+
SCHEMAS_COLLECTION,
|
|
18
|
+
loadPersistedSchema,
|
|
19
|
+
loadPersistedSchemaEntry,
|
|
20
|
+
savePersistedSchema
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=storage-FLR73HHF.js.map
|
package/dist/store/index.js
CHANGED
package/dist/{store-coordination-provider-OLPNRMFO.js → store-coordination-provider-MEQ2MJQG.js}
RENAMED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadFence,
|
|
3
3
|
saveFence
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-453F654R.js";
|
|
5
|
+
import "./chunk-77K4PG6P.js";
|
|
6
6
|
import {
|
|
7
7
|
buildRecordEnvelope
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-SZJ3DLZA.js";
|
|
9
|
+
import "./chunk-BOWNCWZT.js";
|
|
10
|
+
import "./chunk-YSRUYAVU.js";
|
|
11
|
+
import "./chunk-RMF67KCG.js";
|
|
12
12
|
import "./chunk-LP7BEXCT.js";
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-PZ5AY32C.js";
|
|
13
|
+
import "./chunk-SL3JAAKN.js";
|
|
14
|
+
import "./chunk-7MG7SJT7.js";
|
|
15
|
+
import "./chunk-7O3Y4PPD.js";
|
|
17
16
|
|
|
18
17
|
// src/with-shape/schema-update/client-registry.ts
|
|
19
18
|
var META_COLLECTION = "_meta";
|
|
@@ -146,4 +145,4 @@ function unref(timer) {
|
|
|
146
145
|
export {
|
|
147
146
|
StoreMesh
|
|
148
147
|
};
|
|
149
|
-
//# sourceMappingURL=store-coordination-provider-
|
|
148
|
+
//# sourceMappingURL=store-coordination-provider-MEQ2MJQG.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/with-shape/schema-update/client-registry.ts","../src/with-shape/schema-update/store-coordination-provider.ts"],"sourcesContent":["/**\n * Schema-cutover client registry. Each client keeps a\n * heartbeat doc at `_meta/schema-fence:client:<clientId>` carrying its\n * liveness (`lastSeen`) and the fence generation it has quiesced for\n * (`quiescedAtVersion`). Plaintext envelope, like the fence doc.\n */\nimport type { NoydbStore } from '../../kernel/types.js'\nimport { buildRecordEnvelope } from '../../kernel/enclave/index.js'\n\nconst META_COLLECTION = '_meta'\nconst CLIENT_PREFIX = 'schema-fence:client:'\n\nexport interface ClientDoc {\n readonly clientId: string\n readonly lastSeen: number\n readonly quiescedAtVersion: number | null\n /**\n * Session that owns this writer (one user's writers across vaults). Additive\n * and optional so legacy client docs (written without it) keep parsing;\n * readers default it.\n */\n readonly sessionId?: string\n}\n\nexport async function writeClientDoc(\n store: NoydbStore,\n vault: string,\n clientId: string,\n doc: { lastSeen: number; quiescedAtVersion: number | null; sessionId?: string },\n): Promise<void> {\n const recordId = `${CLIENT_PREFIX}${clientId}`\n const envelope = buildRecordEnvelope(\n { collection: META_COLLECTION, id: recordId, version: 1 },\n { iv: '', data: JSON.stringify({ clientId, ...doc }) },\n )\n await store.put(vault, META_COLLECTION, recordId, envelope)\n}\n\nexport async function listClientDocs(store: NoydbStore, vault: string): Promise<ClientDoc[]> {\n const ids = await store.list(vault, META_COLLECTION)\n const out: ClientDoc[] = []\n for (const id of ids) {\n if (!id.startsWith(CLIENT_PREFIX)) continue\n const env = await store.get(vault, META_COLLECTION, id)\n if (!env) continue\n try {\n const parsed = JSON.parse(env._data) as unknown\n if (isClientDoc(parsed)) out.push(parsed)\n } catch { /* skip corrupt */ }\n }\n return out\n}\n\n/**\n * True when every *active* client (lastSeen within staleMs of now) has\n * `quiescedAtVersion === generation`. Stale clients are ignored. An empty\n * active set is vacuously quiesced.\n */\nexport async function activeQuiesced(\n store: NoydbStore,\n vault: string,\n opts: { generation: number; now: number; staleMs: number; excludeClientId?: string },\n): Promise<boolean> {\n const docs = await listClientDocs(store, vault)\n const active = docs.filter(\n d => d.lastSeen >= opts.now - opts.staleMs && d.clientId !== opts.excludeClientId,\n )\n return active.every(d => d.quiescedAtVersion === opts.generation)\n}\n\nfunction isClientDoc(x: unknown): x is ClientDoc {\n if (x === null || typeof x !== 'object') return false\n const o = x as Record<string, unknown>\n return typeof o['clientId'] === 'string'\n && typeof o['lastSeen'] === 'number'\n && (o['quiescedAtVersion'] === null || typeof o['quiescedAtVersion'] === 'number')\n && (o['sessionId'] === undefined || typeof o['sessionId'] === 'string')\n}\n","/**\n * Store-backed default {@link NoydbMesh}.\n *\n * Maps the five coordination-port methods onto today's `_meta/schema-fence`\n * store ops so that, with no `by-*` provider injected, the fence service\n * reproduces its current store-polling behavior byte-for-byte. The `observe*`\n * methods are poll-emit (the legacy {@link FenceWatcher} model); `by-tabs` /\n * `by-peer` override them with real-time push.\n *\n * @module\n */\n\nimport type { NoydbStore } from '../../kernel/types.js'\nimport type { Unsubscribe } from '../../port/with/write-hooks.js'\nimport { loadFence, saveFence, type FenceDoc } from './fence.js'\nimport { writeClientDoc, listClientDocs, type ClientDoc } from './client-registry.js'\nimport type { NoydbMesh, WriterPresence } from '../../port/by/types.js'\n\n/**\n * Default poll cadence for the `observe*` fallbacks (ms). Matches the\n * store-poll granularity the fence-controller uses while waiting for quorum\n * (`delay(50)`); the same value the migration cutover falls back to when no\n * real-time provider is injected.\n */\nconst DEFAULT_POLL_INTERVAL_MS = 50\n\n/** Map a stored {@link ClientDoc} to the port's {@link WriterPresence}. */\nfunction toPresence(doc: ClientDoc): WriterPresence {\n return {\n writerId: doc.clientId,\n // Legacy docs written without a sessionId (and explicit empty) have no\n // session — fall back to the writerId so every presence is session-addressable.\n sessionId: doc.sessionId && doc.sessionId.length > 0 ? doc.sessionId : doc.clientId,\n lastSeen: doc.lastSeen,\n quiescedAtVersion: doc.quiescedAtVersion,\n }\n}\n\nfunction fenceEqual(a: FenceDoc, b: FenceDoc): boolean {\n return a.currentSchemaVersion === b.currentSchemaVersion && a.fenceState === b.fenceState\n}\n\nfunction presenceListEqual(a: readonly WriterPresence[], b: readonly WriterPresence[]): boolean {\n if (a.length !== b.length) return false\n const key = (w: WriterPresence) => `${w.writerId}\\u0000${w.sessionId}\\u0000${w.lastSeen}\\u0000${String(w.quiescedAtVersion)}`\n const bk = new Set(b.map(key))\n return a.every((w) => bk.has(key(w)))\n}\n\nexport class StoreMesh implements NoydbMesh {\n readonly #store: NoydbStore\n readonly #pollIntervalMs: number\n\n constructor(store: NoydbStore, opts?: { pollIntervalMs?: number }) {\n this.#store = store\n this.#pollIntervalMs = opts?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS\n }\n\n async setFence(vault: string, fence: FenceDoc): Promise<void> {\n // RE-READ AND SPREAD, never write the caller's doc whole (#1197).\n //\n // `saveFence` serialises what it is given and does a full `store.put` — no\n // merge. Callers legitimately construct a PARTIAL doc: `FenceController`\n // builds `{ currentSchemaVersion, fenceState }`, which is a valid `FenceDoc`\n // because `schemaHash` is optional. Writing that whole silently erased the\n // hash #946 added, so \"which schema is generation N is answerable from\n // schemaFenceState() alone\" held only until the first drain.\n //\n // The type cannot see the loss: for an optional field, \"absent\" and\n // \"deliberately cleared\" are the same value.\n //\n // Mirrors `persisted-schemas/register.ts:136`, which re-reads for the same\n // reason and documents it — a stale snapshot would roll back a concurrent\n // cutover's version. Re-reading NARROWS that window rather than closing it;\n // fence transitions are orchestrator-driven and serialised, so the residual\n // is the same one that path already accepts.\n const current = await loadFence(this.#store, vault)\n await saveFence(this.#store, vault, { ...current, ...fence })\n }\n\n async readFence(vault: string): Promise<FenceDoc> {\n return loadFence(this.#store, vault)\n }\n\n observeFence(vault: string, onChange: (f: FenceDoc) => void): Unsubscribe {\n let last: FenceDoc | null = null\n let busy = false\n const poll = async () => {\n if (busy) return\n busy = true\n try {\n const fence = await loadFence(this.#store, vault)\n if (last === null || !fenceEqual(last, fence)) {\n last = fence\n onChange(fence)\n }\n } catch {\n /* transient store error — retry next tick */\n } finally {\n busy = false\n }\n }\n void poll() // emit current state once immediately\n const timer = setInterval(() => void poll(), this.#pollIntervalMs)\n unref(timer)\n return () => clearInterval(timer)\n }\n\n async reportPresence(vault: string, p: WriterPresence): Promise<void> {\n await writeClientDoc(this.#store, vault, p.writerId, {\n lastSeen: p.lastSeen,\n quiescedAtVersion: p.quiescedAtVersion,\n sessionId: p.sessionId,\n })\n }\n\n observePresence(vault: string, onChange: (writers: readonly WriterPresence[]) => void): Unsubscribe {\n let last: readonly WriterPresence[] | null = null\n let busy = false\n const poll = async () => {\n if (busy) return\n busy = true\n try {\n const docs = await listClientDocs(this.#store, vault)\n const writers = docs.map(toPresence)\n if (last === null || !presenceListEqual(last, writers)) {\n last = writers\n onChange(writers)\n }\n } catch {\n /* transient store error — retry next tick */\n } finally {\n busy = false\n }\n }\n void poll() // emit current set once immediately\n const timer = setInterval(() => void poll(), this.#pollIntervalMs)\n unref(timer)\n return () => clearInterval(timer)\n }\n\n async reachableWriters(vault: string, o: { staleMs: number; now: number }): Promise<readonly WriterPresence[]> {\n const docs = await listClientDocs(this.#store, vault)\n return docs.map(toPresence).filter((w) => o.now - w.lastSeen <= o.staleMs)\n }\n}\n\n/** Best-effort `unref` so a poll timer never holds the process open (Node only). */\nfunction unref(timer: ReturnType<typeof setInterval>): void {\n const t = timer as unknown as { unref?: () => void }\n if (typeof t.unref === 'function') t.unref()\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/with-shape/schema-update/client-registry.ts","../src/with-shape/schema-update/store-coordination-provider.ts"],"sourcesContent":["/**\n * Schema-cutover client registry. Each client keeps a\n * heartbeat doc at `_meta/schema-fence:client:<clientId>` carrying its\n * liveness (`lastSeen`) and the fence generation it has quiesced for\n * (`quiescedAtVersion`). Plaintext envelope, like the fence doc.\n */\nimport type { NoydbStore } from '../../kernel/types.js'\nimport { buildRecordEnvelope } from '../../kernel/enclave/index.js'\n\nconst META_COLLECTION = '_meta'\nconst CLIENT_PREFIX = 'schema-fence:client:'\n\nexport interface ClientDoc {\n readonly clientId: string\n readonly lastSeen: number\n readonly quiescedAtVersion: number | null\n /**\n * Session that owns this writer (one user's writers across vaults). Additive\n * and optional so legacy client docs (written without it) keep parsing;\n * readers default it.\n */\n readonly sessionId?: string\n}\n\nexport async function writeClientDoc(\n store: NoydbStore,\n vault: string,\n clientId: string,\n doc: { lastSeen: number; quiescedAtVersion: number | null; sessionId?: string },\n): Promise<void> {\n const recordId = `${CLIENT_PREFIX}${clientId}`\n const envelope = buildRecordEnvelope(\n { collection: META_COLLECTION, id: recordId, version: 1 },\n { iv: '', data: JSON.stringify({ clientId, ...doc }) },\n )\n await store.put(vault, META_COLLECTION, recordId, envelope)\n}\n\nexport async function listClientDocs(store: NoydbStore, vault: string): Promise<ClientDoc[]> {\n const ids = await store.list(vault, META_COLLECTION)\n const out: ClientDoc[] = []\n for (const id of ids) {\n if (!id.startsWith(CLIENT_PREFIX)) continue\n const env = await store.get(vault, META_COLLECTION, id)\n if (!env) continue\n try {\n const parsed = JSON.parse(env._data) as unknown\n if (isClientDoc(parsed)) out.push(parsed)\n } catch { /* skip corrupt */ }\n }\n return out\n}\n\n/**\n * True when every *active* client (lastSeen within staleMs of now) has\n * `quiescedAtVersion === generation`. Stale clients are ignored. An empty\n * active set is vacuously quiesced.\n */\nexport async function activeQuiesced(\n store: NoydbStore,\n vault: string,\n opts: { generation: number; now: number; staleMs: number; excludeClientId?: string },\n): Promise<boolean> {\n const docs = await listClientDocs(store, vault)\n const active = docs.filter(\n d => d.lastSeen >= opts.now - opts.staleMs && d.clientId !== opts.excludeClientId,\n )\n return active.every(d => d.quiescedAtVersion === opts.generation)\n}\n\nfunction isClientDoc(x: unknown): x is ClientDoc {\n if (x === null || typeof x !== 'object') return false\n const o = x as Record<string, unknown>\n return typeof o['clientId'] === 'string'\n && typeof o['lastSeen'] === 'number'\n && (o['quiescedAtVersion'] === null || typeof o['quiescedAtVersion'] === 'number')\n && (o['sessionId'] === undefined || typeof o['sessionId'] === 'string')\n}\n","/**\n * Store-backed default {@link NoydbMesh}.\n *\n * Maps the five coordination-port methods onto today's `_meta/schema-fence`\n * store ops so that, with no `by-*` provider injected, the fence service\n * reproduces its current store-polling behavior byte-for-byte. The `observe*`\n * methods are poll-emit (the legacy {@link FenceWatcher} model); `by-tabs` /\n * `by-peer` override them with real-time push.\n *\n * @module\n */\n\nimport type { NoydbStore } from '../../kernel/types.js'\nimport type { Unsubscribe } from '../../port/with/write-hooks.js'\nimport { loadFence, saveFence, type FenceDoc } from './fence.js'\nimport { writeClientDoc, listClientDocs, type ClientDoc } from './client-registry.js'\nimport type { NoydbMesh, WriterPresence } from '../../port/by/types.js'\n\n/**\n * Default poll cadence for the `observe*` fallbacks (ms). Matches the\n * store-poll granularity the fence-controller uses while waiting for quorum\n * (`delay(50)`); the same value the migration cutover falls back to when no\n * real-time provider is injected.\n */\nconst DEFAULT_POLL_INTERVAL_MS = 50\n\n/** Map a stored {@link ClientDoc} to the port's {@link WriterPresence}. */\nfunction toPresence(doc: ClientDoc): WriterPresence {\n return {\n writerId: doc.clientId,\n // Legacy docs written without a sessionId (and explicit empty) have no\n // session — fall back to the writerId so every presence is session-addressable.\n sessionId: doc.sessionId && doc.sessionId.length > 0 ? doc.sessionId : doc.clientId,\n lastSeen: doc.lastSeen,\n quiescedAtVersion: doc.quiescedAtVersion,\n }\n}\n\nfunction fenceEqual(a: FenceDoc, b: FenceDoc): boolean {\n return a.currentSchemaVersion === b.currentSchemaVersion && a.fenceState === b.fenceState\n}\n\nfunction presenceListEqual(a: readonly WriterPresence[], b: readonly WriterPresence[]): boolean {\n if (a.length !== b.length) return false\n const key = (w: WriterPresence) => `${w.writerId}\\u0000${w.sessionId}\\u0000${w.lastSeen}\\u0000${String(w.quiescedAtVersion)}`\n const bk = new Set(b.map(key))\n return a.every((w) => bk.has(key(w)))\n}\n\nexport class StoreMesh implements NoydbMesh {\n readonly #store: NoydbStore\n readonly #pollIntervalMs: number\n\n constructor(store: NoydbStore, opts?: { pollIntervalMs?: number }) {\n this.#store = store\n this.#pollIntervalMs = opts?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS\n }\n\n async setFence(vault: string, fence: FenceDoc): Promise<void> {\n // RE-READ AND SPREAD, never write the caller's doc whole (#1197).\n //\n // `saveFence` serialises what it is given and does a full `store.put` — no\n // merge. Callers legitimately construct a PARTIAL doc: `FenceController`\n // builds `{ currentSchemaVersion, fenceState }`, which is a valid `FenceDoc`\n // because `schemaHash` is optional. Writing that whole silently erased the\n // hash #946 added, so \"which schema is generation N is answerable from\n // schemaFenceState() alone\" held only until the first drain.\n //\n // The type cannot see the loss: for an optional field, \"absent\" and\n // \"deliberately cleared\" are the same value.\n //\n // Mirrors `persisted-schemas/register.ts:136`, which re-reads for the same\n // reason and documents it — a stale snapshot would roll back a concurrent\n // cutover's version. Re-reading NARROWS that window rather than closing it;\n // fence transitions are orchestrator-driven and serialised, so the residual\n // is the same one that path already accepts.\n const current = await loadFence(this.#store, vault)\n await saveFence(this.#store, vault, { ...current, ...fence })\n }\n\n async readFence(vault: string): Promise<FenceDoc> {\n return loadFence(this.#store, vault)\n }\n\n observeFence(vault: string, onChange: (f: FenceDoc) => void): Unsubscribe {\n let last: FenceDoc | null = null\n let busy = false\n const poll = async () => {\n if (busy) return\n busy = true\n try {\n const fence = await loadFence(this.#store, vault)\n if (last === null || !fenceEqual(last, fence)) {\n last = fence\n onChange(fence)\n }\n } catch {\n /* transient store error — retry next tick */\n } finally {\n busy = false\n }\n }\n void poll() // emit current state once immediately\n const timer = setInterval(() => void poll(), this.#pollIntervalMs)\n unref(timer)\n return () => clearInterval(timer)\n }\n\n async reportPresence(vault: string, p: WriterPresence): Promise<void> {\n await writeClientDoc(this.#store, vault, p.writerId, {\n lastSeen: p.lastSeen,\n quiescedAtVersion: p.quiescedAtVersion,\n sessionId: p.sessionId,\n })\n }\n\n observePresence(vault: string, onChange: (writers: readonly WriterPresence[]) => void): Unsubscribe {\n let last: readonly WriterPresence[] | null = null\n let busy = false\n const poll = async () => {\n if (busy) return\n busy = true\n try {\n const docs = await listClientDocs(this.#store, vault)\n const writers = docs.map(toPresence)\n if (last === null || !presenceListEqual(last, writers)) {\n last = writers\n onChange(writers)\n }\n } catch {\n /* transient store error — retry next tick */\n } finally {\n busy = false\n }\n }\n void poll() // emit current set once immediately\n const timer = setInterval(() => void poll(), this.#pollIntervalMs)\n unref(timer)\n return () => clearInterval(timer)\n }\n\n async reachableWriters(vault: string, o: { staleMs: number; now: number }): Promise<readonly WriterPresence[]> {\n const docs = await listClientDocs(this.#store, vault)\n return docs.map(toPresence).filter((w) => o.now - w.lastSeen <= o.staleMs)\n }\n}\n\n/** Best-effort `unref` so a poll timer never holds the process open (Node only). */\nfunction unref(timer: ReturnType<typeof setInterval>): void {\n const t = timer as unknown as { unref?: () => void }\n if (typeof t.unref === 'function') t.unref()\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AASA,IAAM,kBAAkB;AACxB,IAAM,gBAAgB;AActB,eAAsB,eACpB,OACA,OACA,UACA,KACe;AACf,QAAM,WAAW,GAAG,aAAa,GAAG,QAAQ;AAC5C,QAAM,WAAW;AAAA,IACf,EAAE,YAAY,iBAAiB,IAAI,UAAU,SAAS,EAAE;AAAA,IACxD,EAAE,IAAI,IAAI,MAAM,KAAK,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC,EAAE;AAAA,EACvD;AACA,QAAM,MAAM,IAAI,OAAO,iBAAiB,UAAU,QAAQ;AAC5D;AAEA,eAAsB,eAAe,OAAmB,OAAqC;AAC3F,QAAM,MAAM,MAAM,MAAM,KAAK,OAAO,eAAe;AACnD,QAAM,MAAmB,CAAC;AAC1B,aAAW,MAAM,KAAK;AACpB,QAAI,CAAC,GAAG,WAAW,aAAa,EAAG;AACnC,UAAM,MAAM,MAAM,MAAM,IAAI,OAAO,iBAAiB,EAAE;AACtD,QAAI,CAAC,IAAK;AACV,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,IAAI,KAAK;AACnC,UAAI,YAAY,MAAM,EAAG,KAAI,KAAK,MAAM;AAAA,IAC1C,QAAQ;AAAA,IAAqB;AAAA,EAC/B;AACA,SAAO;AACT;AAmBA,SAAS,YAAY,GAA4B;AAC/C,MAAI,MAAM,QAAQ,OAAO,MAAM,SAAU,QAAO;AAChD,QAAM,IAAI;AACV,SAAO,OAAO,EAAE,UAAU,MAAM,YAC3B,OAAO,EAAE,UAAU,MAAM,aACxB,EAAE,mBAAmB,MAAM,QAAQ,OAAO,EAAE,mBAAmB,MAAM,cACrE,EAAE,WAAW,MAAM,UAAa,OAAO,EAAE,WAAW,MAAM;AAClE;;;ACrDA,IAAM,2BAA2B;AAGjC,SAAS,WAAW,KAAgC;AAClD,SAAO;AAAA,IACL,UAAU,IAAI;AAAA;AAAA;AAAA,IAGd,WAAW,IAAI,aAAa,IAAI,UAAU,SAAS,IAAI,IAAI,YAAY,IAAI;AAAA,IAC3E,UAAU,IAAI;AAAA,IACd,mBAAmB,IAAI;AAAA,EACzB;AACF;AAEA,SAAS,WAAW,GAAa,GAAsB;AACrD,SAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,eAAe,EAAE;AACjF;AAEA,SAAS,kBAAkB,GAA8B,GAAuC;AAC9F,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,QAAM,MAAM,CAAC,MAAsB,GAAG,EAAE,QAAQ,KAAS,EAAE,SAAS,KAAS,EAAE,QAAQ,KAAS,OAAO,EAAE,iBAAiB,CAAC;AAC3H,QAAM,KAAK,IAAI,IAAI,EAAE,IAAI,GAAG,CAAC;AAC7B,SAAO,EAAE,MAAM,CAAC,MAAM,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC;AACtC;AAEO,IAAM,YAAN,MAAqC;AAAA,EACjC;AAAA,EACA;AAAA,EAET,YAAY,OAAmB,MAAoC;AACjE,SAAK,SAAS;AACd,SAAK,kBAAkB,MAAM,kBAAkB;AAAA,EACjD;AAAA,EAEA,MAAM,SAAS,OAAe,OAAgC;AAkB5D,UAAM,UAAU,MAAM,UAAU,KAAK,QAAQ,KAAK;AAClD,UAAM,UAAU,KAAK,QAAQ,OAAO,EAAE,GAAG,SAAS,GAAG,MAAM,CAAC;AAAA,EAC9D;AAAA,EAEA,MAAM,UAAU,OAAkC;AAChD,WAAO,UAAU,KAAK,QAAQ,KAAK;AAAA,EACrC;AAAA,EAEA,aAAa,OAAe,UAA8C;AACxE,QAAI,OAAwB;AAC5B,QAAI,OAAO;AACX,UAAM,OAAO,YAAY;AACvB,UAAI,KAAM;AACV,aAAO;AACP,UAAI;AACF,cAAM,QAAQ,MAAM,UAAU,KAAK,QAAQ,KAAK;AAChD,YAAI,SAAS,QAAQ,CAAC,WAAW,MAAM,KAAK,GAAG;AAC7C,iBAAO;AACP,mBAAS,KAAK;AAAA,QAChB;AAAA,MACF,QAAQ;AAAA,MAER,UAAE;AACA,eAAO;AAAA,MACT;AAAA,IACF;AACA,SAAK,KAAK;AACV,UAAM,QAAQ,YAAY,MAAM,KAAK,KAAK,GAAG,KAAK,eAAe;AACjE,UAAM,KAAK;AACX,WAAO,MAAM,cAAc,KAAK;AAAA,EAClC;AAAA,EAEA,MAAM,eAAe,OAAe,GAAkC;AACpE,UAAM,eAAe,KAAK,QAAQ,OAAO,EAAE,UAAU;AAAA,MACnD,UAAU,EAAE;AAAA,MACZ,mBAAmB,EAAE;AAAA,MACrB,WAAW,EAAE;AAAA,IACf,CAAC;AAAA,EACH;AAAA,EAEA,gBAAgB,OAAe,UAAqE;AAClG,QAAI,OAAyC;AAC7C,QAAI,OAAO;AACX,UAAM,OAAO,YAAY;AACvB,UAAI,KAAM;AACV,aAAO;AACP,UAAI;AACF,cAAM,OAAO,MAAM,eAAe,KAAK,QAAQ,KAAK;AACpD,cAAM,UAAU,KAAK,IAAI,UAAU;AACnC,YAAI,SAAS,QAAQ,CAAC,kBAAkB,MAAM,OAAO,GAAG;AACtD,iBAAO;AACP,mBAAS,OAAO;AAAA,QAClB;AAAA,MACF,QAAQ;AAAA,MAER,UAAE;AACA,eAAO;AAAA,MACT;AAAA,IACF;AACA,SAAK,KAAK;AACV,UAAM,QAAQ,YAAY,MAAM,KAAK,KAAK,GAAG,KAAK,eAAe;AACjE,UAAM,KAAK;AACX,WAAO,MAAM,cAAc,KAAK;AAAA,EAClC;AAAA,EAEA,MAAM,iBAAiB,OAAe,GAAyE;AAC7G,UAAM,OAAO,MAAM,eAAe,KAAK,QAAQ,KAAK;AACpD,WAAO,KAAK,IAAI,UAAU,EAAE,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO;AAAA,EAC3E;AACF;AAGA,SAAS,MAAM,OAA6C;AAC1D,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,UAAU,WAAY,GAAE,MAAM;AAC7C;","names":[]}
|
package/dist/sync/index.js
CHANGED
|
@@ -2,21 +2,20 @@ import {
|
|
|
2
2
|
PresenceHandle,
|
|
3
3
|
SyncEngine,
|
|
4
4
|
SyncTransaction
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-CPWQB3N5.js";
|
|
6
6
|
import {
|
|
7
7
|
NO_SYNC
|
|
8
8
|
} from "../chunk-T7IR4N2S.js";
|
|
9
|
-
import "../chunk-
|
|
10
|
-
import "../chunk-
|
|
11
|
-
import "../chunk-
|
|
12
|
-
import "../chunk-
|
|
13
|
-
import "../chunk-
|
|
14
|
-
import "../chunk-
|
|
9
|
+
import "../chunk-FAHMLERK.js";
|
|
10
|
+
import "../chunk-77K4PG6P.js";
|
|
11
|
+
import "../chunk-SZJ3DLZA.js";
|
|
12
|
+
import "../chunk-BOWNCWZT.js";
|
|
13
|
+
import "../chunk-YSRUYAVU.js";
|
|
14
|
+
import "../chunk-RMF67KCG.js";
|
|
15
15
|
import "../chunk-LP7BEXCT.js";
|
|
16
|
-
import "../chunk-
|
|
17
|
-
import "../chunk-
|
|
18
|
-
import "../chunk-
|
|
19
|
-
import "../chunk-PZ5AY32C.js";
|
|
16
|
+
import "../chunk-SL3JAAKN.js";
|
|
17
|
+
import "../chunk-7MG7SJT7.js";
|
|
18
|
+
import "../chunk-7O3Y4PPD.js";
|
|
20
19
|
|
|
21
20
|
// src/with-sync/active.ts
|
|
22
21
|
function withSync() {
|
package/dist/sync/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/with-sync/active.ts"],"sourcesContent":["/**\n * Active sync strategy — `withSync()` returns the real implementation\n * that wires the `SyncEngine`, `SyncTransaction`, and `PresenceHandle`\n * constructors into the Noydb / Vault / Collection hot paths.\n *\n * Consumers opt in by:\n *\n * ```ts\n * import { createNoydb } from '@noy-db/hub'\n * import { withSync } from '@noy-db/hub/sync'\n *\n * const db = await createNoydb({\n * store: localStore,\n * sync: remoteStore,\n * user: ...,\n * syncStrategy: withSync(),\n * })\n * ```\n *\n * The factory delegates to the existing `sync.ts`,\n * `sync-transaction.ts`, and `presence.ts` modules. Splitting the\n * import chain through this file is what lets tsup tree-shake the\n * ~856 LOC of replication + presence machinery out of the default\n * bundle when no `withSync()` import is present.\n *\n * Keyring + grant/revoke/magic-link/delegation stay in the always-on\n * core (or tree-shake via direct named imports) — those are required\n * for any multi-user vault, even purely local ones.\n *\n * @public\n */\n\nimport type { SyncStrategy, BuildSyncEngineOptions } from './strategy.js'\nimport type { PresenceHandleOpts } from './presence.js'\nimport type { Vault } from '../kernel/vault.js'\nimport { SyncEngine } from './engine.js'\nimport { SyncTransaction } from './transaction.js'\nimport { PresenceHandle } from './presence.js'\n\nexport function withSync(): SyncStrategy {\n return {\n buildSyncEngine(opts: BuildSyncEngineOptions): SyncEngine {\n return new SyncEngine(opts)\n },\n buildSyncTransaction(vault: Vault, engine: SyncEngine): SyncTransaction {\n return new SyncTransaction(vault, engine)\n },\n buildPresence<P>(opts: PresenceHandleOpts): PresenceHandle<P> {\n return new PresenceHandle<P>(opts)\n },\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/with-sync/active.ts"],"sourcesContent":["/**\n * Active sync strategy — `withSync()` returns the real implementation\n * that wires the `SyncEngine`, `SyncTransaction`, and `PresenceHandle`\n * constructors into the Noydb / Vault / Collection hot paths.\n *\n * Consumers opt in by:\n *\n * ```ts\n * import { createNoydb } from '@noy-db/hub'\n * import { withSync } from '@noy-db/hub/sync'\n *\n * const db = await createNoydb({\n * store: localStore,\n * sync: remoteStore,\n * user: ...,\n * syncStrategy: withSync(),\n * })\n * ```\n *\n * The factory delegates to the existing `sync.ts`,\n * `sync-transaction.ts`, and `presence.ts` modules. Splitting the\n * import chain through this file is what lets tsup tree-shake the\n * ~856 LOC of replication + presence machinery out of the default\n * bundle when no `withSync()` import is present.\n *\n * Keyring + grant/revoke/magic-link/delegation stay in the always-on\n * core (or tree-shake via direct named imports) — those are required\n * for any multi-user vault, even purely local ones.\n *\n * @public\n */\n\nimport type { SyncStrategy, BuildSyncEngineOptions } from './strategy.js'\nimport type { PresenceHandleOpts } from './presence.js'\nimport type { Vault } from '../kernel/vault.js'\nimport { SyncEngine } from './engine.js'\nimport { SyncTransaction } from './transaction.js'\nimport { PresenceHandle } from './presence.js'\n\nexport function withSync(): SyncStrategy {\n return {\n buildSyncEngine(opts: BuildSyncEngineOptions): SyncEngine {\n return new SyncEngine(opts)\n },\n buildSyncTransaction(vault: Vault, engine: SyncEngine): SyncTransaction {\n return new SyncTransaction(vault, engine)\n },\n buildPresence<P>(opts: PresenceHandleOpts): PresenceHandle<P> {\n return new PresenceHandle<P>(opts)\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAuCO,SAAS,WAAyB;AACvC,SAAO;AAAA,IACL,gBAAgB,MAA0C;AACxD,aAAO,IAAI,WAAW,IAAI;AAAA,IAC5B;AAAA,IACA,qBAAqB,OAAc,QAAqC;AACtE,aAAO,IAAI,gBAAgB,OAAO,MAAM;AAAA,IAC1C;AAAA,IACA,cAAiB,MAA6C;AAC5D,aAAO,IAAI,eAAkB,IAAI;AAAA,IACnC;AAAA,EACF;AACF;","names":[]}
|
package/dist/team/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
MemoryDeviceSeal,
|
|
3
3
|
beginEchoUnlock,
|
|
4
4
|
withTeam
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-EYBQEIUY.js";
|
|
6
6
|
import {
|
|
7
7
|
NO_TEAM,
|
|
8
8
|
burnPaperRecoveryEntry,
|
|
@@ -31,8 +31,8 @@ import {
|
|
|
31
31
|
unwrapMagicLinkGrant,
|
|
32
32
|
updateAuthenticator,
|
|
33
33
|
writeMagicLinkGrant
|
|
34
|
-
} from "../chunk-
|
|
35
|
-
import "../chunk-
|
|
34
|
+
} from "../chunk-RRNRNANG.js";
|
|
35
|
+
import "../chunk-2577DKXB.js";
|
|
36
36
|
import {
|
|
37
37
|
buildEchoBlock,
|
|
38
38
|
buildRecipientKeyringFile,
|
|
@@ -53,22 +53,21 @@ import {
|
|
|
53
53
|
updateKeyringIdentity,
|
|
54
54
|
verifyPrompt,
|
|
55
55
|
verifyTypedEcho
|
|
56
|
-
} from "../chunk-
|
|
57
|
-
import "../chunk-
|
|
58
|
-
import "../chunk-
|
|
59
|
-
import "../chunk-
|
|
60
|
-
import "../chunk-
|
|
61
|
-
import "../chunk-
|
|
62
|
-
import "../chunk-
|
|
63
|
-
import "../chunk-
|
|
64
|
-
import "../chunk-
|
|
56
|
+
} from "../chunk-G56KBWFY.js";
|
|
57
|
+
import "../chunk-KG7TZKZG.js";
|
|
58
|
+
import "../chunk-UGTBXF5U.js";
|
|
59
|
+
import "../chunk-JMGDHKQF.js";
|
|
60
|
+
import "../chunk-77K4PG6P.js";
|
|
61
|
+
import "../chunk-SZJ3DLZA.js";
|
|
62
|
+
import "../chunk-BOWNCWZT.js";
|
|
63
|
+
import "../chunk-YSRUYAVU.js";
|
|
64
|
+
import "../chunk-RMF67KCG.js";
|
|
65
65
|
import "../chunk-LP7BEXCT.js";
|
|
66
|
-
import "../chunk-
|
|
67
|
-
import "../chunk-
|
|
66
|
+
import "../chunk-SL3JAAKN.js";
|
|
67
|
+
import "../chunk-7MG7SJT7.js";
|
|
68
68
|
import {
|
|
69
69
|
TeamNotEnabledError
|
|
70
|
-
} from "../chunk-
|
|
71
|
-
import "../chunk-PZ5AY32C.js";
|
|
70
|
+
} from "../chunk-7O3Y4PPD.js";
|
|
72
71
|
export {
|
|
73
72
|
MemoryDeviceSeal,
|
|
74
73
|
NO_TEAM,
|
package/dist/tiers/index.js
CHANGED
|
@@ -11,21 +11,20 @@ import {
|
|
|
11
11
|
putAtTier,
|
|
12
12
|
syncDerivedOutputs,
|
|
13
13
|
withTiers
|
|
14
|
-
} from "../chunk-
|
|
15
|
-
import "../chunk-
|
|
16
|
-
import "../chunk-
|
|
17
|
-
import "../chunk-
|
|
18
|
-
import "../chunk-
|
|
19
|
-
import "../chunk-
|
|
20
|
-
import "../chunk-
|
|
21
|
-
import "../chunk-
|
|
14
|
+
} from "../chunk-KJIMGIWO.js";
|
|
15
|
+
import "../chunk-UGTBXF5U.js";
|
|
16
|
+
import "../chunk-JMGDHKQF.js";
|
|
17
|
+
import "../chunk-77K4PG6P.js";
|
|
18
|
+
import "../chunk-SZJ3DLZA.js";
|
|
19
|
+
import "../chunk-BOWNCWZT.js";
|
|
20
|
+
import "../chunk-YSRUYAVU.js";
|
|
21
|
+
import "../chunk-RMF67KCG.js";
|
|
22
22
|
import "../chunk-LP7BEXCT.js";
|
|
23
|
-
import "../chunk-
|
|
24
|
-
import "../chunk-
|
|
23
|
+
import "../chunk-SL3JAAKN.js";
|
|
24
|
+
import "../chunk-7MG7SJT7.js";
|
|
25
25
|
import {
|
|
26
26
|
TiersNotEnabledError
|
|
27
|
-
} from "../chunk-
|
|
28
|
-
import "../chunk-PZ5AY32C.js";
|
|
27
|
+
} from "../chunk-7O3Y4PPD.js";
|
|
29
28
|
export {
|
|
30
29
|
NO_TIERS,
|
|
31
30
|
TiersNotEnabledError,
|
package/dist/to/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
NOYDB_ENVELOPE_GENERATION
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-7MG7SJT7.js";
|
|
4
4
|
import {
|
|
5
5
|
ConflictError,
|
|
6
6
|
DuplicateStoreKindError,
|
|
@@ -9,8 +9,7 @@ import {
|
|
|
9
9
|
StoreCapabilityError,
|
|
10
10
|
UnknownStoreKindError,
|
|
11
11
|
isConflictError
|
|
12
|
-
} from "../chunk-
|
|
13
|
-
import "../chunk-PZ5AY32C.js";
|
|
12
|
+
} from "../chunk-7O3Y4PPD.js";
|
|
14
13
|
|
|
15
14
|
// src/port/to/locator.ts
|
|
16
15
|
function isPodStore(store) {
|
package/dist/to/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/port/to/locator.ts"],"sourcesContent":["/**\n * The Locator seam (#945 Task 1): a serializable, CREDENTIALLESS store\n * descriptor plus a factory registry that reconstructs a `NoydbStore` from\n * data.\n *\n * A `StoreDescriptor` is the pod-portable \"what store and where\" — kind,\n * broad topology class, and a kind-specific serializable address/options\n * bag. It is deliberately dumb data: no field on it is ever typed as a\n * function, a `StoreCredentialSource`, or a `StoreCredentials` value, so a\n * descriptor can be written into a pod / manifest / sync payload without an\n * audit for leaked secrets. Anything that must resolve device-side or\n * user-side — a short-lived credential, a device-local directory override,\n * a mounted drive handle — travels out-of-band via `resolve()`'s `opts`,\n * never through the descriptor itself.\n *\n * This module is pure TypeScript: no imports beyond the kernel's own types\n * and errors, no Node built-ins, no crypto. It adds zero runtime\n * dependencies to `@noy-db/hub/to`.\n */\nimport type { NoydbStore, NoydbPodStore, StoreCredentialSource } from '../../kernel/types.js'\nimport { DuplicateStoreKindError, UnknownStoreKindError } from '../../kernel/errors.js'\n\n/**\n * The broad topology bucket a store descriptor's kind falls into —\n * used by callers/UI to group or filter descriptors without knowing every\n * concrete `kind` string.\n */\nexport type StoreClass = 'local' | 'browser' | 'lan' | 'cloud'\n\n/**\n * A serializable, CREDENTIALLESS description of a store instance: enough\n * data to reconstruct a `NoydbStore` via a registered `StoreFactory`, and\n * safe to persist or transmit as-is.\n *\n * CREDENTIALLESS BY CONSTRUCTION: no field here may be typed as a function,\n * a `StoreCredentialSource`, or a `StoreCredentials` value. Credentials\n * NEVER ride the descriptor — they are supplied separately (as a\n * `StoreCredentialSource`) to `StoreLocator.resolve()` at resolve time.\n */\nexport interface StoreDescriptor {\n /** The registered factory key (e.g. `'file'`, `'aws-s3'`, `'webdav'`). */\n readonly kind: string\n /** The broad topology bucket this kind falls into. */\n readonly class: StoreClass\n /** Kind-specific serializable location (e.g. `{ dir }`, `{ bucket, region }`). */\n readonly address: unknown\n /** Kind-specific serializable tuning options. */\n readonly options?: unknown\n}\n\n/**\n * A kind-specific, device-local supplement to a `StoreDescriptor` — a\n * directory override, a mount point, a drive handle — that is resolved\n * device-side and never carried inside a pod alongside the descriptor.\n */\nexport type StoreBinding = unknown\n\n/**\n * Either store shape a factory may produce: the 6-method KV `NoydbStore`, or\n * the whole-vault `NoydbPodStore` implemented by `to-drive` / `to-icloud`.\n *\n * The two are DISJOINT, not sub/supertypes — a pod store has none of the six\n * KV methods — which is why moving between them needs a double cast and why\n * `isPodStore()` exists to narrow instead.\n */\nexport type AnyNoydbStore = NoydbStore | NoydbPodStore\n\n/**\n * Narrow an `AnyNoydbStore` to the pod shape. Discriminates on the\n * `kind: 'bundle'` tag `NoydbPodStore` carries for exactly this purpose; a\n * `NoydbStore` has no `kind` field at all.\n *\n * Use this at a `resolve()` boundary instead of casting — a store resolved\n * from a descriptor read out of a pod is only known to be one of the two\n * shapes at runtime.\n */\nexport function isPodStore(store: AnyNoydbStore): store is NoydbPodStore {\n return (store as NoydbPodStore).kind === 'bundle'\n}\n\n/**\n * Reconstructs a live store from a `StoreDescriptor`. Registered against a\n * `StoreLocator` under the descriptor's `kind`.\n *\n * The type parameter says WHICH shape this factory builds. It defaults to\n * `NoydbStore`, so a bare `StoreFactory` means exactly what it always has;\n * a pod-store factory declares `StoreFactory<NoydbPodStore>` and registers\n * without a cast (#988).\n */\nexport type StoreFactory<S extends AnyNoydbStore = NoydbStore> = (\n descriptor: StoreDescriptor,\n opts: { binding?: StoreBinding; credentials?: StoreCredentialSource },\n) => S | Promise<S>\n\n/** A registry of `StoreFactory`s, keyed by `StoreDescriptor.kind`. */\nexport interface StoreLocator {\n /**\n * Register a factory for `kind`. Throws if `kind` is already registered.\n *\n * Accepts a factory building EITHER store shape — `S` is inferred from the\n * factory's own return type, so a `NoydbPodStore` factory needs no cast.\n */\n register<S extends AnyNoydbStore>(kind: string, factory: StoreFactory<S>): void\n /**\n * Resolve `descriptor` to a live store via its kind's registered factory.\n *\n * Returns `NoydbStore`. If the `kind` was registered with a pod-store\n * factory, the resolved value is really a `NoydbPodStore`: this signature\n * is the seam's remaining unsoundness, kept because widening it to\n * `AnyNoydbStore` would break every existing caller. Where the descriptor's\n * kind is not statically known, prefer `resolveAny()` and `isPodStore()`.\n */\n resolve(\n descriptor: StoreDescriptor,\n opts?: { binding?: StoreBinding; credentials?: StoreCredentialSource },\n ): NoydbStore | Promise<NoydbStore>\n /**\n * As `resolve()`, but typed honestly: the registry is keyed by a runtime\n * `kind` string, so which of the two shapes comes back is not knowable\n * statically. Narrow the result with `isPodStore()`.\n */\n resolveAny(\n descriptor: StoreDescriptor,\n opts?: { binding?: StoreBinding; credentials?: StoreCredentialSource },\n ): AnyNoydbStore | Promise<AnyNoydbStore>\n}\n\n/**\n * Creates a fresh, empty `StoreLocator`.\n *\n * DUPLICATE REGISTRATION: `register()` throws if `kind` is already\n * registered, rather than silently overwriting it (last-wins). A\n * composition root that registers the same kind twice is almost always a\n * mistake — a copy-pasted setup block, two satellite packages fighting\n * over the same kind string — and last-wins would hide it behind whichever\n * registration happened to run last. This mirrors the\n * `DuplicateBehaviorNameError` philosophy (#947): fail loudly at the\n * mistake's source instead of silently at some unrelated `resolve()` call\n * later.\n */\nexport function createStoreLocator(): StoreLocator {\n const factories = new Map<string, StoreFactory<AnyNoydbStore>>()\n\n function lookup(kind: string): StoreFactory<AnyNoydbStore> {\n const factory = factories.get(kind)\n if (!factory) {\n throw new UnknownStoreKindError(kind, [...factories.keys()].sort())\n }\n return factory\n }\n\n return {\n register(kind, factory) {\n if (factories.has(kind)) {\n throw new DuplicateStoreKindError(kind)\n }\n factories.set(kind, factory)\n },\n resolve(descriptor, opts = {}) {\n // The one place the seam's `resolve(): NoydbStore` signature is paid\n // for. A pod-store `kind` really returns a `NoydbPodStore`; the registry\n // is keyed by a runtime string so nothing here can prove otherwise.\n // Concentrated in the module that owns the invariant rather than\n // duplicated as `as unknown as StoreFactory` in every pod-store package\n // (#988). `resolveAny()` is the honest signature over the same call.\n return lookup(descriptor.kind)(descriptor, opts) as NoydbStore | Promise<NoydbStore>\n },\n resolveAny(descriptor, opts = {}) {\n return lookup(descriptor.kind)(descriptor, opts)\n },\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/port/to/locator.ts"],"sourcesContent":["/**\n * The Locator seam (#945 Task 1): a serializable, CREDENTIALLESS store\n * descriptor plus a factory registry that reconstructs a `NoydbStore` from\n * data.\n *\n * A `StoreDescriptor` is the pod-portable \"what store and where\" — kind,\n * broad topology class, and a kind-specific serializable address/options\n * bag. It is deliberately dumb data: no field on it is ever typed as a\n * function, a `StoreCredentialSource`, or a `StoreCredentials` value, so a\n * descriptor can be written into a pod / manifest / sync payload without an\n * audit for leaked secrets. Anything that must resolve device-side or\n * user-side — a short-lived credential, a device-local directory override,\n * a mounted drive handle — travels out-of-band via `resolve()`'s `opts`,\n * never through the descriptor itself.\n *\n * This module is pure TypeScript: no imports beyond the kernel's own types\n * and errors, no Node built-ins, no crypto. It adds zero runtime\n * dependencies to `@noy-db/hub/to`.\n */\nimport type { NoydbStore, NoydbPodStore, StoreCredentialSource } from '../../kernel/types.js'\nimport { DuplicateStoreKindError, UnknownStoreKindError } from '../../kernel/errors.js'\n\n/**\n * The broad topology bucket a store descriptor's kind falls into —\n * used by callers/UI to group or filter descriptors without knowing every\n * concrete `kind` string.\n */\nexport type StoreClass = 'local' | 'browser' | 'lan' | 'cloud'\n\n/**\n * A serializable, CREDENTIALLESS description of a store instance: enough\n * data to reconstruct a `NoydbStore` via a registered `StoreFactory`, and\n * safe to persist or transmit as-is.\n *\n * CREDENTIALLESS BY CONSTRUCTION: no field here may be typed as a function,\n * a `StoreCredentialSource`, or a `StoreCredentials` value. Credentials\n * NEVER ride the descriptor — they are supplied separately (as a\n * `StoreCredentialSource`) to `StoreLocator.resolve()` at resolve time.\n */\nexport interface StoreDescriptor {\n /** The registered factory key (e.g. `'file'`, `'aws-s3'`, `'webdav'`). */\n readonly kind: string\n /** The broad topology bucket this kind falls into. */\n readonly class: StoreClass\n /** Kind-specific serializable location (e.g. `{ dir }`, `{ bucket, region }`). */\n readonly address: unknown\n /** Kind-specific serializable tuning options. */\n readonly options?: unknown\n}\n\n/**\n * A kind-specific, device-local supplement to a `StoreDescriptor` — a\n * directory override, a mount point, a drive handle — that is resolved\n * device-side and never carried inside a pod alongside the descriptor.\n */\nexport type StoreBinding = unknown\n\n/**\n * Either store shape a factory may produce: the 6-method KV `NoydbStore`, or\n * the whole-vault `NoydbPodStore` implemented by `to-drive` / `to-icloud`.\n *\n * The two are DISJOINT, not sub/supertypes — a pod store has none of the six\n * KV methods — which is why moving between them needs a double cast and why\n * `isPodStore()` exists to narrow instead.\n */\nexport type AnyNoydbStore = NoydbStore | NoydbPodStore\n\n/**\n * Narrow an `AnyNoydbStore` to the pod shape. Discriminates on the\n * `kind: 'bundle'` tag `NoydbPodStore` carries for exactly this purpose; a\n * `NoydbStore` has no `kind` field at all.\n *\n * Use this at a `resolve()` boundary instead of casting — a store resolved\n * from a descriptor read out of a pod is only known to be one of the two\n * shapes at runtime.\n */\nexport function isPodStore(store: AnyNoydbStore): store is NoydbPodStore {\n return (store as NoydbPodStore).kind === 'bundle'\n}\n\n/**\n * Reconstructs a live store from a `StoreDescriptor`. Registered against a\n * `StoreLocator` under the descriptor's `kind`.\n *\n * The type parameter says WHICH shape this factory builds. It defaults to\n * `NoydbStore`, so a bare `StoreFactory` means exactly what it always has;\n * a pod-store factory declares `StoreFactory<NoydbPodStore>` and registers\n * without a cast (#988).\n */\nexport type StoreFactory<S extends AnyNoydbStore = NoydbStore> = (\n descriptor: StoreDescriptor,\n opts: { binding?: StoreBinding; credentials?: StoreCredentialSource },\n) => S | Promise<S>\n\n/** A registry of `StoreFactory`s, keyed by `StoreDescriptor.kind`. */\nexport interface StoreLocator {\n /**\n * Register a factory for `kind`. Throws if `kind` is already registered.\n *\n * Accepts a factory building EITHER store shape — `S` is inferred from the\n * factory's own return type, so a `NoydbPodStore` factory needs no cast.\n */\n register<S extends AnyNoydbStore>(kind: string, factory: StoreFactory<S>): void\n /**\n * Resolve `descriptor` to a live store via its kind's registered factory.\n *\n * Returns `NoydbStore`. If the `kind` was registered with a pod-store\n * factory, the resolved value is really a `NoydbPodStore`: this signature\n * is the seam's remaining unsoundness, kept because widening it to\n * `AnyNoydbStore` would break every existing caller. Where the descriptor's\n * kind is not statically known, prefer `resolveAny()` and `isPodStore()`.\n */\n resolve(\n descriptor: StoreDescriptor,\n opts?: { binding?: StoreBinding; credentials?: StoreCredentialSource },\n ): NoydbStore | Promise<NoydbStore>\n /**\n * As `resolve()`, but typed honestly: the registry is keyed by a runtime\n * `kind` string, so which of the two shapes comes back is not knowable\n * statically. Narrow the result with `isPodStore()`.\n */\n resolveAny(\n descriptor: StoreDescriptor,\n opts?: { binding?: StoreBinding; credentials?: StoreCredentialSource },\n ): AnyNoydbStore | Promise<AnyNoydbStore>\n}\n\n/**\n * Creates a fresh, empty `StoreLocator`.\n *\n * DUPLICATE REGISTRATION: `register()` throws if `kind` is already\n * registered, rather than silently overwriting it (last-wins). A\n * composition root that registers the same kind twice is almost always a\n * mistake — a copy-pasted setup block, two satellite packages fighting\n * over the same kind string — and last-wins would hide it behind whichever\n * registration happened to run last. This mirrors the\n * `DuplicateBehaviorNameError` philosophy (#947): fail loudly at the\n * mistake's source instead of silently at some unrelated `resolve()` call\n * later.\n */\nexport function createStoreLocator(): StoreLocator {\n const factories = new Map<string, StoreFactory<AnyNoydbStore>>()\n\n function lookup(kind: string): StoreFactory<AnyNoydbStore> {\n const factory = factories.get(kind)\n if (!factory) {\n throw new UnknownStoreKindError(kind, [...factories.keys()].sort())\n }\n return factory\n }\n\n return {\n register(kind, factory) {\n if (factories.has(kind)) {\n throw new DuplicateStoreKindError(kind)\n }\n factories.set(kind, factory)\n },\n resolve(descriptor, opts = {}) {\n // The one place the seam's `resolve(): NoydbStore` signature is paid\n // for. A pod-store `kind` really returns a `NoydbPodStore`; the registry\n // is keyed by a runtime string so nothing here can prove otherwise.\n // Concentrated in the module that owns the invariant rather than\n // duplicated as `as unknown as StoreFactory` in every pod-store package\n // (#988). `resolveAny()` is the honest signature over the same call.\n return lookup(descriptor.kind)(descriptor, opts) as NoydbStore | Promise<NoydbStore>\n },\n resolveAny(descriptor, opts = {}) {\n return lookup(descriptor.kind)(descriptor, opts)\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AA4EO,SAAS,WAAW,OAA8C;AACvE,SAAQ,MAAwB,SAAS;AAC3C;AA8DO,SAAS,qBAAmC;AACjD,QAAM,YAAY,oBAAI,IAAyC;AAE/D,WAAS,OAAO,MAA2C;AACzD,UAAM,UAAU,UAAU,IAAI,IAAI;AAClC,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,sBAAsB,MAAM,CAAC,GAAG,UAAU,KAAK,CAAC,EAAE,KAAK,CAAC;AAAA,IACpE;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,SAAS,MAAM,SAAS;AACtB,UAAI,UAAU,IAAI,IAAI,GAAG;AACvB,cAAM,IAAI,wBAAwB,IAAI;AAAA,MACxC;AACA,gBAAU,IAAI,MAAM,OAAO;AAAA,IAC7B;AAAA,IACA,QAAQ,YAAY,OAAO,CAAC,GAAG;AAO7B,aAAO,OAAO,WAAW,IAAI,EAAE,YAAY,IAAI;AAAA,IACjD;AAAA,IACA,WAAW,YAAY,OAAO,CAAC,GAAG;AAChC,aAAO,OAAO,WAAW,IAAI,EAAE,YAAY,IAAI;AAAA,IACjD;AAAA,EACF;AACF;","names":[]}
|
|
@@ -3,16 +3,15 @@ import {
|
|
|
3
3
|
TxContext,
|
|
4
4
|
TxVault,
|
|
5
5
|
runTransaction
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-BLN54W3L.js";
|
|
7
7
|
import {
|
|
8
8
|
lazy
|
|
9
9
|
} from "../chunk-5D2ALSM5.js";
|
|
10
10
|
import "../chunk-4AOERLTV.js";
|
|
11
|
-
import "../chunk-
|
|
12
|
-
import "../chunk-PZ5AY32C.js";
|
|
11
|
+
import "../chunk-7O3Y4PPD.js";
|
|
13
12
|
|
|
14
13
|
// src/with-commit/tx/dry-run.ts
|
|
15
|
-
var loadGuardExecutor = lazy(() => import("../executor-
|
|
14
|
+
var loadGuardExecutor = lazy(() => import("../executor-AQVXV5GZ.js"));
|
|
16
15
|
var SEP = " ";
|
|
17
16
|
var keyOf = (op) => `${op.vaultName}${SEP}${op.collectionName}${SEP}${op.id}`;
|
|
18
17
|
async function runDryRun(db, fn) {
|