From 9036269449354de443bd79de2e268074be2c1233 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Thu, 9 Jan 2025 10:04:24 -0600 Subject: [PATCH] db: use triggers and change table for reactivity on native (#4279) * db: fix reactivity issue, use triggers on both platforms, reduce code duplication * Switch to using commitHook/callback function to process changes rather than polling * switch to using updateHook to trigger processChangs() on native * Stop syncing thread unreads on channel focus, this triggers unnecessary inserts/updates to the db. Stop adding duplicate posts to the flush queue in the change listener. Stop adding posts that were just inserted to the flush queue, we only want to invalidate if it's an update to an existing post. * Add syncChannelThreadUnreads back in, but don't insert unless we're certain we have new unreads * Fix merge artefact * update to new type for op-sqlite db connection --- apps/tlon-mobile/ios/Podfile.lock | 164 ++++++------ apps/tlon-mobile/package.json | 2 +- packages/app/features/top/ChannelScreen.tsx | 13 +- packages/app/lib/baseDb.ts | 77 ++++++ packages/app/lib/nativeDb.ts | 168 ++++++------ packages/app/lib/opsqliteConnection.ts | 4 +- .../app/lib/{webTriggers.ts => triggers.ts} | 4 +- packages/app/lib/webDb.ts | 249 +++++++----------- patches/drizzle-orm@0.36.1.patch | 19 +- pnpm-lock.yaml | 66 ++--- 10 files changed, 384 insertions(+), 382 deletions(-) create mode 100644 packages/app/lib/baseDb.ts rename packages/app/lib/{webTriggers.ts => triggers.ts} (97%) diff --git a/apps/tlon-mobile/ios/Podfile.lock b/apps/tlon-mobile/ios/Podfile.lock index a07aa06a5a..118028b13b 100644 --- a/apps/tlon-mobile/ios/Podfile.lock +++ b/apps/tlon-mobile/ios/Podfile.lock @@ -1757,39 +1757,39 @@ SPEC CHECKSUMS: boost: d3f49c53809116a5d38da093a8aa78bf551aed09 BranchSDK: cb046c2714b03e573484ce9e349e2ddbad7016e8 DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 - EASClient: 45d2459af3eb8a0283b50d3de38af7ac9930d747 - EXApplication: 45ce4be704dbde177c98e2e0e38c3aae3bb92535 - EXAV: e160fec51bf6bc41c3b6f7e4c72ea9fd36860cd6 - EXConstants: 348adb88fb0d65892f16732ec5e02e1365c31588 - EXFont: db0560db6676fce70dcc7656483d6560be02cbf0 - EXImageLoader: ba2506b443b9656e93167c104406a2c265924823 + EASClient: a42ee8bf36c93b3128352faf2ae49405ab4f80bd + EXApplication: 137189a3f149b4e8e546884629392c3efc94cbd3 + EXAV: e4f6137431ddc4cb025895046bfefa9612025c35 + EXConstants: 988aa430ca0f76b43cd46b66e7fae3287f9cc2fc + EXFont: 21b9c760abd593ce8f0d5386b558ced76018506f + EXImageLoader: 55080616b2fe9da19ef8c7f706afd9814e279b6b EXJSONUtils: 5c42959e87be238b045ef37cc5268b16a6c0ad4a - EXManifests: 429136cffa3ae82d1ba3b60b7243fb186615562e - EXMediaLibrary: d3bf5c458643f9821b96af328d399ef3f44de7db - EXNotifications: 0df7db27e8cc862e37a30a56775ab883642f2872 - Expo: 72deced93cbf0d42e434764bb432e010ff4d8bdb - expo-dev-client: 90dd9b69d09a3a3788746df8713252f65125a18b - expo-dev-launcher: 0a8a761b63f1702cb513939198f1be0bcebdb235 - expo-dev-menu: 81becfb05d38e4bdcd50bfeec31617ee08567d5e - expo-dev-menu-interface: 44e69ddff62bbc6c5418c200e657635720b5a480 - ExpoBattery: 6cdcb673b99f53b3e9955b03268fd571cff68f51 - ExpoBlur: 3a9548a738624968836926f4aa1e18fa22155640 - ExpoClipboard: 24cc2b881ab6ca2e5b431b1f6d9d4a302adbf9d0 - ExpoDevice: a9a3955ece7cffe8ee98869d01018e4d6ca91bc7 - ExpoFileSystem: df58e1eb2a4d6f1006a1ca70bddfbbf63e52fa4f - ExpoHaptics: c91902e436f3fb0e07aa19acc118018089fa90de - ExpoImage: a70db90f39a7af98930cef91c84e877b1131f3dd - ExpoImageManipulator: 0c2ada7a028619ea1cc0c670bfa90c8ebeaa4af4 - ExpoImagePicker: d06822d74f1f0e7fe7cb070ece0fff6e678fa3eb - ExpoKeepAwake: 3b8cf8533b9212500565a1e41fb080fc5af29918 - ExpoLinearGradient: 501f9bbd83f3ec1d0e0425862b9ef4693605fc1c - ExpoLocalization: bee500d73d9bd5694c70d1e09d62f15d96cd0977 - ExpoModulesCore: 29b3765cf833c60c0a742e8b0693316105af81df - ExpoSecureStore: 4cec57fd2c40dcff05ce2186c39afc1af39d213c - EXSplashScreen: 7bbe853200d944b7266b53d5c79499b0d1fdcb19 + EXManifests: 5e8c29f36c716af768a4ea47ec05e1b89ab93091 + EXMediaLibrary: 70cf1fb7028fda2d682090c9fe57568674e4abab + EXNotifications: e11f0e9a5b657c064a481a5d522f3bc5a07bf7cd + Expo: fb745b3074989670b6641f9f20463e8ee56a69ca + expo-dev-client: dbc8e8a81d17a9d92e083a2856d056ba9a58984d + expo-dev-launcher: 346abfe8bd102bdcf6605dee01380a97635b63dc + expo-dev-menu: 166fc9c7b82641cdead1dc26d958d20a127ee97b + expo-dev-menu-interface: 7ba029c9d1a82ac22b9b584c00514860b060553e + ExpoBattery: 60bf880aea8f769fe39f709a920442542c1bfd62 + ExpoBlur: e832d874bd94afc0645daddbd3162ec1ce172080 + ExpoClipboard: b597982124f067ff9f5b89093eb3d97898d5d877 + ExpoDevice: d204395e17fffdcefa7470bdef33b07719ac41b1 + ExpoFileSystem: 74cc0fae916f9f044248433971dcfc8c3befd057 + ExpoHaptics: 28a771b630353cd6e8dcf1b1e3e693e38ad7c3c3 + ExpoImage: 8cf2d51de3d03b7e984e9b0ba8f19c0c22057001 + ExpoImageManipulator: c1d7cb865eacd620a35659f3da34c70531f10b59 + ExpoImagePicker: 66970181d1c838f444e5e1f81b804ab2d5ff49bd + ExpoKeepAwake: 0f5cad99603a3268e50af9a6eb8b76d0d9ac956c + ExpoLinearGradient: 4ad1449a2408e0435ac959076562b3921f2e32a1 + ExpoLocalization: f5f5d71dc0c9514d3d77b2771144f6fed6398d04 + ExpoModulesCore: 2346e83abf90c2b2c16a54c3fc4a1169ae12816c + ExpoSecureStore: c84ae37d1c36f38524d289c67c3a2e3fc56f1108 + EXSplashScreen: 6bd596128cd52fac91997ebc64f3d394c843a8f9 EXStructuredHeaders: 5b0f47259db047dc1fdfa84752e292c2bfa68ecd - EXTaskManager: c3da046549b52b572a9bddb140e2b3d0e28b2ece - EXUpdates: 8cc7407328c3852e3ce890a381fe0022ae71902b + EXTaskManager: 3e446dbf75cd662aa6e7d6828be5bc26265241c3 + EXUpdates: 40e069f2987861a6d39101c4496e816561f3e167 EXUpdatesInterface: 3e444e2093e25b7ca0999a7d8c16e8392dee70c3 FBLazyVector: 98c189b92292d4bfeac13ffa8df3ce3d84e2fc5b FBReactNativeSpec: f40d89f4be3e854b08cf9b66cba9e9d6c68d863d @@ -1816,81 +1816,81 @@ SPEC CHECKSUMS: libvmaf: 27f523f1e63c694d14d534cd0fddd2fab0ae8711 libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 nanopb: 438bc412db1928dac798aa6fd75726007be04262 - op-sqlite: b0c2f06e0660235bc3dca10714b0498fdbb811bb + op-sqlite: 12039198fdf455a3af0e18420b656c23cf8ecc01 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 - RCT-Folly: cd21f1661364f975ae76b3308167ad66b09f53f5 + RCT-Folly: 7169b2b1c44399c76a47b5deaaba715eeeb476c0 RCTRequired: d362a61864a64315aee00faea8dee6cf5b3f4aad RCTTypeSafety: 09baf60faeab02492dc8bf04ce5af1dda645b86d ReachabilitySwift: 7f151ff156cea1481a8411701195ac6a984f4979 React: b87c7c7c12f8232bd7cfdc4a00bf687144c17e30 React-callinvoker: 67de0bc05ecb7e690345a53a1661cea9b24670b0 - React-Codegen: e81f008b53f3db74336a4324ea2b1ca15d7fe2da - React-Core: 4cb660fe310d6ee6d1e87308c2bec1541e1a40b4 - React-CoreModules: 12f9b33b8e1c67324be550e3c2ef1a03fa1aa473 - React-cxxreact: 4abb46b2ce1fa4eabe1105be8df331fc2dca9706 + React-Codegen: dd60c11cfeb20cdd8b5a787603346d4e04f95499 + React-Core: 4c87a1873c6d11c6d3843582fbc266ba9ea304ce + React-CoreModules: 29ad1cbe757a70575913457bb7c646b7f4d4edf0 + React-cxxreact: 08ffaf2def6fe1ec8ef7f16e208587c96a87978e React-debug: 30d97e8abfec9b6ba0ad111b92ac749d3ace2dd5 - React-Fabric: 93d15dfd53ea0a8ece4d4bc399924571c1ab2d1c - React-FabricImage: 3e7fa7e5bd260008741e60f7ca4f8d5417b5fa7f - React-graphics: fe19a38958a23f951ef36613a34a6e867a6ef8f9 - React-hermes: 1f47d1f8b218565fd980eb00121d40e0ba2edcea - React-ImageManager: 941a294408b8a35cf929cf38d74deb83522c4556 - React-jserrorhandler: c06aabbbcd49aad9ddaa360b1fedaa7a4a0e820e - React-jsi: 0422dc0b680234b3eabee952c20e23ca63e10b3d - React-jsiexecutor: a61443a46b9616f17142d8f63d2e788f9b634be6 + React-Fabric: 8da8e4c0ed603fbed5208f5045cd1582a96ffa3d + React-FabricImage: f0cf2b8a823e98dae20013910bbdd0889bc719c2 + React-graphics: 6dc8dba8a095a962204dd8bd4ca8264a1ed718db + React-hermes: fdaab14cc289d8d9cd45ffd9a3f8aa11157d4c7e + React-ImageManager: 4a39d60f9163fce83474c2b79acec8336742983d + React-jserrorhandler: 81e19a227a75741456c8f7b1240fc8926fb48c7e + React-jsi: 2253621cb2fb5d43a78fec4db8989cf9711039df + React-jsiexecutor: 5e4620a87fbc4ab174d75220f06ba8b53ae8317b React-jsinspector: aee04d04ef553d5e30e52a4de2af958cb060069f - React-logger: ef76a6d8e04672f19be9b3a49f6ecc4c7141399b - React-Mapbuffer: 30deaceab523707646f5f35efad961133e4e1f25 - react-native-branch: 5d4ecda7bae040542f6c9f651a05d3df23d8b35a - react-native-context-menu-view: c7477ad2a9b5005eb45dd168c2dcdd64526dba77 - react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba - react-native-netinfo: 5364263f903da576bdef9c84a76fe243ab06812c - react-native-safe-area-context: 435f4c13ac75ceed6135382ee77d57d1a5b5b2d6 - react-native-webview: dc76301066fed23a66b1198f990056d2f8c67d20 + React-logger: 87a4232dd55485435edfa6803ff0de0b5c9eea1a + React-Mapbuffer: 94db5977cd64330f9e715c19026c9a267d8358a5 + react-native-branch: 021b9c261f732d0950e9a304284779d48bf81109 + react-native-context-menu-view: dcec18eb8882e20596dbb75802e7d19cb87dac02 + react-native-get-random-values: 21325b2244dfa6b58878f51f9aa42821e7ba3d06 + react-native-netinfo: 3aa5637c18834966e0c932de8ae1ae56fea20a97 + react-native-safe-area-context: b97eb6f9e3b7f437806c2ce5983f479f8eb5de4b + react-native-webview: ad868affda04ff2b204de83546193bc0325a8280 React-nativeconfig: 44cd3076b158c39cb6758f238cd3e65953e989c0 - React-NativeModulesApple: d8f0d34218e897ff56525e4446ba4d5e8f74f9c8 + React-NativeModulesApple: af58ca346cf42c3bb4dab9173b6edd0c83c2a21c React-perflogger: c93b6a895eca3f9196656bb20ce0e15ad597a4e9 React-RCTActionSheet: 258842f426709dccbc2af31ca42b0a1807d76ad7 - React-RCTAnimation: e173f27b6e20108354df11ee0ae5a9f7fe09930f - React-RCTAppDelegate: 8c1e725f4be6d5fb55e7ea62b76c929eddcb859e - React-RCTBlob: 2135cb24f3fa9f4617d7cae03aee10724d5122bb - React-RCTFabric: 51dbbcfa09f76183d2fe9d305eedf0f4f7343333 - React-RCTImage: 345d59868e372e90a40f30f9775c8f34c50b5ebb - React-RCTLinking: d9623fb24075a5a7b9d5f263297135c5128ea37c - React-RCTNetwork: 4dfc12857609eae588c212268656bf0ff3ebe1f3 - React-RCTSettings: 5fa0803b17f29d87dc8d4649a1e5a32d4d081237 - React-RCTText: f23cca90ce571720460ee8e3525ff7e5f1af5ad0 - React-RCTVibration: efd2a82f8ecac6e7b363689322215735d1cbcf9e - React-rendererdebug: ea0f77385485b5251a0e61133e3b5f709ef02e9b + React-RCTAnimation: 78c40269e35864f541b7486d17bd82a353c99fbc + React-RCTAppDelegate: d98ec2cdfb161d7a8496990e9649f94018025922 + React-RCTBlob: 593a5dbc58c45e3cccc37ad4498b10766ace26e6 + React-RCTFabric: c589babe0224cb137f64bfa4770e92d752c18012 + React-RCTImage: a0cdbb81db012ebc42c7dbaabdcb15f488c7c391 + React-RCTLinking: 82b6b0a5b2d5c8d3a28997e70bda46bac4be4c6e + React-RCTNetwork: 45e30079bcb987724028c9a93c0110b6d82e4a1f + React-RCTSettings: e67cbe694fe45b080b96b1394d4e45dff1b3ae04 + React-RCTText: 14a54686a1fa0b51b76660c7700980fdec6c3093 + React-RCTVibration: 00561f3d12dca44ed55af9060752bf8cf3fb0bfc + React-rendererdebug: 9e62f84756f080d88ed01b8063355c3a042934ed React-rncore: e7f10bc6dbd75fa137583bd3b2bc4880203dbc1d React-runtimeexecutor: bf98e8973ed4c45139fbbaf2c34af44053acc9a9 - React-runtimescheduler: e6288bce4309ba16280ce54690b4d7109091ff43 - React-utils: 28bc17e3e21dc06646b45a003718e0fe6034c66f - ReactCommon: e7f772a7660fc683a6ba0a0e0bebd45f4977ee57 - recaptcha-enterprise-react-native: 82f186a0ab29c91c4d4bd08f7758f849e91b4558 + React-runtimescheduler: 7a4ccc1854c5918dee508536fad93172b4c49629 + React-utils: 59bbef368c9ba8b6e27416b46933cd03a35f2841 + ReactCommon: 656e520d76937c8d781ef82a7186a4af7160d814 + recaptcha-enterprise-react-native: 7d63c5bdde3b48996b984a86ac2b536a1d8f5f16 RecaptchaEnterprise: dc302910b77963a0cc6f6908407e30b35268a755 RecaptchaInterop: 7d1a4a01a6b2cb1610a47ef3f85f0c411434cb21 - RNCAsyncStorage: a03b770a50541a761447cea9c24536047832124d - RNCClipboard: c20b93d3a1b47ce86dcffa5c0af5dd59938e47e1 - RNDeviceInfo: addb9b427c2822a2d8e94c87a136a224e0af738c - RNFBApp: e905ce948d9290555d9ab1303449827eb5119b5c - RNFBCrashlytics: e9459c8656ccbbb53d12afe47b4a96194c858745 - RNFBPerf: ae6cfdac3e06eab3d4d092944e979e95a69b0179 - RNFlashList: 1076a3fb7c4608a8cdf265f0783592b8fc41b6a7 - RNGestureHandler: 9852b7617e28551f5d339309c73ebf221ba1013d - RNReanimated: 1b07d2def747e4e5510ecd8460b2862029ee3df2 - RNScreens: 1174f55dd2f72abe45a4e48bf20e552feb051600 - RNSVG: a9e095acf2e207f2ef491870523ed455636cf3b8 + RNCAsyncStorage: 618d03a5f52fbccb3d7010076bc54712844c18ef + RNCClipboard: 090462274cc05b02628bd158baf6d73c3abe8441 + RNDeviceInfo: db5c64a060e66e5db3102d041ebe3ef307a85120 + RNFBApp: 91311b27bc9a33e23b76a62825afd1635501018a + RNFBCrashlytics: c3219ef7a0c779f2428236215781c38e7892f6f9 + RNFBPerf: 2c926ff255c704a644dd53572008cba47c67ada0 + RNFlashList: 4b4b6b093afc0df60ae08f9cbf6ccd4c836c667a + RNGestureHandler: 79c035e2243d3b7f4f353e7eb32869eace6b596c + RNReanimated: 4b1bce37b188450e3a2d03c925edd5fb11d4bb2d + RNScreens: a4d9ce8f68f833f4e42410140eafd88e38bba163 + RNSVG: 3f65a03e0c61a8495dee92bf82545ed9041cbf3b SDWebImage: 750adf017a315a280c60fde706ab1e552a3ae4e9 SDWebImageAVIFCoder: 8348fef6d0ec69e129c66c9fe4d74fbfbf366112 SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c SDWebImageWebPCoder: af09429398d99d524cae2fe00f6f0f6e491ed102 SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 sqlite3: f163dbbb7aa3339ad8fc622782c2d9d7b72f7e9c - tentap: 2a4256b6641a27d72d2b5fe2eb94cd1a203e4975 + tentap: 2cf2e387dd284bf867010eb7d0f91618fb35b673 UMAppLoader: 5df85360d65cabaef544be5424ac64672e648482 Yoga: fb61b2337c7688c81a137e5560b3cbb515289f91 PODFILE CHECKSUM: 0cb7a78e5777e69c86c1bf4bb5135fd660376dbe -COCOAPODS: 1.16.2 +COCOAPODS: 1.15.2 diff --git a/apps/tlon-mobile/package.json b/apps/tlon-mobile/package.json index 3c4615c436..49c6c1ae95 100644 --- a/apps/tlon-mobile/package.json +++ b/apps/tlon-mobile/package.json @@ -44,7 +44,7 @@ "@dev-plugins/react-query": "^0.0.6", "@google-cloud/recaptcha-enterprise-react-native": "^18.3.0", "@gorhom/bottom-sheet": "^4.5.1", - "@op-engineering/op-sqlite": "5.0.5", + "@op-engineering/op-sqlite": "11.2.4", "@react-native-async-storage/async-storage": "1.21.0", "@react-native-clipboard/clipboard": "^1.14.0", "@react-native-community/netinfo": "11.1.0", diff --git a/packages/app/features/top/ChannelScreen.tsx b/packages/app/features/top/ChannelScreen.tsx index 8d3c768143..682fdddecd 100644 --- a/packages/app/features/top/ChannelScreen.tsx +++ b/packages/app/features/top/ChannelScreen.tsx @@ -64,11 +64,6 @@ export default function ChannelScreen(props: Props) { const channelIsPending = !channel || channel.isPendingChannel; useFocusEffect( useCallback(() => { - if (!channelIsPending) { - store.syncChannelThreadUnreads(channelId, { - priority: store.SyncPriority.High, - }); - } // Mark the channel as visited when we unfocus/leave this screen () => { if (!channelIsPending) { @@ -101,6 +96,14 @@ export default function ChannelScreen(props: Props) { }, [groupId, channelId]) ); + useEffect(() => { + if (!channelIsPending) { + store.syncChannelThreadUnreads(channelId, { + priority: store.SyncPriority.High, + }); + } + }, [channelIsPending, channelId]); + const [channelNavOpen, setChannelNavOpen] = React.useState(false); const [inviteSheetGroup, setInviteSheetGroup] = React.useState(); diff --git a/packages/app/lib/baseDb.ts b/packages/app/lib/baseDb.ts new file mode 100644 index 0000000000..e49bba330b --- /dev/null +++ b/packages/app/lib/baseDb.ts @@ -0,0 +1,77 @@ +import { createDevLogger } from '@tloncorp/shared'; +import { handleChange } from '@tloncorp/shared/db'; +import { sql } from 'drizzle-orm'; +import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import { useEffect, useMemo, useState } from 'react'; + +export const enableLogger = false; +export const logger = createDevLogger('db', enableLogger); + +export const changeLogTable = sqliteTable('__change_log', { + id: integer('id').primaryKey(), + table_name: text('table_name').notNull(), + operation: text('operation').notNull(), + row_id: integer('row_id'), + row_data: text('row_data'), + timestamp: integer('timestamp').default(sql`(strftime('%s', 'now'))`), +}); + +export function useMigrations(db: BaseDb) { + const [hasSucceeded, setHasSucceeded] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + logger.log('running migrations'); + const startTime = Date.now(); + db.runMigrations() + .then(() => setHasSucceeded(true)) + .catch((e) => { + logger.log('failed to migrate database', e); + setError(e); + }) + .finally(() => + logger.log('migrations complete in', Date.now() - startTime + 'ms') + ); + }, [db]); + + return useMemo( + () => ({ + success: hasSucceeded, + error: error, + }), + [hasSucceeded, error] + ); +} + +export abstract class BaseDb { + protected client: any = null; + protected isPolling = false; + + abstract setupDb(): Promise; + abstract purgeDb(): Promise; + abstract getDbPath(): Promise; + abstract runMigrations(): Promise; + + protected async processChanges() { + if (!this.client) return; + + try { + const changes = await this.client.select().from(changeLogTable).all(); + for (const change of changes) { + handleChange({ + table: change.table_name, + operation: change.operation as 'INSERT' | 'UPDATE' | 'DELETE', + row: JSON.parse(change.row_data ?? ''), + }); + } + await this.client.delete(changeLogTable).run(); + } catch (e) { + logger.error('failed to process changes:', e); + } + } + + async resetDb() { + await this.purgeDb(); + await this.runMigrations(); + } +} diff --git a/packages/app/lib/nativeDb.ts b/packages/app/lib/nativeDb.ts index c8ca44d71f..82fea91cfe 100644 --- a/packages/app/lib/nativeDb.ts +++ b/packages/app/lib/nativeDb.ts @@ -1,108 +1,90 @@ import { open } from '@op-engineering/op-sqlite'; -import { createDevLogger, escapeLog } from '@tloncorp/shared'; -import { handleChange, schema, setClient } from '@tloncorp/shared/db'; -import { AnySqliteDatabase } from '@tloncorp/shared/db'; -import { useEffect, useMemo, useState } from 'react'; +import { escapeLog } from '@tloncorp/shared'; +import { schema, setClient } from '@tloncorp/shared/db'; +import { + BaseDb, + enableLogger, + logger, + useMigrations as useMigrationsBase, +} from './baseDb'; import { OPSQLite$SQLiteConnection } from './opsqliteConnection'; import { SQLiteConnection } from './sqliteConnection'; +import { TRIGGER_SETUP } from './triggers'; -let connection: SQLiteConnection | null = null; -let client: AnySqliteDatabase | null = null; +export class NativeDb extends BaseDb { + private connection: SQLiteConnection | null = null; -const enableLogger = false; -const logger = createDevLogger('db', enableLogger); + async setupDb() { + if (this.connection || this.client) { + logger.warn('setupDb called multiple times, ignoring'); + return; + } + this.connection = new OPSQLite$SQLiteConnection( + // NB: the iOS code in SQLiteDB.swift relies on this path - if you change + // this, you should change that too. + open({ location: 'default', name: 'tlon.sqlite' }) + ); + // Experimental SQLite settings. May cause crashes. More here: + // https://ospfranco.notion.site/Configuration-6b8b9564afcc4ac6b6b377fe34475090 + this.connection.execute('PRAGMA mmap_size=268435456'); + this.connection.execute('PRAGMA journal_mode=MEMORY'); + this.connection.execute('PRAGMA synchronous=OFF'); -export function setupDb() { - if (connection || client) { - logger.warn('setupDb called multiple times, ignoring'); - return; - } - connection = new OPSQLite$SQLiteConnection( - // NB: the iOS code in SQLiteDB.swift relies on this path - if you change - // this, you should change that too. - open({ location: 'default', name: 'tlon.sqlite' }) - ); - // Experimental SQLite settings. May cause crashes. More here: - // https://ospfranco.notion.site/Configuration-6b8b9564afcc4ac6b6b377fe34475090 - connection.execute('PRAGMA mmap_size=268435456'); - connection.execute('PRAGMA journal_mode=MEMORY'); - connection.execute('PRAGMA synchronous=OFF'); - - connection.updateHook(handleChange); - - client = connection.createClient({ - schema, - logger: enableLogger - ? { - logQuery(query, params) { - logger.log(escapeLog(query), params); - }, - } - : undefined, - }); - setClient(client); - logger.log('SQLite database opened at', connection.getDbPath()); -} + this.connection.updateHook(() => { + this.processChanges(); + }); -export async function purgeDb() { - if (!connection) { - logger.warn('purgeDb called before setupDb, ignoring'); - return; + this.client = this.connection.createClient({ + schema, + logger: enableLogger + ? { + logQuery(query, params) { + logger.log(escapeLog(query), params); + }, + } + : undefined, + }); + setClient(this.client); + logger.log('SQLite database opened at', this.connection.getDbPath()); } - logger.log('purging sqlite database'); - connection.close(); - connection.delete(); - connection = null; - client = null; - logger.log('purged sqlite database, recreating'); - setupDb(); -} - -export function getDbPath() { - return connection?.getDbPath(); -} - -export function useMigrations() { - const [hasSucceeded, setHasSucceeded] = useState(false); - const [error, setError] = useState(null); - useEffect(() => { - logger.log('running migrations'); - const startTime = Date.now(); - runMigrations() - .then(() => setHasSucceeded(true)) - .catch((e) => { - logger.log('failed to migrate database', e); - setError(e); - }) - .finally(() => - logger.log('migrations complete in', Date.now() - startTime + 'ms') - ); - }, []); + async purgeDb() { + if (!this.connection) { + logger.warn('purgeDb called before setupDb, ignoring'); + return; + } + logger.log('purging sqlite database'); + this.connection.close(); + this.connection.delete(); + this.connection = null; + this.client = null; + logger.log('purged sqlite database, recreating'); + await this.setupDb(); + } - return useMemo( - () => ({ - success: hasSucceeded, - error: error, - }), - [hasSucceeded, error] - ); -} + async getDbPath(): Promise { + return this.connection?.getDbPath(); + } -async function runMigrations() { - try { - await connection?.migrateClient(client!); - return; - } catch (e) { - logger.log('migrations failed, purging db and retrying', e); + async runMigrations() { + try { + await this.connection?.migrateClient(this.client!); + this.connection?.execute(TRIGGER_SETUP); + return; + } catch (e) { + logger.log('migrations failed, purging db and retrying', e); + } + await this.purgeDb(); + await this.connection?.migrateClient(this.client!); + logger.log("migrations succeeded after purge, shouldn't happen often"); } - await purgeDb(); - await connection?.migrateClient(client!); - logger.log("migrations succeeded after purge, shouldn't happen often"); } -export async function resetDb() { - await purgeDb(); - await connection?.migrateClient(client!); -} +// Create singleton instance +const nativeDb = new NativeDb(); +export const setupDb = () => nativeDb.setupDb(); +export const purgeDb = () => nativeDb.purgeDb(); +export const getDbPath = () => nativeDb.getDbPath(); +export const resetDb = () => nativeDb.resetDb(); +export const useMigrations = () => useMigrationsBase(nativeDb); diff --git a/packages/app/lib/opsqliteConnection.ts b/packages/app/lib/opsqliteConnection.ts index 8c8232afac..c0333f72c1 100644 --- a/packages/app/lib/opsqliteConnection.ts +++ b/packages/app/lib/opsqliteConnection.ts @@ -1,4 +1,4 @@ -import { OPSQLiteConnection } from '@op-engineering/op-sqlite'; +import { DB } from '@op-engineering/op-sqlite'; import { migrations } from '@tloncorp/shared/db/migrations'; import { Schema } from '@tloncorp/shared/db/types'; import { DrizzleConfig } from 'drizzle-orm'; @@ -11,7 +11,7 @@ import { SQLiteConnection } from './sqliteConnection'; export class OPSQLite$SQLiteConnection implements SQLiteConnection> { - constructor(private connection: OPSQLiteConnection) {} + constructor(private connection: DB) {} execute(query: string): void { this.connection.execute(query); diff --git a/packages/app/lib/webTriggers.ts b/packages/app/lib/triggers.ts similarity index 97% rename from packages/app/lib/webTriggers.ts rename to packages/app/lib/triggers.ts index 91dad7acfb..b54ec525ae 100644 --- a/packages/app/lib/webTriggers.ts +++ b/packages/app/lib/triggers.ts @@ -78,8 +78,8 @@ BEGIN VALUES ( 'post_reactions', 'DELETE', - OLD.id, - json_object('id', OLD.id, 'post_id', OLD.post_id) + OLD.value, + json_object('value', OLD.value, 'post_id', OLD.post_id) ); END; diff --git a/packages/app/lib/webDb.ts b/packages/app/lib/webDb.ts index 44b86f0cf1..81d300865f 100644 --- a/packages/app/lib/webDb.ts +++ b/packages/app/lib/webDb.ts @@ -1,186 +1,113 @@ -import { createDevLogger } from '@tloncorp/shared'; import type { Schema } from '@tloncorp/shared/db'; -import { handleChange, schema, setClient } from '@tloncorp/shared/db'; +import { schema, setClient } from '@tloncorp/shared/db'; import { migrations } from '@tloncorp/shared/db/migrations'; -import { sql } from 'drizzle-orm'; -import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'; import { drizzle } from 'drizzle-orm/sqlite-proxy'; -import { useEffect, useMemo, useState } from 'react'; import { SQLocalDrizzle } from 'sqlocal/drizzle'; +import { BaseDb, logger, useMigrations as useMigrationsBase } from './baseDb'; +import { TRIGGER_SETUP } from './triggers'; import migrate from './webMigrator'; -import { TRIGGER_SETUP } from './webTriggers'; -const POLL_INTERVAL = 100; +export class WebDb extends BaseDb { + private sqlocal: SQLocalDrizzle | null = null; -let sqlocal: SQLocalDrizzle | null = null; -let client: ReturnType> | null = null; - -const enableLogger = false; -const logger = createDevLogger('db', enableLogger); + async setupDb() { + if (this.sqlocal || this.client) { + logger.warn('setupDb called multiple times, ignoring'); + return; + } + try { + this.sqlocal = new SQLocalDrizzle({ + databasePath: 'tlon.sqlite', + verbose: false, + }); -export async function setupDb() { - if (sqlocal || client) { - logger.warn('setupDb called multiple times, ignoring'); - return; - } - try { - sqlocal = new SQLocalDrizzle({ - databasePath: 'tlon.sqlite', - verbose: enableLogger, - }); - - logger.log('sqlocal instance created', { sqlocal }); - // Experimental SQLite settings. May cause crashes. More here: - // https://ospfranco.notion.site/Configuration-6b8b9564afcc4ac6b6b377fe34475090 - await sqlocal.sql('PRAGMA mmap_size=268435456'); - // await sqlocal.sql('PRAGMA journal_mode=MEMORY'); - await sqlocal.sql('PRAGMA synchronous=OFF'); - await sqlocal.sql('PRAGMA journal_mode=WAL'); - - const { driver } = sqlocal; - - client = drizzle(driver, { - schema, - logger: enableLogger - ? { - logQuery(query, params) { - logger.log(query, params); - }, - } - : undefined, - }); - - const dbInfo = await sqlocal.getDatabaseInfo(); - logger.log('SQLite database opened:', dbInfo); - - setClient(client); - } catch (e) { - logger.error('Failed to setup SQLite db', e); - } -} + logger.log('sqlocal instance created', { sqlocal: this.sqlocal }); + // Experimental SQLite settings. May cause crashes. More here: + // https://ospfranco.notion.site/Configuration-6b8b9564afcc4ac6b6b377fe34475090 + await this.sqlocal.sql('PRAGMA mmap_size=268435456'); + // await this.sqlocal.sql('PRAGMA journal_mode=MEMORY'); + await this.sqlocal.sql('PRAGMA synchronous=OFF'); + await this.sqlocal.sql('PRAGMA journal_mode=WAL'); -export async function checkDb() { - if (!sqlocal) { - logger.warn('checkDb called before setupDb, ignoring'); - return; - } - const dbInfo = await sqlocal.getDatabaseInfo(); - logger.log('SQLite database info:', dbInfo); - return dbInfo; -} + await this.sqlocal.createCallbackFunction('processChanges', async () => + this.processChanges() + ); -let isPolling = false; + const { driver } = this.sqlocal; -function startChangePolling() { - if (isPolling) return; - isPolling = true; - pollChanges(); -} + this.client = drizzle(driver, { schema }); + setClient(this.client); -const changeLogTable = sqliteTable('__change_log', { - id: integer('id').primaryKey(), - table_name: text('table_name').notNull(), - operation: text('operation').notNull(), - row_id: integer('row_id'), - row_data: text('row_data'), - timestamp: integer('timestamp').default(sql`(strftime('%s', 'now'))`), -}); - -async function pollChanges() { - if (!client) return; - - try { - const changes = await client.select().from(changeLogTable).all(); - - for (const change of changes) { - handleChange({ - table: change.table_name, - operation: change.operation as 'INSERT' | 'UPDATE' | 'DELETE', - row: JSON.parse(change.row_data ?? ''), - }); + const dbInfo = await this.sqlocal.getDatabaseInfo(); + logger.log('SQLite database opened:', dbInfo); + } catch (e) { + logger.error('Failed to setup SQLite db', e); } - - // Clear processed changes - await client.delete(changeLogTable).run(); - } catch (error) { - console.error('Error polling changes:', error); - } finally { - // Schedule next poll - setTimeout(pollChanges, POLL_INTERVAL); // Poll every second } -} -export async function purgeDb() { - if (!sqlocal) { - logger.warn('purgeDb called before setupDb, ignoring'); - return; + async checkDb() { + if (!this.sqlocal) { + logger.warn('checkDb called before setupDb, ignoring'); + return; + } + const dbInfo = await this.sqlocal.getDatabaseInfo(); + logger.log('SQLite database info:', dbInfo); + return dbInfo; } - logger.log('purging sqlite database'); - sqlocal.destroy(); - sqlocal = null; - client = null; - logger.log('purged sqlite database, recreating'); - await setupDb(); -} - -export async function getDbPath() { - return sqlocal?.getDatabaseInfo().then((info) => info.databasePath); -} -export function useMigrations() { - const [hasSucceeded, setHasSucceeded] = useState(false); - const [error, setError] = useState(null); - - useEffect(() => { - async function performMigrations() { - logger.log('running migrations'); - const startTime = Date.now(); - - try { - await runMigrations(); - setHasSucceeded(true); - logger.log('migrations complete in', Date.now() - startTime + 'ms'); - } catch (e) { - logger.log('failed to migrate database', e); - setError(e); - } + async purgeDb() { + if (!this.sqlocal) { + logger.warn('purgeDb called before setupDb, ignoring'); + return; } + logger.log('purging sqlite database'); + this.sqlocal.destroy(); + this.sqlocal = null; + this.client = null; + logger.log('purged sqlite database, recreating'); + await this.setupDb(); + } - performMigrations(); - }, []); - - return useMemo( - () => ({ - success: hasSucceeded, - error: error, - }), - [hasSucceeded, error] - ); -} - -async function runMigrations() { - if (!client || !sqlocal) { - logger.warn('runMigrations called before setupDb, ignoring'); - return; + async getDbPath() { + return this.sqlocal?.getDatabaseInfo().then((info) => info.databasePath); } - try { - logger.log('runMigrations: starting migration'); - await migrate(client, migrations, sqlocal); - logger.log('runMigrations: migrations succeeded'); - await sqlocal.sql(TRIGGER_SETUP); - startChangePolling(); - return; - } catch (e) { - logger.log('migrations failed, purging db and retrying', e); + async runMigrations() { + if (!this.client || !this.sqlocal) { + logger.warn('runMigrations called before setupDb, ignoring'); + return; + } + + try { + logger.log('runMigrations: starting migration'); + await migrate(this.client, migrations, this.sqlocal); + logger.log('runMigrations: migrations succeeded'); + await this.sqlocal.sql(TRIGGER_SETUP); + + await this.sqlocal.sql(` + CREATE TRIGGER IF NOT EXISTS after_changes_insert + AFTER INSERT ON __change_log + BEGIN + SELECT processChanges(); + END; + `); + + return; + } catch (e) { + logger.log('migrations failed, purging db and retrying', e); + } + await this.purgeDb(); + await migrate(this.client, migrations, this.sqlocal); + logger.log("migrations succeeded after purge, shouldn't happen often"); } - await purgeDb(); - await migrate(client, migrations, sqlocal); - logger.log("migrations succeeded after purge, shouldn't happen often"); } -export async function resetDb() { - await purgeDb(); - await migrate(client!, migrations, sqlocal!); -} +// Create singleton instance +const webDb = new WebDb(); +export const setupDb = () => webDb.setupDb(); +export const checkDb = () => webDb.checkDb(); +export const purgeDb = () => webDb.purgeDb(); +export const getDbPath = () => webDb.getDbPath(); +export const resetDb = () => webDb.resetDb(); +export const useMigrations = () => useMigrationsBase(webDb); diff --git a/patches/drizzle-orm@0.36.1.patch b/patches/drizzle-orm@0.36.1.patch index 93e826c369..c32bb66088 100644 --- a/patches/drizzle-orm@0.36.1.patch +++ b/patches/drizzle-orm@0.36.1.patch @@ -220,7 +220,7 @@ index 07cae6b6cb6c0c1a11d283ae26aad1c1c1d7cd4d..021c904e0b9d22fad58853669c341b51 } export {}; diff --git a/op-sqlite/session.js b/op-sqlite/session.js -index ff84604e7998182ef78fbffc8d20019aaf76755e..454f154677b6761798419b9196b1ba5674cbb5c6 100644 +index ff84604e7998182ef78fbffc8d20019aaf76755e..b924059196f0bc7f368bebd920aa0cbca89f65eb 100644 --- a/op-sqlite/session.js +++ b/op-sqlite/session.js @@ -1,6 +1,6 @@ @@ -285,8 +285,12 @@ index ff84604e7998182ef78fbffc8d20019aaf76755e..454f154677b6761798419b9196b1ba56 } } class OPSQLitePreparedQuery extends SQLitePreparedQuery { -@@ -72,7 +69,14 @@ class OPSQLitePreparedQuery extends SQLitePreparedQuery { - return this.client.executeAsync(this.query.sql, params); +@@ -69,10 +66,17 @@ class OPSQLitePreparedQuery extends SQLitePreparedQuery { + run(placeholderValues) { + const params = fillPlaceholders(this.query.params, placeholderValues ?? {}); + this.logger.logQuery(this.query.sql, params); +- return this.client.executeAsync(this.query.sql, params); ++ return this.client.execute(this.query.sql, params); } async all(placeholderValues) { - const { fields, joinsNotNullableMap, query, logger, customResultMapper, client } = this; @@ -317,6 +321,15 @@ index ff84604e7998182ef78fbffc8d20019aaf76755e..454f154677b6761798419b9196b1ba56 const params = fillPlaceholders(query.params, placeholderValues ?? {}); logger.logQuery(query.sql, params); if (!fields && !customResultMapper) { +@@ -105,7 +116,7 @@ class OPSQLitePreparedQuery extends SQLitePreparedQuery { + values(placeholderValues) { + const params = fillPlaceholders(this.query.params, placeholderValues ?? {}); + this.logger.logQuery(this.query.sql, params); +- return this.client.executeRawAsync(this.query.sql, params); ++ return this.client.executeRaw(this.query.sql, params); + } + /** @internal */ + isResponseInArrayMode() { diff --git a/op-sqlite/session.js.map b/op-sqlite/session.js.map index e9d9ca06b6bed8fa265438fc41fe35e20723f61a..ebfae284accc68b35aae9d8dc8e61fdbe07f8f15 100644 --- a/op-sqlite/session.js.map diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d5887b0d85..ea70e29713 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,7 +40,7 @@ patchedDependencies: hash: 5ofxtlxe6za2xqrbq5pqbz7wb4 path: patches/any-ascii@0.3.2.patch drizzle-orm@0.36.1: - hash: 7epepcnfyprhmo2nsyfz6xaw3i + hash: emfaialij3z7aqdjf53okvbhey path: patches/drizzle-orm@0.36.1.patch react-cosmos@6.1.1: hash: y5kop5rmmd7jprmzzewk4whiie @@ -127,8 +127,8 @@ importers: specifier: ^4.5.1 version: 4.6.0(@types/react-native@0.73.0(@babel/core@7.25.2)(@babel/preset-env@7.23.7(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(@types/react@18.2.55)(react-native-gesture-handler@2.20.0(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.23.7(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native-reanimated@3.8.1(patch_hash=iololnqcieadxwydikuxdlowf4)(@babel/core@7.25.2)(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.23.7(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.23.7(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0) '@op-engineering/op-sqlite': - specifier: 5.0.5 - version: 5.0.5(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.23.7(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + specifier: 11.2.4 + version: 11.2.4(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.23.7(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0) '@react-native-async-storage/async-storage': specifier: 1.21.0 version: 1.21.0(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.23.7(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0)) @@ -1180,7 +1180,7 @@ importers: version: 0.19.12(encoding@0.1.13)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) sqlocal: specifier: ^0.11.1 - version: 0.11.1(drizzle-orm@0.36.1(patch_hash=7epepcnfyprhmo2nsyfz6xaw3i)(@op-engineering/op-sqlite@5.0.5(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.9)(@types/react@18.2.55)(better-sqlite3@9.4.5)(react@18.2.0)) + version: 0.11.1(drizzle-orm@0.36.1(patch_hash=emfaialij3z7aqdjf53okvbhey)(@op-engineering/op-sqlite@11.2.4(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.9)(@types/react@18.2.55)(better-sqlite3@9.4.5)(react@18.2.0)) tailwindcss-opentype: specifier: ^1.1.0 version: 1.1.0(tailwindcss@3.4.1) @@ -1403,7 +1403,7 @@ importers: version: 3.1.0(base-64@1.0.0)(react-native-fetch-api@3.0.0)(react-native-get-random-values@1.11.0(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0)))(react-native-url-polyfill@2.0.0(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0)))(text-encoding@0.7.0)(web-streams-polyfill@3.3.3) sqlocal: specifier: ^0.11.1 - version: 0.11.1(drizzle-orm@0.36.1(patch_hash=7epepcnfyprhmo2nsyfz6xaw3i)(@op-engineering/op-sqlite@5.0.5(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.9)(@types/react@18.2.55)(better-sqlite3@9.4.5)(react@18.2.0)) + version: 0.11.1(drizzle-orm@0.36.1(patch_hash=emfaialij3z7aqdjf53okvbhey)(@op-engineering/op-sqlite@11.2.4(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.9)(@types/react@18.2.55)(better-sqlite3@9.4.5)(react@18.2.0)) zustand: specifier: ^3.7.2 version: 3.7.2(react@18.2.0) @@ -1498,7 +1498,7 @@ importers: version: 3.0.0 drizzle-orm: specifier: 0.36.1 - version: 0.36.1(patch_hash=7epepcnfyprhmo2nsyfz6xaw3i)(@op-engineering/op-sqlite@5.0.5(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.9)(@types/react@18.2.55)(better-sqlite3@9.4.5)(react@18.2.0) + version: 0.36.1(patch_hash=emfaialij3z7aqdjf53okvbhey)(@op-engineering/op-sqlite@11.2.4(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.9)(@types/react@18.2.55)(better-sqlite3@9.4.5)(react@18.2.0) exponential-backoff: specifier: ^3.1.1 version: 3.1.1 @@ -4443,11 +4443,11 @@ packages: engines: {node: '>=10'} deprecated: This functionality has been moved to @npmcli/fs - '@op-engineering/op-sqlite@5.0.5': - resolution: {integrity: sha512-7BK+U2EY4S8ZUc0zKuRQkDiadP3lyhSimspu74GQsjzT3tjmbcrJ19rg7+D0T1bAAOJNbyHQhWZvcKtkHiBRRg==} + '@op-engineering/op-sqlite@11.2.4': + resolution: {integrity: sha512-YL3n2zxbv+VrEgFw3JYdhGbwhs4Gqt5/ofcyx3F4Bd+cI37lmhbGxTPNnJZvFo4fGFMpEfnNVgz2hOPKR8EpNg==} peerDependencies: react: '*' - react-native: '*' + react-native: '>0.73.0' '@open-draft/until@1.0.3': resolution: {integrity: sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==} @@ -14298,16 +14298,16 @@ snapshots: '@aws-sdk/abort-controller@3.190.0': dependencies: '@aws-sdk/types': 3.190.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/chunked-blob-reader-native@3.188.0': dependencies: '@aws-sdk/util-base64-browser': 3.188.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/chunked-blob-reader@3.188.0': dependencies: - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/client-s3@3.190.0': dependencies: @@ -14464,7 +14464,7 @@ snapshots: '@aws-sdk/property-provider': 3.190.0 '@aws-sdk/types': 3.190.0 '@aws-sdk/url-parser': 3.190.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/credential-provider-ini@3.190.0': dependencies: @@ -14545,7 +14545,7 @@ snapshots: dependencies: '@aws-sdk/eventstream-codec': 3.190.0 '@aws-sdk/types': 3.190.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/fetch-http-handler@3.190.0': dependencies: @@ -14580,7 +14580,7 @@ snapshots: '@aws-sdk/is-array-buffer@3.188.0': dependencies: - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/md5-js@3.190.0': dependencies: @@ -14675,7 +14675,7 @@ snapshots: '@aws-sdk/protocol-http': 3.190.0 '@aws-sdk/signature-v4': 3.190.0 '@aws-sdk/types': 3.190.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/middleware-serde@3.190.0': dependencies: @@ -14724,7 +14724,7 @@ snapshots: '@aws-sdk/property-provider@3.190.0': dependencies: '@aws-sdk/types': 3.190.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/protocol-http@3.190.0': dependencies: @@ -14735,12 +14735,12 @@ snapshots: dependencies: '@aws-sdk/types': 3.190.0 '@aws-sdk/util-uri-escape': 3.188.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/querystring-parser@3.190.0': dependencies: '@aws-sdk/types': 3.190.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/s3-request-presigner@3.190.0': dependencies: @@ -14761,7 +14761,7 @@ snapshots: '@aws-sdk/shared-ini-file-loader@3.190.0': dependencies: '@aws-sdk/types': 3.190.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/signature-v4-multi-region@3.190.0': dependencies: @@ -14778,7 +14778,7 @@ snapshots: '@aws-sdk/util-hex-encoding': 3.188.0 '@aws-sdk/util-middleware': 3.190.0 '@aws-sdk/util-uri-escape': 3.188.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/smithy-client@3.190.0': dependencies: @@ -14796,7 +14796,7 @@ snapshots: '@aws-sdk/util-arn-parser@3.188.0': dependencies: - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/util-base64-browser@3.188.0': dependencies: @@ -14818,11 +14818,11 @@ snapshots: '@aws-sdk/util-buffer-from@3.188.0': dependencies: '@aws-sdk/is-array-buffer': 3.188.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/util-config-provider@3.188.0': dependencies: - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/util-create-request@3.190.0': dependencies: @@ -14855,15 +14855,15 @@ snapshots: '@aws-sdk/util-hex-encoding@3.188.0': dependencies: - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/util-locate-window@3.188.0': dependencies: - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/util-middleware@3.190.0': dependencies: - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/util-stream-browser@3.190.0': dependencies: @@ -18802,12 +18802,12 @@ snapshots: mkdirp: 1.0.4 rimraf: 3.0.2 - '@op-engineering/op-sqlite@5.0.5(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.23.7(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0)': + '@op-engineering/op-sqlite@11.2.4(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.23.7(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0)': dependencies: react: 18.2.0 react-native: 0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.23.7(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0) - '@op-engineering/op-sqlite@5.0.5(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0)': + '@op-engineering/op-sqlite@11.2.4(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0)': dependencies: react: 18.2.0 react-native: 0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0) @@ -24439,9 +24439,9 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.36.1(patch_hash=7epepcnfyprhmo2nsyfz6xaw3i)(@op-engineering/op-sqlite@5.0.5(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.9)(@types/react@18.2.55)(better-sqlite3@9.4.5)(react@18.2.0): + drizzle-orm@0.36.1(patch_hash=emfaialij3z7aqdjf53okvbhey)(@op-engineering/op-sqlite@11.2.4(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.9)(@types/react@18.2.55)(better-sqlite3@9.4.5)(react@18.2.0): optionalDependencies: - '@op-engineering/op-sqlite': 5.0.5(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + '@op-engineering/op-sqlite': 11.2.4(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0) '@opentelemetry/api': 1.9.0 '@types/better-sqlite3': 7.6.9 '@types/react': 18.2.55 @@ -30342,13 +30342,13 @@ snapshots: sprintf-js@1.0.3: {} - sqlocal@0.11.1(drizzle-orm@0.36.1(patch_hash=7epepcnfyprhmo2nsyfz6xaw3i)(@op-engineering/op-sqlite@5.0.5(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.9)(@types/react@18.2.55)(better-sqlite3@9.4.5)(react@18.2.0)): + sqlocal@0.11.1(drizzle-orm@0.36.1(patch_hash=emfaialij3z7aqdjf53okvbhey)(@op-engineering/op-sqlite@11.2.4(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.9)(@types/react@18.2.55)(better-sqlite3@9.4.5)(react@18.2.0)): dependencies: '@sqlite.org/sqlite-wasm': 3.46.0-build2 coincident: 1.2.3 nanoid: 5.0.7 optionalDependencies: - drizzle-orm: 0.36.1(patch_hash=7epepcnfyprhmo2nsyfz6xaw3i)(@op-engineering/op-sqlite@5.0.5(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.9)(@types/react@18.2.55)(better-sqlite3@9.4.5)(react@18.2.0) + drizzle-orm: 0.36.1(patch_hash=emfaialij3z7aqdjf53okvbhey)(@op-engineering/op-sqlite@11.2.4(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.9)(@types/react@18.2.55)(better-sqlite3@9.4.5)(react@18.2.0) transitivePeerDependencies: - bufferutil - utf-8-validate