pomegranate-db 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE.md +38 -0
- package/PomegranateDB.podspec +67 -0
- package/README.md +122 -0
- package/dist/adapters/expo-sqlite/ExpoSQLiteDriver.d.ts +34 -0
- package/dist/adapters/expo-sqlite/ExpoSQLiteDriver.js +155 -0
- package/dist/adapters/expo-sqlite/index.d.ts +2 -0
- package/dist/adapters/expo-sqlite/index.js +6 -0
- package/dist/adapters/index.d.ts +7 -0
- package/dist/adapters/index.js +13 -0
- package/dist/adapters/loki/LokiAdapter.d.ts +100 -0
- package/dist/adapters/loki/LokiAdapter.js +144 -0
- package/dist/adapters/loki/index.d.ts +6 -0
- package/dist/adapters/loki/index.js +12 -0
- package/dist/adapters/loki/worker/LokiDispatcher.d.ts +21 -0
- package/dist/adapters/loki/worker/LokiDispatcher.js +63 -0
- package/dist/adapters/loki/worker/LokiExecutor.d.ts +96 -0
- package/dist/adapters/loki/worker/LokiExecutor.js +462 -0
- package/dist/adapters/loki/worker/SynchronousWorker.d.ts +22 -0
- package/dist/adapters/loki/worker/SynchronousWorker.js +76 -0
- package/dist/adapters/loki/worker/loki.worker.d.ts +14 -0
- package/dist/adapters/loki/worker/loki.worker.js +112 -0
- package/dist/adapters/loki/worker/types.d.ts +44 -0
- package/dist/adapters/loki/worker/types.js +11 -0
- package/dist/adapters/native-sqlite/NativeSQLiteDriver.d.ts +55 -0
- package/dist/adapters/native-sqlite/NativeSQLiteDriver.js +145 -0
- package/dist/adapters/native-sqlite/index.d.ts +2 -0
- package/dist/adapters/native-sqlite/index.js +6 -0
- package/dist/adapters/op-sqlite/OpSQLiteDriver.d.ts +49 -0
- package/dist/adapters/op-sqlite/OpSQLiteDriver.js +140 -0
- package/dist/adapters/op-sqlite/index.d.ts +2 -0
- package/dist/adapters/op-sqlite/index.js +6 -0
- package/dist/adapters/sqlite/SQLiteAdapter.d.ts +70 -0
- package/dist/adapters/sqlite/SQLiteAdapter.js +264 -0
- package/dist/adapters/sqlite/index.d.ts +2 -0
- package/dist/adapters/sqlite/index.js +6 -0
- package/dist/adapters/sqlite/sql.d.ts +35 -0
- package/dist/adapters/sqlite/sql.js +258 -0
- package/dist/adapters/types.d.ts +93 -0
- package/dist/adapters/types.js +9 -0
- package/dist/collection/Collection.d.ts +103 -0
- package/dist/collection/Collection.js +245 -0
- package/dist/collection/index.d.ts +2 -0
- package/dist/collection/index.js +6 -0
- package/dist/database/Database.d.ts +128 -0
- package/dist/database/Database.js +245 -0
- package/dist/database/index.d.ts +2 -0
- package/dist/database/index.js +6 -0
- package/dist/encryption/index.d.ts +62 -0
- package/dist/encryption/index.js +276 -0
- package/dist/encryption/nodeCrypto.d.ts +18 -0
- package/dist/encryption/nodeCrypto.js +25 -0
- package/dist/encryption/nodeCrypto.native.d.ts +13 -0
- package/dist/encryption/nodeCrypto.native.js +26 -0
- package/dist/expo.d.ts +12 -0
- package/dist/expo.js +32 -0
- package/dist/hooks/index.d.ts +115 -0
- package/dist/hooks/index.js +285 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +57 -0
- package/dist/model/Model.d.ts +92 -0
- package/dist/model/Model.js +251 -0
- package/dist/model/index.d.ts +2 -0
- package/dist/model/index.js +7 -0
- package/dist/observable/Subject.d.ts +60 -0
- package/dist/observable/Subject.js +132 -0
- package/dist/observable/index.d.ts +2 -0
- package/dist/observable/index.js +10 -0
- package/dist/query/QueryBuilder.d.ts +51 -0
- package/dist/query/QueryBuilder.js +165 -0
- package/dist/query/index.d.ts +2 -0
- package/dist/query/index.js +7 -0
- package/dist/query/types.d.ts +60 -0
- package/dist/query/types.js +9 -0
- package/dist/schema/builder.d.ts +68 -0
- package/dist/schema/builder.js +168 -0
- package/dist/schema/index.d.ts +2 -0
- package/dist/schema/index.js +7 -0
- package/dist/schema/types.d.ts +108 -0
- package/dist/schema/types.js +9 -0
- package/dist/sync/index.d.ts +2 -0
- package/dist/sync/index.js +6 -0
- package/dist/sync/sync.d.ts +15 -0
- package/dist/sync/sync.js +182 -0
- package/dist/sync/types.d.ts +41 -0
- package/dist/sync/types.js +6 -0
- package/dist/utils/index.d.ts +45 -0
- package/dist/utils/index.js +99 -0
- package/expo-plugin/index.d.ts +68 -0
- package/expo-plugin/index.js +83 -0
- package/native/android-jsi/build.gradle +45 -0
- package/native/android-jsi/src/main/AndroidManifest.xml +2 -0
- package/native/android-jsi/src/main/cpp/CMakeLists.txt +73 -0
- package/native/android-jsi/src/main/cpp/DatabasePlatformAndroid.cpp +107 -0
- package/native/android-jsi/src/main/cpp/DatabasePlatformAndroid.h +16 -0
- package/native/android-jsi/src/main/cpp/JSIInstaller.cpp +27 -0
- package/native/android-jsi/src/main/java/com/pomegranate/jsi/JSIInstaller.kt +43 -0
- package/native/android-jsi/src/main/java/com/pomegranate/jsi/PomegranateJSIModule.kt +39 -0
- package/native/android-jsi/src/main/java/com/pomegranate/jsi/PomegranateJSIPackage.kt +17 -0
- package/native/ios/DatabasePlatformIOS.mm +83 -0
- package/native/ios/PomegranateJSI.h +15 -0
- package/native/ios/PomegranateJSI.mm +59 -0
- package/native/shared/Database.cpp +283 -0
- package/native/shared/Database.h +84 -0
- package/native/shared/Sqlite.cpp +61 -0
- package/native/shared/Sqlite.h +67 -0
- package/native/shared/sqlite3/sqlite3.c +260493 -0
- package/native/shared/sqlite3/sqlite3.h +13583 -0
- package/package.json +127 -0
- package/react-native.config.js +28 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync types — compatible with the Watermelon backend sync protocol.
|
|
3
|
+
*/
|
|
4
|
+
import type { RawRecord } from '../schema/types';
|
|
5
|
+
export interface SyncPullResult {
|
|
6
|
+
/** Changes from the server, grouped by table */
|
|
7
|
+
changes: SyncTableChanges;
|
|
8
|
+
/** Server timestamp of this pull */
|
|
9
|
+
timestamp: number;
|
|
10
|
+
}
|
|
11
|
+
/** Changes for all tables */
|
|
12
|
+
export type SyncTableChanges = Record<string, SyncTableChangeSet>;
|
|
13
|
+
/** Changes for a single table */
|
|
14
|
+
export interface SyncTableChangeSet {
|
|
15
|
+
created: RawRecord[];
|
|
16
|
+
updated: RawRecord[];
|
|
17
|
+
deleted: string[];
|
|
18
|
+
}
|
|
19
|
+
export interface SyncPushPayload {
|
|
20
|
+
changes: SyncTableChanges;
|
|
21
|
+
lastPulledAt: number;
|
|
22
|
+
}
|
|
23
|
+
export interface SyncConfig {
|
|
24
|
+
pullChanges: (params: {
|
|
25
|
+
lastPulledAt: number | null;
|
|
26
|
+
}) => Promise<SyncPullResult>;
|
|
27
|
+
pushChanges: (params: SyncPushPayload) => Promise<void>;
|
|
28
|
+
/** Optional: called when sync encounters a conflict */
|
|
29
|
+
onConflict?: (local: RawRecord, remote: RawRecord) => RawRecord;
|
|
30
|
+
/** Optional: tables to sync. If not specified, all tables are synced. */
|
|
31
|
+
tables?: string[];
|
|
32
|
+
}
|
|
33
|
+
export type SyncState = 'idle' | 'pulling' | 'pushing' | 'applying' | 'complete' | 'error';
|
|
34
|
+
export interface SyncLog {
|
|
35
|
+
startedAt: number;
|
|
36
|
+
finishedAt?: number;
|
|
37
|
+
state: SyncState;
|
|
38
|
+
pullTimestamp?: number;
|
|
39
|
+
pushedTables?: string[];
|
|
40
|
+
error?: string;
|
|
41
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions: ID generation, timestamps, etc.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Generate a unique ID suitable for record primary keys.
|
|
6
|
+
* Uses a combination of timestamp + random to avoid collisions.
|
|
7
|
+
* In production, you'd swap this for a proper UUID or CUID generator.
|
|
8
|
+
*/
|
|
9
|
+
export declare function generateId(): string;
|
|
10
|
+
/**
|
|
11
|
+
* Current timestamp in milliseconds (epoch).
|
|
12
|
+
*/
|
|
13
|
+
export declare function now(): number;
|
|
14
|
+
/**
|
|
15
|
+
* Sanitize a table name to prevent injection.
|
|
16
|
+
*/
|
|
17
|
+
export declare function sanitizeTableName(name: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Sanitize a column name to prevent injection.
|
|
20
|
+
*/
|
|
21
|
+
export declare function sanitizeColumnName(name: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Convert a Date to epoch ms for storage.
|
|
24
|
+
*/
|
|
25
|
+
export declare function dateToTimestamp(date: Date | number | null): number | null;
|
|
26
|
+
/**
|
|
27
|
+
* Convert epoch ms back to a Date.
|
|
28
|
+
*/
|
|
29
|
+
export declare function timestampToDate(ts: number | null): Date | null;
|
|
30
|
+
/**
|
|
31
|
+
* Simple deep-freeze for objects (one level).
|
|
32
|
+
*/
|
|
33
|
+
export declare function freeze<T extends object>(obj: T): Readonly<T>;
|
|
34
|
+
/**
|
|
35
|
+
* Invariant — throws if condition is falsy.
|
|
36
|
+
*/
|
|
37
|
+
export declare function invariant(condition: unknown, message: string): asserts condition;
|
|
38
|
+
/**
|
|
39
|
+
* Logger (can be swapped out).
|
|
40
|
+
*/
|
|
41
|
+
export declare const logger: {
|
|
42
|
+
warn(msg: string, ...args: unknown[]): void;
|
|
43
|
+
error(msg: string, ...args: unknown[]): void;
|
|
44
|
+
debug(msg: string, ...args: unknown[]): void;
|
|
45
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Utility functions: ID generation, timestamps, etc.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.logger = void 0;
|
|
7
|
+
exports.generateId = generateId;
|
|
8
|
+
exports.now = now;
|
|
9
|
+
exports.sanitizeTableName = sanitizeTableName;
|
|
10
|
+
exports.sanitizeColumnName = sanitizeColumnName;
|
|
11
|
+
exports.dateToTimestamp = dateToTimestamp;
|
|
12
|
+
exports.timestampToDate = timestampToDate;
|
|
13
|
+
exports.freeze = freeze;
|
|
14
|
+
exports.invariant = invariant;
|
|
15
|
+
let _idCounter = 0;
|
|
16
|
+
/**
|
|
17
|
+
* Generate a unique ID suitable for record primary keys.
|
|
18
|
+
* Uses a combination of timestamp + random to avoid collisions.
|
|
19
|
+
* In production, you'd swap this for a proper UUID or CUID generator.
|
|
20
|
+
*/
|
|
21
|
+
function generateId() {
|
|
22
|
+
const timestamp = Date.now().toString(36);
|
|
23
|
+
const random = Math.random().toString(36).slice(2, 10);
|
|
24
|
+
const counter = (_idCounter++).toString(36);
|
|
25
|
+
return `${timestamp}${random}${counter}`;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Current timestamp in milliseconds (epoch).
|
|
29
|
+
*/
|
|
30
|
+
function now() {
|
|
31
|
+
return Date.now();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Sanitize a table name to prevent injection.
|
|
35
|
+
*/
|
|
36
|
+
function sanitizeTableName(name) {
|
|
37
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
38
|
+
throw new Error(`Invalid table name: "${name}"`);
|
|
39
|
+
}
|
|
40
|
+
return name;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Sanitize a column name to prevent injection.
|
|
44
|
+
*/
|
|
45
|
+
function sanitizeColumnName(name) {
|
|
46
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
47
|
+
throw new Error(`Invalid column name: "${name}"`);
|
|
48
|
+
}
|
|
49
|
+
return name;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Convert a Date to epoch ms for storage.
|
|
53
|
+
*/
|
|
54
|
+
function dateToTimestamp(date) {
|
|
55
|
+
if (date === null)
|
|
56
|
+
return null;
|
|
57
|
+
if (typeof date === 'number')
|
|
58
|
+
return date;
|
|
59
|
+
return date.getTime();
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Convert epoch ms back to a Date.
|
|
63
|
+
*/
|
|
64
|
+
function timestampToDate(ts) {
|
|
65
|
+
if (ts === null)
|
|
66
|
+
return null;
|
|
67
|
+
return new Date(ts);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Simple deep-freeze for objects (one level).
|
|
71
|
+
*/
|
|
72
|
+
function freeze(obj) {
|
|
73
|
+
return Object.freeze(obj);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Invariant — throws if condition is falsy.
|
|
77
|
+
*/
|
|
78
|
+
function invariant(condition, message) {
|
|
79
|
+
if (!condition) {
|
|
80
|
+
throw new Error(`Invariant violation: ${message}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Logger (can be swapped out).
|
|
85
|
+
*/
|
|
86
|
+
exports.logger = {
|
|
87
|
+
warn(msg, ...args) {
|
|
88
|
+
console.warn(`[PomegranateDB] ${msg}`, ...args);
|
|
89
|
+
},
|
|
90
|
+
error(msg, ...args) {
|
|
91
|
+
console.error(`[PomegranateDB] ${msg}`, ...args);
|
|
92
|
+
},
|
|
93
|
+
debug(msg, ...args) {
|
|
94
|
+
if (process.env.NODE_ENV === 'development') {
|
|
95
|
+
console.debug(`[PomegranateDB] ${msg}`, ...args);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PomegranateDB Expo Config Plugin.
|
|
3
|
+
*
|
|
4
|
+
* Provides first-class Expo support by configuring the native
|
|
5
|
+
* projects for optimal SQLite performance.
|
|
6
|
+
*
|
|
7
|
+
* Usage in app.json / app.config.js:
|
|
8
|
+
*
|
|
9
|
+
* {
|
|
10
|
+
* "expo": {
|
|
11
|
+
* "plugins": [
|
|
12
|
+
* "pomegranate-db/expo-plugin"
|
|
13
|
+
* ]
|
|
14
|
+
* }
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* Or with options:
|
|
18
|
+
*
|
|
19
|
+
* {
|
|
20
|
+
* "expo": {
|
|
21
|
+
* "plugins": [
|
|
22
|
+
* ["pomegranate-db/expo-plugin", {
|
|
23
|
+
* "enableFTS": true,
|
|
24
|
+
* "useSQLCipher": false
|
|
25
|
+
* }]
|
|
26
|
+
* ]
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* This plugin:
|
|
31
|
+
* - Ensures expo-sqlite is configured with FTS support (optional)
|
|
32
|
+
* - Adds iOS/Android build settings for SQLite performance
|
|
33
|
+
* - Configures ProGuard rules for Android release builds
|
|
34
|
+
*/
|
|
35
|
+
interface PomegranatePluginConfig {
|
|
36
|
+
/**
|
|
37
|
+
* Enable SQLite FTS3, FTS4 and FTS5 full-text search extensions.
|
|
38
|
+
* Required if you use PomegranateDB's search() functionality with
|
|
39
|
+
* the SQLiteAdapter + ExpoSQLiteDriver.
|
|
40
|
+
* @default true
|
|
41
|
+
*/
|
|
42
|
+
enableFTS?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Use SQLCipher for encrypted database support.
|
|
45
|
+
* When true, the database file is encrypted at rest.
|
|
46
|
+
* @default false
|
|
47
|
+
*/
|
|
48
|
+
useSQLCipher?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Custom SQLite compile flags.
|
|
51
|
+
* Passed directly to expo-sqlite's config plugin.
|
|
52
|
+
*/
|
|
53
|
+
customBuildFlags?: string;
|
|
54
|
+
}
|
|
55
|
+
type ExpoConfig = {
|
|
56
|
+
name: string;
|
|
57
|
+
slug: string;
|
|
58
|
+
plugins?: (string | [string, Record<string, unknown>])[];
|
|
59
|
+
[key: string]: unknown;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Expo config plugin for PomegranateDB.
|
|
63
|
+
*
|
|
64
|
+
* Automatically configures expo-sqlite with the right build flags
|
|
65
|
+
* for PomegranateDB features (FTS, encryption, etc.).
|
|
66
|
+
*/
|
|
67
|
+
declare function withPomegranateDB(config: ExpoConfig, pluginConfig?: PomegranatePluginConfig): ExpoConfig;
|
|
68
|
+
export default withPomegranateDB;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* PomegranateDB Expo Config Plugin.
|
|
4
|
+
*
|
|
5
|
+
* Provides first-class Expo support by configuring the native
|
|
6
|
+
* projects for optimal SQLite performance.
|
|
7
|
+
*
|
|
8
|
+
* Usage in app.json / app.config.js:
|
|
9
|
+
*
|
|
10
|
+
* {
|
|
11
|
+
* "expo": {
|
|
12
|
+
* "plugins": [
|
|
13
|
+
* "pomegranate-db/expo-plugin"
|
|
14
|
+
* ]
|
|
15
|
+
* }
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* Or with options:
|
|
19
|
+
*
|
|
20
|
+
* {
|
|
21
|
+
* "expo": {
|
|
22
|
+
* "plugins": [
|
|
23
|
+
* ["pomegranate-db/expo-plugin", {
|
|
24
|
+
* "enableFTS": true,
|
|
25
|
+
* "useSQLCipher": false
|
|
26
|
+
* }]
|
|
27
|
+
* ]
|
|
28
|
+
* }
|
|
29
|
+
* }
|
|
30
|
+
*
|
|
31
|
+
* This plugin:
|
|
32
|
+
* - Ensures expo-sqlite is configured with FTS support (optional)
|
|
33
|
+
* - Adds iOS/Android build settings for SQLite performance
|
|
34
|
+
* - Configures ProGuard rules for Android release builds
|
|
35
|
+
*/
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
/**
|
|
38
|
+
* Expo config plugin for PomegranateDB.
|
|
39
|
+
*
|
|
40
|
+
* Automatically configures expo-sqlite with the right build flags
|
|
41
|
+
* for PomegranateDB features (FTS, encryption, etc.).
|
|
42
|
+
*/
|
|
43
|
+
function withPomegranateDB(config, pluginConfig = {}) {
|
|
44
|
+
const { enableFTS = true, useSQLCipher = false, customBuildFlags, } = pluginConfig;
|
|
45
|
+
// Build the expo-sqlite plugin config
|
|
46
|
+
const expoSQLiteConfig = {
|
|
47
|
+
enableFTS,
|
|
48
|
+
useSQLCipher,
|
|
49
|
+
};
|
|
50
|
+
if (customBuildFlags) {
|
|
51
|
+
expoSQLiteConfig.ios = { customBuildFlags };
|
|
52
|
+
}
|
|
53
|
+
// Check if expo-sqlite plugin already exists
|
|
54
|
+
const plugins = config.plugins || [];
|
|
55
|
+
const existingIndex = plugins.findIndex((p) => {
|
|
56
|
+
if (typeof p === 'string')
|
|
57
|
+
return p === 'expo-sqlite';
|
|
58
|
+
if (Array.isArray(p))
|
|
59
|
+
return p[0] === 'expo-sqlite';
|
|
60
|
+
return false;
|
|
61
|
+
});
|
|
62
|
+
if (existingIndex >= 0) {
|
|
63
|
+
// Merge our config with existing expo-sqlite config
|
|
64
|
+
const existing = plugins[existingIndex];
|
|
65
|
+
if (typeof existing === 'string') {
|
|
66
|
+
plugins[existingIndex] = ['expo-sqlite', expoSQLiteConfig];
|
|
67
|
+
}
|
|
68
|
+
else if (Array.isArray(existing)) {
|
|
69
|
+
plugins[existingIndex] = [
|
|
70
|
+
'expo-sqlite',
|
|
71
|
+
{ ...(existing[1] || {}), ...expoSQLiteConfig },
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
// Add expo-sqlite plugin
|
|
77
|
+
plugins.push(['expo-sqlite', expoSQLiteConfig]);
|
|
78
|
+
}
|
|
79
|
+
config.plugins = plugins;
|
|
80
|
+
return config;
|
|
81
|
+
}
|
|
82
|
+
module.exports = withPomegranateDB;
|
|
83
|
+
exports.default = withPomegranateDB;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
apply plugin: 'com.android.library'
|
|
2
|
+
apply plugin: 'kotlin-android'
|
|
3
|
+
|
|
4
|
+
def DEFAULT_COMPILE_SDK_VERSION = 36
|
|
5
|
+
def DEFAULT_MIN_SDK_VERSION = 24
|
|
6
|
+
def DEFAULT_TARGET_SDK_VERSION = 36
|
|
7
|
+
def DEFAULT_NDK_VERSION = "27.1.12297006"
|
|
8
|
+
|
|
9
|
+
android {
|
|
10
|
+
compileSdkVersion rootProject.hasProperty('compileSdkVersion') ? rootProject.compileSdkVersion : DEFAULT_COMPILE_SDK_VERSION
|
|
11
|
+
ndkVersion rootProject.hasProperty('ndkVersion') ? rootProject.ndkVersion : DEFAULT_NDK_VERSION
|
|
12
|
+
|
|
13
|
+
namespace "com.pomegranate.jsi"
|
|
14
|
+
|
|
15
|
+
defaultConfig {
|
|
16
|
+
minSdkVersion rootProject.hasProperty('minSdkVersion') ? rootProject.minSdkVersion : DEFAULT_MIN_SDK_VERSION
|
|
17
|
+
targetSdkVersion rootProject.hasProperty('targetSdkVersion') ? rootProject.targetSdkVersion : DEFAULT_TARGET_SDK_VERSION
|
|
18
|
+
versionCode 1
|
|
19
|
+
versionName "1.0"
|
|
20
|
+
externalNativeBuild {
|
|
21
|
+
cmake {
|
|
22
|
+
// Use C++17 for structured bindings, string_view, etc.
|
|
23
|
+
cppFlags "-std=c++17"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
externalNativeBuild {
|
|
28
|
+
cmake {
|
|
29
|
+
path "src/main/cpp/CMakeLists.txt"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
compileOptions {
|
|
34
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
35
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
kotlinOptions {
|
|
39
|
+
jvmTarget = "17"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
dependencies {
|
|
44
|
+
implementation 'com.facebook.react:react-native:+'
|
|
45
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
PROJECT(pomegranate-jsi C CXX)
|
|
2
|
+
cmake_minimum_required(VERSION 3.13)
|
|
3
|
+
|
|
4
|
+
# ─── Path resolution ──────────────────────────────────────────────────────────
|
|
5
|
+
# This CMakeLists.txt lives at:
|
|
6
|
+
# native/android-jsi/src/main/cpp/CMakeLists.txt (dev / source repo)
|
|
7
|
+
# node_modules/pomegranate-db/native/android-jsi/src/main/cpp/ (installed)
|
|
8
|
+
#
|
|
9
|
+
# In dev: 5 levels up → repo root → node_modules/react-native
|
|
10
|
+
# In installed: 7 levels up → consumer app root → node_modules/react-native
|
|
11
|
+
|
|
12
|
+
set(SHARED_PATH ../../../../shared/)
|
|
13
|
+
set(SQLITE_PATH ../../../../shared/sqlite3/)
|
|
14
|
+
|
|
15
|
+
# Auto-detect react-native location (dev vs installed-in-node_modules)
|
|
16
|
+
foreach(candidate
|
|
17
|
+
"${CMAKE_CURRENT_SOURCE_DIR}/../../../../../node_modules/react-native"
|
|
18
|
+
"${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../node_modules/react-native"
|
|
19
|
+
)
|
|
20
|
+
if(EXISTS "${candidate}/ReactCommon/jsi/jsi/jsi.h")
|
|
21
|
+
set(REACT_NATIVE_ROOT "${candidate}")
|
|
22
|
+
break()
|
|
23
|
+
endif()
|
|
24
|
+
endforeach()
|
|
25
|
+
|
|
26
|
+
if(NOT REACT_NATIVE_ROOT)
|
|
27
|
+
message(FATAL_ERROR
|
|
28
|
+
"pomegranate-jsi: Cannot find react-native.\n"
|
|
29
|
+
"Looked at 5 and 7 parent directories from ${CMAKE_CURRENT_SOURCE_DIR}.\n"
|
|
30
|
+
"Ensure react-native is installed in your project's node_modules.")
|
|
31
|
+
endif()
|
|
32
|
+
|
|
33
|
+
message(STATUS "pomegranate-jsi: react-native found at ${REACT_NATIVE_ROOT}")
|
|
34
|
+
|
|
35
|
+
# ─── Header search paths ─────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
include_directories(
|
|
38
|
+
${SHARED_PATH}
|
|
39
|
+
${SQLITE_PATH}
|
|
40
|
+
${REACT_NATIVE_ROOT}/React
|
|
41
|
+
${REACT_NATIVE_ROOT}/React/Base
|
|
42
|
+
${REACT_NATIVE_ROOT}/ReactCommon
|
|
43
|
+
${REACT_NATIVE_ROOT}/ReactCommon/jsi
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# ─── Build configuration ─────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
set(CMAKE_CXX_FLAGS_DEBUG "-O2")
|
|
49
|
+
set(CMAKE_CXX_FLAGS_RELEASE "-O2")
|
|
50
|
+
|
|
51
|
+
# ─── Source files ─────────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
file(GLOB ANDROID_JSI_SRC_FILES ./*.cpp)
|
|
54
|
+
file(GLOB SHARED_SRC_FILES ${SHARED_PATH}/*.cpp)
|
|
55
|
+
|
|
56
|
+
add_library(pomegranate-jsi SHARED
|
|
57
|
+
# SQLite amalgamation
|
|
58
|
+
${SQLITE_PATH}/sqlite3.c
|
|
59
|
+
# Our shared C++ sources
|
|
60
|
+
${SHARED_SRC_FILES}
|
|
61
|
+
# Android-specific JNI sources
|
|
62
|
+
${ANDROID_JSI_SRC_FILES}
|
|
63
|
+
# JSI implementation (needed for linking)
|
|
64
|
+
${REACT_NATIVE_ROOT}/ReactCommon/jsi/jsi/jsi.cpp
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Enable Android 16kb native library alignment (required for modern Android)
|
|
68
|
+
target_link_options(pomegranate-jsi PRIVATE "-Wl,-z,max-page-size=16384")
|
|
69
|
+
|
|
70
|
+
target_link_libraries(pomegranate-jsi
|
|
71
|
+
android
|
|
72
|
+
log
|
|
73
|
+
)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PomegranateDB — Android platform implementation.
|
|
3
|
+
*
|
|
4
|
+
* Provides platform::resolveDatabasePath via JNI callback to Java,
|
|
5
|
+
* and platform::consoleLog/consoleError via Android log.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#include <android/log.h>
|
|
9
|
+
#include <cassert>
|
|
10
|
+
#include <mutex>
|
|
11
|
+
#include <sqlite3.h>
|
|
12
|
+
|
|
13
|
+
#include "Database.h"
|
|
14
|
+
#include "DatabasePlatformAndroid.h"
|
|
15
|
+
|
|
16
|
+
#define LOG_TAG "pomegranate.jsi"
|
|
17
|
+
|
|
18
|
+
namespace pomegranate {
|
|
19
|
+
namespace platform {
|
|
20
|
+
|
|
21
|
+
// ─── Logging ─────────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
void consoleLog(const std::string &message) {
|
|
24
|
+
__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "%s\n", message.c_str());
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
void consoleError(const std::string &message) {
|
|
28
|
+
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "%s\n", message.c_str());
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ─── SQLite initialization ───────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
static void sqliteLogCallback(void *, int err, const char *message) {
|
|
34
|
+
int errType = err & 255;
|
|
35
|
+
if (errType == SQLITE_WARNING) {
|
|
36
|
+
__android_log_print(ANDROID_LOG_WARN, LOG_TAG, "sqlite (%d) %s\n", err, message);
|
|
37
|
+
} else if (errType == 0 || errType == SQLITE_CONSTRAINT || errType == SQLITE_SCHEMA || errType == SQLITE_NOTICE) {
|
|
38
|
+
// verbose, skip
|
|
39
|
+
} else {
|
|
40
|
+
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "sqlite (%d) %s\n", err, message);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
std::once_flag sqliteInitFlag;
|
|
45
|
+
|
|
46
|
+
static void initializeSqlite() {
|
|
47
|
+
std::call_once(sqliteInitFlag, []() {
|
|
48
|
+
sqlite3_config(SQLITE_CONFIG_LOG, &sqliteLogCallback, nullptr);
|
|
49
|
+
sqlite3_soft_heap_limit(8 * 1024 * 1024);
|
|
50
|
+
sqlite3_initialize();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ─── JNI path resolution ────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
static JavaVM *jvm = nullptr;
|
|
57
|
+
|
|
58
|
+
void configureJNI(JNIEnv *env) {
|
|
59
|
+
assert(env);
|
|
60
|
+
if (env->GetJavaVM(&jvm) != JNI_OK) {
|
|
61
|
+
consoleError("PomegranateDB: cannot get JavaVM");
|
|
62
|
+
std::abort();
|
|
63
|
+
}
|
|
64
|
+
assert(jvm);
|
|
65
|
+
initializeSqlite();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
std::string resolveDatabasePath(const std::string &dbName) {
|
|
69
|
+
JNIEnv *env;
|
|
70
|
+
assert(jvm);
|
|
71
|
+
if (jvm->AttachCurrentThread(&env, nullptr) != JNI_OK) {
|
|
72
|
+
throw std::runtime_error("PomegranateDB: JVM thread attach failed");
|
|
73
|
+
}
|
|
74
|
+
assert(env);
|
|
75
|
+
|
|
76
|
+
jclass clazz = env->FindClass("com/pomegranate/jsi/JSIInstaller");
|
|
77
|
+
if (clazz == nullptr) {
|
|
78
|
+
throw std::runtime_error("PomegranateDB: missing JSIInstaller class");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
jmethodID mid = env->GetStaticMethodID(clazz, "_resolveDatabasePath", "(Ljava/lang/String;)Ljava/lang/String;");
|
|
82
|
+
if (mid == nullptr) {
|
|
83
|
+
throw std::runtime_error("PomegranateDB: missing _resolveDatabasePath method");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
jobject jniPath = env->NewStringUTF(dbName.c_str());
|
|
87
|
+
if (jniPath == nullptr) {
|
|
88
|
+
throw std::runtime_error("PomegranateDB: could not construct Java string");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
jstring jniResolvedPath = (jstring)env->CallStaticObjectMethod(clazz, mid, jniPath);
|
|
92
|
+
if (env->ExceptionCheck()) {
|
|
93
|
+
throw std::runtime_error("PomegranateDB: exception resolving database path");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const char *cResolvedPath = env->GetStringUTFChars(jniResolvedPath, 0);
|
|
97
|
+
if (cResolvedPath == nullptr) {
|
|
98
|
+
throw std::runtime_error("PomegranateDB: failed to get resolved path string");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
std::string resolvedPath(cResolvedPath);
|
|
102
|
+
env->ReleaseStringUTFChars(jniResolvedPath, cResolvedPath);
|
|
103
|
+
return resolvedPath;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
} // namespace platform
|
|
107
|
+
} // namespace pomegranate
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PomegranateDB — Android platform declarations.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#pragma once
|
|
6
|
+
|
|
7
|
+
#include <jni.h>
|
|
8
|
+
|
|
9
|
+
namespace pomegranate {
|
|
10
|
+
namespace platform {
|
|
11
|
+
|
|
12
|
+
/** Must be called once from JNI before any database operations. */
|
|
13
|
+
void configureJNI(JNIEnv *env);
|
|
14
|
+
|
|
15
|
+
} // namespace platform
|
|
16
|
+
} // namespace pomegranate
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PomegranateDB — JNI bridge.
|
|
3
|
+
*
|
|
4
|
+
* Called from Java to install the JSI binding into the JS runtime.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
#include <jni.h>
|
|
8
|
+
#include <jsi/jsi.h>
|
|
9
|
+
#include <cassert>
|
|
10
|
+
|
|
11
|
+
#include "Database.h"
|
|
12
|
+
#include "DatabasePlatformAndroid.h"
|
|
13
|
+
|
|
14
|
+
using namespace facebook;
|
|
15
|
+
|
|
16
|
+
extern "C" JNIEXPORT void JNICALL Java_com_pomegranate_jsi_JSIInstaller_installBinding(JNIEnv *env, jobject thiz,
|
|
17
|
+
jlong runtimePtr) {
|
|
18
|
+
jsi::Runtime *runtime = reinterpret_cast<jsi::Runtime *>(runtimePtr);
|
|
19
|
+
assert(runtime != nullptr);
|
|
20
|
+
|
|
21
|
+
pomegranate::platform::configureJNI(env);
|
|
22
|
+
pomegranate::Database::install(*runtime);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
extern "C" JNIEXPORT void JNICALL Java_com_pomegranate_jsi_JSIInstaller_destroy(JNIEnv *env, jclass clazz) {
|
|
26
|
+
// Cleanup if needed in future
|
|
27
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
package com.pomegranate.jsi
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* JNI bridge between Kotlin and C++ JSI binding.
|
|
7
|
+
* Loads the native library and installs the JSI global function.
|
|
8
|
+
*/
|
|
9
|
+
internal object JSIInstaller {
|
|
10
|
+
private var context: Context? = null
|
|
11
|
+
|
|
12
|
+
init {
|
|
13
|
+
System.loadLibrary("pomegranate-jsi")
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
fun install(
|
|
17
|
+
context: Context,
|
|
18
|
+
javaScriptContextHolder: Long,
|
|
19
|
+
) {
|
|
20
|
+
this.context = context
|
|
21
|
+
installBinding(javaScriptContextHolder)
|
|
22
|
+
// Call _resolveDatabasePath to prevent ProGuard/R8 from stripping it
|
|
23
|
+
_resolveDatabasePath("")
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Called from C++ to resolve a database name to an absolute path.
|
|
28
|
+
*/
|
|
29
|
+
@Suppress("FunctionName") // JNI naming convention
|
|
30
|
+
@JvmStatic
|
|
31
|
+
fun _resolveDatabasePath(dbName: String): String {
|
|
32
|
+
// On some systems there is a lock on /databases folder
|
|
33
|
+
return context!!
|
|
34
|
+
.getDatabasePath("$dbName.db")
|
|
35
|
+
.path
|
|
36
|
+
.replace("/databases", "")
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private external fun installBinding(javaScriptContextHolder: Long)
|
|
40
|
+
|
|
41
|
+
@JvmStatic
|
|
42
|
+
external fun destroy()
|
|
43
|
+
}
|