@tanstack/expo-db-sqlite-persistence 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -0
- package/dist/cjs/expo-sqlite-driver.cjs +131 -0
- package/dist/cjs/expo-sqlite-driver.cjs.map +1 -0
- package/dist/cjs/expo-sqlite-driver.d.cts +42 -0
- package/dist/cjs/expo.cjs +79 -0
- package/dist/cjs/expo.cjs.map +1 -0
- package/dist/cjs/expo.d.cts +11 -0
- package/dist/cjs/index.cjs +10 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/index.d.cts +4 -0
- package/dist/esm/expo-sqlite-driver.d.ts +42 -0
- package/dist/esm/expo-sqlite-driver.js +131 -0
- package/dist/esm/expo-sqlite-driver.js.map +1 -0
- package/dist/esm/expo.d.ts +11 -0
- package/dist/esm/expo.js +79 -0
- package/dist/esm/expo.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/package.json +73 -0
- package/src/expo-sqlite-driver.ts +238 -0
- package/src/expo.ts +159 -0
- package/src/index.ts +11 -0
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# @tanstack/expo-db-sqlite-persistence
|
|
2
|
+
|
|
3
|
+
Thin SQLite persistence for Expo apps using the official `expo-sqlite` adapter.
|
|
4
|
+
|
|
5
|
+
## Public API
|
|
6
|
+
|
|
7
|
+
- `createExpoSQLitePersistence(...)`
|
|
8
|
+
- `persistedCollectionOptions(...)` (re-exported from core)
|
|
9
|
+
- Advanced driver entrypoint:
|
|
10
|
+
`@tanstack/expo-db-sqlite-persistence/expo-sqlite-driver`
|
|
11
|
+
|
|
12
|
+
## Quick start
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import * as SQLite from 'expo-sqlite'
|
|
16
|
+
import { createCollection } from '@tanstack/db'
|
|
17
|
+
import {
|
|
18
|
+
createExpoSQLitePersistence,
|
|
19
|
+
persistedCollectionOptions,
|
|
20
|
+
} from '@tanstack/expo-db-sqlite-persistence'
|
|
21
|
+
|
|
22
|
+
type Todo = {
|
|
23
|
+
id: string
|
|
24
|
+
title: string
|
|
25
|
+
completed: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const database = await SQLite.openDatabaseAsync(`tanstack-db.sqlite`)
|
|
29
|
+
|
|
30
|
+
// One shared persistence instance for the whole database.
|
|
31
|
+
const persistence = createExpoSQLitePersistence({
|
|
32
|
+
database,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
export const todosCollection = createCollection(
|
|
36
|
+
persistedCollectionOptions<Todo, string>({
|
|
37
|
+
id: `todos`,
|
|
38
|
+
getKey: (todo) => todo.id,
|
|
39
|
+
persistence,
|
|
40
|
+
schemaVersion: 1, // Per-collection schema version
|
|
41
|
+
}),
|
|
42
|
+
)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Notes
|
|
46
|
+
|
|
47
|
+
- This package targets the official `expo-sqlite` async database API.
|
|
48
|
+
- Requires `expo-sqlite` `^55.0.10` (documented as a peer dependency).
|
|
49
|
+
- `createExpoSQLitePersistence` is shared across collections.
|
|
50
|
+
- Mode defaults (`sync-present` vs `sync-absent`) are inferred from whether a
|
|
51
|
+
`sync` config is present in `persistedCollectionOptions`.
|
|
52
|
+
- The React Native `op-sqlite` wrapper remains available in
|
|
53
|
+
`@tanstack/react-native-db-sqlite-persistence`.
|
|
54
|
+
- Expo web is not part of the emulator-backed E2E path in this package. Use the
|
|
55
|
+
browser SQLite package for browser-focused persistence coverage.
|
|
56
|
+
|
|
57
|
+
## E2E
|
|
58
|
+
|
|
59
|
+
- `pnpm --filter @tanstack/expo-db-sqlite-persistence test:e2e`
|
|
60
|
+
runs the shared Node-backed conformance suite.
|
|
61
|
+
- `pnpm --filter @tanstack/expo-db-sqlite-persistence test:e2e:expo:ios`
|
|
62
|
+
runs the real Expo iOS Simulator path.
|
|
63
|
+
- `pnpm --filter @tanstack/expo-db-sqlite-persistence test:e2e:expo:android`
|
|
64
|
+
runs the real Expo Android Emulator path.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const dbSqlitePersistenceCore = require("@tanstack/db-sqlite-persistence-core");
|
|
4
|
+
function hasExistingDatabase(options) {
|
|
5
|
+
return `database` in options;
|
|
6
|
+
}
|
|
7
|
+
function assertTransactionCallbackHasDriverArg(fn) {
|
|
8
|
+
if (fn.length > 0) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
throw new dbSqlitePersistenceCore.InvalidPersistedCollectionConfigError(
|
|
12
|
+
`SQLiteDriver.transaction callback must accept the transaction driver argument`
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
function isExpoSQLiteDatabaseLike(value) {
|
|
16
|
+
return typeof value === `object` && value !== null && typeof value.execAsync === `function` && typeof value.getAllAsync === `function` && typeof value.runAsync === `function` && typeof value.withExclusiveTransactionAsync === `function`;
|
|
17
|
+
}
|
|
18
|
+
class ExpoSQLiteDriver {
|
|
19
|
+
constructor(options) {
|
|
20
|
+
this.queue = Promise.resolve();
|
|
21
|
+
this.nextSavepointId = 1;
|
|
22
|
+
if (hasExistingDatabase(options)) {
|
|
23
|
+
if (!isExpoSQLiteDatabaseLike(options.database)) {
|
|
24
|
+
throw new dbSqlitePersistenceCore.InvalidPersistedCollectionConfigError(
|
|
25
|
+
`Expo SQLite database must provide execAsync/getAllAsync/runAsync/withExclusiveTransactionAsync`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
this.databasePromise = Promise.resolve(options.database);
|
|
29
|
+
this.ownsDatabase = false;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
this.databasePromise = Promise.resolve(options.openDatabase()).then(
|
|
33
|
+
(database) => {
|
|
34
|
+
if (!isExpoSQLiteDatabaseLike(database)) {
|
|
35
|
+
throw new dbSqlitePersistenceCore.InvalidPersistedCollectionConfigError(
|
|
36
|
+
`Expo SQLite openDatabase() must resolve a database with execAsync/getAllAsync/runAsync/withExclusiveTransactionAsync`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return database;
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
this.ownsDatabase = true;
|
|
43
|
+
}
|
|
44
|
+
async exec(sql) {
|
|
45
|
+
await this.enqueue(async () => {
|
|
46
|
+
const database = await this.getDatabase();
|
|
47
|
+
await database.execAsync(sql);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
async query(sql, params = []) {
|
|
51
|
+
return this.enqueue(async () => {
|
|
52
|
+
const database = await this.getDatabase();
|
|
53
|
+
return database.getAllAsync(sql, normalizeParams(params));
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async run(sql, params = []) {
|
|
57
|
+
await this.enqueue(async () => {
|
|
58
|
+
const database = await this.getDatabase();
|
|
59
|
+
await database.runAsync(sql, normalizeParams(params));
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async transaction(fn) {
|
|
63
|
+
assertTransactionCallbackHasDriverArg(fn);
|
|
64
|
+
return this.transactionWithDriver(fn);
|
|
65
|
+
}
|
|
66
|
+
async transactionWithDriver(fn) {
|
|
67
|
+
return this.enqueue(async () => {
|
|
68
|
+
const database = await this.getDatabase();
|
|
69
|
+
return database.withExclusiveTransactionAsync(async (transaction) => {
|
|
70
|
+
const transactionDriver = this.createTransactionDriver(transaction);
|
|
71
|
+
return fn(transactionDriver);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
async close() {
|
|
76
|
+
const database = await this.getDatabase();
|
|
77
|
+
if (!this.ownsDatabase || typeof database.closeAsync !== `function`) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
await database.closeAsync();
|
|
81
|
+
}
|
|
82
|
+
async getDatabase() {
|
|
83
|
+
return this.databasePromise;
|
|
84
|
+
}
|
|
85
|
+
enqueue(operation) {
|
|
86
|
+
const queuedOperation = this.queue.then(operation, operation);
|
|
87
|
+
this.queue = queuedOperation.then(
|
|
88
|
+
() => void 0,
|
|
89
|
+
() => void 0
|
|
90
|
+
);
|
|
91
|
+
return queuedOperation;
|
|
92
|
+
}
|
|
93
|
+
createTransactionDriver(transaction) {
|
|
94
|
+
const transactionDriver = {
|
|
95
|
+
exec: async (sql) => {
|
|
96
|
+
await transaction.execAsync(sql);
|
|
97
|
+
},
|
|
98
|
+
query: async (sql, params = []) => {
|
|
99
|
+
return transaction.getAllAsync(sql, normalizeParams(params));
|
|
100
|
+
},
|
|
101
|
+
run: async (sql, params = []) => {
|
|
102
|
+
await transaction.runAsync(sql, normalizeParams(params));
|
|
103
|
+
},
|
|
104
|
+
transaction: async (fn) => {
|
|
105
|
+
assertTransactionCallbackHasDriverArg(fn);
|
|
106
|
+
return this.runNestedTransaction(transaction, transactionDriver, fn);
|
|
107
|
+
},
|
|
108
|
+
transactionWithDriver: async (fn) => this.runNestedTransaction(transaction, transactionDriver, fn)
|
|
109
|
+
};
|
|
110
|
+
return transactionDriver;
|
|
111
|
+
}
|
|
112
|
+
async runNestedTransaction(transaction, transactionDriver, fn) {
|
|
113
|
+
const savepointName = `tsdb_sp_${this.nextSavepointId}`;
|
|
114
|
+
this.nextSavepointId++;
|
|
115
|
+
await transaction.execAsync(`SAVEPOINT ${savepointName}`);
|
|
116
|
+
try {
|
|
117
|
+
const result = await fn(transactionDriver);
|
|
118
|
+
await transaction.execAsync(`RELEASE SAVEPOINT ${savepointName}`);
|
|
119
|
+
return result;
|
|
120
|
+
} catch (error) {
|
|
121
|
+
await transaction.execAsync(`ROLLBACK TO SAVEPOINT ${savepointName}`);
|
|
122
|
+
await transaction.execAsync(`RELEASE SAVEPOINT ${savepointName}`);
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function normalizeParams(params) {
|
|
128
|
+
return params.length > 0 ? [...params] : void 0;
|
|
129
|
+
}
|
|
130
|
+
exports.ExpoSQLiteDriver = ExpoSQLiteDriver;
|
|
131
|
+
//# sourceMappingURL=expo-sqlite-driver.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expo-sqlite-driver.cjs","sources":["../../src/expo-sqlite-driver.ts"],"sourcesContent":["import { InvalidPersistedCollectionConfigError } from '@tanstack/db-sqlite-persistence-core'\nimport type { SQLiteDriver } from '@tanstack/db-sqlite-persistence-core'\n\nexport type ExpoSQLiteBindParams =\n | ReadonlyArray<unknown>\n | Record<string, unknown>\n\nexport type ExpoSQLiteRunResult = {\n changes: number\n lastInsertRowId: number\n}\n\nexport type ExpoSQLiteQueryable = {\n execAsync: (sql: string) => Promise<void>\n getAllAsync: <T>(\n sql: string,\n params?: ExpoSQLiteBindParams,\n ) => Promise<ReadonlyArray<T>>\n runAsync: (\n sql: string,\n params?: ExpoSQLiteBindParams,\n ) => Promise<ExpoSQLiteRunResult>\n}\n\nexport type ExpoSQLiteTransaction = ExpoSQLiteQueryable\n\nexport type ExpoSQLiteDatabaseLike = ExpoSQLiteQueryable & {\n withExclusiveTransactionAsync: <T>(\n task: (transaction: ExpoSQLiteTransaction) => Promise<T>,\n ) => Promise<T>\n closeAsync?: () => Promise<void>\n}\n\ntype ExpoSQLiteExistingDatabaseOptions = {\n database: ExpoSQLiteDatabaseLike\n}\n\ntype ExpoSQLiteOpenDatabaseOptions = {\n openDatabase: () => Promise<ExpoSQLiteDatabaseLike> | ExpoSQLiteDatabaseLike\n}\n\nexport type ExpoSQLiteDriverOptions =\n | ExpoSQLiteExistingDatabaseOptions\n | ExpoSQLiteOpenDatabaseOptions\n\nfunction hasExistingDatabase(\n options: ExpoSQLiteDriverOptions,\n): options is ExpoSQLiteExistingDatabaseOptions {\n return `database` in options\n}\n\nfunction assertTransactionCallbackHasDriverArg(\n fn: (transactionDriver: SQLiteDriver) => Promise<unknown>,\n): void {\n if (fn.length > 0) {\n return\n }\n\n throw new InvalidPersistedCollectionConfigError(\n `SQLiteDriver.transaction callback must accept the transaction driver argument`,\n )\n}\n\nfunction isExpoSQLiteDatabaseLike(\n value: unknown,\n): value is ExpoSQLiteDatabaseLike {\n return (\n typeof value === `object` &&\n value !== null &&\n typeof (value as ExpoSQLiteDatabaseLike).execAsync === `function` &&\n typeof (value as ExpoSQLiteDatabaseLike).getAllAsync === `function` &&\n typeof (value as ExpoSQLiteDatabaseLike).runAsync === `function` &&\n typeof (value as ExpoSQLiteDatabaseLike).withExclusiveTransactionAsync ===\n `function`\n )\n}\n\nexport class ExpoSQLiteDriver implements SQLiteDriver {\n private readonly databasePromise: Promise<ExpoSQLiteDatabaseLike>\n private readonly ownsDatabase: boolean\n private queue: Promise<void> = Promise.resolve()\n private nextSavepointId = 1\n\n constructor(options: ExpoSQLiteDriverOptions) {\n if (hasExistingDatabase(options)) {\n if (!isExpoSQLiteDatabaseLike(options.database)) {\n throw new InvalidPersistedCollectionConfigError(\n `Expo SQLite database must provide execAsync/getAllAsync/runAsync/withExclusiveTransactionAsync`,\n )\n }\n\n this.databasePromise = Promise.resolve(options.database)\n this.ownsDatabase = false\n return\n }\n\n this.databasePromise = Promise.resolve(options.openDatabase()).then(\n (database) => {\n if (!isExpoSQLiteDatabaseLike(database)) {\n throw new InvalidPersistedCollectionConfigError(\n `Expo SQLite openDatabase() must resolve a database with execAsync/getAllAsync/runAsync/withExclusiveTransactionAsync`,\n )\n }\n\n return database\n },\n )\n this.ownsDatabase = true\n }\n\n async exec(sql: string): Promise<void> {\n await this.enqueue(async () => {\n const database = await this.getDatabase()\n await database.execAsync(sql)\n })\n }\n\n async query<T>(\n sql: string,\n params: ReadonlyArray<unknown> = [],\n ): Promise<ReadonlyArray<T>> {\n return this.enqueue(async () => {\n const database = await this.getDatabase()\n return database.getAllAsync<T>(sql, normalizeParams(params))\n })\n }\n\n async run(sql: string, params: ReadonlyArray<unknown> = []): Promise<void> {\n await this.enqueue(async () => {\n const database = await this.getDatabase()\n await database.runAsync(sql, normalizeParams(params))\n })\n }\n\n async transaction<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n assertTransactionCallbackHasDriverArg(fn)\n return this.transactionWithDriver(fn)\n }\n\n async transactionWithDriver<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n return this.enqueue(async () => {\n const database = await this.getDatabase()\n return database.withExclusiveTransactionAsync(async (transaction) => {\n const transactionDriver = this.createTransactionDriver(transaction)\n return fn(transactionDriver)\n })\n })\n }\n\n async close(): Promise<void> {\n const database = await this.getDatabase()\n if (!this.ownsDatabase || typeof database.closeAsync !== `function`) {\n return\n }\n\n await database.closeAsync()\n }\n\n async getDatabase(): Promise<ExpoSQLiteDatabaseLike> {\n return this.databasePromise\n }\n\n private enqueue<T>(operation: () => Promise<T>): Promise<T> {\n const queuedOperation = this.queue.then(operation, operation)\n this.queue = queuedOperation.then(\n () => undefined,\n () => undefined,\n )\n return queuedOperation\n }\n\n private createTransactionDriver(\n transaction: ExpoSQLiteTransaction,\n ): SQLiteDriver {\n const transactionDriver: SQLiteDriver = {\n exec: async (sql) => {\n await transaction.execAsync(sql)\n },\n query: async <T>(\n sql: string,\n params: ReadonlyArray<unknown> = [],\n ): Promise<ReadonlyArray<T>> => {\n return transaction.getAllAsync<T>(sql, normalizeParams(params))\n },\n run: async (sql: string, params: ReadonlyArray<unknown> = []) => {\n await transaction.runAsync(sql, normalizeParams(params))\n },\n transaction: async <T>(\n fn: (nestedTransactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> => {\n assertTransactionCallbackHasDriverArg(fn)\n return this.runNestedTransaction(transaction, transactionDriver, fn)\n },\n transactionWithDriver: async <T>(\n fn: (nestedTransactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> =>\n this.runNestedTransaction(transaction, transactionDriver, fn),\n }\n\n return transactionDriver\n }\n\n private async runNestedTransaction<T>(\n transaction: ExpoSQLiteTransaction,\n transactionDriver: SQLiteDriver,\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n const savepointName = `tsdb_sp_${this.nextSavepointId}`\n this.nextSavepointId++\n await transaction.execAsync(`SAVEPOINT ${savepointName}`)\n\n try {\n const result = await fn(transactionDriver)\n await transaction.execAsync(`RELEASE SAVEPOINT ${savepointName}`)\n return result\n } catch (error) {\n await transaction.execAsync(`ROLLBACK TO SAVEPOINT ${savepointName}`)\n await transaction.execAsync(`RELEASE SAVEPOINT ${savepointName}`)\n throw error\n }\n }\n}\n\nfunction normalizeParams(\n params: ReadonlyArray<unknown>,\n): ExpoSQLiteBindParams | undefined {\n return params.length > 0 ? [...params] : undefined\n}\n\nexport function createExpoSQLiteDriver(\n options: ExpoSQLiteDriverOptions,\n): ExpoSQLiteDriver {\n return new ExpoSQLiteDriver(options)\n}\n"],"names":["InvalidPersistedCollectionConfigError"],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { SQLiteDriver } from '@tanstack/db-sqlite-persistence-core';
|
|
2
|
+
export type ExpoSQLiteBindParams = ReadonlyArray<unknown> | Record<string, unknown>;
|
|
3
|
+
export type ExpoSQLiteRunResult = {
|
|
4
|
+
changes: number;
|
|
5
|
+
lastInsertRowId: number;
|
|
6
|
+
};
|
|
7
|
+
export type ExpoSQLiteQueryable = {
|
|
8
|
+
execAsync: (sql: string) => Promise<void>;
|
|
9
|
+
getAllAsync: <T>(sql: string, params?: ExpoSQLiteBindParams) => Promise<ReadonlyArray<T>>;
|
|
10
|
+
runAsync: (sql: string, params?: ExpoSQLiteBindParams) => Promise<ExpoSQLiteRunResult>;
|
|
11
|
+
};
|
|
12
|
+
export type ExpoSQLiteTransaction = ExpoSQLiteQueryable;
|
|
13
|
+
export type ExpoSQLiteDatabaseLike = ExpoSQLiteQueryable & {
|
|
14
|
+
withExclusiveTransactionAsync: <T>(task: (transaction: ExpoSQLiteTransaction) => Promise<T>) => Promise<T>;
|
|
15
|
+
closeAsync?: () => Promise<void>;
|
|
16
|
+
};
|
|
17
|
+
type ExpoSQLiteExistingDatabaseOptions = {
|
|
18
|
+
database: ExpoSQLiteDatabaseLike;
|
|
19
|
+
};
|
|
20
|
+
type ExpoSQLiteOpenDatabaseOptions = {
|
|
21
|
+
openDatabase: () => Promise<ExpoSQLiteDatabaseLike> | ExpoSQLiteDatabaseLike;
|
|
22
|
+
};
|
|
23
|
+
export type ExpoSQLiteDriverOptions = ExpoSQLiteExistingDatabaseOptions | ExpoSQLiteOpenDatabaseOptions;
|
|
24
|
+
export declare class ExpoSQLiteDriver implements SQLiteDriver {
|
|
25
|
+
private readonly databasePromise;
|
|
26
|
+
private readonly ownsDatabase;
|
|
27
|
+
private queue;
|
|
28
|
+
private nextSavepointId;
|
|
29
|
+
constructor(options: ExpoSQLiteDriverOptions);
|
|
30
|
+
exec(sql: string): Promise<void>;
|
|
31
|
+
query<T>(sql: string, params?: ReadonlyArray<unknown>): Promise<ReadonlyArray<T>>;
|
|
32
|
+
run(sql: string, params?: ReadonlyArray<unknown>): Promise<void>;
|
|
33
|
+
transaction<T>(fn: (transactionDriver: SQLiteDriver) => Promise<T>): Promise<T>;
|
|
34
|
+
transactionWithDriver<T>(fn: (transactionDriver: SQLiteDriver) => Promise<T>): Promise<T>;
|
|
35
|
+
close(): Promise<void>;
|
|
36
|
+
getDatabase(): Promise<ExpoSQLiteDatabaseLike>;
|
|
37
|
+
private enqueue;
|
|
38
|
+
private createTransactionDriver;
|
|
39
|
+
private runNestedTransaction;
|
|
40
|
+
}
|
|
41
|
+
export declare function createExpoSQLiteDriver(options: ExpoSQLiteDriverOptions): ExpoSQLiteDriver;
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const dbSqlitePersistenceCore = require("@tanstack/db-sqlite-persistence-core");
|
|
4
|
+
const expoSqliteDriver = require("./expo-sqlite-driver.cjs");
|
|
5
|
+
function normalizeSchemaMismatchPolicy(policy) {
|
|
6
|
+
if (policy === `throw`) {
|
|
7
|
+
return `sync-absent-error`;
|
|
8
|
+
}
|
|
9
|
+
return policy;
|
|
10
|
+
}
|
|
11
|
+
function resolveSchemaMismatchPolicy(explicitPolicy, mode) {
|
|
12
|
+
if (explicitPolicy) {
|
|
13
|
+
return normalizeSchemaMismatchPolicy(explicitPolicy);
|
|
14
|
+
}
|
|
15
|
+
return mode === `sync-present` ? `sync-present-reset` : `sync-absent-error`;
|
|
16
|
+
}
|
|
17
|
+
function createAdapterCacheKey(schemaMismatchPolicy, schemaVersion) {
|
|
18
|
+
const schemaVersionKey = schemaVersion === void 0 ? `schema:default` : `schema:${schemaVersion}`;
|
|
19
|
+
return `${schemaMismatchPolicy}|${schemaVersionKey}`;
|
|
20
|
+
}
|
|
21
|
+
function createInternalSQLiteDriver(options) {
|
|
22
|
+
return new expoSqliteDriver.ExpoSQLiteDriver({
|
|
23
|
+
database: options.database
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function resolveAdapterBaseOptions(options) {
|
|
27
|
+
return {
|
|
28
|
+
appliedTxPruneMaxRows: options.appliedTxPruneMaxRows,
|
|
29
|
+
appliedTxPruneMaxAgeSeconds: options.appliedTxPruneMaxAgeSeconds,
|
|
30
|
+
pullSinceReloadThreshold: options.pullSinceReloadThreshold
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function createExpoSQLitePersistence(options) {
|
|
34
|
+
const { coordinator, schemaMismatchPolicy } = options;
|
|
35
|
+
const driver = createInternalSQLiteDriver(options);
|
|
36
|
+
const adapterBaseOptions = resolveAdapterBaseOptions(options);
|
|
37
|
+
const resolvedCoordinator = coordinator ?? new dbSqlitePersistenceCore.SingleProcessCoordinator();
|
|
38
|
+
const adapterCache = /* @__PURE__ */ new Map();
|
|
39
|
+
const getAdapterForCollection = (mode, schemaVersion) => {
|
|
40
|
+
const resolvedSchemaMismatchPolicy = resolveSchemaMismatchPolicy(
|
|
41
|
+
schemaMismatchPolicy,
|
|
42
|
+
mode
|
|
43
|
+
);
|
|
44
|
+
const cacheKey = createAdapterCacheKey(
|
|
45
|
+
resolvedSchemaMismatchPolicy,
|
|
46
|
+
schemaVersion
|
|
47
|
+
);
|
|
48
|
+
const cachedAdapter = adapterCache.get(cacheKey);
|
|
49
|
+
if (cachedAdapter) {
|
|
50
|
+
return cachedAdapter;
|
|
51
|
+
}
|
|
52
|
+
const adapter = dbSqlitePersistenceCore.createSQLiteCorePersistenceAdapter({
|
|
53
|
+
...adapterBaseOptions,
|
|
54
|
+
driver,
|
|
55
|
+
schemaMismatchPolicy: resolvedSchemaMismatchPolicy,
|
|
56
|
+
...schemaVersion === void 0 ? {} : { schemaVersion }
|
|
57
|
+
});
|
|
58
|
+
adapterCache.set(cacheKey, adapter);
|
|
59
|
+
return adapter;
|
|
60
|
+
};
|
|
61
|
+
const createCollectionPersistence = (mode, schemaVersion) => ({
|
|
62
|
+
adapter: getAdapterForCollection(
|
|
63
|
+
mode,
|
|
64
|
+
schemaVersion
|
|
65
|
+
),
|
|
66
|
+
coordinator: resolvedCoordinator
|
|
67
|
+
});
|
|
68
|
+
const defaultPersistence = createCollectionPersistence(
|
|
69
|
+
`sync-absent`,
|
|
70
|
+
void 0
|
|
71
|
+
);
|
|
72
|
+
return {
|
|
73
|
+
...defaultPersistence,
|
|
74
|
+
resolvePersistenceForCollection: ({ mode, schemaVersion }) => createCollectionPersistence(mode, schemaVersion),
|
|
75
|
+
resolvePersistenceForMode: (mode) => createCollectionPersistence(mode, void 0)
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
exports.createExpoSQLitePersistence = createExpoSQLitePersistence;
|
|
79
|
+
//# sourceMappingURL=expo.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expo.cjs","sources":["../../src/expo.ts"],"sourcesContent":["import {\n SingleProcessCoordinator,\n createSQLiteCorePersistenceAdapter,\n} from '@tanstack/db-sqlite-persistence-core'\nimport { ExpoSQLiteDriver } from './expo-sqlite-driver'\nimport type {\n PersistedCollectionCoordinator,\n PersistedCollectionMode,\n PersistedCollectionPersistence,\n SQLiteCoreAdapterOptions,\n SQLiteDriver,\n} from '@tanstack/db-sqlite-persistence-core'\nimport type { ExpoSQLiteDatabaseLike } from './expo-sqlite-driver'\n\nexport type { ExpoSQLiteDatabaseLike } from './expo-sqlite-driver'\n\ntype ExpoSQLiteCoreSchemaMismatchPolicy =\n | `sync-present-reset`\n | `sync-absent-error`\n | `reset`\n\nexport type ExpoSQLiteSchemaMismatchPolicy =\n | ExpoSQLiteCoreSchemaMismatchPolicy\n | `throw`\n\nexport type ExpoSQLitePersistenceOptions = Omit<\n SQLiteCoreAdapterOptions,\n `driver` | `schemaVersion` | `schemaMismatchPolicy`\n> & {\n database: ExpoSQLiteDatabaseLike\n coordinator?: PersistedCollectionCoordinator\n schemaMismatchPolicy?: ExpoSQLiteSchemaMismatchPolicy\n}\n\nfunction normalizeSchemaMismatchPolicy(\n policy: ExpoSQLiteSchemaMismatchPolicy,\n): ExpoSQLiteCoreSchemaMismatchPolicy {\n if (policy === `throw`) {\n return `sync-absent-error`\n }\n\n return policy\n}\n\nfunction resolveSchemaMismatchPolicy(\n explicitPolicy: ExpoSQLiteSchemaMismatchPolicy | undefined,\n mode: PersistedCollectionMode,\n): ExpoSQLiteCoreSchemaMismatchPolicy {\n if (explicitPolicy) {\n return normalizeSchemaMismatchPolicy(explicitPolicy)\n }\n\n return mode === `sync-present` ? `sync-present-reset` : `sync-absent-error`\n}\n\nfunction createAdapterCacheKey(\n schemaMismatchPolicy: ExpoSQLiteCoreSchemaMismatchPolicy,\n schemaVersion: number | undefined,\n): string {\n const schemaVersionKey =\n schemaVersion === undefined ? `schema:default` : `schema:${schemaVersion}`\n return `${schemaMismatchPolicy}|${schemaVersionKey}`\n}\n\nfunction createInternalSQLiteDriver(\n options: ExpoSQLitePersistenceOptions,\n): SQLiteDriver {\n return new ExpoSQLiteDriver({\n database: options.database,\n })\n}\n\nfunction resolveAdapterBaseOptions(\n options: ExpoSQLitePersistenceOptions,\n): Omit<\n SQLiteCoreAdapterOptions,\n `driver` | `schemaVersion` | `schemaMismatchPolicy`\n> {\n return {\n appliedTxPruneMaxRows: options.appliedTxPruneMaxRows,\n appliedTxPruneMaxAgeSeconds: options.appliedTxPruneMaxAgeSeconds,\n pullSinceReloadThreshold: options.pullSinceReloadThreshold,\n }\n}\n\nexport function createExpoSQLitePersistence<\n T extends object,\n TKey extends string | number = string | number,\n>(\n options: ExpoSQLitePersistenceOptions,\n): PersistedCollectionPersistence<T, TKey> {\n const { coordinator, schemaMismatchPolicy } = options\n const driver = createInternalSQLiteDriver(options)\n const adapterBaseOptions = resolveAdapterBaseOptions(options)\n const resolvedCoordinator = coordinator ?? new SingleProcessCoordinator()\n const adapterCache = new Map<\n string,\n ReturnType<\n typeof createSQLiteCorePersistenceAdapter<\n Record<string, unknown>,\n string | number\n >\n >\n >()\n\n const getAdapterForCollection = (\n mode: PersistedCollectionMode,\n schemaVersion: number | undefined,\n ) => {\n const resolvedSchemaMismatchPolicy = resolveSchemaMismatchPolicy(\n schemaMismatchPolicy,\n mode,\n )\n const cacheKey = createAdapterCacheKey(\n resolvedSchemaMismatchPolicy,\n schemaVersion,\n )\n const cachedAdapter = adapterCache.get(cacheKey)\n if (cachedAdapter) {\n return cachedAdapter\n }\n\n const adapter = createSQLiteCorePersistenceAdapter<\n Record<string, unknown>,\n string | number\n >({\n ...adapterBaseOptions,\n driver,\n schemaMismatchPolicy: resolvedSchemaMismatchPolicy,\n ...(schemaVersion === undefined ? {} : { schemaVersion }),\n })\n adapterCache.set(cacheKey, adapter)\n return adapter\n }\n\n const createCollectionPersistence = (\n mode: PersistedCollectionMode,\n schemaVersion: number | undefined,\n ): PersistedCollectionPersistence<T, TKey> => ({\n adapter: getAdapterForCollection(\n mode,\n schemaVersion,\n ) as unknown as PersistedCollectionPersistence<T, TKey>[`adapter`],\n coordinator: resolvedCoordinator,\n })\n\n const defaultPersistence = createCollectionPersistence(\n `sync-absent`,\n undefined,\n )\n\n return {\n ...defaultPersistence,\n resolvePersistenceForCollection: ({ mode, schemaVersion }) =>\n createCollectionPersistence(mode, schemaVersion),\n resolvePersistenceForMode: (mode) =>\n createCollectionPersistence(mode, undefined),\n }\n}\n"],"names":["ExpoSQLiteDriver","SingleProcessCoordinator","createSQLiteCorePersistenceAdapter"],"mappings":";;;;AAkCA,SAAS,8BACP,QACoC;AACpC,MAAI,WAAW,SAAS;AACtB,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEA,SAAS,4BACP,gBACA,MACoC;AACpC,MAAI,gBAAgB;AAClB,WAAO,8BAA8B,cAAc;AAAA,EACrD;AAEA,SAAO,SAAS,iBAAiB,uBAAuB;AAC1D;AAEA,SAAS,sBACP,sBACA,eACQ;AACR,QAAM,mBACJ,kBAAkB,SAAY,mBAAmB,UAAU,aAAa;AAC1E,SAAO,GAAG,oBAAoB,IAAI,gBAAgB;AACpD;AAEA,SAAS,2BACP,SACc;AACd,SAAO,IAAIA,iBAAAA,iBAAiB;AAAA,IAC1B,UAAU,QAAQ;AAAA,EAAA,CACnB;AACH;AAEA,SAAS,0BACP,SAIA;AACA,SAAO;AAAA,IACL,uBAAuB,QAAQ;AAAA,IAC/B,6BAA6B,QAAQ;AAAA,IACrC,0BAA0B,QAAQ;AAAA,EAAA;AAEtC;AAEO,SAAS,4BAId,SACyC;AACzC,QAAM,EAAE,aAAa,qBAAA,IAAyB;AAC9C,QAAM,SAAS,2BAA2B,OAAO;AACjD,QAAM,qBAAqB,0BAA0B,OAAO;AAC5D,QAAM,sBAAsB,eAAe,IAAIC,iDAAA;AAC/C,QAAM,mCAAmB,IAAA;AAUzB,QAAM,0BAA0B,CAC9B,MACA,kBACG;AACH,UAAM,+BAA+B;AAAA,MACnC;AAAA,MACA;AAAA,IAAA;AAEF,UAAM,WAAW;AAAA,MACf;AAAA,MACA;AAAA,IAAA;AAEF,UAAM,gBAAgB,aAAa,IAAI,QAAQ;AAC/C,QAAI,eAAe;AACjB,aAAO;AAAA,IACT;AAEA,UAAM,UAAUC,wBAAAA,mCAGd;AAAA,MACA,GAAG;AAAA,MACH;AAAA,MACA,sBAAsB;AAAA,MACtB,GAAI,kBAAkB,SAAY,CAAA,IAAK,EAAE,cAAA;AAAA,IAAc,CACxD;AACD,iBAAa,IAAI,UAAU,OAAO;AAClC,WAAO;AAAA,EACT;AAEA,QAAM,8BAA8B,CAClC,MACA,mBAC6C;AAAA,IAC7C,SAAS;AAAA,MACP;AAAA,MACA;AAAA,IAAA;AAAA,IAEF,aAAa;AAAA,EAAA;AAGf,QAAM,qBAAqB;AAAA,IACzB;AAAA,IACA;AAAA,EAAA;AAGF,SAAO;AAAA,IACL,GAAG;AAAA,IACH,iCAAiC,CAAC,EAAE,MAAM,oBACxC,4BAA4B,MAAM,aAAa;AAAA,IACjD,2BAA2B,CAAC,SAC1B,4BAA4B,MAAM,MAAS;AAAA,EAAA;AAEjD;;"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { PersistedCollectionCoordinator, PersistedCollectionPersistence, SQLiteCoreAdapterOptions } from '@tanstack/db-sqlite-persistence-core';
|
|
2
|
+
import { ExpoSQLiteDatabaseLike } from './expo-sqlite-driver.cjs';
|
|
3
|
+
export type { ExpoSQLiteDatabaseLike } from './expo-sqlite-driver.cjs';
|
|
4
|
+
type ExpoSQLiteCoreSchemaMismatchPolicy = `sync-present-reset` | `sync-absent-error` | `reset`;
|
|
5
|
+
export type ExpoSQLiteSchemaMismatchPolicy = ExpoSQLiteCoreSchemaMismatchPolicy | `throw`;
|
|
6
|
+
export type ExpoSQLitePersistenceOptions = Omit<SQLiteCoreAdapterOptions, `driver` | `schemaVersion` | `schemaMismatchPolicy`> & {
|
|
7
|
+
database: ExpoSQLiteDatabaseLike;
|
|
8
|
+
coordinator?: PersistedCollectionCoordinator;
|
|
9
|
+
schemaMismatchPolicy?: ExpoSQLiteSchemaMismatchPolicy;
|
|
10
|
+
};
|
|
11
|
+
export declare function createExpoSQLitePersistence<T extends object, TKey extends string | number = string | number>(options: ExpoSQLitePersistenceOptions): PersistedCollectionPersistence<T, TKey>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const expo = require("./expo.cjs");
|
|
4
|
+
const dbSqlitePersistenceCore = require("@tanstack/db-sqlite-persistence-core");
|
|
5
|
+
exports.createExpoSQLitePersistence = expo.createExpoSQLitePersistence;
|
|
6
|
+
Object.defineProperty(exports, "persistedCollectionOptions", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: () => dbSqlitePersistenceCore.persistedCollectionOptions
|
|
9
|
+
});
|
|
10
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createExpoSQLitePersistence } from './expo.cjs';
|
|
2
|
+
export type { ExpoSQLiteDatabaseLike, ExpoSQLitePersistenceOptions, ExpoSQLiteSchemaMismatchPolicy, } from './expo.cjs';
|
|
3
|
+
export { persistedCollectionOptions } from '@tanstack/db-sqlite-persistence-core';
|
|
4
|
+
export type { PersistedCollectionCoordinator, PersistedCollectionPersistence, } from '@tanstack/db-sqlite-persistence-core';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { SQLiteDriver } from '@tanstack/db-sqlite-persistence-core';
|
|
2
|
+
export type ExpoSQLiteBindParams = ReadonlyArray<unknown> | Record<string, unknown>;
|
|
3
|
+
export type ExpoSQLiteRunResult = {
|
|
4
|
+
changes: number;
|
|
5
|
+
lastInsertRowId: number;
|
|
6
|
+
};
|
|
7
|
+
export type ExpoSQLiteQueryable = {
|
|
8
|
+
execAsync: (sql: string) => Promise<void>;
|
|
9
|
+
getAllAsync: <T>(sql: string, params?: ExpoSQLiteBindParams) => Promise<ReadonlyArray<T>>;
|
|
10
|
+
runAsync: (sql: string, params?: ExpoSQLiteBindParams) => Promise<ExpoSQLiteRunResult>;
|
|
11
|
+
};
|
|
12
|
+
export type ExpoSQLiteTransaction = ExpoSQLiteQueryable;
|
|
13
|
+
export type ExpoSQLiteDatabaseLike = ExpoSQLiteQueryable & {
|
|
14
|
+
withExclusiveTransactionAsync: <T>(task: (transaction: ExpoSQLiteTransaction) => Promise<T>) => Promise<T>;
|
|
15
|
+
closeAsync?: () => Promise<void>;
|
|
16
|
+
};
|
|
17
|
+
type ExpoSQLiteExistingDatabaseOptions = {
|
|
18
|
+
database: ExpoSQLiteDatabaseLike;
|
|
19
|
+
};
|
|
20
|
+
type ExpoSQLiteOpenDatabaseOptions = {
|
|
21
|
+
openDatabase: () => Promise<ExpoSQLiteDatabaseLike> | ExpoSQLiteDatabaseLike;
|
|
22
|
+
};
|
|
23
|
+
export type ExpoSQLiteDriverOptions = ExpoSQLiteExistingDatabaseOptions | ExpoSQLiteOpenDatabaseOptions;
|
|
24
|
+
export declare class ExpoSQLiteDriver implements SQLiteDriver {
|
|
25
|
+
private readonly databasePromise;
|
|
26
|
+
private readonly ownsDatabase;
|
|
27
|
+
private queue;
|
|
28
|
+
private nextSavepointId;
|
|
29
|
+
constructor(options: ExpoSQLiteDriverOptions);
|
|
30
|
+
exec(sql: string): Promise<void>;
|
|
31
|
+
query<T>(sql: string, params?: ReadonlyArray<unknown>): Promise<ReadonlyArray<T>>;
|
|
32
|
+
run(sql: string, params?: ReadonlyArray<unknown>): Promise<void>;
|
|
33
|
+
transaction<T>(fn: (transactionDriver: SQLiteDriver) => Promise<T>): Promise<T>;
|
|
34
|
+
transactionWithDriver<T>(fn: (transactionDriver: SQLiteDriver) => Promise<T>): Promise<T>;
|
|
35
|
+
close(): Promise<void>;
|
|
36
|
+
getDatabase(): Promise<ExpoSQLiteDatabaseLike>;
|
|
37
|
+
private enqueue;
|
|
38
|
+
private createTransactionDriver;
|
|
39
|
+
private runNestedTransaction;
|
|
40
|
+
}
|
|
41
|
+
export declare function createExpoSQLiteDriver(options: ExpoSQLiteDriverOptions): ExpoSQLiteDriver;
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { InvalidPersistedCollectionConfigError } from "@tanstack/db-sqlite-persistence-core";
|
|
2
|
+
function hasExistingDatabase(options) {
|
|
3
|
+
return `database` in options;
|
|
4
|
+
}
|
|
5
|
+
function assertTransactionCallbackHasDriverArg(fn) {
|
|
6
|
+
if (fn.length > 0) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
10
|
+
`SQLiteDriver.transaction callback must accept the transaction driver argument`
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
function isExpoSQLiteDatabaseLike(value) {
|
|
14
|
+
return typeof value === `object` && value !== null && typeof value.execAsync === `function` && typeof value.getAllAsync === `function` && typeof value.runAsync === `function` && typeof value.withExclusiveTransactionAsync === `function`;
|
|
15
|
+
}
|
|
16
|
+
class ExpoSQLiteDriver {
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.queue = Promise.resolve();
|
|
19
|
+
this.nextSavepointId = 1;
|
|
20
|
+
if (hasExistingDatabase(options)) {
|
|
21
|
+
if (!isExpoSQLiteDatabaseLike(options.database)) {
|
|
22
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
23
|
+
`Expo SQLite database must provide execAsync/getAllAsync/runAsync/withExclusiveTransactionAsync`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
this.databasePromise = Promise.resolve(options.database);
|
|
27
|
+
this.ownsDatabase = false;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
this.databasePromise = Promise.resolve(options.openDatabase()).then(
|
|
31
|
+
(database) => {
|
|
32
|
+
if (!isExpoSQLiteDatabaseLike(database)) {
|
|
33
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
34
|
+
`Expo SQLite openDatabase() must resolve a database with execAsync/getAllAsync/runAsync/withExclusiveTransactionAsync`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return database;
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
this.ownsDatabase = true;
|
|
41
|
+
}
|
|
42
|
+
async exec(sql) {
|
|
43
|
+
await this.enqueue(async () => {
|
|
44
|
+
const database = await this.getDatabase();
|
|
45
|
+
await database.execAsync(sql);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async query(sql, params = []) {
|
|
49
|
+
return this.enqueue(async () => {
|
|
50
|
+
const database = await this.getDatabase();
|
|
51
|
+
return database.getAllAsync(sql, normalizeParams(params));
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async run(sql, params = []) {
|
|
55
|
+
await this.enqueue(async () => {
|
|
56
|
+
const database = await this.getDatabase();
|
|
57
|
+
await database.runAsync(sql, normalizeParams(params));
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
async transaction(fn) {
|
|
61
|
+
assertTransactionCallbackHasDriverArg(fn);
|
|
62
|
+
return this.transactionWithDriver(fn);
|
|
63
|
+
}
|
|
64
|
+
async transactionWithDriver(fn) {
|
|
65
|
+
return this.enqueue(async () => {
|
|
66
|
+
const database = await this.getDatabase();
|
|
67
|
+
return database.withExclusiveTransactionAsync(async (transaction) => {
|
|
68
|
+
const transactionDriver = this.createTransactionDriver(transaction);
|
|
69
|
+
return fn(transactionDriver);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
async close() {
|
|
74
|
+
const database = await this.getDatabase();
|
|
75
|
+
if (!this.ownsDatabase || typeof database.closeAsync !== `function`) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
await database.closeAsync();
|
|
79
|
+
}
|
|
80
|
+
async getDatabase() {
|
|
81
|
+
return this.databasePromise;
|
|
82
|
+
}
|
|
83
|
+
enqueue(operation) {
|
|
84
|
+
const queuedOperation = this.queue.then(operation, operation);
|
|
85
|
+
this.queue = queuedOperation.then(
|
|
86
|
+
() => void 0,
|
|
87
|
+
() => void 0
|
|
88
|
+
);
|
|
89
|
+
return queuedOperation;
|
|
90
|
+
}
|
|
91
|
+
createTransactionDriver(transaction) {
|
|
92
|
+
const transactionDriver = {
|
|
93
|
+
exec: async (sql) => {
|
|
94
|
+
await transaction.execAsync(sql);
|
|
95
|
+
},
|
|
96
|
+
query: async (sql, params = []) => {
|
|
97
|
+
return transaction.getAllAsync(sql, normalizeParams(params));
|
|
98
|
+
},
|
|
99
|
+
run: async (sql, params = []) => {
|
|
100
|
+
await transaction.runAsync(sql, normalizeParams(params));
|
|
101
|
+
},
|
|
102
|
+
transaction: async (fn) => {
|
|
103
|
+
assertTransactionCallbackHasDriverArg(fn);
|
|
104
|
+
return this.runNestedTransaction(transaction, transactionDriver, fn);
|
|
105
|
+
},
|
|
106
|
+
transactionWithDriver: async (fn) => this.runNestedTransaction(transaction, transactionDriver, fn)
|
|
107
|
+
};
|
|
108
|
+
return transactionDriver;
|
|
109
|
+
}
|
|
110
|
+
async runNestedTransaction(transaction, transactionDriver, fn) {
|
|
111
|
+
const savepointName = `tsdb_sp_${this.nextSavepointId}`;
|
|
112
|
+
this.nextSavepointId++;
|
|
113
|
+
await transaction.execAsync(`SAVEPOINT ${savepointName}`);
|
|
114
|
+
try {
|
|
115
|
+
const result = await fn(transactionDriver);
|
|
116
|
+
await transaction.execAsync(`RELEASE SAVEPOINT ${savepointName}`);
|
|
117
|
+
return result;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
await transaction.execAsync(`ROLLBACK TO SAVEPOINT ${savepointName}`);
|
|
120
|
+
await transaction.execAsync(`RELEASE SAVEPOINT ${savepointName}`);
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function normalizeParams(params) {
|
|
126
|
+
return params.length > 0 ? [...params] : void 0;
|
|
127
|
+
}
|
|
128
|
+
export {
|
|
129
|
+
ExpoSQLiteDriver
|
|
130
|
+
};
|
|
131
|
+
//# sourceMappingURL=expo-sqlite-driver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expo-sqlite-driver.js","sources":["../../src/expo-sqlite-driver.ts"],"sourcesContent":["import { InvalidPersistedCollectionConfigError } from '@tanstack/db-sqlite-persistence-core'\nimport type { SQLiteDriver } from '@tanstack/db-sqlite-persistence-core'\n\nexport type ExpoSQLiteBindParams =\n | ReadonlyArray<unknown>\n | Record<string, unknown>\n\nexport type ExpoSQLiteRunResult = {\n changes: number\n lastInsertRowId: number\n}\n\nexport type ExpoSQLiteQueryable = {\n execAsync: (sql: string) => Promise<void>\n getAllAsync: <T>(\n sql: string,\n params?: ExpoSQLiteBindParams,\n ) => Promise<ReadonlyArray<T>>\n runAsync: (\n sql: string,\n params?: ExpoSQLiteBindParams,\n ) => Promise<ExpoSQLiteRunResult>\n}\n\nexport type ExpoSQLiteTransaction = ExpoSQLiteQueryable\n\nexport type ExpoSQLiteDatabaseLike = ExpoSQLiteQueryable & {\n withExclusiveTransactionAsync: <T>(\n task: (transaction: ExpoSQLiteTransaction) => Promise<T>,\n ) => Promise<T>\n closeAsync?: () => Promise<void>\n}\n\ntype ExpoSQLiteExistingDatabaseOptions = {\n database: ExpoSQLiteDatabaseLike\n}\n\ntype ExpoSQLiteOpenDatabaseOptions = {\n openDatabase: () => Promise<ExpoSQLiteDatabaseLike> | ExpoSQLiteDatabaseLike\n}\n\nexport type ExpoSQLiteDriverOptions =\n | ExpoSQLiteExistingDatabaseOptions\n | ExpoSQLiteOpenDatabaseOptions\n\nfunction hasExistingDatabase(\n options: ExpoSQLiteDriverOptions,\n): options is ExpoSQLiteExistingDatabaseOptions {\n return `database` in options\n}\n\nfunction assertTransactionCallbackHasDriverArg(\n fn: (transactionDriver: SQLiteDriver) => Promise<unknown>,\n): void {\n if (fn.length > 0) {\n return\n }\n\n throw new InvalidPersistedCollectionConfigError(\n `SQLiteDriver.transaction callback must accept the transaction driver argument`,\n )\n}\n\nfunction isExpoSQLiteDatabaseLike(\n value: unknown,\n): value is ExpoSQLiteDatabaseLike {\n return (\n typeof value === `object` &&\n value !== null &&\n typeof (value as ExpoSQLiteDatabaseLike).execAsync === `function` &&\n typeof (value as ExpoSQLiteDatabaseLike).getAllAsync === `function` &&\n typeof (value as ExpoSQLiteDatabaseLike).runAsync === `function` &&\n typeof (value as ExpoSQLiteDatabaseLike).withExclusiveTransactionAsync ===\n `function`\n )\n}\n\nexport class ExpoSQLiteDriver implements SQLiteDriver {\n private readonly databasePromise: Promise<ExpoSQLiteDatabaseLike>\n private readonly ownsDatabase: boolean\n private queue: Promise<void> = Promise.resolve()\n private nextSavepointId = 1\n\n constructor(options: ExpoSQLiteDriverOptions) {\n if (hasExistingDatabase(options)) {\n if (!isExpoSQLiteDatabaseLike(options.database)) {\n throw new InvalidPersistedCollectionConfigError(\n `Expo SQLite database must provide execAsync/getAllAsync/runAsync/withExclusiveTransactionAsync`,\n )\n }\n\n this.databasePromise = Promise.resolve(options.database)\n this.ownsDatabase = false\n return\n }\n\n this.databasePromise = Promise.resolve(options.openDatabase()).then(\n (database) => {\n if (!isExpoSQLiteDatabaseLike(database)) {\n throw new InvalidPersistedCollectionConfigError(\n `Expo SQLite openDatabase() must resolve a database with execAsync/getAllAsync/runAsync/withExclusiveTransactionAsync`,\n )\n }\n\n return database\n },\n )\n this.ownsDatabase = true\n }\n\n async exec(sql: string): Promise<void> {\n await this.enqueue(async () => {\n const database = await this.getDatabase()\n await database.execAsync(sql)\n })\n }\n\n async query<T>(\n sql: string,\n params: ReadonlyArray<unknown> = [],\n ): Promise<ReadonlyArray<T>> {\n return this.enqueue(async () => {\n const database = await this.getDatabase()\n return database.getAllAsync<T>(sql, normalizeParams(params))\n })\n }\n\n async run(sql: string, params: ReadonlyArray<unknown> = []): Promise<void> {\n await this.enqueue(async () => {\n const database = await this.getDatabase()\n await database.runAsync(sql, normalizeParams(params))\n })\n }\n\n async transaction<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n assertTransactionCallbackHasDriverArg(fn)\n return this.transactionWithDriver(fn)\n }\n\n async transactionWithDriver<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n return this.enqueue(async () => {\n const database = await this.getDatabase()\n return database.withExclusiveTransactionAsync(async (transaction) => {\n const transactionDriver = this.createTransactionDriver(transaction)\n return fn(transactionDriver)\n })\n })\n }\n\n async close(): Promise<void> {\n const database = await this.getDatabase()\n if (!this.ownsDatabase || typeof database.closeAsync !== `function`) {\n return\n }\n\n await database.closeAsync()\n }\n\n async getDatabase(): Promise<ExpoSQLiteDatabaseLike> {\n return this.databasePromise\n }\n\n private enqueue<T>(operation: () => Promise<T>): Promise<T> {\n const queuedOperation = this.queue.then(operation, operation)\n this.queue = queuedOperation.then(\n () => undefined,\n () => undefined,\n )\n return queuedOperation\n }\n\n private createTransactionDriver(\n transaction: ExpoSQLiteTransaction,\n ): SQLiteDriver {\n const transactionDriver: SQLiteDriver = {\n exec: async (sql) => {\n await transaction.execAsync(sql)\n },\n query: async <T>(\n sql: string,\n params: ReadonlyArray<unknown> = [],\n ): Promise<ReadonlyArray<T>> => {\n return transaction.getAllAsync<T>(sql, normalizeParams(params))\n },\n run: async (sql: string, params: ReadonlyArray<unknown> = []) => {\n await transaction.runAsync(sql, normalizeParams(params))\n },\n transaction: async <T>(\n fn: (nestedTransactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> => {\n assertTransactionCallbackHasDriverArg(fn)\n return this.runNestedTransaction(transaction, transactionDriver, fn)\n },\n transactionWithDriver: async <T>(\n fn: (nestedTransactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> =>\n this.runNestedTransaction(transaction, transactionDriver, fn),\n }\n\n return transactionDriver\n }\n\n private async runNestedTransaction<T>(\n transaction: ExpoSQLiteTransaction,\n transactionDriver: SQLiteDriver,\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n const savepointName = `tsdb_sp_${this.nextSavepointId}`\n this.nextSavepointId++\n await transaction.execAsync(`SAVEPOINT ${savepointName}`)\n\n try {\n const result = await fn(transactionDriver)\n await transaction.execAsync(`RELEASE SAVEPOINT ${savepointName}`)\n return result\n } catch (error) {\n await transaction.execAsync(`ROLLBACK TO SAVEPOINT ${savepointName}`)\n await transaction.execAsync(`RELEASE SAVEPOINT ${savepointName}`)\n throw error\n }\n }\n}\n\nfunction normalizeParams(\n params: ReadonlyArray<unknown>,\n): ExpoSQLiteBindParams | undefined {\n return params.length > 0 ? [...params] : undefined\n}\n\nexport function createExpoSQLiteDriver(\n options: ExpoSQLiteDriverOptions,\n): ExpoSQLiteDriver {\n return new ExpoSQLiteDriver(options)\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { PersistedCollectionCoordinator, PersistedCollectionPersistence, SQLiteCoreAdapterOptions } from '@tanstack/db-sqlite-persistence-core';
|
|
2
|
+
import { ExpoSQLiteDatabaseLike } from './expo-sqlite-driver.js';
|
|
3
|
+
export type { ExpoSQLiteDatabaseLike } from './expo-sqlite-driver.js';
|
|
4
|
+
type ExpoSQLiteCoreSchemaMismatchPolicy = `sync-present-reset` | `sync-absent-error` | `reset`;
|
|
5
|
+
export type ExpoSQLiteSchemaMismatchPolicy = ExpoSQLiteCoreSchemaMismatchPolicy | `throw`;
|
|
6
|
+
export type ExpoSQLitePersistenceOptions = Omit<SQLiteCoreAdapterOptions, `driver` | `schemaVersion` | `schemaMismatchPolicy`> & {
|
|
7
|
+
database: ExpoSQLiteDatabaseLike;
|
|
8
|
+
coordinator?: PersistedCollectionCoordinator;
|
|
9
|
+
schemaMismatchPolicy?: ExpoSQLiteSchemaMismatchPolicy;
|
|
10
|
+
};
|
|
11
|
+
export declare function createExpoSQLitePersistence<T extends object, TKey extends string | number = string | number>(options: ExpoSQLitePersistenceOptions): PersistedCollectionPersistence<T, TKey>;
|
package/dist/esm/expo.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { SingleProcessCoordinator, createSQLiteCorePersistenceAdapter } from "@tanstack/db-sqlite-persistence-core";
|
|
2
|
+
import { ExpoSQLiteDriver } from "./expo-sqlite-driver.js";
|
|
3
|
+
function normalizeSchemaMismatchPolicy(policy) {
|
|
4
|
+
if (policy === `throw`) {
|
|
5
|
+
return `sync-absent-error`;
|
|
6
|
+
}
|
|
7
|
+
return policy;
|
|
8
|
+
}
|
|
9
|
+
function resolveSchemaMismatchPolicy(explicitPolicy, mode) {
|
|
10
|
+
if (explicitPolicy) {
|
|
11
|
+
return normalizeSchemaMismatchPolicy(explicitPolicy);
|
|
12
|
+
}
|
|
13
|
+
return mode === `sync-present` ? `sync-present-reset` : `sync-absent-error`;
|
|
14
|
+
}
|
|
15
|
+
function createAdapterCacheKey(schemaMismatchPolicy, schemaVersion) {
|
|
16
|
+
const schemaVersionKey = schemaVersion === void 0 ? `schema:default` : `schema:${schemaVersion}`;
|
|
17
|
+
return `${schemaMismatchPolicy}|${schemaVersionKey}`;
|
|
18
|
+
}
|
|
19
|
+
function createInternalSQLiteDriver(options) {
|
|
20
|
+
return new ExpoSQLiteDriver({
|
|
21
|
+
database: options.database
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function resolveAdapterBaseOptions(options) {
|
|
25
|
+
return {
|
|
26
|
+
appliedTxPruneMaxRows: options.appliedTxPruneMaxRows,
|
|
27
|
+
appliedTxPruneMaxAgeSeconds: options.appliedTxPruneMaxAgeSeconds,
|
|
28
|
+
pullSinceReloadThreshold: options.pullSinceReloadThreshold
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function createExpoSQLitePersistence(options) {
|
|
32
|
+
const { coordinator, schemaMismatchPolicy } = options;
|
|
33
|
+
const driver = createInternalSQLiteDriver(options);
|
|
34
|
+
const adapterBaseOptions = resolveAdapterBaseOptions(options);
|
|
35
|
+
const resolvedCoordinator = coordinator ?? new SingleProcessCoordinator();
|
|
36
|
+
const adapterCache = /* @__PURE__ */ new Map();
|
|
37
|
+
const getAdapterForCollection = (mode, schemaVersion) => {
|
|
38
|
+
const resolvedSchemaMismatchPolicy = resolveSchemaMismatchPolicy(
|
|
39
|
+
schemaMismatchPolicy,
|
|
40
|
+
mode
|
|
41
|
+
);
|
|
42
|
+
const cacheKey = createAdapterCacheKey(
|
|
43
|
+
resolvedSchemaMismatchPolicy,
|
|
44
|
+
schemaVersion
|
|
45
|
+
);
|
|
46
|
+
const cachedAdapter = adapterCache.get(cacheKey);
|
|
47
|
+
if (cachedAdapter) {
|
|
48
|
+
return cachedAdapter;
|
|
49
|
+
}
|
|
50
|
+
const adapter = createSQLiteCorePersistenceAdapter({
|
|
51
|
+
...adapterBaseOptions,
|
|
52
|
+
driver,
|
|
53
|
+
schemaMismatchPolicy: resolvedSchemaMismatchPolicy,
|
|
54
|
+
...schemaVersion === void 0 ? {} : { schemaVersion }
|
|
55
|
+
});
|
|
56
|
+
adapterCache.set(cacheKey, adapter);
|
|
57
|
+
return adapter;
|
|
58
|
+
};
|
|
59
|
+
const createCollectionPersistence = (mode, schemaVersion) => ({
|
|
60
|
+
adapter: getAdapterForCollection(
|
|
61
|
+
mode,
|
|
62
|
+
schemaVersion
|
|
63
|
+
),
|
|
64
|
+
coordinator: resolvedCoordinator
|
|
65
|
+
});
|
|
66
|
+
const defaultPersistence = createCollectionPersistence(
|
|
67
|
+
`sync-absent`,
|
|
68
|
+
void 0
|
|
69
|
+
);
|
|
70
|
+
return {
|
|
71
|
+
...defaultPersistence,
|
|
72
|
+
resolvePersistenceForCollection: ({ mode, schemaVersion }) => createCollectionPersistence(mode, schemaVersion),
|
|
73
|
+
resolvePersistenceForMode: (mode) => createCollectionPersistence(mode, void 0)
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export {
|
|
77
|
+
createExpoSQLitePersistence
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=expo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expo.js","sources":["../../src/expo.ts"],"sourcesContent":["import {\n SingleProcessCoordinator,\n createSQLiteCorePersistenceAdapter,\n} from '@tanstack/db-sqlite-persistence-core'\nimport { ExpoSQLiteDriver } from './expo-sqlite-driver'\nimport type {\n PersistedCollectionCoordinator,\n PersistedCollectionMode,\n PersistedCollectionPersistence,\n SQLiteCoreAdapterOptions,\n SQLiteDriver,\n} from '@tanstack/db-sqlite-persistence-core'\nimport type { ExpoSQLiteDatabaseLike } from './expo-sqlite-driver'\n\nexport type { ExpoSQLiteDatabaseLike } from './expo-sqlite-driver'\n\ntype ExpoSQLiteCoreSchemaMismatchPolicy =\n | `sync-present-reset`\n | `sync-absent-error`\n | `reset`\n\nexport type ExpoSQLiteSchemaMismatchPolicy =\n | ExpoSQLiteCoreSchemaMismatchPolicy\n | `throw`\n\nexport type ExpoSQLitePersistenceOptions = Omit<\n SQLiteCoreAdapterOptions,\n `driver` | `schemaVersion` | `schemaMismatchPolicy`\n> & {\n database: ExpoSQLiteDatabaseLike\n coordinator?: PersistedCollectionCoordinator\n schemaMismatchPolicy?: ExpoSQLiteSchemaMismatchPolicy\n}\n\nfunction normalizeSchemaMismatchPolicy(\n policy: ExpoSQLiteSchemaMismatchPolicy,\n): ExpoSQLiteCoreSchemaMismatchPolicy {\n if (policy === `throw`) {\n return `sync-absent-error`\n }\n\n return policy\n}\n\nfunction resolveSchemaMismatchPolicy(\n explicitPolicy: ExpoSQLiteSchemaMismatchPolicy | undefined,\n mode: PersistedCollectionMode,\n): ExpoSQLiteCoreSchemaMismatchPolicy {\n if (explicitPolicy) {\n return normalizeSchemaMismatchPolicy(explicitPolicy)\n }\n\n return mode === `sync-present` ? `sync-present-reset` : `sync-absent-error`\n}\n\nfunction createAdapterCacheKey(\n schemaMismatchPolicy: ExpoSQLiteCoreSchemaMismatchPolicy,\n schemaVersion: number | undefined,\n): string {\n const schemaVersionKey =\n schemaVersion === undefined ? `schema:default` : `schema:${schemaVersion}`\n return `${schemaMismatchPolicy}|${schemaVersionKey}`\n}\n\nfunction createInternalSQLiteDriver(\n options: ExpoSQLitePersistenceOptions,\n): SQLiteDriver {\n return new ExpoSQLiteDriver({\n database: options.database,\n })\n}\n\nfunction resolveAdapterBaseOptions(\n options: ExpoSQLitePersistenceOptions,\n): Omit<\n SQLiteCoreAdapterOptions,\n `driver` | `schemaVersion` | `schemaMismatchPolicy`\n> {\n return {\n appliedTxPruneMaxRows: options.appliedTxPruneMaxRows,\n appliedTxPruneMaxAgeSeconds: options.appliedTxPruneMaxAgeSeconds,\n pullSinceReloadThreshold: options.pullSinceReloadThreshold,\n }\n}\n\nexport function createExpoSQLitePersistence<\n T extends object,\n TKey extends string | number = string | number,\n>(\n options: ExpoSQLitePersistenceOptions,\n): PersistedCollectionPersistence<T, TKey> {\n const { coordinator, schemaMismatchPolicy } = options\n const driver = createInternalSQLiteDriver(options)\n const adapterBaseOptions = resolveAdapterBaseOptions(options)\n const resolvedCoordinator = coordinator ?? new SingleProcessCoordinator()\n const adapterCache = new Map<\n string,\n ReturnType<\n typeof createSQLiteCorePersistenceAdapter<\n Record<string, unknown>,\n string | number\n >\n >\n >()\n\n const getAdapterForCollection = (\n mode: PersistedCollectionMode,\n schemaVersion: number | undefined,\n ) => {\n const resolvedSchemaMismatchPolicy = resolveSchemaMismatchPolicy(\n schemaMismatchPolicy,\n mode,\n )\n const cacheKey = createAdapterCacheKey(\n resolvedSchemaMismatchPolicy,\n schemaVersion,\n )\n const cachedAdapter = adapterCache.get(cacheKey)\n if (cachedAdapter) {\n return cachedAdapter\n }\n\n const adapter = createSQLiteCorePersistenceAdapter<\n Record<string, unknown>,\n string | number\n >({\n ...adapterBaseOptions,\n driver,\n schemaMismatchPolicy: resolvedSchemaMismatchPolicy,\n ...(schemaVersion === undefined ? {} : { schemaVersion }),\n })\n adapterCache.set(cacheKey, adapter)\n return adapter\n }\n\n const createCollectionPersistence = (\n mode: PersistedCollectionMode,\n schemaVersion: number | undefined,\n ): PersistedCollectionPersistence<T, TKey> => ({\n adapter: getAdapterForCollection(\n mode,\n schemaVersion,\n ) as unknown as PersistedCollectionPersistence<T, TKey>[`adapter`],\n coordinator: resolvedCoordinator,\n })\n\n const defaultPersistence = createCollectionPersistence(\n `sync-absent`,\n undefined,\n )\n\n return {\n ...defaultPersistence,\n resolvePersistenceForCollection: ({ mode, schemaVersion }) =>\n createCollectionPersistence(mode, schemaVersion),\n resolvePersistenceForMode: (mode) =>\n createCollectionPersistence(mode, undefined),\n }\n}\n"],"names":[],"mappings":";;AAkCA,SAAS,8BACP,QACoC;AACpC,MAAI,WAAW,SAAS;AACtB,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEA,SAAS,4BACP,gBACA,MACoC;AACpC,MAAI,gBAAgB;AAClB,WAAO,8BAA8B,cAAc;AAAA,EACrD;AAEA,SAAO,SAAS,iBAAiB,uBAAuB;AAC1D;AAEA,SAAS,sBACP,sBACA,eACQ;AACR,QAAM,mBACJ,kBAAkB,SAAY,mBAAmB,UAAU,aAAa;AAC1E,SAAO,GAAG,oBAAoB,IAAI,gBAAgB;AACpD;AAEA,SAAS,2BACP,SACc;AACd,SAAO,IAAI,iBAAiB;AAAA,IAC1B,UAAU,QAAQ;AAAA,EAAA,CACnB;AACH;AAEA,SAAS,0BACP,SAIA;AACA,SAAO;AAAA,IACL,uBAAuB,QAAQ;AAAA,IAC/B,6BAA6B,QAAQ;AAAA,IACrC,0BAA0B,QAAQ;AAAA,EAAA;AAEtC;AAEO,SAAS,4BAId,SACyC;AACzC,QAAM,EAAE,aAAa,qBAAA,IAAyB;AAC9C,QAAM,SAAS,2BAA2B,OAAO;AACjD,QAAM,qBAAqB,0BAA0B,OAAO;AAC5D,QAAM,sBAAsB,eAAe,IAAI,yBAAA;AAC/C,QAAM,mCAAmB,IAAA;AAUzB,QAAM,0BAA0B,CAC9B,MACA,kBACG;AACH,UAAM,+BAA+B;AAAA,MACnC;AAAA,MACA;AAAA,IAAA;AAEF,UAAM,WAAW;AAAA,MACf;AAAA,MACA;AAAA,IAAA;AAEF,UAAM,gBAAgB,aAAa,IAAI,QAAQ;AAC/C,QAAI,eAAe;AACjB,aAAO;AAAA,IACT;AAEA,UAAM,UAAU,mCAGd;AAAA,MACA,GAAG;AAAA,MACH;AAAA,MACA,sBAAsB;AAAA,MACtB,GAAI,kBAAkB,SAAY,CAAA,IAAK,EAAE,cAAA;AAAA,IAAc,CACxD;AACD,iBAAa,IAAI,UAAU,OAAO;AAClC,WAAO;AAAA,EACT;AAEA,QAAM,8BAA8B,CAClC,MACA,mBAC6C;AAAA,IAC7C,SAAS;AAAA,MACP;AAAA,MACA;AAAA,IAAA;AAAA,IAEF,aAAa;AAAA,EAAA;AAGf,QAAM,qBAAqB;AAAA,IACzB;AAAA,IACA;AAAA,EAAA;AAGF,SAAO;AAAA,IACL,GAAG;AAAA,IACH,iCAAiC,CAAC,EAAE,MAAM,oBACxC,4BAA4B,MAAM,aAAa;AAAA,IACjD,2BAA2B,CAAC,SAC1B,4BAA4B,MAAM,MAAS;AAAA,EAAA;AAEjD;"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createExpoSQLitePersistence } from './expo.js';
|
|
2
|
+
export type { ExpoSQLiteDatabaseLike, ExpoSQLitePersistenceOptions, ExpoSQLiteSchemaMismatchPolicy, } from './expo.js';
|
|
3
|
+
export { persistedCollectionOptions } from '@tanstack/db-sqlite-persistence-core';
|
|
4
|
+
export type { PersistedCollectionCoordinator, PersistedCollectionPersistence, } from '@tanstack/db-sqlite-persistence-core';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tanstack/expo-db-sqlite-persistence",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Expo SQLite persisted collection adapter for TanStack DB",
|
|
5
|
+
"author": "TanStack Team",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/TanStack/db.git",
|
|
10
|
+
"directory": "packages/expo-db-sqlite-persistence"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://tanstack.com/db",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"sqlite",
|
|
15
|
+
"expo",
|
|
16
|
+
"expo-sqlite",
|
|
17
|
+
"persistence",
|
|
18
|
+
"typescript"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "vite build",
|
|
22
|
+
"dev": "vite build --watch",
|
|
23
|
+
"lint": "eslint . --fix",
|
|
24
|
+
"test": "vitest --run",
|
|
25
|
+
"test:e2e": "pnpm --filter @tanstack/db-ivm build && pnpm --filter @tanstack/db build && pnpm --filter @tanstack/db-sqlite-persistence-core build && pnpm --filter @tanstack/expo-db-sqlite-persistence build && vitest --config vitest.e2e.config.ts --run",
|
|
26
|
+
"test:e2e:expo:ios": "pnpm --filter @tanstack/db-ivm build && pnpm --filter @tanstack/db build && pnpm --filter @tanstack/db-sqlite-persistence-core build && pnpm --filter @tanstack/expo-db-sqlite-persistence build && TANSTACK_DB_MOBILE_REQUIRE_RUNTIME_FACTORY=1 TANSTACK_DB_MOBILE_SQLITE_FACTORY_MODULE=./tests/helpers/expo-emulator-database-factory.ts TANSTACK_DB_EXPO_RUNTIME_PLATFORM=ios vitest --config vitest.e2e.config.ts --run",
|
|
27
|
+
"test:e2e:expo:android": "pnpm --filter @tanstack/db-ivm build && pnpm --filter @tanstack/db build && pnpm --filter @tanstack/db-sqlite-persistence-core build && pnpm --filter @tanstack/expo-db-sqlite-persistence build && TANSTACK_DB_MOBILE_REQUIRE_RUNTIME_FACTORY=1 TANSTACK_DB_MOBILE_SQLITE_FACTORY_MODULE=./tests/helpers/expo-emulator-database-factory.ts TANSTACK_DB_EXPO_RUNTIME_PLATFORM=android vitest --config vitest.e2e.config.ts --run"
|
|
28
|
+
},
|
|
29
|
+
"type": "module",
|
|
30
|
+
"main": "dist/cjs/index.cjs",
|
|
31
|
+
"module": "dist/esm/index.js",
|
|
32
|
+
"types": "dist/esm/index.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"import": {
|
|
36
|
+
"types": "./dist/esm/index.d.ts",
|
|
37
|
+
"default": "./dist/esm/index.js"
|
|
38
|
+
},
|
|
39
|
+
"require": {
|
|
40
|
+
"types": "./dist/cjs/index.d.cts",
|
|
41
|
+
"default": "./dist/cjs/index.cjs"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"./expo-sqlite-driver": {
|
|
45
|
+
"import": {
|
|
46
|
+
"types": "./dist/esm/expo-sqlite-driver.d.ts",
|
|
47
|
+
"default": "./dist/esm/expo-sqlite-driver.js"
|
|
48
|
+
},
|
|
49
|
+
"require": {
|
|
50
|
+
"types": "./dist/cjs/expo-sqlite-driver.d.cts",
|
|
51
|
+
"default": "./dist/cjs/expo-sqlite-driver.cjs"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"./package.json": "./package.json"
|
|
55
|
+
},
|
|
56
|
+
"sideEffects": false,
|
|
57
|
+
"files": [
|
|
58
|
+
"dist",
|
|
59
|
+
"src"
|
|
60
|
+
],
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"@tanstack/db-sqlite-persistence-core": "workspace:*"
|
|
63
|
+
},
|
|
64
|
+
"peerDependencies": {
|
|
65
|
+
"expo-sqlite": "^55.0.10",
|
|
66
|
+
"typescript": ">=4.7"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
70
|
+
"@vitest/coverage-istanbul": "^3.2.4",
|
|
71
|
+
"better-sqlite3": "^12.6.2"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { InvalidPersistedCollectionConfigError } from '@tanstack/db-sqlite-persistence-core'
|
|
2
|
+
import type { SQLiteDriver } from '@tanstack/db-sqlite-persistence-core'
|
|
3
|
+
|
|
4
|
+
export type ExpoSQLiteBindParams =
|
|
5
|
+
| ReadonlyArray<unknown>
|
|
6
|
+
| Record<string, unknown>
|
|
7
|
+
|
|
8
|
+
export type ExpoSQLiteRunResult = {
|
|
9
|
+
changes: number
|
|
10
|
+
lastInsertRowId: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type ExpoSQLiteQueryable = {
|
|
14
|
+
execAsync: (sql: string) => Promise<void>
|
|
15
|
+
getAllAsync: <T>(
|
|
16
|
+
sql: string,
|
|
17
|
+
params?: ExpoSQLiteBindParams,
|
|
18
|
+
) => Promise<ReadonlyArray<T>>
|
|
19
|
+
runAsync: (
|
|
20
|
+
sql: string,
|
|
21
|
+
params?: ExpoSQLiteBindParams,
|
|
22
|
+
) => Promise<ExpoSQLiteRunResult>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type ExpoSQLiteTransaction = ExpoSQLiteQueryable
|
|
26
|
+
|
|
27
|
+
export type ExpoSQLiteDatabaseLike = ExpoSQLiteQueryable & {
|
|
28
|
+
withExclusiveTransactionAsync: <T>(
|
|
29
|
+
task: (transaction: ExpoSQLiteTransaction) => Promise<T>,
|
|
30
|
+
) => Promise<T>
|
|
31
|
+
closeAsync?: () => Promise<void>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type ExpoSQLiteExistingDatabaseOptions = {
|
|
35
|
+
database: ExpoSQLiteDatabaseLike
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type ExpoSQLiteOpenDatabaseOptions = {
|
|
39
|
+
openDatabase: () => Promise<ExpoSQLiteDatabaseLike> | ExpoSQLiteDatabaseLike
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type ExpoSQLiteDriverOptions =
|
|
43
|
+
| ExpoSQLiteExistingDatabaseOptions
|
|
44
|
+
| ExpoSQLiteOpenDatabaseOptions
|
|
45
|
+
|
|
46
|
+
function hasExistingDatabase(
|
|
47
|
+
options: ExpoSQLiteDriverOptions,
|
|
48
|
+
): options is ExpoSQLiteExistingDatabaseOptions {
|
|
49
|
+
return `database` in options
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function assertTransactionCallbackHasDriverArg(
|
|
53
|
+
fn: (transactionDriver: SQLiteDriver) => Promise<unknown>,
|
|
54
|
+
): void {
|
|
55
|
+
if (fn.length > 0) {
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
60
|
+
`SQLiteDriver.transaction callback must accept the transaction driver argument`,
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isExpoSQLiteDatabaseLike(
|
|
65
|
+
value: unknown,
|
|
66
|
+
): value is ExpoSQLiteDatabaseLike {
|
|
67
|
+
return (
|
|
68
|
+
typeof value === `object` &&
|
|
69
|
+
value !== null &&
|
|
70
|
+
typeof (value as ExpoSQLiteDatabaseLike).execAsync === `function` &&
|
|
71
|
+
typeof (value as ExpoSQLiteDatabaseLike).getAllAsync === `function` &&
|
|
72
|
+
typeof (value as ExpoSQLiteDatabaseLike).runAsync === `function` &&
|
|
73
|
+
typeof (value as ExpoSQLiteDatabaseLike).withExclusiveTransactionAsync ===
|
|
74
|
+
`function`
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export class ExpoSQLiteDriver implements SQLiteDriver {
|
|
79
|
+
private readonly databasePromise: Promise<ExpoSQLiteDatabaseLike>
|
|
80
|
+
private readonly ownsDatabase: boolean
|
|
81
|
+
private queue: Promise<void> = Promise.resolve()
|
|
82
|
+
private nextSavepointId = 1
|
|
83
|
+
|
|
84
|
+
constructor(options: ExpoSQLiteDriverOptions) {
|
|
85
|
+
if (hasExistingDatabase(options)) {
|
|
86
|
+
if (!isExpoSQLiteDatabaseLike(options.database)) {
|
|
87
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
88
|
+
`Expo SQLite database must provide execAsync/getAllAsync/runAsync/withExclusiveTransactionAsync`,
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.databasePromise = Promise.resolve(options.database)
|
|
93
|
+
this.ownsDatabase = false
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.databasePromise = Promise.resolve(options.openDatabase()).then(
|
|
98
|
+
(database) => {
|
|
99
|
+
if (!isExpoSQLiteDatabaseLike(database)) {
|
|
100
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
101
|
+
`Expo SQLite openDatabase() must resolve a database with execAsync/getAllAsync/runAsync/withExclusiveTransactionAsync`,
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return database
|
|
106
|
+
},
|
|
107
|
+
)
|
|
108
|
+
this.ownsDatabase = true
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async exec(sql: string): Promise<void> {
|
|
112
|
+
await this.enqueue(async () => {
|
|
113
|
+
const database = await this.getDatabase()
|
|
114
|
+
await database.execAsync(sql)
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async query<T>(
|
|
119
|
+
sql: string,
|
|
120
|
+
params: ReadonlyArray<unknown> = [],
|
|
121
|
+
): Promise<ReadonlyArray<T>> {
|
|
122
|
+
return this.enqueue(async () => {
|
|
123
|
+
const database = await this.getDatabase()
|
|
124
|
+
return database.getAllAsync<T>(sql, normalizeParams(params))
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async run(sql: string, params: ReadonlyArray<unknown> = []): Promise<void> {
|
|
129
|
+
await this.enqueue(async () => {
|
|
130
|
+
const database = await this.getDatabase()
|
|
131
|
+
await database.runAsync(sql, normalizeParams(params))
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async transaction<T>(
|
|
136
|
+
fn: (transactionDriver: SQLiteDriver) => Promise<T>,
|
|
137
|
+
): Promise<T> {
|
|
138
|
+
assertTransactionCallbackHasDriverArg(fn)
|
|
139
|
+
return this.transactionWithDriver(fn)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async transactionWithDriver<T>(
|
|
143
|
+
fn: (transactionDriver: SQLiteDriver) => Promise<T>,
|
|
144
|
+
): Promise<T> {
|
|
145
|
+
return this.enqueue(async () => {
|
|
146
|
+
const database = await this.getDatabase()
|
|
147
|
+
return database.withExclusiveTransactionAsync(async (transaction) => {
|
|
148
|
+
const transactionDriver = this.createTransactionDriver(transaction)
|
|
149
|
+
return fn(transactionDriver)
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async close(): Promise<void> {
|
|
155
|
+
const database = await this.getDatabase()
|
|
156
|
+
if (!this.ownsDatabase || typeof database.closeAsync !== `function`) {
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
await database.closeAsync()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async getDatabase(): Promise<ExpoSQLiteDatabaseLike> {
|
|
164
|
+
return this.databasePromise
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private enqueue<T>(operation: () => Promise<T>): Promise<T> {
|
|
168
|
+
const queuedOperation = this.queue.then(operation, operation)
|
|
169
|
+
this.queue = queuedOperation.then(
|
|
170
|
+
() => undefined,
|
|
171
|
+
() => undefined,
|
|
172
|
+
)
|
|
173
|
+
return queuedOperation
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private createTransactionDriver(
|
|
177
|
+
transaction: ExpoSQLiteTransaction,
|
|
178
|
+
): SQLiteDriver {
|
|
179
|
+
const transactionDriver: SQLiteDriver = {
|
|
180
|
+
exec: async (sql) => {
|
|
181
|
+
await transaction.execAsync(sql)
|
|
182
|
+
},
|
|
183
|
+
query: async <T>(
|
|
184
|
+
sql: string,
|
|
185
|
+
params: ReadonlyArray<unknown> = [],
|
|
186
|
+
): Promise<ReadonlyArray<T>> => {
|
|
187
|
+
return transaction.getAllAsync<T>(sql, normalizeParams(params))
|
|
188
|
+
},
|
|
189
|
+
run: async (sql: string, params: ReadonlyArray<unknown> = []) => {
|
|
190
|
+
await transaction.runAsync(sql, normalizeParams(params))
|
|
191
|
+
},
|
|
192
|
+
transaction: async <T>(
|
|
193
|
+
fn: (nestedTransactionDriver: SQLiteDriver) => Promise<T>,
|
|
194
|
+
): Promise<T> => {
|
|
195
|
+
assertTransactionCallbackHasDriverArg(fn)
|
|
196
|
+
return this.runNestedTransaction(transaction, transactionDriver, fn)
|
|
197
|
+
},
|
|
198
|
+
transactionWithDriver: async <T>(
|
|
199
|
+
fn: (nestedTransactionDriver: SQLiteDriver) => Promise<T>,
|
|
200
|
+
): Promise<T> =>
|
|
201
|
+
this.runNestedTransaction(transaction, transactionDriver, fn),
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return transactionDriver
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private async runNestedTransaction<T>(
|
|
208
|
+
transaction: ExpoSQLiteTransaction,
|
|
209
|
+
transactionDriver: SQLiteDriver,
|
|
210
|
+
fn: (transactionDriver: SQLiteDriver) => Promise<T>,
|
|
211
|
+
): Promise<T> {
|
|
212
|
+
const savepointName = `tsdb_sp_${this.nextSavepointId}`
|
|
213
|
+
this.nextSavepointId++
|
|
214
|
+
await transaction.execAsync(`SAVEPOINT ${savepointName}`)
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const result = await fn(transactionDriver)
|
|
218
|
+
await transaction.execAsync(`RELEASE SAVEPOINT ${savepointName}`)
|
|
219
|
+
return result
|
|
220
|
+
} catch (error) {
|
|
221
|
+
await transaction.execAsync(`ROLLBACK TO SAVEPOINT ${savepointName}`)
|
|
222
|
+
await transaction.execAsync(`RELEASE SAVEPOINT ${savepointName}`)
|
|
223
|
+
throw error
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function normalizeParams(
|
|
229
|
+
params: ReadonlyArray<unknown>,
|
|
230
|
+
): ExpoSQLiteBindParams | undefined {
|
|
231
|
+
return params.length > 0 ? [...params] : undefined
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function createExpoSQLiteDriver(
|
|
235
|
+
options: ExpoSQLiteDriverOptions,
|
|
236
|
+
): ExpoSQLiteDriver {
|
|
237
|
+
return new ExpoSQLiteDriver(options)
|
|
238
|
+
}
|
package/src/expo.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SingleProcessCoordinator,
|
|
3
|
+
createSQLiteCorePersistenceAdapter,
|
|
4
|
+
} from '@tanstack/db-sqlite-persistence-core'
|
|
5
|
+
import { ExpoSQLiteDriver } from './expo-sqlite-driver'
|
|
6
|
+
import type {
|
|
7
|
+
PersistedCollectionCoordinator,
|
|
8
|
+
PersistedCollectionMode,
|
|
9
|
+
PersistedCollectionPersistence,
|
|
10
|
+
SQLiteCoreAdapterOptions,
|
|
11
|
+
SQLiteDriver,
|
|
12
|
+
} from '@tanstack/db-sqlite-persistence-core'
|
|
13
|
+
import type { ExpoSQLiteDatabaseLike } from './expo-sqlite-driver'
|
|
14
|
+
|
|
15
|
+
export type { ExpoSQLiteDatabaseLike } from './expo-sqlite-driver'
|
|
16
|
+
|
|
17
|
+
type ExpoSQLiteCoreSchemaMismatchPolicy =
|
|
18
|
+
| `sync-present-reset`
|
|
19
|
+
| `sync-absent-error`
|
|
20
|
+
| `reset`
|
|
21
|
+
|
|
22
|
+
export type ExpoSQLiteSchemaMismatchPolicy =
|
|
23
|
+
| ExpoSQLiteCoreSchemaMismatchPolicy
|
|
24
|
+
| `throw`
|
|
25
|
+
|
|
26
|
+
export type ExpoSQLitePersistenceOptions = Omit<
|
|
27
|
+
SQLiteCoreAdapterOptions,
|
|
28
|
+
`driver` | `schemaVersion` | `schemaMismatchPolicy`
|
|
29
|
+
> & {
|
|
30
|
+
database: ExpoSQLiteDatabaseLike
|
|
31
|
+
coordinator?: PersistedCollectionCoordinator
|
|
32
|
+
schemaMismatchPolicy?: ExpoSQLiteSchemaMismatchPolicy
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeSchemaMismatchPolicy(
|
|
36
|
+
policy: ExpoSQLiteSchemaMismatchPolicy,
|
|
37
|
+
): ExpoSQLiteCoreSchemaMismatchPolicy {
|
|
38
|
+
if (policy === `throw`) {
|
|
39
|
+
return `sync-absent-error`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return policy
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function resolveSchemaMismatchPolicy(
|
|
46
|
+
explicitPolicy: ExpoSQLiteSchemaMismatchPolicy | undefined,
|
|
47
|
+
mode: PersistedCollectionMode,
|
|
48
|
+
): ExpoSQLiteCoreSchemaMismatchPolicy {
|
|
49
|
+
if (explicitPolicy) {
|
|
50
|
+
return normalizeSchemaMismatchPolicy(explicitPolicy)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return mode === `sync-present` ? `sync-present-reset` : `sync-absent-error`
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function createAdapterCacheKey(
|
|
57
|
+
schemaMismatchPolicy: ExpoSQLiteCoreSchemaMismatchPolicy,
|
|
58
|
+
schemaVersion: number | undefined,
|
|
59
|
+
): string {
|
|
60
|
+
const schemaVersionKey =
|
|
61
|
+
schemaVersion === undefined ? `schema:default` : `schema:${schemaVersion}`
|
|
62
|
+
return `${schemaMismatchPolicy}|${schemaVersionKey}`
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function createInternalSQLiteDriver(
|
|
66
|
+
options: ExpoSQLitePersistenceOptions,
|
|
67
|
+
): SQLiteDriver {
|
|
68
|
+
return new ExpoSQLiteDriver({
|
|
69
|
+
database: options.database,
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function resolveAdapterBaseOptions(
|
|
74
|
+
options: ExpoSQLitePersistenceOptions,
|
|
75
|
+
): Omit<
|
|
76
|
+
SQLiteCoreAdapterOptions,
|
|
77
|
+
`driver` | `schemaVersion` | `schemaMismatchPolicy`
|
|
78
|
+
> {
|
|
79
|
+
return {
|
|
80
|
+
appliedTxPruneMaxRows: options.appliedTxPruneMaxRows,
|
|
81
|
+
appliedTxPruneMaxAgeSeconds: options.appliedTxPruneMaxAgeSeconds,
|
|
82
|
+
pullSinceReloadThreshold: options.pullSinceReloadThreshold,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function createExpoSQLitePersistence<
|
|
87
|
+
T extends object,
|
|
88
|
+
TKey extends string | number = string | number,
|
|
89
|
+
>(
|
|
90
|
+
options: ExpoSQLitePersistenceOptions,
|
|
91
|
+
): PersistedCollectionPersistence<T, TKey> {
|
|
92
|
+
const { coordinator, schemaMismatchPolicy } = options
|
|
93
|
+
const driver = createInternalSQLiteDriver(options)
|
|
94
|
+
const adapterBaseOptions = resolveAdapterBaseOptions(options)
|
|
95
|
+
const resolvedCoordinator = coordinator ?? new SingleProcessCoordinator()
|
|
96
|
+
const adapterCache = new Map<
|
|
97
|
+
string,
|
|
98
|
+
ReturnType<
|
|
99
|
+
typeof createSQLiteCorePersistenceAdapter<
|
|
100
|
+
Record<string, unknown>,
|
|
101
|
+
string | number
|
|
102
|
+
>
|
|
103
|
+
>
|
|
104
|
+
>()
|
|
105
|
+
|
|
106
|
+
const getAdapterForCollection = (
|
|
107
|
+
mode: PersistedCollectionMode,
|
|
108
|
+
schemaVersion: number | undefined,
|
|
109
|
+
) => {
|
|
110
|
+
const resolvedSchemaMismatchPolicy = resolveSchemaMismatchPolicy(
|
|
111
|
+
schemaMismatchPolicy,
|
|
112
|
+
mode,
|
|
113
|
+
)
|
|
114
|
+
const cacheKey = createAdapterCacheKey(
|
|
115
|
+
resolvedSchemaMismatchPolicy,
|
|
116
|
+
schemaVersion,
|
|
117
|
+
)
|
|
118
|
+
const cachedAdapter = adapterCache.get(cacheKey)
|
|
119
|
+
if (cachedAdapter) {
|
|
120
|
+
return cachedAdapter
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const adapter = createSQLiteCorePersistenceAdapter<
|
|
124
|
+
Record<string, unknown>,
|
|
125
|
+
string | number
|
|
126
|
+
>({
|
|
127
|
+
...adapterBaseOptions,
|
|
128
|
+
driver,
|
|
129
|
+
schemaMismatchPolicy: resolvedSchemaMismatchPolicy,
|
|
130
|
+
...(schemaVersion === undefined ? {} : { schemaVersion }),
|
|
131
|
+
})
|
|
132
|
+
adapterCache.set(cacheKey, adapter)
|
|
133
|
+
return adapter
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const createCollectionPersistence = (
|
|
137
|
+
mode: PersistedCollectionMode,
|
|
138
|
+
schemaVersion: number | undefined,
|
|
139
|
+
): PersistedCollectionPersistence<T, TKey> => ({
|
|
140
|
+
adapter: getAdapterForCollection(
|
|
141
|
+
mode,
|
|
142
|
+
schemaVersion,
|
|
143
|
+
) as unknown as PersistedCollectionPersistence<T, TKey>[`adapter`],
|
|
144
|
+
coordinator: resolvedCoordinator,
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
const defaultPersistence = createCollectionPersistence(
|
|
148
|
+
`sync-absent`,
|
|
149
|
+
undefined,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
...defaultPersistence,
|
|
154
|
+
resolvePersistenceForCollection: ({ mode, schemaVersion }) =>
|
|
155
|
+
createCollectionPersistence(mode, schemaVersion),
|
|
156
|
+
resolvePersistenceForMode: (mode) =>
|
|
157
|
+
createCollectionPersistence(mode, undefined),
|
|
158
|
+
}
|
|
159
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { createExpoSQLitePersistence } from './expo'
|
|
2
|
+
export type {
|
|
3
|
+
ExpoSQLiteDatabaseLike,
|
|
4
|
+
ExpoSQLitePersistenceOptions,
|
|
5
|
+
ExpoSQLiteSchemaMismatchPolicy,
|
|
6
|
+
} from './expo'
|
|
7
|
+
export { persistedCollectionOptions } from '@tanstack/db-sqlite-persistence-core'
|
|
8
|
+
export type {
|
|
9
|
+
PersistedCollectionCoordinator,
|
|
10
|
+
PersistedCollectionPersistence,
|
|
11
|
+
} from '@tanstack/db-sqlite-persistence-core'
|