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
package/dist/expo.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PomegranateDB — Expo entry point.
|
|
3
|
+
*
|
|
4
|
+
* Convenience module that re-exports everything from the main
|
|
5
|
+
* package plus the Expo-specific driver.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { Database, SQLiteAdapter, createExpoSQLiteDriver } from 'pomegranate-db/expo';
|
|
9
|
+
*/
|
|
10
|
+
export * from './index';
|
|
11
|
+
export { createExpoSQLiteDriver } from './adapters/expo-sqlite';
|
|
12
|
+
export type { ExpoSQLiteDriverConfig } from './adapters/expo-sqlite';
|
package/dist/expo.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* PomegranateDB — Expo entry point.
|
|
4
|
+
*
|
|
5
|
+
* Convenience module that re-exports everything from the main
|
|
6
|
+
* package plus the Expo-specific driver.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* import { Database, SQLiteAdapter, createExpoSQLiteDriver } from 'pomegranate-db/expo';
|
|
10
|
+
*/
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
23
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.createExpoSQLiteDriver = void 0;
|
|
27
|
+
// Re-export everything from main package
|
|
28
|
+
__exportStar(require("./index"), exports);
|
|
29
|
+
// Export Expo-specific driver
|
|
30
|
+
var expo_sqlite_1 = require("./adapters/expo-sqlite");
|
|
31
|
+
Object.defineProperty(exports, "createExpoSQLiteDriver", { enumerable: true, get: function () { return expo_sqlite_1.createExpoSQLiteDriver; } });
|
|
32
|
+
//# sourceMappingURL=expo.js.map
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React hooks for PomegranateDB.
|
|
3
|
+
*
|
|
4
|
+
* Provides reactive hooks that subscribe to database observables
|
|
5
|
+
* and re-render components when data changes.
|
|
6
|
+
*/
|
|
7
|
+
import type { Collection } from '../collection/Collection';
|
|
8
|
+
import type { Model } from '../model/Model';
|
|
9
|
+
import type { QueryBuilder } from '../query/QueryBuilder';
|
|
10
|
+
import type { Observable } from '../observable/Subject';
|
|
11
|
+
/**
|
|
12
|
+
* Subscribe to any Observable and return its latest value.
|
|
13
|
+
*/
|
|
14
|
+
export declare function useObservable<T>(observable: Observable<T> | null | undefined, initialValue: T): T;
|
|
15
|
+
/**
|
|
16
|
+
* Execute a query and subscribe to live updates.
|
|
17
|
+
* Re-runs the query whenever the collection changes.
|
|
18
|
+
*/
|
|
19
|
+
export declare function useLiveQuery<M extends Model>(collection: Collection<M> | null | undefined, buildQuery?: (qb: QueryBuilder) => void, deps?: unknown[]): {
|
|
20
|
+
results: M[];
|
|
21
|
+
isLoading: boolean;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Observe a single record by ID.
|
|
25
|
+
* Returns null if not found, undefined while loading.
|
|
26
|
+
*/
|
|
27
|
+
export declare function useById<M extends Model>(collection: Collection<M> | null | undefined, id: string | null | undefined): {
|
|
28
|
+
record: M | null | undefined;
|
|
29
|
+
isLoading: boolean;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Observe a specific field on a record.
|
|
33
|
+
* Only re-renders when that field changes.
|
|
34
|
+
*/
|
|
35
|
+
export declare function useField<M extends Model>(record: M | null | undefined, fieldName: string): {
|
|
36
|
+
value: unknown;
|
|
37
|
+
isLoading: boolean;
|
|
38
|
+
};
|
|
39
|
+
export interface UseSearchOptions {
|
|
40
|
+
term: string;
|
|
41
|
+
fields: string[];
|
|
42
|
+
limit?: number;
|
|
43
|
+
offset?: number;
|
|
44
|
+
orderBy?: Record<string, 'asc' | 'desc'>;
|
|
45
|
+
extend?: (qb: QueryBuilder) => void;
|
|
46
|
+
}
|
|
47
|
+
export interface UseSearchResult<M extends Model> {
|
|
48
|
+
results: M[];
|
|
49
|
+
total: number;
|
|
50
|
+
isLoading: boolean;
|
|
51
|
+
hasMore: boolean;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Full-text search with pagination and live results.
|
|
55
|
+
*/
|
|
56
|
+
export declare function useSearch<M extends Model>(collection: Collection<M> | null | undefined, options: UseSearchOptions, deps?: unknown[]): UseSearchResult<M>;
|
|
57
|
+
/**
|
|
58
|
+
* Observe the count of records matching a query.
|
|
59
|
+
*/
|
|
60
|
+
export declare function useCount<M extends Model>(collection: Collection<M> | null | undefined, buildQuery?: (qb: QueryBuilder) => void, deps?: unknown[]): {
|
|
61
|
+
count: number;
|
|
62
|
+
isLoading: boolean;
|
|
63
|
+
};
|
|
64
|
+
import { type ReactNode } from 'react';
|
|
65
|
+
import { Database } from '../database/Database';
|
|
66
|
+
import type { DatabaseConfig } from '../database/Database';
|
|
67
|
+
/**
|
|
68
|
+
* Provide a Database instance to the React tree.
|
|
69
|
+
*/
|
|
70
|
+
export declare const DatabaseProvider: import("react").Provider<Database | null>;
|
|
71
|
+
/**
|
|
72
|
+
* Get the Database instance from context.
|
|
73
|
+
*/
|
|
74
|
+
export declare function useDatabase(): Database;
|
|
75
|
+
/**
|
|
76
|
+
* Get a collection by model class from context.
|
|
77
|
+
*/
|
|
78
|
+
export declare function useCollection<M extends Model>(modelClass: {
|
|
79
|
+
schema: {
|
|
80
|
+
table: string;
|
|
81
|
+
};
|
|
82
|
+
}): Collection<M>;
|
|
83
|
+
/**
|
|
84
|
+
* Props for DatabaseSuspenseProvider.
|
|
85
|
+
*/
|
|
86
|
+
export interface DatabaseSuspenseProviderProps extends DatabaseConfig {
|
|
87
|
+
children: ReactNode;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* A Suspense-compatible database provider.
|
|
91
|
+
*
|
|
92
|
+
* Creates and initializes a Database, suspending the component tree until
|
|
93
|
+
* the database is ready. Wrap with `<Suspense fallback={...}>` to show
|
|
94
|
+
* a loading state.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```tsx
|
|
98
|
+
* import { Suspense } from 'react';
|
|
99
|
+
* import { DatabaseSuspenseProvider, useLiveQuery } from 'pomegranate-db';
|
|
100
|
+
*
|
|
101
|
+
* function App() {
|
|
102
|
+
* return (
|
|
103
|
+
* <Suspense fallback={<Text>Preparing database...</Text>}>
|
|
104
|
+
* <DatabaseSuspenseProvider
|
|
105
|
+
* adapter={new LokiAdapter({ databaseName: 'myapp' })}
|
|
106
|
+
* models={[Post, Comment]}
|
|
107
|
+
* >
|
|
108
|
+
* <MyApp />
|
|
109
|
+
* </DatabaseSuspenseProvider>
|
|
110
|
+
* </Suspense>
|
|
111
|
+
* );
|
|
112
|
+
* }
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export declare function DatabaseSuspenseProvider({ children, ...config }: DatabaseSuspenseProviderProps): ReactNode;
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* React hooks for PomegranateDB.
|
|
4
|
+
*
|
|
5
|
+
* Provides reactive hooks that subscribe to database observables
|
|
6
|
+
* and re-render components when data changes.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.DatabaseProvider = void 0;
|
|
10
|
+
exports.useObservable = useObservable;
|
|
11
|
+
exports.useLiveQuery = useLiveQuery;
|
|
12
|
+
exports.useById = useById;
|
|
13
|
+
exports.useField = useField;
|
|
14
|
+
exports.useSearch = useSearch;
|
|
15
|
+
exports.useCount = useCount;
|
|
16
|
+
exports.useDatabase = useDatabase;
|
|
17
|
+
exports.useCollection = useCollection;
|
|
18
|
+
exports.DatabaseSuspenseProvider = DatabaseSuspenseProvider;
|
|
19
|
+
const react_1 = require("react");
|
|
20
|
+
// ─── useObservable ─────────────────────────────────────────────────────────
|
|
21
|
+
/**
|
|
22
|
+
* Subscribe to any Observable and return its latest value.
|
|
23
|
+
*/
|
|
24
|
+
function useObservable(observable, initialValue) {
|
|
25
|
+
const [value, setValue] = (0, react_1.useState)(initialValue);
|
|
26
|
+
(0, react_1.useEffect)(() => {
|
|
27
|
+
if (!observable)
|
|
28
|
+
return;
|
|
29
|
+
const unsub = observable.subscribe((v) => {
|
|
30
|
+
setValue(v);
|
|
31
|
+
});
|
|
32
|
+
return unsub;
|
|
33
|
+
}, [observable]);
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
// ─── useLiveQuery ──────────────────────────────────────────────────────────
|
|
37
|
+
/**
|
|
38
|
+
* Execute a query and subscribe to live updates.
|
|
39
|
+
* Re-runs the query whenever the collection changes.
|
|
40
|
+
*/
|
|
41
|
+
function useLiveQuery(collection, buildQuery, deps = []) {
|
|
42
|
+
const [results, setResults] = (0, react_1.useState)([]);
|
|
43
|
+
const [isLoading, setIsLoading] = (0, react_1.useState)(true);
|
|
44
|
+
(0, react_1.useEffect)(() => {
|
|
45
|
+
if (!collection)
|
|
46
|
+
return;
|
|
47
|
+
setIsLoading(true);
|
|
48
|
+
const qb = buildQuery ? collection.query(buildQuery) : collection.query();
|
|
49
|
+
const observable = collection.observeQuery(qb);
|
|
50
|
+
const unsub = observable.subscribe((records) => {
|
|
51
|
+
setResults(records);
|
|
52
|
+
setIsLoading(false);
|
|
53
|
+
});
|
|
54
|
+
return unsub;
|
|
55
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
56
|
+
}, [collection, ...deps]);
|
|
57
|
+
return { results, isLoading };
|
|
58
|
+
}
|
|
59
|
+
// ─── useById ──────────────────────────────────────────────────────────────
|
|
60
|
+
/**
|
|
61
|
+
* Observe a single record by ID.
|
|
62
|
+
* Returns null if not found, undefined while loading.
|
|
63
|
+
*/
|
|
64
|
+
function useById(collection, id) {
|
|
65
|
+
const [record, setRecord] = (0, react_1.useState)(undefined);
|
|
66
|
+
const [isLoading, setIsLoading] = (0, react_1.useState)(true);
|
|
67
|
+
(0, react_1.useEffect)(() => {
|
|
68
|
+
if (!collection || !id) {
|
|
69
|
+
setRecord(null);
|
|
70
|
+
setIsLoading(false);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
setIsLoading(true);
|
|
74
|
+
const observable = collection.observeById(id);
|
|
75
|
+
const unsub = observable.subscribe((r) => {
|
|
76
|
+
setRecord(r);
|
|
77
|
+
setIsLoading(false);
|
|
78
|
+
});
|
|
79
|
+
return unsub;
|
|
80
|
+
}, [collection, id]);
|
|
81
|
+
return { record, isLoading };
|
|
82
|
+
}
|
|
83
|
+
// ─── useField ─────────────────────────────────────────────────────────────
|
|
84
|
+
/**
|
|
85
|
+
* Observe a specific field on a record.
|
|
86
|
+
* Only re-renders when that field changes.
|
|
87
|
+
*/
|
|
88
|
+
function useField(record, fieldName) {
|
|
89
|
+
const [value, setValue] = (0, react_1.useState)(undefined);
|
|
90
|
+
const [isLoading, setIsLoading] = (0, react_1.useState)(true);
|
|
91
|
+
(0, react_1.useEffect)(() => {
|
|
92
|
+
if (!record) {
|
|
93
|
+
setValue(undefined);
|
|
94
|
+
setIsLoading(false);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// Get initial value
|
|
98
|
+
try {
|
|
99
|
+
setValue(record.getField(fieldName));
|
|
100
|
+
setIsLoading(false);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
setIsLoading(false);
|
|
104
|
+
}
|
|
105
|
+
const observable = record.observeField(fieldName);
|
|
106
|
+
const unsub = observable.subscribe((v) => {
|
|
107
|
+
setValue(v);
|
|
108
|
+
setIsLoading(false);
|
|
109
|
+
});
|
|
110
|
+
return unsub;
|
|
111
|
+
}, [record, fieldName]);
|
|
112
|
+
return { value, isLoading };
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Full-text search with pagination and live results.
|
|
116
|
+
*/
|
|
117
|
+
function useSearch(collection, options, deps = []) {
|
|
118
|
+
const [results, setResults] = (0, react_1.useState)([]);
|
|
119
|
+
const [total, setTotal] = (0, react_1.useState)(0);
|
|
120
|
+
const [isLoading, setIsLoading] = (0, react_1.useState)(true);
|
|
121
|
+
const limit = options.limit ?? 50;
|
|
122
|
+
const offset = options.offset ?? 0;
|
|
123
|
+
(0, react_1.useEffect)(() => {
|
|
124
|
+
if (!collection || !options.term) {
|
|
125
|
+
setResults([]);
|
|
126
|
+
setTotal(0);
|
|
127
|
+
setIsLoading(false);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
setIsLoading(true);
|
|
131
|
+
let cancelled = false;
|
|
132
|
+
const doSearch = async () => {
|
|
133
|
+
try {
|
|
134
|
+
const result = await collection.search({
|
|
135
|
+
term: options.term,
|
|
136
|
+
fields: options.fields,
|
|
137
|
+
limit,
|
|
138
|
+
offset,
|
|
139
|
+
orderBy: options.orderBy,
|
|
140
|
+
extend: options.extend,
|
|
141
|
+
});
|
|
142
|
+
if (!cancelled) {
|
|
143
|
+
setResults(result.records);
|
|
144
|
+
setTotal(result.total);
|
|
145
|
+
setIsLoading(false);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
if (!cancelled) {
|
|
150
|
+
setIsLoading(false);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
doSearch();
|
|
155
|
+
// Re-run search when collection changes
|
|
156
|
+
const unsub = collection.changes$.subscribe(() => {
|
|
157
|
+
doSearch();
|
|
158
|
+
});
|
|
159
|
+
return () => {
|
|
160
|
+
cancelled = true;
|
|
161
|
+
unsub();
|
|
162
|
+
};
|
|
163
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
164
|
+
}, [collection, options.term, options.fields.join(','), limit, offset, ...deps]);
|
|
165
|
+
const hasMore = offset + limit < total;
|
|
166
|
+
return { results, total, isLoading, hasMore };
|
|
167
|
+
}
|
|
168
|
+
// ─── useCount ─────────────────────────────────────────────────────────────
|
|
169
|
+
/**
|
|
170
|
+
* Observe the count of records matching a query.
|
|
171
|
+
*/
|
|
172
|
+
function useCount(collection, buildQuery, deps = []) {
|
|
173
|
+
const [count, setCount] = (0, react_1.useState)(0);
|
|
174
|
+
const [isLoading, setIsLoading] = (0, react_1.useState)(true);
|
|
175
|
+
(0, react_1.useEffect)(() => {
|
|
176
|
+
if (!collection)
|
|
177
|
+
return;
|
|
178
|
+
setIsLoading(true);
|
|
179
|
+
const qb = buildQuery ? collection.query(buildQuery) : collection.query();
|
|
180
|
+
const observable = collection.observeCount(qb);
|
|
181
|
+
const unsub = observable.subscribe((c) => {
|
|
182
|
+
setCount(c);
|
|
183
|
+
setIsLoading(false);
|
|
184
|
+
});
|
|
185
|
+
return unsub;
|
|
186
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
187
|
+
}, [collection, ...deps]);
|
|
188
|
+
return { count, isLoading };
|
|
189
|
+
}
|
|
190
|
+
// ─── DatabaseProvider ──────────────────────────────────────────────────────
|
|
191
|
+
const react_2 = require("react");
|
|
192
|
+
const Database_1 = require("../database/Database");
|
|
193
|
+
const DatabaseContext = (0, react_2.createContext)(null);
|
|
194
|
+
/**
|
|
195
|
+
* Provide a Database instance to the React tree.
|
|
196
|
+
*/
|
|
197
|
+
exports.DatabaseProvider = DatabaseContext.Provider;
|
|
198
|
+
/**
|
|
199
|
+
* Get the Database instance from context.
|
|
200
|
+
*/
|
|
201
|
+
function useDatabase() {
|
|
202
|
+
const db = (0, react_2.useContext)(DatabaseContext);
|
|
203
|
+
if (!db) {
|
|
204
|
+
throw new Error('useDatabase() must be used within a <DatabaseProvider>. ' +
|
|
205
|
+
'Wrap your app with <DatabaseProvider value={db}>.');
|
|
206
|
+
}
|
|
207
|
+
return db;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Get a collection by model class from context.
|
|
211
|
+
*/
|
|
212
|
+
function useCollection(modelClass) {
|
|
213
|
+
const db = useDatabase();
|
|
214
|
+
return db.collection(modelClass.schema.table);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Create a Suspense-compatible resource that initializes a Database.
|
|
218
|
+
* Follows the React Suspense "throw a promise" contract:
|
|
219
|
+
* - First call: throws a promise (React suspends)
|
|
220
|
+
* - After resolved: returns the Database
|
|
221
|
+
* - On error: throws the error (React error boundary catches it)
|
|
222
|
+
*/
|
|
223
|
+
function createDatabaseResource(config) {
|
|
224
|
+
const db = new Database_1.Database(config);
|
|
225
|
+
let status = {
|
|
226
|
+
state: 'pending',
|
|
227
|
+
promise: db.initialize().then(() => {
|
|
228
|
+
status = { state: 'resolved', value: db };
|
|
229
|
+
}, (error) => {
|
|
230
|
+
status = { state: 'rejected', error: error };
|
|
231
|
+
}),
|
|
232
|
+
};
|
|
233
|
+
return {
|
|
234
|
+
read() {
|
|
235
|
+
switch (status.state) {
|
|
236
|
+
case 'pending':
|
|
237
|
+
throw status.promise;
|
|
238
|
+
case 'rejected':
|
|
239
|
+
throw status.error;
|
|
240
|
+
case 'resolved':
|
|
241
|
+
return status.value;
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
// Cache to avoid re-creating the resource on re-render
|
|
247
|
+
const resourceCache = new WeakMap();
|
|
248
|
+
/**
|
|
249
|
+
* A Suspense-compatible database provider.
|
|
250
|
+
*
|
|
251
|
+
* Creates and initializes a Database, suspending the component tree until
|
|
252
|
+
* the database is ready. Wrap with `<Suspense fallback={...}>` to show
|
|
253
|
+
* a loading state.
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* ```tsx
|
|
257
|
+
* import { Suspense } from 'react';
|
|
258
|
+
* import { DatabaseSuspenseProvider, useLiveQuery } from 'pomegranate-db';
|
|
259
|
+
*
|
|
260
|
+
* function App() {
|
|
261
|
+
* return (
|
|
262
|
+
* <Suspense fallback={<Text>Preparing database...</Text>}>
|
|
263
|
+
* <DatabaseSuspenseProvider
|
|
264
|
+
* adapter={new LokiAdapter({ databaseName: 'myapp' })}
|
|
265
|
+
* models={[Post, Comment]}
|
|
266
|
+
* >
|
|
267
|
+
* <MyApp />
|
|
268
|
+
* </DatabaseSuspenseProvider>
|
|
269
|
+
* </Suspense>
|
|
270
|
+
* );
|
|
271
|
+
* }
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
function DatabaseSuspenseProvider({ children, ...config }) {
|
|
275
|
+
// Use the adapter as cache key — stable across re-renders
|
|
276
|
+
const cacheKey = config.adapter;
|
|
277
|
+
let resource = resourceCache.get(cacheKey);
|
|
278
|
+
if (!resource) {
|
|
279
|
+
resource = createDatabaseResource(config);
|
|
280
|
+
resourceCache.set(cacheKey, resource);
|
|
281
|
+
}
|
|
282
|
+
const db = resource.read(); // Suspends if not ready
|
|
283
|
+
return (0, react_2.createElement)(exports.DatabaseProvider, { value: db }, children);
|
|
284
|
+
}
|
|
285
|
+
//# sourceMappingURL=index.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PomegranateDB — main entry point.
|
|
3
|
+
*
|
|
4
|
+
* Reactive offline-first database with sync support.
|
|
5
|
+
*/
|
|
6
|
+
export { m } from './schema';
|
|
7
|
+
export type { ColumnType, ColumnDescriptor, TextColumn, NumberColumn, BooleanColumn, DateColumn, BelongsToDescriptor, HasManyDescriptor, RelationDescriptor, FieldDescriptor, SchemaFields, ModelSchema, ResolvedColumn, ResolvedRelation, DatabaseSchema, TableSchema, SyncStatus, RawRecord, InferCreatePatch, InferUpdatePatch, InferRecord, } from './schema';
|
|
8
|
+
export { Model } from './model';
|
|
9
|
+
export type { ModelStatic } from './model';
|
|
10
|
+
export { Collection } from './collection';
|
|
11
|
+
export type { CollectionChangeType, CollectionChange } from './collection';
|
|
12
|
+
export { Database } from './database';
|
|
13
|
+
export type { DatabaseConfig, DatabaseEvent } from './database';
|
|
14
|
+
export { QueryBuilder, query } from './query';
|
|
15
|
+
export type { QueryDescriptor, SearchDescriptor, ComparisonOperator, SortOrder, Condition, WhereClause, OrderByClause, BatchOperation, } from './query';
|
|
16
|
+
export { SQLiteAdapter } from './adapters';
|
|
17
|
+
export type { SQLiteAdapterConfig, SQLiteDriver } from './adapters';
|
|
18
|
+
export { LokiAdapter, SynchronousWorker } from './adapters';
|
|
19
|
+
export type { LokiAdapterConfig, WorkerInterface } from './adapters';
|
|
20
|
+
export { createExpoSQLiteDriver } from './adapters';
|
|
21
|
+
export type { ExpoSQLiteDriverConfig } from './adapters';
|
|
22
|
+
export type { StorageAdapter, EncryptionConfig, Migration, MigrationStep } from './adapters';
|
|
23
|
+
export { performSync } from './sync';
|
|
24
|
+
export type { SyncConfig, SyncPullResult, SyncPushPayload, SyncTableChanges, SyncTableChangeSet, SyncState, SyncLog, } from './sync';
|
|
25
|
+
export { EncryptionManager, EncryptingAdapter } from './encryption';
|
|
26
|
+
export { Subject, BehaviorSubject, SharedObservable } from './observable';
|
|
27
|
+
export type { Observable, Listener, Unsubscribe } from './observable';
|
|
28
|
+
export { useObservable, useLiveQuery, useById, useField, useSearch, useCount, useDatabase, useCollection, DatabaseProvider, DatabaseSuspenseProvider, } from './hooks';
|
|
29
|
+
export type { UseSearchOptions, UseSearchResult, DatabaseSuspenseProviderProps } from './hooks';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* PomegranateDB — main entry point.
|
|
4
|
+
*
|
|
5
|
+
* Reactive offline-first database with sync support.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.DatabaseSuspenseProvider = exports.DatabaseProvider = exports.useCollection = exports.useDatabase = exports.useCount = exports.useSearch = exports.useField = exports.useById = exports.useLiveQuery = exports.useObservable = exports.SharedObservable = exports.BehaviorSubject = exports.Subject = exports.EncryptingAdapter = exports.EncryptionManager = exports.performSync = exports.createExpoSQLiteDriver = exports.SynchronousWorker = exports.LokiAdapter = exports.SQLiteAdapter = exports.query = exports.QueryBuilder = exports.Database = exports.Collection = exports.Model = exports.m = void 0;
|
|
9
|
+
// ─── Schema ────────────────────────────────────────────────────────────────
|
|
10
|
+
var schema_1 = require("./schema");
|
|
11
|
+
Object.defineProperty(exports, "m", { enumerable: true, get: function () { return schema_1.m; } });
|
|
12
|
+
// ─── Model ─────────────────────────────────────────────────────────────────
|
|
13
|
+
var model_1 = require("./model");
|
|
14
|
+
Object.defineProperty(exports, "Model", { enumerable: true, get: function () { return model_1.Model; } });
|
|
15
|
+
// ─── Collection ────────────────────────────────────────────────────────────
|
|
16
|
+
var collection_1 = require("./collection");
|
|
17
|
+
Object.defineProperty(exports, "Collection", { enumerable: true, get: function () { return collection_1.Collection; } });
|
|
18
|
+
// ─── Database ──────────────────────────────────────────────────────────────
|
|
19
|
+
var database_1 = require("./database");
|
|
20
|
+
Object.defineProperty(exports, "Database", { enumerable: true, get: function () { return database_1.Database; } });
|
|
21
|
+
// ─── Query ─────────────────────────────────────────────────────────────────
|
|
22
|
+
var query_1 = require("./query");
|
|
23
|
+
Object.defineProperty(exports, "QueryBuilder", { enumerable: true, get: function () { return query_1.QueryBuilder; } });
|
|
24
|
+
Object.defineProperty(exports, "query", { enumerable: true, get: function () { return query_1.query; } });
|
|
25
|
+
// ─── Adapters ──────────────────────────────────────────────────────────────
|
|
26
|
+
var adapters_1 = require("./adapters");
|
|
27
|
+
Object.defineProperty(exports, "SQLiteAdapter", { enumerable: true, get: function () { return adapters_1.SQLiteAdapter; } });
|
|
28
|
+
var adapters_2 = require("./adapters");
|
|
29
|
+
Object.defineProperty(exports, "LokiAdapter", { enumerable: true, get: function () { return adapters_2.LokiAdapter; } });
|
|
30
|
+
Object.defineProperty(exports, "SynchronousWorker", { enumerable: true, get: function () { return adapters_2.SynchronousWorker; } });
|
|
31
|
+
var adapters_3 = require("./adapters");
|
|
32
|
+
Object.defineProperty(exports, "createExpoSQLiteDriver", { enumerable: true, get: function () { return adapters_3.createExpoSQLiteDriver; } });
|
|
33
|
+
// ─── Sync ──────────────────────────────────────────────────────────────────
|
|
34
|
+
var sync_1 = require("./sync");
|
|
35
|
+
Object.defineProperty(exports, "performSync", { enumerable: true, get: function () { return sync_1.performSync; } });
|
|
36
|
+
// ─── Encryption ────────────────────────────────────────────────────────────
|
|
37
|
+
var encryption_1 = require("./encryption");
|
|
38
|
+
Object.defineProperty(exports, "EncryptionManager", { enumerable: true, get: function () { return encryption_1.EncryptionManager; } });
|
|
39
|
+
Object.defineProperty(exports, "EncryptingAdapter", { enumerable: true, get: function () { return encryption_1.EncryptingAdapter; } });
|
|
40
|
+
// ─── Observable ────────────────────────────────────────────────────────────
|
|
41
|
+
var observable_1 = require("./observable");
|
|
42
|
+
Object.defineProperty(exports, "Subject", { enumerable: true, get: function () { return observable_1.Subject; } });
|
|
43
|
+
Object.defineProperty(exports, "BehaviorSubject", { enumerable: true, get: function () { return observable_1.BehaviorSubject; } });
|
|
44
|
+
Object.defineProperty(exports, "SharedObservable", { enumerable: true, get: function () { return observable_1.SharedObservable; } });
|
|
45
|
+
// ─── Hooks (React) ─────────────────────────────────────────────────────────
|
|
46
|
+
var hooks_1 = require("./hooks");
|
|
47
|
+
Object.defineProperty(exports, "useObservable", { enumerable: true, get: function () { return hooks_1.useObservable; } });
|
|
48
|
+
Object.defineProperty(exports, "useLiveQuery", { enumerable: true, get: function () { return hooks_1.useLiveQuery; } });
|
|
49
|
+
Object.defineProperty(exports, "useById", { enumerable: true, get: function () { return hooks_1.useById; } });
|
|
50
|
+
Object.defineProperty(exports, "useField", { enumerable: true, get: function () { return hooks_1.useField; } });
|
|
51
|
+
Object.defineProperty(exports, "useSearch", { enumerable: true, get: function () { return hooks_1.useSearch; } });
|
|
52
|
+
Object.defineProperty(exports, "useCount", { enumerable: true, get: function () { return hooks_1.useCount; } });
|
|
53
|
+
Object.defineProperty(exports, "useDatabase", { enumerable: true, get: function () { return hooks_1.useDatabase; } });
|
|
54
|
+
Object.defineProperty(exports, "useCollection", { enumerable: true, get: function () { return hooks_1.useCollection; } });
|
|
55
|
+
Object.defineProperty(exports, "DatabaseProvider", { enumerable: true, get: function () { return hooks_1.DatabaseProvider; } });
|
|
56
|
+
Object.defineProperty(exports, "DatabaseSuspenseProvider", { enumerable: true, get: function () { return hooks_1.DatabaseSuspenseProvider; } });
|
|
57
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model base class.
|
|
3
|
+
*
|
|
4
|
+
* Each model instance represents a single database record.
|
|
5
|
+
* Models are schema-first: the schema defines columns, relations,
|
|
6
|
+
* and the TypeScript types are inferred from it.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* class Post extends Model<typeof PostSchema> {
|
|
10
|
+
* static schema = PostSchema;
|
|
11
|
+
*
|
|
12
|
+
* publish = this.writer(async () => {
|
|
13
|
+
* await this.update({ status: 'published' });
|
|
14
|
+
* });
|
|
15
|
+
* }
|
|
16
|
+
*/
|
|
17
|
+
import type { ModelSchema, RawRecord, SyncStatus } from '../schema/types';
|
|
18
|
+
import type { Observable } from '../observable/Subject';
|
|
19
|
+
/** Minimal interface for what a Collection provides to a Model */
|
|
20
|
+
export interface ModelCollectionRef {
|
|
21
|
+
readonly table: string;
|
|
22
|
+
_update(id: string, raw: Partial<RawRecord>): Promise<void>;
|
|
23
|
+
_delete(id: string): Promise<void>;
|
|
24
|
+
_destroyPermanently(id: string): Promise<void>;
|
|
25
|
+
_getDatabase(): ModelDatabaseRef;
|
|
26
|
+
}
|
|
27
|
+
/** Minimal interface for what a Database provides */
|
|
28
|
+
export interface ModelDatabaseRef {
|
|
29
|
+
_ensureInWriter(action: string): void;
|
|
30
|
+
_batch(operations: Array<{
|
|
31
|
+
type: string;
|
|
32
|
+
table: string;
|
|
33
|
+
rawRecord?: Record<string, unknown>;
|
|
34
|
+
id?: string;
|
|
35
|
+
}>): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
export type ModelStatic<S extends ModelSchema = ModelSchema> = {
|
|
38
|
+
new (collection: ModelCollectionRef, raw: RawRecord): Model<S>;
|
|
39
|
+
schema: S;
|
|
40
|
+
};
|
|
41
|
+
export declare class Model<S extends ModelSchema = ModelSchema> {
|
|
42
|
+
#private;
|
|
43
|
+
static schema: ModelSchema;
|
|
44
|
+
/** The record id */
|
|
45
|
+
readonly id: string;
|
|
46
|
+
/** Reference back to the owning collection */
|
|
47
|
+
readonly collection: ModelCollectionRef;
|
|
48
|
+
constructor(collection: ModelCollectionRef, raw: RawRecord);
|
|
49
|
+
/** Get the current raw record */
|
|
50
|
+
get _rawRecord(): RawRecord;
|
|
51
|
+
/** Sync status of this record */
|
|
52
|
+
get syncStatus(): SyncStatus;
|
|
53
|
+
/** Changed fields (comma-separated) */
|
|
54
|
+
get changedFields(): string;
|
|
55
|
+
/**
|
|
56
|
+
* Get a field value, converting from raw storage form.
|
|
57
|
+
*/
|
|
58
|
+
getField(fieldName: string): unknown;
|
|
59
|
+
/**
|
|
60
|
+
* Set field value(s) on the raw record (does NOT persist — internal use).
|
|
61
|
+
*/
|
|
62
|
+
_setRaw(updates: Partial<RawRecord>): void;
|
|
63
|
+
/** Observe changes to this record */
|
|
64
|
+
observe(): Observable<this>;
|
|
65
|
+
/** Observe a specific field */
|
|
66
|
+
observeField(fieldName: string): Observable<unknown>;
|
|
67
|
+
/**
|
|
68
|
+
* Update this record with a patch of field values.
|
|
69
|
+
* Must be called inside `db.write()`.
|
|
70
|
+
*/
|
|
71
|
+
update(patch: Record<string, unknown>): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Mark this record as deleted (soft delete for sync).
|
|
74
|
+
* Must be called inside `db.write()`.
|
|
75
|
+
*/
|
|
76
|
+
markAsDeleted(): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Permanently destroy this record.
|
|
79
|
+
* Must be called inside `db.write()`.
|
|
80
|
+
*/
|
|
81
|
+
destroyPermanently(): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Create a bound writer method.
|
|
84
|
+
* The returned function, when called, will run inside the current write transaction.
|
|
85
|
+
*/
|
|
86
|
+
writer<Args extends unknown[], R>(fn: (...args: Args) => Promise<R>): (...args: Args) => Promise<R>;
|
|
87
|
+
/**
|
|
88
|
+
* Return raw values suitable for the sync push payload.
|
|
89
|
+
*/
|
|
90
|
+
toPushPayload(): Record<string, unknown>;
|
|
91
|
+
}
|
|
92
|
+
export declare function createRawRecord(schema: ModelSchema, patch: Record<string, unknown>, id?: string): RawRecord;
|