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.
Files changed (110) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +38 -0
  3. package/PomegranateDB.podspec +67 -0
  4. package/README.md +122 -0
  5. package/dist/adapters/expo-sqlite/ExpoSQLiteDriver.d.ts +34 -0
  6. package/dist/adapters/expo-sqlite/ExpoSQLiteDriver.js +155 -0
  7. package/dist/adapters/expo-sqlite/index.d.ts +2 -0
  8. package/dist/adapters/expo-sqlite/index.js +6 -0
  9. package/dist/adapters/index.d.ts +7 -0
  10. package/dist/adapters/index.js +13 -0
  11. package/dist/adapters/loki/LokiAdapter.d.ts +100 -0
  12. package/dist/adapters/loki/LokiAdapter.js +144 -0
  13. package/dist/adapters/loki/index.d.ts +6 -0
  14. package/dist/adapters/loki/index.js +12 -0
  15. package/dist/adapters/loki/worker/LokiDispatcher.d.ts +21 -0
  16. package/dist/adapters/loki/worker/LokiDispatcher.js +63 -0
  17. package/dist/adapters/loki/worker/LokiExecutor.d.ts +96 -0
  18. package/dist/adapters/loki/worker/LokiExecutor.js +462 -0
  19. package/dist/adapters/loki/worker/SynchronousWorker.d.ts +22 -0
  20. package/dist/adapters/loki/worker/SynchronousWorker.js +76 -0
  21. package/dist/adapters/loki/worker/loki.worker.d.ts +14 -0
  22. package/dist/adapters/loki/worker/loki.worker.js +112 -0
  23. package/dist/adapters/loki/worker/types.d.ts +44 -0
  24. package/dist/adapters/loki/worker/types.js +11 -0
  25. package/dist/adapters/native-sqlite/NativeSQLiteDriver.d.ts +55 -0
  26. package/dist/adapters/native-sqlite/NativeSQLiteDriver.js +145 -0
  27. package/dist/adapters/native-sqlite/index.d.ts +2 -0
  28. package/dist/adapters/native-sqlite/index.js +6 -0
  29. package/dist/adapters/op-sqlite/OpSQLiteDriver.d.ts +49 -0
  30. package/dist/adapters/op-sqlite/OpSQLiteDriver.js +140 -0
  31. package/dist/adapters/op-sqlite/index.d.ts +2 -0
  32. package/dist/adapters/op-sqlite/index.js +6 -0
  33. package/dist/adapters/sqlite/SQLiteAdapter.d.ts +70 -0
  34. package/dist/adapters/sqlite/SQLiteAdapter.js +264 -0
  35. package/dist/adapters/sqlite/index.d.ts +2 -0
  36. package/dist/adapters/sqlite/index.js +6 -0
  37. package/dist/adapters/sqlite/sql.d.ts +35 -0
  38. package/dist/adapters/sqlite/sql.js +258 -0
  39. package/dist/adapters/types.d.ts +93 -0
  40. package/dist/adapters/types.js +9 -0
  41. package/dist/collection/Collection.d.ts +103 -0
  42. package/dist/collection/Collection.js +245 -0
  43. package/dist/collection/index.d.ts +2 -0
  44. package/dist/collection/index.js +6 -0
  45. package/dist/database/Database.d.ts +128 -0
  46. package/dist/database/Database.js +245 -0
  47. package/dist/database/index.d.ts +2 -0
  48. package/dist/database/index.js +6 -0
  49. package/dist/encryption/index.d.ts +62 -0
  50. package/dist/encryption/index.js +276 -0
  51. package/dist/encryption/nodeCrypto.d.ts +18 -0
  52. package/dist/encryption/nodeCrypto.js +25 -0
  53. package/dist/encryption/nodeCrypto.native.d.ts +13 -0
  54. package/dist/encryption/nodeCrypto.native.js +26 -0
  55. package/dist/expo.d.ts +12 -0
  56. package/dist/expo.js +32 -0
  57. package/dist/hooks/index.d.ts +115 -0
  58. package/dist/hooks/index.js +285 -0
  59. package/dist/index.d.ts +29 -0
  60. package/dist/index.js +57 -0
  61. package/dist/model/Model.d.ts +92 -0
  62. package/dist/model/Model.js +251 -0
  63. package/dist/model/index.d.ts +2 -0
  64. package/dist/model/index.js +7 -0
  65. package/dist/observable/Subject.d.ts +60 -0
  66. package/dist/observable/Subject.js +132 -0
  67. package/dist/observable/index.d.ts +2 -0
  68. package/dist/observable/index.js +10 -0
  69. package/dist/query/QueryBuilder.d.ts +51 -0
  70. package/dist/query/QueryBuilder.js +165 -0
  71. package/dist/query/index.d.ts +2 -0
  72. package/dist/query/index.js +7 -0
  73. package/dist/query/types.d.ts +60 -0
  74. package/dist/query/types.js +9 -0
  75. package/dist/schema/builder.d.ts +68 -0
  76. package/dist/schema/builder.js +168 -0
  77. package/dist/schema/index.d.ts +2 -0
  78. package/dist/schema/index.js +7 -0
  79. package/dist/schema/types.d.ts +108 -0
  80. package/dist/schema/types.js +9 -0
  81. package/dist/sync/index.d.ts +2 -0
  82. package/dist/sync/index.js +6 -0
  83. package/dist/sync/sync.d.ts +15 -0
  84. package/dist/sync/sync.js +182 -0
  85. package/dist/sync/types.d.ts +41 -0
  86. package/dist/sync/types.js +6 -0
  87. package/dist/utils/index.d.ts +45 -0
  88. package/dist/utils/index.js +99 -0
  89. package/expo-plugin/index.d.ts +68 -0
  90. package/expo-plugin/index.js +83 -0
  91. package/native/android-jsi/build.gradle +45 -0
  92. package/native/android-jsi/src/main/AndroidManifest.xml +2 -0
  93. package/native/android-jsi/src/main/cpp/CMakeLists.txt +73 -0
  94. package/native/android-jsi/src/main/cpp/DatabasePlatformAndroid.cpp +107 -0
  95. package/native/android-jsi/src/main/cpp/DatabasePlatformAndroid.h +16 -0
  96. package/native/android-jsi/src/main/cpp/JSIInstaller.cpp +27 -0
  97. package/native/android-jsi/src/main/java/com/pomegranate/jsi/JSIInstaller.kt +43 -0
  98. package/native/android-jsi/src/main/java/com/pomegranate/jsi/PomegranateJSIModule.kt +39 -0
  99. package/native/android-jsi/src/main/java/com/pomegranate/jsi/PomegranateJSIPackage.kt +17 -0
  100. package/native/ios/DatabasePlatformIOS.mm +83 -0
  101. package/native/ios/PomegranateJSI.h +15 -0
  102. package/native/ios/PomegranateJSI.mm +59 -0
  103. package/native/shared/Database.cpp +283 -0
  104. package/native/shared/Database.h +84 -0
  105. package/native/shared/Sqlite.cpp +61 -0
  106. package/native/shared/Sqlite.h +67 -0
  107. package/native/shared/sqlite3/sqlite3.c +260493 -0
  108. package/native/shared/sqlite3/sqlite3.h +13583 -0
  109. package/package.json +127 -0
  110. 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,6 @@
1
+ "use strict";
2
+ /**
3
+ * Sync types — compatible with the Watermelon backend sync protocol.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ //# sourceMappingURL=types.js.map
@@ -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,2 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <manifest />
@@ -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
+ }