diff --git a/.changeset/beige-actors-flash.md b/.changeset/beige-actors-flash.md new file mode 100644 index 000000000..aeacf8d6a --- /dev/null +++ b/.changeset/beige-actors-flash.md @@ -0,0 +1,8 @@ +--- +'@powersync/react-native': minor +--- + +Introduced `executeRaw` member to `RNQSDBAdapter` to match `DBAdapter` interface. +It handles SQLite query results differently to `execute` - to preserve all columns, preventing duplicate column names from being overwritten. + +The implementation for RNQS will currently fall back to `execute`, preserving current behavior. Users requiring this functionality should migrate to `@powersync/op-sqlite`. diff --git a/.changeset/eleven-cups-rescue.md b/.changeset/eleven-cups-rescue.md new file mode 100644 index 000000000..26cc28f75 --- /dev/null +++ b/.changeset/eleven-cups-rescue.md @@ -0,0 +1,5 @@ +--- +'@powersync/drizzle-driver': minor +--- + +Using `executeRaw` internally for queries instead of `execute`. This function processes SQLite query results differently to preserve all columns, preventing duplicate column names from being overwritten. diff --git a/.changeset/fair-squids-chew.md b/.changeset/fair-squids-chew.md new file mode 100644 index 000000000..5e32589d6 --- /dev/null +++ b/.changeset/fair-squids-chew.md @@ -0,0 +1,8 @@ +--- +'@powersync/op-sqlite': minor +'@powersync/common': minor +'@powersync/node': minor +'@powersync/web': minor +--- + +Introduced `executeRaw`, which processes SQLite query results differently to preserve all columns, preventing duplicate column names from being overwritten. diff --git a/packages/common/src/client/AbstractPowerSyncDatabase.ts b/packages/common/src/client/AbstractPowerSyncDatabase.ts index c0f8b0f41..843deb4c8 100644 --- a/packages/common/src/client/AbstractPowerSyncDatabase.ts +++ b/packages/common/src/client/AbstractPowerSyncDatabase.ts @@ -660,6 +660,11 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver Promise; + /** + * Execute a single write statement and return raw results. + * Unlike `execute`, which returns an object with structured key-value pairs, + * `executeRaw` returns a nested array of raw values, where each row is + * represented as an array of column values without field names. + * + * Example result: + * + * ```[ [ '1', 'list 1', '33', 'Post content', '1' ] ]``` + * + * Where as `execute`'s `rows._array` would have been: + * + * ```[ { id: '33', name: 'list 1', content: 'Post content', list_id: '1' } ]``` + */ + executeRaw: (query: string, params?: any[] | undefined) => Promise; } export interface Transaction extends LockContext { @@ -95,6 +110,7 @@ export interface DBLockOptions { export interface DBAdapter extends BaseObserverInterface, DBGetUtils { close: () => void | Promise; execute: (query: string, params?: any[]) => Promise; + executeRaw: (query: string, params?: any[]) => Promise; executeBatch: (query: string, params?: any[][]) => Promise; name: string; readLock: (fn: (tx: LockContext) => Promise, options?: DBLockOptions) => Promise; diff --git a/packages/drizzle-driver/src/sqlite/PowerSyncSQLitePreparedQuery.ts b/packages/drizzle-driver/src/sqlite/PowerSyncSQLitePreparedQuery.ts index 5229caef0..8a5779aca 100644 --- a/packages/drizzle-driver/src/sqlite/PowerSyncSQLitePreparedQuery.ts +++ b/packages/drizzle-driver/src/sqlite/PowerSyncSQLitePreparedQuery.ts @@ -54,12 +54,12 @@ export class PowerSyncSQLitePreparedQuery< } const rows = (await this.values(placeholderValues)) as unknown[][]; - const valueRows = rows.map((row) => Object.values(row)); + if (customResultMapper) { - const mapped = customResultMapper(valueRows) as T['all']; + const mapped = customResultMapper(rows) as T['all']; return mapped; } - return valueRows.map((row) => mapResultRow(fields!, row, (this as any).joinsNotNullableMap)); + return rows.map((row) => mapResultRow(fields!, row, (this as any).joinsNotNullableMap)); } async get(placeholderValues?: Record): Promise { @@ -80,18 +80,17 @@ export class PowerSyncSQLitePreparedQuery< } if (customResultMapper) { - const valueRows = rows.map((row) => Object.values(row)); - return customResultMapper(valueRows) as T['get']; + return customResultMapper(rows) as T['get']; } - return mapResultRow(fields!, Object.values(row), joinsNotNullableMap); + return mapResultRow(fields!, row, joinsNotNullableMap); } async values(placeholderValues?: Record): Promise { const params = fillPlaceholders(this.query.params, placeholderValues ?? {}); this.logger.logQuery(this.query.sql, params); - const rs = await this.db.execute(this.query.sql, params); - return rs.rows?._array ?? []; + + return await this.db.executeRaw(this.query.sql, params); } isResponseInArrayMode(): boolean { diff --git a/packages/drizzle-driver/tests/sqlite/query.test.ts b/packages/drizzle-driver/tests/sqlite/query.test.ts index 30e565911..63c48472d 100644 --- a/packages/drizzle-driver/tests/sqlite/query.test.ts +++ b/packages/drizzle-driver/tests/sqlite/query.test.ts @@ -56,9 +56,10 @@ describe('PowerSyncSQLitePreparedQuery', () => { const preparedQuery = new PowerSyncSQLitePreparedQuery(powerSyncDb, query, loggerMock, undefined, 'all', false); const values = await preparedQuery.values(); + expect(values).toEqual([ - { id: '1', name: 'Alice' }, - { id: '2', name: 'Bob' } + ['1', 'Alice'], + ['2', 'Bob'] ]); }); }); diff --git a/packages/drizzle-driver/tests/sqlite/relationship.test.ts b/packages/drizzle-driver/tests/sqlite/relationship.test.ts new file mode 100644 index 000000000..ef178a9a5 --- /dev/null +++ b/packages/drizzle-driver/tests/sqlite/relationship.test.ts @@ -0,0 +1,120 @@ +import { AbstractPowerSyncDatabase, column, Schema, Table } from '@powersync/common'; +import { PowerSyncDatabase } from '@powersync/web'; +import { eq, relations } from 'drizzle-orm'; +import { sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import * as SUT from '../../src/sqlite/PowerSyncSQLiteDatabase'; + +const users = new Table({ + name: column.text +}); + +const posts = new Table({ + content: column.text, + title: column.text, + user_id: column.text +}); + +const drizzleUsers = sqliteTable('users', { + id: text('id').primaryKey().notNull(), + name: text('name').notNull() +}); + +const drizzlePosts = sqliteTable('posts', { + id: text('id').primaryKey().notNull(), + content: text('content').notNull(), + title: text('title').notNull(), + user_id: text('user_id') + .notNull() + .references(() => drizzleUsers.id) +}); + +const usersRelations = relations(drizzleUsers, ({ one, many }) => ({ + posts: many(drizzlePosts) +})); + +const postsRelations = relations(drizzlePosts, ({ one }) => ({ + user: one(drizzleUsers, { + fields: [drizzlePosts.user_id], + references: [drizzleUsers.id] + }) +})); + +const PsSchema = new Schema({ users, posts }); +const DrizzleSchema = { users: drizzleUsers, posts: drizzlePosts, usersRelations, postsRelations }; + +describe('Relationship tests', () => { + let powerSyncDb: AbstractPowerSyncDatabase; + let db: SUT.PowerSyncSQLiteDatabase; + + beforeEach(async () => { + powerSyncDb = new PowerSyncDatabase({ + database: { + dbFilename: 'test.db' + }, + schema: PsSchema + }); + db = SUT.wrapPowerSyncWithDrizzle(powerSyncDb, { schema: DrizzleSchema, logger: { logQuery: () => {} } }); + + await powerSyncDb.init(); + + await db.insert(drizzleUsers).values({ id: '1', name: 'Alice' }); + await db.insert(drizzlePosts).values({ id: '33', content: 'Post content', title: 'Post title', user_id: '1' }); + }); + + afterEach(async () => { + await powerSyncDb?.disconnectAndClear(); + }); + + it('should retrieve a user with posts', async () => { + const result = await db.query.users.findMany({ with: { posts: true } }); + + expect(result).toEqual([ + { id: '1', name: 'Alice', posts: [{ id: '33', content: 'Post content', title: 'Post title', user_id: '1' }] } + ]); + }); + + it('should retrieve a post with its user', async () => { + const result = await db.query.posts.findMany({ with: { user: true } }); + + expect(result).toEqual([ + { + id: '33', + content: 'Post content', + title: 'Post title', + user_id: '1', + user: { id: '1', name: 'Alice' } + } + ]); + }); + + it('should return a user and posts using leftJoin', async () => { + const result = await db + .select() + .from(drizzleUsers) + .leftJoin(drizzlePosts, eq(drizzleUsers.id, drizzlePosts.user_id)); + + expect(result[0].users).toEqual({ id: '1', name: 'Alice' }); + expect(result[0].posts).toEqual({ id: '33', content: 'Post content', title: 'Post title', user_id: '1' }); + }); + + it('should return a user and posts using rightJoin', async () => { + const result = await db + .select() + .from(drizzleUsers) + .rightJoin(drizzlePosts, eq(drizzleUsers.id, drizzlePosts.user_id)); + + expect(result[0].users).toEqual({ id: '1', name: 'Alice' }); + expect(result[0].posts).toEqual({ id: '33', content: 'Post content', title: 'Post title', user_id: '1' }); + }); + + it('should return a user and posts using fullJoin', async () => { + const result = await db + .select() + .from(drizzleUsers) + .fullJoin(drizzlePosts, eq(drizzleUsers.id, drizzlePosts.user_id)); + + expect(result[0].users).toEqual({ id: '1', name: 'Alice' }); + expect(result[0].posts).toEqual({ id: '33', content: 'Post content', title: 'Post title', user_id: '1' }); + }); +}); diff --git a/packages/node/package.json b/packages/node/package.json index 72c1ce21e..bd75da8b7 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -54,6 +54,8 @@ }, "devDependencies": { "@types/async-lock": "^1.4.0", + "drizzle-orm": "^0.35.2", + "@powersync/drizzle-driver": "workspace:*", "rollup": "4.14.3", "typescript": "^5.5.3", "vitest": "^3.0.5" diff --git a/packages/node/src/db/AsyncDatabase.ts b/packages/node/src/db/AsyncDatabase.ts index 61abae7ec..20816ab89 100644 --- a/packages/node/src/db/AsyncDatabase.ts +++ b/packages/node/src/db/AsyncDatabase.ts @@ -13,6 +13,7 @@ export interface AsyncDatabaseOpener { export interface AsyncDatabase { execute: (query: string, params: any[]) => Promise; + executeRaw: (query: string, params: any[]) => Promise; executeBatch: (query: string, params: any[][]) => Promise; close: () => Promise; // Collect table updates made since the last call to collectCommittedUpdates. diff --git a/packages/node/src/db/BetterSQLite3DBAdapter.ts b/packages/node/src/db/BetterSQLite3DBAdapter.ts index 8709d01ad..70dccf818 100644 --- a/packages/node/src/db/BetterSQLite3DBAdapter.ts +++ b/packages/node/src/db/BetterSQLite3DBAdapter.ts @@ -65,7 +65,7 @@ export class BetterSQLite3DBAdapter extends BaseObserver impl if (isCommonJsModule) { worker = workerFactory(path.resolve(__dirname, 'DefaultWorker.cjs'), { name: workerName }); } else { - worker = workerFactory(new URL('./DefaultWorker.js', import.meta.url), { name: workerName}); + worker = workerFactory(new URL('./DefaultWorker.js', import.meta.url), { name: workerName }); } const listeners = new WeakMap void>(); @@ -231,6 +231,7 @@ export class BetterSQLite3DBAdapter extends BaseObserver impl await connection.execute('BEGIN'); const result = await fn({ execute: (query, params) => connection.execute(query, params), + executeRaw: (query, params) => connection.executeRaw(query, params), executeBatch: (query, params) => connection.executeBatch(query, params), get: (query, params) => connection.get(query, params), getAll: (query, params) => connection.getAll(query, params), @@ -267,6 +268,10 @@ export class BetterSQLite3DBAdapter extends BaseObserver impl return this.writeLock((ctx) => ctx.execute(query, params)); } + executeRaw(query: string, params?: any[] | undefined): Promise { + return this.writeLock((ctx) => ctx.executeRaw(query, params)); + } + executeBatch(query: string, params?: any[][]): Promise { return this.writeTransaction((ctx) => ctx.executeBatch(query, params)); } diff --git a/packages/node/src/db/RemoteConnection.ts b/packages/node/src/db/RemoteConnection.ts index 0a090f523..aa61d39c8 100644 --- a/packages/node/src/db/RemoteConnection.ts +++ b/packages/node/src/db/RemoteConnection.ts @@ -29,6 +29,10 @@ export class RemoteConnection implements LockContext { return RemoteConnection.wrapQueryResult(result); } + async executeRaw(query: string, params?: any[] | undefined): Promise { + return await this.database.executeRaw(query, params ?? []); + } + async getAll(sql: string, parameters?: any[]): Promise { const res = await this.execute(sql, parameters); return res.rows?._array ?? []; diff --git a/packages/node/src/db/SqliteWorker.ts b/packages/node/src/db/SqliteWorker.ts index f2f840126..f3ecf97f1 100644 --- a/packages/node/src/db/SqliteWorker.ts +++ b/packages/node/src/db/SqliteWorker.ts @@ -66,6 +66,17 @@ class BlockingAsyncDatabase implements AsyncDatabase { } } + async executeRaw(query: string, params: any[]) { + const stmt = this.db.prepare(query); + + if (stmt.reader) { + return stmt.raw().all(params); + } else { + stmt.raw().run(params); + return []; + } + } + async executeBatch(query: string, params: any[][]) { params = params ?? []; diff --git a/packages/node/tests/DrizzleNode.test.ts b/packages/node/tests/DrizzleNode.test.ts new file mode 100644 index 000000000..9598aceb7 --- /dev/null +++ b/packages/node/tests/DrizzleNode.test.ts @@ -0,0 +1,99 @@ +import { sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import { eq, relations } from 'drizzle-orm'; + +import { databaseTest } from './utils'; +import { wrapPowerSyncWithDrizzle } from '@powersync/drizzle-driver'; +import { PowerSyncDatabase } from '../lib'; +import { expect } from 'vitest'; + +export const drizzleLists = sqliteTable('lists', { + id: text('id'), + name: text('name') +}); + +export const drizzleTodos = sqliteTable('todos', { + id: text('id'), + content: text('content'), + list_id: text('list_id') +}); + +export const listsRelations = relations(drizzleLists, ({ one, many }) => ({ + todos: many(drizzleTodos) +})); + +export const todosRelations = relations(drizzleTodos, ({ one, many }) => ({ + list: one(drizzleLists, { + fields: [drizzleTodos.list_id], + references: [drizzleLists.id] + }) +})); + +export const drizzleSchema = { + lists: drizzleLists, + todos: drizzleTodos, + listsRelations, + todosRelations +}; + +const setupDrizzle = async (database: PowerSyncDatabase) => { + const db = wrapPowerSyncWithDrizzle(database, { + schema: drizzleSchema + }); + + await db.insert(drizzleLists).values({ id: '1', name: 'list 1' }); + await db.insert(drizzleTodos).values({ id: '33', content: 'Post content', list_id: '1' }); + return db; +}; + +databaseTest('should retrieve a list with todos', async ({ database }) => { + const db = await setupDrizzle(database); + + const result = await db.query.lists.findMany({ with: { todos: true } }); + + expect(result).toEqual([{ id: '1', name: 'list 1', todos: [{ id: '33', content: 'Post content', list_id: '1' }] }]); +}); + +databaseTest('should retrieve a todo with its list', async ({ database }) => { + const db = await setupDrizzle(database); + + const result = await db.query.todos.findMany({ with: { list: true } }); + + expect(result).toEqual([ + { + id: '33', + content: 'Post content', + list_id: '1', + list: { id: '1', name: 'list 1' } + } + ]); +}); + +databaseTest('should return a list and todos using leftJoin', async ({ database }) => { + const db = await setupDrizzle(database); + + const result = await db.select().from(drizzleLists).leftJoin(drizzleTodos, eq(drizzleLists.id, drizzleTodos.list_id)); + + expect(result[0].lists).toEqual({ id: '1', name: 'list 1' }); + expect(result[0].todos).toEqual({ id: '33', content: 'Post content', list_id: '1' }); +}); + +databaseTest('should return a list and todos using rightJoin', async ({ database }) => { + const db = await setupDrizzle(database); + + const result = await db + .select() + .from(drizzleLists) + .rightJoin(drizzleTodos, eq(drizzleLists.id, drizzleTodos.list_id)); + + expect(result[0].lists).toEqual({ id: '1', name: 'list 1' }); + expect(result[0].todos).toEqual({ id: '33', content: 'Post content', list_id: '1' }); +}); + +databaseTest('should return a list and todos using fullJoin', async ({ database }) => { + const db = await setupDrizzle(database); + + const result = await db.select().from(drizzleLists).fullJoin(drizzleTodos, eq(drizzleLists.id, drizzleTodos.list_id)); + + expect(result[0].lists).toEqual({ id: '1', name: 'list 1' }); + expect(result[0].todos).toEqual({ id: '33', content: 'Post content', list_id: '1' }); +}); diff --git a/packages/node/tests/utils.ts b/packages/node/tests/utils.ts index 602da98e5..b354ef981 100644 --- a/packages/node/tests/utils.ts +++ b/packages/node/tests/utils.ts @@ -13,11 +13,17 @@ export async function createTempDir() { export const LIST_TABLE = 'lists'; export const TODO_TABLE = 'todos'; +const lists = new Table({ + name: column.text +}); + const todos = new Table({ - content: column.text + content: column.text, + list_id: column.text }); export const AppSchema = new Schema({ + lists, todos }); diff --git a/packages/powersync-op-sqlite/src/db/OPSQLiteConnection.ts b/packages/powersync-op-sqlite/src/db/OPSQLiteConnection.ts index bb95dd16e..5704a48b1 100644 --- a/packages/powersync-op-sqlite/src/db/OPSQLiteConnection.ts +++ b/packages/powersync-op-sqlite/src/db/OPSQLiteConnection.ts @@ -97,6 +97,10 @@ export class OPSQLiteConnection extends BaseObserver { }; } + async executeRaw(query: string, params?: any[]): Promise { + return await this.DB.executeRaw(query, params); + } + async executeBatch(query: string, params: any[][] = []): Promise { const tuple: SQLBatchTuple[] = [[query, params[0]]]; params.slice(1).forEach((p) => tuple.push([query, p])); diff --git a/packages/powersync-op-sqlite/src/db/OPSqliteAdapter.ts b/packages/powersync-op-sqlite/src/db/OPSqliteAdapter.ts index 6bc035b46..3bb800fea 100644 --- a/packages/powersync-op-sqlite/src/db/OPSqliteAdapter.ts +++ b/packages/powersync-op-sqlite/src/db/OPSqliteAdapter.ts @@ -245,6 +245,10 @@ export class OPSQLiteDBAdapter extends BaseObserver implement return this.writeLock((ctx) => ctx.execute(query, params)); } + executeRaw(query: string, params?: any[]) { + return this.writeLock((ctx) => ctx.executeRaw(query, params)); + } + async executeBatch(query: string, params: any[][] = []): Promise { return this.writeLock((ctx) => ctx.executeBatch(query, params)); } @@ -272,6 +276,7 @@ export class OPSQLiteDBAdapter extends BaseObserver implement await connection.execute('BEGIN'); const result = await fn({ execute: (query, params) => connection.execute(query, params), + executeRaw: (query, params) => connection.executeRaw(query, params), get: (query, params) => connection.get(query, params), getAll: (query, params) => connection.getAll(query, params), getOptional: (query, params) => connection.getOptional(query, params), diff --git a/packages/react-native/src/db/adapters/react-native-quick-sqlite/RNQSDBAdapter.ts b/packages/react-native/src/db/adapters/react-native-quick-sqlite/RNQSDBAdapter.ts index 2b82d74f2..36c05c23b 100644 --- a/packages/react-native/src/db/adapters/react-native-quick-sqlite/RNQSDBAdapter.ts +++ b/packages/react-native/src/db/adapters/react-native-quick-sqlite/RNQSDBAdapter.ts @@ -8,7 +8,11 @@ import { DBGetUtils, QueryResult } from '@powersync/common'; -import type { QuickSQLiteConnection } from '@journeyapps/react-native-quick-sqlite'; +import type { + QuickSQLiteConnection, + LockContext as RNQSLockContext, + TransactionContext as RNQSTransactionContext +} from '@journeyapps/react-native-quick-sqlite'; /** * Adapter for React Native Quick SQLite @@ -43,25 +47,34 @@ export class RNQSDBAdapter extends BaseObserver implements DB } readLock(fn: (tx: PowerSyncLockContext) => Promise, options?: DBLockOptions): Promise { - return this.baseDB.readLock((dbTx) => fn(this.generateDBHelpers(dbTx)), options); + return this.baseDB.readLock((dbTx) => fn(this.generateDBHelpers(this.generateContext(dbTx))), options); } readTransaction(fn: (tx: PowerSyncTransaction) => Promise, options?: DBLockOptions): Promise { - return this.baseDB.readTransaction((dbTx) => fn(this.generateDBHelpers(dbTx)), options); + return this.baseDB.readTransaction((dbTx) => fn(this.generateDBHelpers(this.generateContext(dbTx))), options); } writeLock(fn: (tx: PowerSyncLockContext) => Promise, options?: DBLockOptions): Promise { - return this.baseDB.writeLock((dbTx) => fn(this.generateDBHelpers(dbTx)), options); + return this.baseDB.writeLock((dbTx) => fn(this.generateDBHelpers(this.generateContext(dbTx))), options); } writeTransaction(fn: (tx: PowerSyncTransaction) => Promise, options?: DBLockOptions): Promise { - return this.baseDB.writeTransaction((dbTx) => fn(this.generateDBHelpers(dbTx)), options); + return this.baseDB.writeTransaction((dbTx) => fn(this.generateDBHelpers(this.generateContext(dbTx))), options); } execute(query: string, params?: any[]) { return this.baseDB.execute(query, params); } + /** + * 'executeRaw' is not implemented in RNQS, this falls back to 'execute'. + */ + async executeRaw(query: string, params?: any[]): Promise { + const result = await this.baseDB.execute(query, params); + const rows = result.rows?._array ?? []; + return rows.map((row) => Object.values(row)); + } + async executeBatch(query: string, params: any[][] = []): Promise { const commands: any[] = []; @@ -75,6 +88,18 @@ export class RNQSDBAdapter extends BaseObserver implements DB }; } + generateContext(ctx: T) { + return { + ...ctx, + // 'executeRaw' is not implemented in RNQS, this falls back to 'execute'. + executeRaw: async (sql: string, params?: any[]) => { + const result = await ctx.execute(sql, params); + const rows = result.rows?._array ?? []; + return rows.map((row) => Object.values(row)); + } + }; + } + /** * This provides a top-level read only execute method which is executed inside a read-lock. * This is necessary since the high level `execute` method uses a write-lock under diff --git a/packages/web/src/db/adapters/AsyncDatabaseConnection.ts b/packages/web/src/db/adapters/AsyncDatabaseConnection.ts index e581e984d..1bd832142 100644 --- a/packages/web/src/db/adapters/AsyncDatabaseConnection.ts +++ b/packages/web/src/db/adapters/AsyncDatabaseConnection.ts @@ -21,6 +21,7 @@ export interface AsyncDatabaseConnection; close(): Promise; execute(sql: string, params?: any[]): Promise; + executeRaw(sql: string, params?: any[]): Promise; executeBatch(sql: string, params?: any[]): Promise; registerOnTableChange(callback: OnTableChangeCallback): Promise<() => void>; getConfig(): Promise; diff --git a/packages/web/src/db/adapters/LockedAsyncDatabaseAdapter.ts b/packages/web/src/db/adapters/LockedAsyncDatabaseAdapter.ts index f3893ffbe..8e585ed08 100644 --- a/packages/web/src/db/adapters/LockedAsyncDatabaseAdapter.ts +++ b/packages/web/src/db/adapters/LockedAsyncDatabaseAdapter.ts @@ -69,7 +69,8 @@ export class LockedAsyncDatabaseAdapter } this.dbGetHelpers = this.generateDBHelpers({ - execute: (query, params) => this.acquireLock(() => this._execute(query, params)) + execute: (query, params) => this.acquireLock(() => this._execute(query, params)), + executeRaw: (query, params) => this.acquireLock(() => this._executeRaw(query, params)) }); this.initPromise = this._init(); } @@ -138,6 +139,10 @@ export class LockedAsyncDatabaseAdapter return this.writeLock((ctx) => ctx.execute(query, params)); } + async executeRaw(query: string, params?: any[] | undefined): Promise { + return this.writeLock((ctx) => ctx.executeRaw(query, params)); + } + async executeBatch(query: string, params?: any[][]): Promise { return this.writeLock((ctx) => this._executeBatch(query, params)); } @@ -169,12 +174,16 @@ export class LockedAsyncDatabaseAdapter async readLock(fn: (tx: LockContext) => Promise, options?: DBLockOptions | undefined): Promise { await this.waitForInitialized(); - return this.acquireLock(async () => fn(this.generateDBHelpers({ execute: this._execute }))); + return this.acquireLock(async () => + fn(this.generateDBHelpers({ execute: this._execute, executeRaw: this._executeRaw })) + ); } async writeLock(fn: (tx: LockContext) => Promise, options?: DBLockOptions | undefined): Promise { await this.waitForInitialized(); - return this.acquireLock(async () => fn(this.generateDBHelpers({ execute: this._execute }))); + return this.acquireLock(async () => + fn(this.generateDBHelpers({ execute: this._execute, executeRaw: this._executeRaw })) + ); } protected acquireLock(callback: () => Promise): Promise { @@ -189,9 +198,12 @@ export class LockedAsyncDatabaseAdapter return this.writeLock(this.wrapTransaction(fn)); } - private generateDBHelpers Promise }>( - tx: T - ): T & DBGetUtils { + private generateDBHelpers< + T extends { + execute: (sql: string, params?: any[]) => Promise; + executeRaw: (sql: string, params?: any[]) => Promise; + } + >(tx: T): T & DBGetUtils { return { ...tx, /** @@ -283,6 +295,14 @@ export class LockedAsyncDatabaseAdapter }; }; + /** + * Wraps the worker executeRaw function, awaiting for it to be available + */ + private _executeRaw = async (sql: string, bindings?: any[]): Promise => { + await this.waitForInitialized(); + return await this.baseDB.executeRaw(sql, bindings); + }; + /** * Wraps the worker executeBatch function, awaiting for it to be available */ diff --git a/packages/web/src/db/adapters/SSRDBAdapter.ts b/packages/web/src/db/adapters/SSRDBAdapter.ts index b808d7477..f460da412 100644 --- a/packages/web/src/db/adapters/SSRDBAdapter.ts +++ b/packages/web/src/db/adapters/SSRDBAdapter.ts @@ -53,6 +53,10 @@ export class SSRDBAdapter extends BaseObserver implements DBA return this.writeMutex.runExclusive(async () => MOCK_QUERY_RESPONSE); } + async executeRaw(query: string, params?: any[]): Promise { + return this.writeMutex.runExclusive(async () => []); + } + async executeBatch(query: string, params?: any[][]): Promise { return this.writeMutex.runExclusive(async () => MOCK_QUERY_RESPONSE); } diff --git a/packages/web/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.ts b/packages/web/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.ts index 32c5ab82b..5004ad9ea 100644 --- a/packages/web/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.ts +++ b/packages/web/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.ts @@ -112,6 +112,10 @@ export class WorkerWrappedAsyncDatabaseConnection { + return this.baseConnection.executeRaw(sql, params); + } + executeBatch(sql: string, params?: any[]): Promise { return this.baseConnection.executeBatch(sql, params); } diff --git a/packages/web/src/db/adapters/wa-sqlite/WASQLiteConnection.ts b/packages/web/src/db/adapters/wa-sqlite/WASQLiteConnection.ts index 3f8336b12..de96668c6 100644 --- a/packages/web/src/db/adapters/wa-sqlite/WASQLiteConnection.ts +++ b/packages/web/src/db/adapters/wa-sqlite/WASQLiteConnection.ts @@ -334,6 +334,12 @@ export class WASqliteConnection }); } + async executeRaw(sql: string | TemplateStringsArray, bindings?: any[]): Promise { + return this.acquireExecuteLock(async () => { + return this.executeSingleStatementRaw(sql, bindings); + }); + } + async close() { this.broadcastChannel?.close(); await this.sqliteAPI.close(this.dbP); @@ -360,6 +366,44 @@ export class WASqliteConnection sql: string | TemplateStringsArray, bindings?: any[] ): Promise { + const results = await this._execute(sql, bindings); + + const rows: Record[] = []; + for (const resultSet of results) { + for (const row of resultSet.rows) { + const outRow: Record = {}; + resultSet.columns.forEach((key, index) => { + outRow[key] = row[index]; + }); + rows.push(outRow); + } + } + + const result = { + insertId: this.sqliteAPI.last_insert_id(this.dbP), + rowsAffected: this.sqliteAPI.changes(this.dbP), + rows: { + _array: rows, + length: rows.length + } + }; + + return result; + } + + /** + * This executes a single statement using SQLite3 and returns the results as an array of arrays. + */ + protected async executeSingleStatementRaw(sql: string | TemplateStringsArray, bindings?: any[]): Promise { + const results = await this._execute(sql, bindings); + + return results.flatMap((resultset) => resultset.rows.map((row) => resultset.columns.map((_, index) => row[index]))); + } + + private async _execute( + sql: string | TemplateStringsArray, + bindings?: any[] + ): Promise<{ columns: string[]; rows: SQLiteCompatibleType[][] }[]> { const results = []; for await (const stmt of this.sqliteAPI.statements(this.dbP, sql as string)) { let columns; @@ -395,26 +439,6 @@ export class WASqliteConnection } } - const rows: Record[] = []; - for (const resultSet of results) { - for (const row of resultSet.rows) { - const outRow: Record = {}; - resultSet.columns.forEach((key, index) => { - outRow[key] = row[index]; - }); - rows.push(outRow); - } - } - - const result = { - insertId: this.sqliteAPI.last_insert_id(this.dbP), - rowsAffected: this.sqliteAPI.changes(this.dbP), - rows: { - _array: rows, - length: rows.length - } - }; - - return result; + return results; } } diff --git a/packages/web/src/worker/db/WASQLiteDB.worker.ts b/packages/web/src/worker/db/WASQLiteDB.worker.ts index 5e2e169a8..7e28b024d 100644 --- a/packages/web/src/worker/db/WASQLiteDB.worker.ts +++ b/packages/web/src/worker/db/WASQLiteDB.worker.ts @@ -30,6 +30,7 @@ const openWorkerConnection = async (options: ResolvedWASQLiteOpenFactoryOptions) getConfig: Comlink.proxy(() => connection.getConfig()), close: Comlink.proxy(() => connection.close()), execute: Comlink.proxy(async (sql: string, params?: any[]) => connection.execute(sql, params)), + executeRaw: Comlink.proxy(async (sql: string, params?: any[]) => connection.executeRaw(sql, params)), executeBatch: Comlink.proxy(async (sql: string, params?: any[]) => connection.executeBatch(sql, params)), registerOnTableChange: Comlink.proxy(async (callback) => { // Proxy the callback remove function diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 53377d195..3367c0dd3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -137,7 +137,7 @@ importers: version: 0.1.11(react-native@0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) '@react-navigation/drawer': specifier: ^6.6.15 - version: 6.7.2(8a892ff6c949d4273486936ff7d0b326) + version: 6.7.2(l2wxk4fvyl3ebgrlaryoia2mpm) '@react-navigation/native': specifier: ^6.1.17 version: 6.1.18(react-native@0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) @@ -161,7 +161,7 @@ importers: version: 1.11.3 expo-router: specifier: 3.5.21 - version: 3.5.21(861b7493f2d52858e88dcfa7bffd38c4) + version: 3.5.21(itxjk4e5lx4jky57qutbl2llka) expo-splash-screen: specifier: ~0.27.4 version: 0.27.6(encoding@0.1.13)(expo-modules-autolinking@1.11.3)(expo@51.0.27(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(encoding@0.1.13)) @@ -215,7 +215,7 @@ importers: version: 10.2.0 react-navigation-stack: specifier: ^2.10.4 - version: 2.10.4(723df46775cc6e8dbfaef5bf48d4f911) + version: 2.10.4(qmdtutm2q5vv2bqwrj2rmb5zum) typed-async-storage: specifier: ^3.1.2 version: 3.1.2 @@ -785,7 +785,7 @@ importers: version: 6.3.1(expo@51.0.27(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(encoding@0.1.13)) expo-router: specifier: ^3.5.15 - version: 3.5.21(c189db6b79bdaefc0f767c4cd94a478a) + version: 3.5.21(gc6ebsds2rxeucccxhmqtwmlpi) expo-splash-screen: specifier: ~0.27.4 version: 0.27.6(encoding@0.1.13)(expo-modules-autolinking@1.11.1)(expo@51.0.27(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(encoding@0.1.13)) @@ -882,7 +882,7 @@ importers: version: 0.1.11(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) '@react-navigation/drawer': specifier: ^6.6.3 - version: 6.7.2(fe8cd8328c484d4e3eaed8eea351852b) + version: 6.7.2(f5uupuoecme7pb3346nlwm73my) '@react-navigation/native': specifier: ^6.0.0 version: 6.1.18(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) @@ -921,7 +921,7 @@ importers: version: 6.3.1(expo@51.0.37(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(encoding@0.1.13)) expo-router: specifier: 3.5.23 - version: 3.5.23(2f86f7434a59b644ba234fab7be01c9e) + version: 3.5.23(x45f6tg66eoafhyrv4brrngbdm) expo-secure-store: specifier: ~13.0.1 version: 13.0.2(expo@51.0.37(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(encoding@0.1.13)) @@ -969,7 +969,7 @@ importers: version: 3.31.1(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) react-navigation-stack: specifier: ^2.10.4 - version: 2.10.4(cf0911ea264205029347060226fe0d29) + version: 2.10.4(b23yjknfeew5kcy4o5zrlfz5ae) devDependencies: '@babel/core': specifier: ^7.24.5 @@ -1033,7 +1033,7 @@ importers: version: 0.1.11(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) '@react-navigation/drawer': specifier: ^6.6.3 - version: 6.7.2(fe8cd8328c484d4e3eaed8eea351852b) + version: 6.7.2(f5uupuoecme7pb3346nlwm73my) '@react-navigation/native': specifier: ^6.0.0 version: 6.1.18(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) @@ -1066,7 +1066,7 @@ importers: version: 6.3.1(expo@51.0.27(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(encoding@0.1.13)) expo-router: specifier: 3.5.21 - version: 3.5.21(43cc03a7fb538f7aef105856925492f6) + version: 3.5.21(qrxjjyxvihi5xb6jovt7bb6fjy) expo-secure-store: specifier: ~13.0.1 version: 13.0.2(expo@51.0.27(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(encoding@0.1.13)) @@ -1126,7 +1126,7 @@ importers: version: 0.19.12(encoding@0.1.13)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react-navigation-stack: specifier: ^2.10.4 - version: 2.10.4(cf0911ea264205029347060226fe0d29) + version: 2.10.4(b23yjknfeew5kcy4o5zrlfz5ae) devDependencies: '@babel/core': specifier: ^7.24.5 @@ -1719,9 +1719,15 @@ importers: specifier: ^4.4.2 version: 4.4.2 devDependencies: + '@powersync/drizzle-driver': + specifier: workspace:* + version: link:../drizzle-driver '@types/async-lock': specifier: ^1.4.0 version: 1.4.2 + drizzle-orm: + specifier: ^0.35.2 + version: 0.35.2(@op-engineering/op-sqlite@11.4.8(react-native@0.77.0(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@react-native-community/cli-server-api@15.1.3)(@types/react@18.3.18)(react@18.3.1))(react@18.3.1))(@types/better-sqlite3@7.6.12)(@types/react@18.3.18)(better-sqlite3@11.7.2)(kysely@0.27.4)(react@18.3.1) rollup: specifier: 4.14.3 version: 4.14.3 @@ -30197,9 +30203,7 @@ snapshots: transitivePeerDependencies: - '@babel/core' - '@babel/preset-env' - - bufferutil - supports-color - - utf-8-validate '@react-native/normalize-color@2.1.0': {} @@ -30332,7 +30336,20 @@ snapshots: react-is: 16.13.1 use-latest-callback: 0.2.1(react@18.2.0) - '@react-navigation/drawer@6.7.2(3f3d461ed9a1c5b87bb0ca8ce18d5723)': + '@react-navigation/drawer@6.7.2(f5uupuoecme7pb3346nlwm73my)': + dependencies: + '@react-navigation/elements': 1.3.31(@react-navigation/native@6.1.18(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + '@react-navigation/native': 6.1.18(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + color: 4.2.3 + react: 18.2.0 + react-native: 0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0) + react-native-gesture-handler: 2.16.2(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + react-native-reanimated: 3.10.1(@babel/core@7.24.5)(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + react-native-safe-area-context: 4.10.5(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + react-native-screens: 3.31.1(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + warn-once: 0.1.1 + + '@react-navigation/drawer@6.7.2(ghvgknxqxtkf2snjzmu2bwsmre)': dependencies: '@react-navigation/elements': 1.3.31(@react-navigation/native@6.1.18(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@4.10.1(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) '@react-navigation/native': 6.1.18(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) @@ -30346,7 +30363,7 @@ snapshots: warn-once: 0.1.1 optional: true - '@react-navigation/drawer@6.7.2(8a892ff6c949d4273486936ff7d0b326)': + '@react-navigation/drawer@6.7.2(l2wxk4fvyl3ebgrlaryoia2mpm)': dependencies: '@react-navigation/elements': 1.3.31(@react-navigation/native@6.1.18(react-native@0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native@0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) '@react-navigation/native': 6.1.18(react-native@0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) @@ -30359,19 +30376,6 @@ snapshots: react-native-screens: 3.31.1(react-native@0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) warn-once: 0.1.1 - '@react-navigation/drawer@6.7.2(fe8cd8328c484d4e3eaed8eea351852b)': - dependencies: - '@react-navigation/elements': 1.3.31(@react-navigation/native@6.1.18(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) - '@react-navigation/native': 6.1.18(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) - color: 4.2.3 - react: 18.2.0 - react-native: 0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0) - react-native-gesture-handler: 2.16.2(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) - react-native-reanimated: 3.10.1(@babel/core@7.24.5)(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) - react-native-safe-area-context: 4.10.5(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) - react-native-screens: 3.31.1(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) - warn-once: 0.1.1 - '@react-navigation/elements@1.3.31(@react-navigation/native@6.1.18(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@4.10.1(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0)': dependencies: '@react-navigation/native': 6.1.18(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) @@ -37468,26 +37472,26 @@ snapshots: dependencies: invariant: 2.2.4 - expo-router@3.5.21(43cc03a7fb538f7aef105856925492f6): + expo-router@3.5.21(gc6ebsds2rxeucccxhmqtwmlpi): dependencies: - '@expo/metro-runtime': 3.2.1(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0)) - '@expo/server': 0.4.4(typescript@5.5.4) + '@expo/metro-runtime': 3.2.1(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0)) + '@expo/server': 0.4.4(typescript@5.3.3) '@radix-ui/react-slot': 1.0.1(react@18.2.0) - '@react-navigation/bottom-tabs': 6.5.20(@react-navigation/native@6.1.18(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native-screens@3.31.1(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) - '@react-navigation/native': 6.1.18(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) - '@react-navigation/native-stack': 6.9.26(@react-navigation/native@6.1.18(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native-screens@3.31.1(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) - expo: 51.0.27(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(encoding@0.1.13) - expo-constants: 16.0.2(expo@51.0.27(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(encoding@0.1.13)) - expo-linking: 6.3.1(expo@51.0.27(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(encoding@0.1.13)) - expo-splash-screen: 0.27.5(encoding@0.1.13)(expo-modules-autolinking@1.11.3)(expo@51.0.27(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(encoding@0.1.13)) + '@react-navigation/bottom-tabs': 6.5.20(@react-navigation/native@6.1.18(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@4.10.1(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native-screens@3.31.1(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + '@react-navigation/native': 6.1.18(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + '@react-navigation/native-stack': 6.9.26(@react-navigation/native@6.1.18(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@4.10.1(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native-screens@3.31.1(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + expo: 51.0.27(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(encoding@0.1.13) + expo-constants: 16.0.2(expo@51.0.27(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(encoding@0.1.13)) + expo-linking: 6.3.1(expo@51.0.27(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(encoding@0.1.13)) + expo-splash-screen: 0.27.5(encoding@0.1.13)(expo-modules-autolinking@1.11.1)(expo@51.0.27(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(encoding@0.1.13)) expo-status-bar: 1.12.1 react-native-helmet-async: 2.0.4(react@18.2.0) - react-native-safe-area-context: 4.10.5(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) - react-native-screens: 3.31.1(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + react-native-safe-area-context: 4.10.1(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + react-native-screens: 3.31.1(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) schema-utils: 4.2.0 optionalDependencies: - '@react-navigation/drawer': 6.7.2(fe8cd8328c484d4e3eaed8eea351852b) - react-native-reanimated: 3.10.1(@babel/core@7.24.5)(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + '@react-navigation/drawer': 6.7.2(ghvgknxqxtkf2snjzmu2bwsmre) + react-native-reanimated: 3.10.1(@babel/core@7.24.5)(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) transitivePeerDependencies: - encoding - expo-modules-autolinking @@ -37496,7 +37500,7 @@ snapshots: - supports-color - typescript - expo-router@3.5.21(861b7493f2d52858e88dcfa7bffd38c4): + expo-router@3.5.21(itxjk4e5lx4jky57qutbl2llka): dependencies: '@expo/metro-runtime': 3.2.1(react-native@0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0)) '@expo/server': 0.4.4(typescript@5.5.4) @@ -37514,7 +37518,7 @@ snapshots: react-native-screens: 3.31.1(react-native@0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) schema-utils: 4.2.0 optionalDependencies: - '@react-navigation/drawer': 6.7.2(8a892ff6c949d4273486936ff7d0b326) + '@react-navigation/drawer': 6.7.2(l2wxk4fvyl3ebgrlaryoia2mpm) react-native-reanimated: 3.10.1(@babel/core@7.26.10)(react-native@0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) transitivePeerDependencies: - encoding @@ -37524,26 +37528,26 @@ snapshots: - supports-color - typescript - expo-router@3.5.21(c189db6b79bdaefc0f767c4cd94a478a): + expo-router@3.5.21(qrxjjyxvihi5xb6jovt7bb6fjy): dependencies: - '@expo/metro-runtime': 3.2.1(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0)) - '@expo/server': 0.4.4(typescript@5.3.3) + '@expo/metro-runtime': 3.2.1(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0)) + '@expo/server': 0.4.4(typescript@5.5.4) '@radix-ui/react-slot': 1.0.1(react@18.2.0) - '@react-navigation/bottom-tabs': 6.5.20(@react-navigation/native@6.1.18(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@4.10.1(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native-screens@3.31.1(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) - '@react-navigation/native': 6.1.18(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) - '@react-navigation/native-stack': 6.9.26(@react-navigation/native@6.1.18(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@4.10.1(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native-screens@3.31.1(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) - expo: 51.0.27(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(encoding@0.1.13) - expo-constants: 16.0.2(expo@51.0.27(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(encoding@0.1.13)) - expo-linking: 6.3.1(expo@51.0.27(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(encoding@0.1.13)) - expo-splash-screen: 0.27.5(encoding@0.1.13)(expo-modules-autolinking@1.11.1)(expo@51.0.27(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(encoding@0.1.13)) + '@react-navigation/bottom-tabs': 6.5.20(@react-navigation/native@6.1.18(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native-screens@3.31.1(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + '@react-navigation/native': 6.1.18(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + '@react-navigation/native-stack': 6.9.26(@react-navigation/native@6.1.18(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native-screens@3.31.1(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + expo: 51.0.27(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(encoding@0.1.13) + expo-constants: 16.0.2(expo@51.0.27(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(encoding@0.1.13)) + expo-linking: 6.3.1(expo@51.0.27(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(encoding@0.1.13)) + expo-splash-screen: 0.27.5(encoding@0.1.13)(expo-modules-autolinking@1.11.3)(expo@51.0.27(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(encoding@0.1.13)) expo-status-bar: 1.12.1 react-native-helmet-async: 2.0.4(react@18.2.0) - react-native-safe-area-context: 4.10.1(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) - react-native-screens: 3.31.1(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + react-native-safe-area-context: 4.10.5(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + react-native-screens: 3.31.1(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) schema-utils: 4.2.0 optionalDependencies: - '@react-navigation/drawer': 6.7.2(3f3d461ed9a1c5b87bb0ca8ce18d5723) - react-native-reanimated: 3.10.1(@babel/core@7.24.5)(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.26.9(@babel/core@7.24.5))(@types/react@18.3.11)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + '@react-navigation/drawer': 6.7.2(f5uupuoecme7pb3346nlwm73my) + react-native-reanimated: 3.10.1(@babel/core@7.24.5)(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) transitivePeerDependencies: - encoding - expo-modules-autolinking @@ -37552,7 +37556,7 @@ snapshots: - supports-color - typescript - expo-router@3.5.23(2f86f7434a59b644ba234fab7be01c9e): + expo-router@3.5.23(x45f6tg66eoafhyrv4brrngbdm): dependencies: '@expo/metro-runtime': 3.2.3(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0)) '@expo/server': 0.4.4(typescript@5.5.4) @@ -37570,7 +37574,7 @@ snapshots: react-native-screens: 3.31.1(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) schema-utils: 4.2.0 optionalDependencies: - '@react-navigation/drawer': 6.7.2(fe8cd8328c484d4e3eaed8eea351852b) + '@react-navigation/drawer': 6.7.2(f5uupuoecme7pb3346nlwm73my) react-native-reanimated: 3.10.1(@babel/core@7.24.5)(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) transitivePeerDependencies: - encoding @@ -41014,7 +41018,7 @@ snapshots: metro-source-map@0.81.3: dependencies: '@babel/traverse': 7.26.4 - '@babel/traverse--for-generate-function-map': '@babel/traverse@7.26.4' + '@babel/traverse--for-generate-function-map': '@babel/traverse@7.26.10' '@babel/types': 7.26.3 flow-enums-runtime: 0.0.6 invariant: 2.2.4 @@ -44385,19 +44389,7 @@ snapshots: - supports-color - utf-8-validate - react-navigation-stack@2.10.4(723df46775cc6e8dbfaef5bf48d4f911): - dependencies: - '@react-native-community/masked-view': 0.1.11(react-native@0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) - color: 3.2.1 - react: 18.2.0 - react-native: 0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0) - react-native-gesture-handler: 2.16.2(react-native@0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) - react-native-iphone-x-helper: 1.3.1(react-native@0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0)) - react-native-safe-area-context: 4.10.5(react-native@0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) - react-native-screens: 3.31.1(react-native@0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) - react-navigation: 4.4.4(react-native@0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) - - react-navigation-stack@2.10.4(cf0911ea264205029347060226fe0d29): + react-navigation-stack@2.10.4(b23yjknfeew5kcy4o5zrlfz5ae): dependencies: '@react-native-community/masked-view': 0.1.11(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) color: 3.2.1 @@ -44409,6 +44401,18 @@ snapshots: react-native-screens: 3.31.1(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) react-navigation: 4.4.4(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + react-navigation-stack@2.10.4(qmdtutm2q5vv2bqwrj2rmb5zum): + dependencies: + '@react-native-community/masked-view': 0.1.11(react-native@0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + color: 3.2.1 + react: 18.2.0 + react-native: 0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0) + react-native-gesture-handler: 2.16.2(react-native@0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + react-native-iphone-x-helper: 1.3.1(react-native@0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0)) + react-native-safe-area-context: 4.10.5(react-native@0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + react-native-screens: 3.31.1(react-native@0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + react-navigation: 4.4.4(react-native@0.74.5(@babel/core@7.26.10)(@babel/preset-env@7.25.7(@babel/core@7.26.10))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + react-navigation@4.4.4(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0): dependencies: '@react-navigation/core': 3.7.9(react@18.2.0)