[{"data":1,"prerenderedAt":448},["ShallowReactive",2],{"project-38972358":3},{"id":4,"name":5,"line":6,"line_source":7,"title":8,"url":9,"domain":10,"maker":11,"posted_at":12,"year":13,"points":14,"comments":15,"rank_in_year":16,"shown_in_year":17,"top":18,"tier":19,"category":20,"subcategory":21,"format":22,"keywords":23,"reception":28,"sentiment":29,"stance_mix":30,"scored_comments":35,"aspects":36,"strip":47,"post_text":49,"what":50,"said":91,"maker_in_thread":242,"shape":252,"grid":257,"attempts":412,"same_week":413,"maker_shows":345,"maker_hidden":440,"reader_version":441,"card":442},"38972358","RemoteStorage","sync localStorage across devices and browsers","title","Show HN: RemoteStorage – sync localStorage across devices and browsers","https:\u002F\u002Fgithub.com\u002FFrigadeHQ\u002Fremote-storage","github.com","pancomplex",1705086060,2024,129,62,657,3515,19,"notable","dev_tools","frontend state library","library",[24,25,26,27],"localstorage sync","cross device state","remote storage","frontend state","mixed",-0.076,{"positive":31,"neutral":32,"mixed":33,"critical":34},7,15,2,11,35,{"design":37,"idea":40,"execution":42,"docs":44,"performance":45,"privacy":46},{"positive":38,"critical":39},1,0,{"positive":41,"critical":31},6,{"positive":39,"critical":43},3,{"positive":39,"critical":39},{"positive":39,"critical":38},{"positive":39,"critical":39},[48,43,43,38,38,38,39,39,43,38,48,39,43,39,48,43,38,43,38,38,48,48,39,48,39,39,39,43,38,48,43,39,43,43,38,39,43,39,43,38],5,"Hey HN! Wanted to share a new library and server I've recently been working on called remoteStorage.\n\nWhen building frontends with Javascript, the native localStorage API is super useful for keeping track of state between sessions in the same browser, but it's not as good a solution when your data needs to be shared across multiple devices or browsers.\n\nFor instance, let's say you want to show a welcome modal to all new users that sign up for your product. If you use localStorage to track if a user has already seen this modal, your users will end up getting the experience repeatedly every time",{"problem":51,"audience":57,"pricing":61,"motivation":62,"time_to_build":61,"stack":66,"openness":89,"stage":90,"team_size":89},{"value":52,"source":53,"evidence":54},"localStorage data does not persist across devices or browsers","maker",[55],{"id":56},"post",{"value":58,"source":53,"evidence":59},"Frontend developers who need per-user state shared across devices",[60],{"id":56},null,{"value":63,"source":53,"evidence":64},"Frontend state like welcome-modal flags is lost when users switch devices or browsers",[65],{"id":56},[67,71,75,79,85],{"value":68,"source":53,"evidence":69},"TypeScript",[70],{"id":56},{"value":72,"source":53,"evidence":73},"Nest.js",[74],{"id":56},{"value":76,"source":53,"evidence":77},"Redis",[78],{"id":56},{"value":80,"source":53,"evidence":81},"Fastify",[82],{"id":83,"by":11,"text":84},"38975525","Good question. I like the idea of having a configuration that includes optimistic writes and perhaps even reads from localStorage for projects that have very low latency\u002Funstable…",{"value":86,"source":53,"evidence":87},"Docker",[88],{"id":56},"unknown","beta",{"praise":92,"objections":117,"requests":168,"questions":195,"alternatives":212},[93,109],{"value":94,"source":95,"evidence":96},"Simple API that feels like localStorage","commenter",[97,101,105],{"id":98,"by":99,"text":100},"38974253","paulgb","Your landing page at https:\u002F\u002Fremote.storage\u002F is so good, I love the minimalism. Six lines of code tell me exactly what it does.",{"id":102,"by":103,"text":104},"38974870","matlin","I love the simplicity and kinda wish that was actually built into Chrome. I know it would probably break things but if Google could automatically sync local storage and IndexedDB…",{"id":106,"by":107,"text":108},"38974972","simlevesque","That's so cool ! I was thinking how I'd make work syncing my offline first app and this looks promising",{"value":110,"source":95,"evidence":111},"Fills a real need for cross-device frontend preferences",[112,116],{"id":113,"by":114,"text":115},"38978420","buro9","I would state the opposite, the use case is huge. To add something new, that needs to persist, will not require going to a backend team and begging for a new API from them. A…",{"id":106,"by":107,"text":108},[118,128,142,157],{"value":119,"source":95,"evidence":120},"Writes are async, so existing sync code must change",[121,125],{"id":122,"by":123,"text":124},"38975476","joshstrange","Very cool overall but the sync->async change means you can't always use it as a drop-in replacement. I assume it's making network calls in those cases which will take…",{"id":126,"by":123,"text":127},"38975569","Totally fair point and I understand that as-is it’s way simpler than what I proposed. I just know there are places I have to change a whole chain of functions from sync to async…",{"value":129,"source":95,"evidence":130},"Conflict resolution and offline behaviour unclear or missing",[131,135,138],{"id":132,"by":133,"text":134},"38976126","eps","How are the conflicts resolved? Machine A is online and sets \"x\" to 1. Machine B is offline and sets \"x\" to 2. Machine B goes online. What the resulting state on A and on B? What…",{"id":136,"by":133,"text":137},"38976366","I see. The case in question is when the user switches between two devices, each of which may be sometimes offline. This can cause older changes hitting the server later than the…",{"id":139,"by":140,"text":141},"38983546","valsk","Based on the comments I think this is just, not yet implemented. But is one of the next steps for truly cross device storage.",{"value":143,"source":95,"evidence":144},"Name collides with existing remoteStorage.io protocol",[145,149,153],{"id":146,"by":147,"text":148},"38974088","vidarh","The name Remotestorage is already used for a protocol for portable user-controlled storage. See e.g. Remotestorage.io.",{"id":150,"by":151,"text":152},"38976568","airtonix","how is this different from remotestorage.io ?",{"id":154,"by":155,"text":156},"38974484","SushiHippie","Not to be confused with https:\u002F\u002Fremotestorage.io\u002F",{"value":158,"source":95,"evidence":159},"Backend needs its own auth and user ID, which is hard to get",[160,164],{"id":161,"by":162,"text":163},"38976653","stevage","The bit about finding a unique ID for the user is the whole pain point though. The only way to do that really is for the user to create an account on some service that I host.…",{"id":165,"by":166,"text":167},"38973535","toomuchtodo","Cookies or passkeys? Seems very complex when you can use something simply to track state and identity. Passkeys sync across cloud storage, for example, even if you’re using a…",[169,179,184],{"value":170,"source":95,"evidence":171},"Add JWT authentication",[172,176],{"id":173,"by":174,"text":175},"38974714","maxwellg","On the authentication front - JWTs would be a great fit here! The developer's backend could issue a JWT, which is validated by the remoteStorage backend. Then, use the `sub`…",{"id":177,"by":140,"text":178},"38983557","I like this but umm yeah I can roll my own pretty easily. If you had a simple auth solution built in I'd use it!",{"value":180,"source":95,"evidence":181},"Offline support and optimistic local writes",[182,183],{"id":122,"by":123,"text":124},{"id":136,"by":133,"text":137},{"value":185,"source":95,"evidence":186},"OpenAPI spec or API documentation",[187,191],{"id":188,"by":189,"text":190},"38974769","shunyaekam","What is special with the server? I was surprised you offered a community-hosted server. An OpenAPI spec should be enough, right?",{"id":192,"by":193,"text":194},"38986312","novoreorx","I think you should give an API specification so that people don’t bother using public API or hosting another service could implement it directly in the existing backend.",[196,208],{"value":197,"source":95,"evidence":198},"How does this differ from remotestorage.io and PouchDB",[199,203,204],{"id":200,"by":201,"text":202},"38974676","amadeuspagel","How does this compare to PouchDB[1]? [1]: https:\u002F\u002Fpouchdb.com\u002F",{"id":150,"by":151,"text":152},{"id":205,"by":206,"text":207},"38977205","mxuribe","At first I got confused, because i thought this was a refreshed version of https:\u002F\u002Fremotestorage.io ....but, i see now that its different\u002Fseparate.",{"value":209,"source":95,"evidence":210},"How are conflicts resolved between devices",[211],{"id":132,"by":133,"text":134},[213,217,222,229,235],{"value":214,"source":95,"evidence":215},"PouchDB",[216],{"id":200,"by":201,"text":202},{"value":218,"source":95,"evidence":219},"remotestorage.io",[220,221],{"id":154,"by":155,"text":156},{"id":150,"by":151,"text":152},{"value":223,"source":95,"evidence":224},"ElectricSQL",[225],{"id":226,"by":227,"text":228},"38975291","internetter","how does this compare to https:\u002F\u002Felectric-sql.com\u002F",{"value":230,"source":95,"evidence":231},"Triplit",[232],{"id":233,"by":103,"text":234},"38975150","Your app might not need it but if you want partial replication or relational querying in addition to offline-first syncing then check out https:\u002F\u002Fgithub.com\u002Faspen-cloud\u002Ftriplit…",{"value":236,"source":95,"evidence":237},"cr-sqlite",[238],{"id":239,"by":240,"text":241},"38976876","AlexErrant","I'm a happy user of https:\u002F\u002Fgithub.com\u002Fvlcn-io\u002Fcr-sqlite\u002F",{"comments":243,"answered":41,"key":244},16,[245,248,251],{"id":246,"text":247},"38974621","Haha good call! Let's do it.",{"id":249,"text":250},"38980267","You totally _should_ have your own backend \u002F auth when using this. It is not a database. But it’s dead simple if e.g. using something like Clerk.dev for your auth.",{"id":83,"text":84},{"commenters":253,"max_depth":254,"maker_replies":243,"first_comment_mins":255,"lifespan_hours":256},40,4,84,271,[258,260,264,266,270,274,277,279,280,282,284,286,288,290,293,295,299,301,304,306,307,309,312,315,318,320,322,323,326,329,332,333,335,337,340,344,346,348,349,351,353,354,357,361,363,366,369,372,374,376,377,380,384,386,390,394,396,400,403,405,408,410],[98,39,48,259,99,39],"project\u002Fpositive\u002Fdesign",[261,39,43,262,263,39],"38974476","project\u002Fneutral\u002Fdesign","SeanAnderson",[246,39,39,265,11,38],"maker\u002Fneutral\u002Fnone",[267,39,43,268,269,39],"38976943","project\u002Fneutral\u002Fidea","peeters",[271,39,38,272,273,39],"38976799","project\u002Fcritical\u002Fexecution","culopatin",[275,39,43,276,269,39],"38976922","project\u002Fmixed\u002Fdocs",[161,38,38,278,162,39],"project\u002Fcritical\u002Fidea",[249,38,39,265,11,38],[122,33,38,281,123,39],"project\u002Fcritical\u002Fperformance",[83,33,39,283,11,38],"maker\u002Fneutral\u002Fperformance",[126,33,39,285,123,39],"commenter\u002Fneutral\u002Fexecution",[287,33,39,265,11,38],"38975606",[205,43,43,289,206,39],"project\u002Fneutral\u002Fnone",[291,254,38,278,292,39],"38976744","tcper",[113,254,39,294,114,39],"commenter\u002Fpositive\u002Fidea",[296,254,39,297,298,39],"38986832","commenter\u002Fcritical\u002Fidea","antifa",[173,48,48,300,174,39],"project\u002Fpositive\u002Fidea",[302,48,39,303,11,38],"38974760","maker\u002Fpositive\u002Fidea",[305,48,39,289,11,38],"39112684",[200,41,43,268,201,39],[308,41,39,268,11,38],"38974789",[310,31,48,300,311,39],"38975238","ocdtrekkie",[132,313,43,314,133,39],8,"project\u002Fneutral\u002Fexecution",[316,313,43,314,317,39],"38976194","NegativeLatency",[319,313,38,272,133,39],"38976323",[321,313,39,314,11,38],"38976197",[136,313,43,314,133,39],[324,313,38,278,325,39],"38982151","Wowfunhappy",[327,313,38,272,328,39],"38976684","mondrian",[330,313,38,278,331,39],"38977785","bandergirl",[139,313,43,314,140,39],[102,334,48,300,103,39],9,[336,334,39,265,11,38],"38974906",[338,334,48,303,339,39],"38976607","DANmode",[341,334,39,342,343,39],"38977258","commenter\u002Fneutral\u002Fnone","darknavi",[106,345,48,300,107,39],10,[233,345,39,347,103,39],"other\u002Fneutral\u002Fnone",[226,345,39,347,227,39],[350,345,39,347,103,39],"38977049",[352,345,39,303,11,38],"38975231",[239,345,39,347,240,39],[355,34,43,268,356,39],"38978388","notso411",[358,359,39,347,360,39],"38976917",12,"evbogue",[165,362,38,278,166,39],13,[364,362,48,300,365,39],"38973648","cantSpellSober",[367,362,39,368,11,38],"38973857","maker\u002Fneutral\u002Fidea",[192,370,43,371,193,39],14,"project\u002Fneutral\u002Fdocs",[177,32,43,373,140,39],"project\u002Fmixed\u002Fidea",[375,32,39,368,11,38],"38983697",[188,243,43,371,189,39],[378,243,39,379,11,38],"38974864","maker\u002Fneutral\u002Fdocs",[381,382,43,268,383,39],"38974850",17,"lxe",[385,382,39,368,11,38],"38974878",[387,388,38,278,389,39],"38973478",18,"thatwasunusual",[391,388,39,392,393,39],"38973879","commenter\u002Fneutral\u002Fidea","eddd-ddde",[395,388,39,379,11,38],"38973906",[397,18,43,398,399,39],"38975922","project\u002Fneutral\u002Fprivacy","thih9",[401,18,39,402,11,38],"38976027","maker\u002Fneutral\u002Fprivacy",[154,404,39,347,155,39],20,[406,404,43,289,407,39],"39004641","kevincox",[146,409,38,278,147,39],21,[150,411,43,268,151,39],22,[],[414,419,424,428,432,436],{"id":415,"name":416,"points":417,"category":418},"39022693","sitinshade.com",963,"consumer_social",{"id":420,"name":421,"points":422,"category":423},"38951172","GodotOS",534,"productivity",{"id":425,"name":426,"points":427,"category":20},"38987109","#!\u002Fusr\u002Fbin\u002Fenv docker run",496,{"id":429,"name":430,"points":431,"category":20},"38971966","Marimo",448,{"id":433,"name":434,"points":435,"category":423},"38988019","Citadel",338,{"id":437,"name":438,"points":439,"category":20},"39027543","Htmldocs",226,false,"v2",{"id":443,"name":5,"line":6,"ai":440,"cat":20,"year":13,"points":14,"comments":15,"rec":28,"s":444,"top":18,"kw":445,"maker":11,"domain":10,"tier":19,"attempts":39,"strip":446,"url":9,"mix":447,"praise":94,"objection":119,"sub":21},38972358,-0.08,[24,25,26,27],"5331110031503053131155050003153033103031",[31,32,33,34],1794036648096]