ofplatform-rn 0.1.0-alpha.0 → 0.1.0-alpha.1
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/dist/adapters/db/RnSqliteAdapter.d.ts +80 -0
- package/dist/adapters/db/RnSqliteQueryBuilder.d.ts +31 -0
- package/dist/adapters/db/index.d.ts +3 -1
- package/dist/adapters/export/RnExportAdapter.d.ts +92 -0
- package/dist/adapters/export/index.d.ts +1 -0
- package/dist/adapters/http/RnFetchHttpAdapter.d.ts +29 -0
- package/dist/adapters/http/index.d.ts +2 -1
- package/dist/adapters/notification/RnNotificationAdapter.d.ts +81 -0
- package/dist/adapters/notification/index.d.ts +1 -0
- package/dist/adapters/platform/RnPlatformAdapter.d.ts +74 -0
- package/dist/adapters/platform/index.d.ts +1 -0
- package/dist/adapters/socket/RnWebSocketAdapter.d.ts +80 -0
- package/dist/adapters/socket/index.d.ts +2 -1
- package/dist/adapters/storage/AsyncStorageAdapter.d.ts +51 -0
- package/dist/adapters/storage/index.d.ts +2 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.esm.js +5 -1
- package/dist/index.js +5 -1
- package/package.json +8 -6
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { ColumnDefinition, DbAdapter, LiteralValue, QueryOptions, TableSchema } from 'ofcore';
|
|
2
|
+
export interface RnSqliteDriverResult {
|
|
3
|
+
/** Rows returned by SELECT statements. */
|
|
4
|
+
rows: Record<string, unknown>[];
|
|
5
|
+
/** Number of rows affected (INSERT/UPDATE/DELETE). */
|
|
6
|
+
rowsAffected?: number;
|
|
7
|
+
/** Last inserted row ID (INSERT). */
|
|
8
|
+
insertId?: string | number | bigint;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Minimal async SQLite driver interface.
|
|
12
|
+
*
|
|
13
|
+
* Any React Native SQLite library can be wrapped to satisfy this contract:
|
|
14
|
+
*
|
|
15
|
+
* | Library | Wrapper effort |
|
|
16
|
+
* |--------------------------------------|----------------|
|
|
17
|
+
* | `expo-sqlite` (v14+) | 1–2 lines |
|
|
18
|
+
* | `@op-engineering/op-sqlite` | 1–2 lines |
|
|
19
|
+
* | `react-native-sqlite-storage` | thin Promise wrapper |
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* // expo-sqlite v14
|
|
24
|
+
* import * as SQLite from 'expo-sqlite';
|
|
25
|
+
* const db = SQLite.openDatabaseSync('app.db');
|
|
26
|
+
* const driver: RnSqliteDriver = {
|
|
27
|
+
* async execute(sql, params = []) {
|
|
28
|
+
* const result = await db.runAsync(sql, params);
|
|
29
|
+
* return { rows: result.rows ?? [], rowsAffected: result.changes, insertId: result.lastInsertRowId };
|
|
30
|
+
* },
|
|
31
|
+
* };
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export interface RnSqliteDriver {
|
|
35
|
+
execute(sql: string, params?: (string | number | boolean | null)[]): Promise<RnSqliteDriverResult>;
|
|
36
|
+
}
|
|
37
|
+
export interface RnSqliteAdapterOptions {
|
|
38
|
+
driver: RnSqliteDriver;
|
|
39
|
+
tableNamePrefix?: string;
|
|
40
|
+
tableNameMapper?: (tableName: string) => string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* React Native SQLite adapter implementing the full `DbAdapter` contract.
|
|
44
|
+
*
|
|
45
|
+
* Uses an injectable `RnSqliteDriver` so it is decoupled from any specific
|
|
46
|
+
* React Native SQLite library. The adapter handles:
|
|
47
|
+
* - Schema versioning (`_schema_version` table)
|
|
48
|
+
* - Table and column management (`addTable`, `addColumn`)
|
|
49
|
+
* - Full CRUD with type coercion (boolean, date, json)
|
|
50
|
+
* - Advanced querying (filters, joins, aggregates, sort, pagination, projections)
|
|
51
|
+
* - Bulk operations and savepoint-based nested transactions
|
|
52
|
+
* - Table name prefix / mapper support for multi-tenant scenarios
|
|
53
|
+
*/
|
|
54
|
+
export declare class RnSqliteAdapter implements DbAdapter {
|
|
55
|
+
private readonly driver;
|
|
56
|
+
private savepointDepth;
|
|
57
|
+
private readonly schemaMap;
|
|
58
|
+
private readonly tableNamePrefix;
|
|
59
|
+
private tableNameResolver?;
|
|
60
|
+
private metaInitialized;
|
|
61
|
+
constructor(options: RnSqliteAdapterOptions);
|
|
62
|
+
setTableNameResolver(resolver: (logicalTableName: string) => string): void;
|
|
63
|
+
resolveTableName(tableName: string): string;
|
|
64
|
+
private ensureMetaInit;
|
|
65
|
+
getSchemaVersion(): Promise<number>;
|
|
66
|
+
setSchemaVersion(version: number): Promise<void>;
|
|
67
|
+
addTable(table: TableSchema): Promise<void>;
|
|
68
|
+
addColumn(table: string, column: ColumnDefinition): Promise<void>;
|
|
69
|
+
get<T>(table: string, idOrOptions: string | QueryOptions): Promise<T | null>;
|
|
70
|
+
query<T>(table: string, options?: QueryOptions): Promise<T[]>;
|
|
71
|
+
create<T>(table: string, data: Partial<Record<string, LiteralValue>>): Promise<T>;
|
|
72
|
+
update<T>(table: string, id: string, updates: Partial<Record<string, LiteralValue>>): Promise<T>;
|
|
73
|
+
delete(table: string, id: string): Promise<void>;
|
|
74
|
+
bulkCreate<T>(table: string, rows: Array<Partial<Record<string, LiteralValue>>>): Promise<T[]>;
|
|
75
|
+
bulkUpdate<T>(table: string, rows: Array<{
|
|
76
|
+
id: string;
|
|
77
|
+
updates: Partial<Record<string, LiteralValue>>;
|
|
78
|
+
}>): Promise<T[]>;
|
|
79
|
+
transaction<T>(callback: (tx: this) => Promise<T>): Promise<T>;
|
|
80
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { FilterExpression, GroupBySpec, LiteralValue, QueryOptions, SortSpec } from 'ofcore';
|
|
2
|
+
export interface BuiltQuery {
|
|
3
|
+
sql: string;
|
|
4
|
+
params: (string | number | boolean | null)[];
|
|
5
|
+
projectionMap?: Array<{
|
|
6
|
+
key: string;
|
|
7
|
+
alias: string;
|
|
8
|
+
field: string;
|
|
9
|
+
}>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Pure static SQL query builder for SQLite — React Native edition.
|
|
13
|
+
*
|
|
14
|
+
* Generates parameterised SQL compatible with any RN SQLite driver
|
|
15
|
+
* (expo-sqlite, @op-engineering/op-sqlite, react-native-sqlite-storage, etc.).
|
|
16
|
+
* No runtime dependencies — plain string and array operations only.
|
|
17
|
+
*/
|
|
18
|
+
export declare class RnSqliteQueryBuilder {
|
|
19
|
+
private static escapeIdentifier;
|
|
20
|
+
static escapeLiteral(s: string): string;
|
|
21
|
+
private static normalizeValue;
|
|
22
|
+
private static buildValueExpr;
|
|
23
|
+
private static buildFieldCondition;
|
|
24
|
+
static buildFilter(expr: FilterExpression, mainAlias?: string): BuiltQuery;
|
|
25
|
+
static buildGroupBy(groupBy?: GroupBySpec[], mainAlias?: string): string;
|
|
26
|
+
static buildSort(sorts?: SortSpec[], mainAlias?: string): string;
|
|
27
|
+
static buildSelect(table: string, options?: QueryOptions): BuiltQuery;
|
|
28
|
+
static buildInsert(table: string, data: Record<string, LiteralValue>): BuiltQuery;
|
|
29
|
+
static buildUpdate(table: string, id: string, updates: Record<string, LiteralValue>): BuiltQuery;
|
|
30
|
+
static buildDelete(table: string, id: string): BuiltQuery;
|
|
31
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { ExportAdapter } from 'ofcore';
|
|
2
|
+
/**
|
|
3
|
+
* Backend yang diinjeksi ke RnExportAdapter.
|
|
4
|
+
*
|
|
5
|
+
* Operasi file di React Native bergantung pada library pilihan host:
|
|
6
|
+
* react-native-fs, expo-file-system, atau @dr.pogodin/react-native-fs.
|
|
7
|
+
* Operasi share menggunakan react-native-share atau expo-sharing.
|
|
8
|
+
*
|
|
9
|
+
* Karena library-library ini membutuhkan native setup per-project,
|
|
10
|
+
* tidak dibundel di sini.
|
|
11
|
+
*
|
|
12
|
+
* @example dengan react-native-fs + react-native-share:
|
|
13
|
+
* ```ts
|
|
14
|
+
* import RNFS from 'react-native-fs';
|
|
15
|
+
* import Share from 'react-native-share';
|
|
16
|
+
*
|
|
17
|
+
* const backend: RnExportBackend = {
|
|
18
|
+
* async writeFile(filePath, content) {
|
|
19
|
+
* await RNFS.writeFile(filePath, content, 'utf8');
|
|
20
|
+
* },
|
|
21
|
+
* async share(filePath) {
|
|
22
|
+
* await Share.open({ url: `file://${filePath}` });
|
|
23
|
+
* },
|
|
24
|
+
* };
|
|
25
|
+
*
|
|
26
|
+
* const adapter = new RnExportAdapter({
|
|
27
|
+
* documentsPath: RNFS.DocumentDirectoryPath,
|
|
28
|
+
* backend,
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @example dengan expo-file-system + expo-sharing:
|
|
33
|
+
* ```ts
|
|
34
|
+
* import * as FileSystem from 'expo-file-system';
|
|
35
|
+
* import * as Sharing from 'expo-sharing';
|
|
36
|
+
*
|
|
37
|
+
* const backend: RnExportBackend = {
|
|
38
|
+
* async writeFile(filePath, content) {
|
|
39
|
+
* await FileSystem.writeAsStringAsync(filePath, content, { encoding: 'utf8' });
|
|
40
|
+
* },
|
|
41
|
+
* async share(filePath) {
|
|
42
|
+
* if (await Sharing.isAvailableAsync()) {
|
|
43
|
+
* await Sharing.shareAsync(filePath);
|
|
44
|
+
* }
|
|
45
|
+
* },
|
|
46
|
+
* };
|
|
47
|
+
*
|
|
48
|
+
* const adapter = new RnExportAdapter({
|
|
49
|
+
* documentsPath: FileSystem.documentDirectory ?? '',
|
|
50
|
+
* backend,
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export interface RnExportBackend {
|
|
55
|
+
/**
|
|
56
|
+
* Menulis konten teks ke file di path yang diberikan.
|
|
57
|
+
*/
|
|
58
|
+
writeFile(filePath: string, content: string): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Membuka dialog share native (share sheet) untuk file di path yang diberikan.
|
|
61
|
+
* Opsional: jika tidak diisi, share adalah no-op.
|
|
62
|
+
*/
|
|
63
|
+
share?(filePath: string): Promise<void>;
|
|
64
|
+
}
|
|
65
|
+
export interface RnExportAdapterOptions {
|
|
66
|
+
/**
|
|
67
|
+
* Path direktori dokumen yang sudah di-resolve sebelumnya.
|
|
68
|
+
*
|
|
69
|
+
* Di RN, path ini berbeda per platform dan library:
|
|
70
|
+
* - react-native-fs: `RNFS.DocumentDirectoryPath`
|
|
71
|
+
* - expo-file-system: `FileSystem.documentDirectory`
|
|
72
|
+
*
|
|
73
|
+
* Path harus berupa string absolut yang valid tanpa trailing slash.
|
|
74
|
+
*/
|
|
75
|
+
documentsPath: string;
|
|
76
|
+
backend: RnExportBackend;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* React Native export adapter implementing the `ExportAdapter` contract.
|
|
80
|
+
*
|
|
81
|
+
* `getDocumentPath()` bersifat sync (sesuai interface ExportAdapter) —
|
|
82
|
+
* oleh karena itu `documentsPath` harus sudah di-resolve sebelum
|
|
83
|
+
* membuat adapter ini.
|
|
84
|
+
*/
|
|
85
|
+
export declare class RnExportAdapter implements ExportAdapter {
|
|
86
|
+
private readonly documentsPath;
|
|
87
|
+
private readonly backend;
|
|
88
|
+
constructor(options: RnExportAdapterOptions);
|
|
89
|
+
getDocumentPath(fileName: string): string;
|
|
90
|
+
writeFile(filePath: string, content: string): Promise<void>;
|
|
91
|
+
share(filePath: string): Promise<void>;
|
|
92
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './RnExportAdapter';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { HttpAdapter, HttpRequest, HttpResponse } from 'ofcore';
|
|
2
|
+
export type RnFetchImplementation = (input: string, init?: RequestInit) => Promise<Response>;
|
|
3
|
+
export interface RnFetchHttpAdapterOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Override the fetch implementation. Defaults to `globalThis.fetch`.
|
|
6
|
+
*
|
|
7
|
+
* React Native exposes a global `fetch` compatible with the browser Fetch API.
|
|
8
|
+
* Pass a custom implementation for interceptors, certificate pinning, or testing.
|
|
9
|
+
*/
|
|
10
|
+
fetch?: RnFetchImplementation;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* React Native HTTP adapter backed by the Fetch API.
|
|
14
|
+
*
|
|
15
|
+
* React Native ships its own Fetch implementation (`global.fetch`) that is
|
|
16
|
+
* API-compatible with the browser standard. This adapter targets that surface.
|
|
17
|
+
*
|
|
18
|
+
* Features:
|
|
19
|
+
* - Query parameter serialisation (null/undefined values are omitted)
|
|
20
|
+
* - Automatic `content-type: application/json` for requests with a body
|
|
21
|
+
* - Timeout support via `AbortController`
|
|
22
|
+
* - Response body parsed as JSON when `content-type` indicates JSON,
|
|
23
|
+
* otherwise returns raw text
|
|
24
|
+
*/
|
|
25
|
+
export declare class RnFetchHttpAdapter implements HttpAdapter {
|
|
26
|
+
private readonly fetchImpl;
|
|
27
|
+
constructor(options?: RnFetchHttpAdapterOptions);
|
|
28
|
+
request<T = unknown>(request: HttpRequest): Promise<HttpResponse<T>>;
|
|
29
|
+
}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export
|
|
1
|
+
export * from './RnFetchHttpAdapter';
|
|
2
|
+
export declare const OFPLATFORM_RN_HTTP_ADAPTERS_READY = true;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { NotificationAdapter, NotificationOptions } from 'ofcore';
|
|
2
|
+
/**
|
|
3
|
+
* Backend yang diinjeksi ke RnNotificationAdapter.
|
|
4
|
+
*
|
|
5
|
+
* Implementasi tipikal menggunakan @notifee/react-native atau
|
|
6
|
+
* expo-notifications. Karena library ini membutuhkan native setup
|
|
7
|
+
* per-project (AndroidManifest, Info.plist), tidak dibundel di sini —
|
|
8
|
+
* host application yang menyiapkan dan menginjeksi backend-nya.
|
|
9
|
+
*
|
|
10
|
+
* @example dengan @notifee/react-native:
|
|
11
|
+
* ```ts
|
|
12
|
+
* import notifee, { AndroidImportance } from '@notifee/react-native';
|
|
13
|
+
*
|
|
14
|
+
* const backend: RnNotificationBackend = {
|
|
15
|
+
* async configure() { await notifee.requestPermission(); },
|
|
16
|
+
* async createChannel({ channelId, channelName, importance }) {
|
|
17
|
+
* await notifee.createChannel({
|
|
18
|
+
* id: channelId,
|
|
19
|
+
* name: channelName,
|
|
20
|
+
* importance: importance ?? AndroidImportance.DEFAULT,
|
|
21
|
+
* });
|
|
22
|
+
* },
|
|
23
|
+
* async localNotification({ channelId, title, message }) {
|
|
24
|
+
* await notifee.displayNotification({
|
|
25
|
+
* title,
|
|
26
|
+
* body: message,
|
|
27
|
+
* android: { channelId: channelId ?? 'default' },
|
|
28
|
+
* });
|
|
29
|
+
* },
|
|
30
|
+
* };
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export interface RnNotificationBackend {
|
|
34
|
+
/**
|
|
35
|
+
* Inisialisasi notifikasi — minta permission (iOS) atau setup awal lainnya.
|
|
36
|
+
* Dipanggil saat `NotificationAdapter.configure()` dipanggil.
|
|
37
|
+
* Opsional: jika tidak diisi, configure adalah no-op.
|
|
38
|
+
*/
|
|
39
|
+
configure?(options: unknown): void | Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Membuat Android notification channel (API 26+).
|
|
42
|
+
* Dipanggil saat `NotificationAdapter.createChannel()` dipanggil.
|
|
43
|
+
* Opsional: jika tidak diisi, createChannel adalah no-op.
|
|
44
|
+
*/
|
|
45
|
+
createChannel?(options: {
|
|
46
|
+
channelId: string;
|
|
47
|
+
channelName: string;
|
|
48
|
+
importance?: number;
|
|
49
|
+
}): void | Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Menampilkan notifikasi lokal.
|
|
52
|
+
* Method ini wajib diisi karena merupakan fungsi utama adapter.
|
|
53
|
+
*/
|
|
54
|
+
localNotification(options: NotificationOptions): void | Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
export interface RnNotificationAdapterOptions {
|
|
57
|
+
backend: RnNotificationBackend;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* React Native notification adapter implementing the `NotificationAdapter` contract.
|
|
61
|
+
*
|
|
62
|
+
* Mendelegasikan semua operasi ke backend yang diinjeksi via constructor.
|
|
63
|
+
* Backend bertanggung jawab atas integrasi library notifikasi native
|
|
64
|
+
* (notifee, expo-notifications, dll).
|
|
65
|
+
*
|
|
66
|
+
* `configure()` dan `createChannel()` bersifat async-capable di backend
|
|
67
|
+
* namun sync di interface — ini konsisten dengan pola ElectronNotificationAdapter.
|
|
68
|
+
* Host yang membutuhkan async setup sebaiknya menyelesaikannya sebelum
|
|
69
|
+
* memanggil configure/createChannel.
|
|
70
|
+
*/
|
|
71
|
+
export declare class RnNotificationAdapter implements NotificationAdapter {
|
|
72
|
+
private readonly backend;
|
|
73
|
+
constructor(options: RnNotificationAdapterOptions);
|
|
74
|
+
configure(options: unknown): void;
|
|
75
|
+
createChannel(options: {
|
|
76
|
+
channelId: string;
|
|
77
|
+
channelName: string;
|
|
78
|
+
importance?: number;
|
|
79
|
+
}): void;
|
|
80
|
+
localNotification(options: NotificationOptions): void;
|
|
81
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './RnNotificationAdapter';
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { PlatformAdapter } from 'ofcore';
|
|
2
|
+
export interface RnPlatformAdapterOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Build-time environment variables.
|
|
5
|
+
*
|
|
6
|
+
* Di React Native, `process.env` hanya mengekspos variabel yang
|
|
7
|
+
* di-inline oleh Metro bundler saat build (bukan runtime env).
|
|
8
|
+
* Untuk variabel yang dikelola oleh react-native-config atau
|
|
9
|
+
* expo-constants, sertakan di sini agar `getEnvVar()` bisa menemukannya.
|
|
10
|
+
*
|
|
11
|
+
* Jika tidak disertakan, `getEnvVar()` membaca dari `process.env`
|
|
12
|
+
* (hanya variabel yang di-inline Metro yang tersedia).
|
|
13
|
+
*/
|
|
14
|
+
envVars?: Record<string, string | undefined>;
|
|
15
|
+
/**
|
|
16
|
+
* Fungsi pemeriksa konektivitas jaringan.
|
|
17
|
+
*
|
|
18
|
+
* Defaultnya menggunakan `navigator.onLine` jika tersedia.
|
|
19
|
+
* Untuk akurasi lebih tinggi, inject dari @react-native-community/netinfo:
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import NetInfo from '@react-native-community/netinfo';
|
|
24
|
+
* // ...
|
|
25
|
+
* isOnline: () => netInfoState.isConnected ?? true,
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
isOnline?: () => boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Fungsi SHA-256 untuk hashing PIN.
|
|
31
|
+
*
|
|
32
|
+
* Default menggunakan `crypto.subtle.digest('SHA-256', ...)` yang
|
|
33
|
+
* tersedia di React Native dengan Hermes (≥0.71) dan JSC modern.
|
|
34
|
+
*
|
|
35
|
+
* Jika runtime tidak mendukung Web Crypto API, injeksikan implementasi
|
|
36
|
+
* dari library pilihan:
|
|
37
|
+
*
|
|
38
|
+
* @example dengan @noble/hashes (pure JS, semua environment):
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { sha256 } from '@noble/hashes/sha256';
|
|
41
|
+
* import { bytesToHex } from '@noble/hashes/utils';
|
|
42
|
+
* const hashPin = async (pin: string) => bytesToHex(sha256(pin));
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @example dengan expo-crypto:
|
|
46
|
+
* ```ts
|
|
47
|
+
* import * as Crypto from 'expo-crypto';
|
|
48
|
+
* const hashPin = (pin: string) =>
|
|
49
|
+
* Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, pin);
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
hashPin?: (pin: string) => Promise<string>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* React Native platform adapter implementing the `PlatformAdapter` contract.
|
|
56
|
+
*
|
|
57
|
+
* Semua operasi platform-spesifik (env vars, network, crypto) dapat
|
|
58
|
+
* di-override via options — mengikuti pola dependency injection yang
|
|
59
|
+
* konsisten dengan adapter lain di ofplatform-rn.
|
|
60
|
+
*
|
|
61
|
+
* Hash algorithm yang digunakan (SHA-256 via Web Crypto / fallback inject)
|
|
62
|
+
* identik dengan ElectronPlatformAdapter sehingga pinHash kompatibel
|
|
63
|
+
* lintas platform.
|
|
64
|
+
*/
|
|
65
|
+
export declare class RnPlatformAdapter implements PlatformAdapter {
|
|
66
|
+
private readonly envVars;
|
|
67
|
+
private readonly isOnlineFn;
|
|
68
|
+
private readonly hashPinFn;
|
|
69
|
+
constructor(options?: RnPlatformAdapterOptions);
|
|
70
|
+
getEnvVar(key: string): string | undefined;
|
|
71
|
+
isOnline(): boolean;
|
|
72
|
+
hashPin(pin: string): Promise<string>;
|
|
73
|
+
verifyPin(pin: string, hashedPin: string): Promise<boolean>;
|
|
74
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './RnPlatformAdapter';
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { SocketAdapter, SocketConnectOptions, SocketEventHandler } from 'ofcore';
|
|
2
|
+
export interface RnWebSocketMessage {
|
|
3
|
+
event: string;
|
|
4
|
+
payload: unknown;
|
|
5
|
+
}
|
|
6
|
+
export interface RnWebSocketAdapterOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Enable automatic reconnection on unexpected connection loss.
|
|
9
|
+
* @default true
|
|
10
|
+
*/
|
|
11
|
+
reconnect?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Maximum number of reconnection attempts before giving up.
|
|
14
|
+
* @default 5
|
|
15
|
+
*/
|
|
16
|
+
maxReconnectAttempts?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Initial delay between reconnection attempts in milliseconds.
|
|
19
|
+
* Grows exponentially on each subsequent attempt.
|
|
20
|
+
* @default 500
|
|
21
|
+
*/
|
|
22
|
+
reconnectDelayMs?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Maximum delay cap for exponential backoff in milliseconds.
|
|
25
|
+
* @default 30000
|
|
26
|
+
*/
|
|
27
|
+
maxReconnectDelayMs?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Override the WebSocket constructor.
|
|
30
|
+
* Defaults to `globalThis.WebSocket` (available natively in React Native).
|
|
31
|
+
* Pass a mock for unit testing.
|
|
32
|
+
*/
|
|
33
|
+
webSocket?: typeof WebSocket;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* React Native WebSocket adapter implementing the `SocketAdapter` contract.
|
|
37
|
+
*
|
|
38
|
+
* React Native ships a native WebSocket implementation. This adapter wraps
|
|
39
|
+
* it with event-name routing, automatic reconnection, and clean lifecycle
|
|
40
|
+
* management.
|
|
41
|
+
*
|
|
42
|
+
* ## Message protocol
|
|
43
|
+
* All messages on the wire are JSON-encoded `RnWebSocketMessage` objects:
|
|
44
|
+
* ```json
|
|
45
|
+
* { "event": "<event-name>", "payload": <any> }
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* ## Headers limitation
|
|
49
|
+
* React Native's WebSocket implementation supports custom headers via a third
|
|
50
|
+
* constructor argument (non-standard, RN-specific extension). However, for
|
|
51
|
+
* portability the adapter relies on `SocketConnectOptions.query` for
|
|
52
|
+
* request-time metadata such as auth tokens.
|
|
53
|
+
*
|
|
54
|
+
* ## Reconnection
|
|
55
|
+
* Unexpected disconnections trigger exponential-backoff reconnection unless
|
|
56
|
+
* `options.reconnect` is `false` or the maximum attempt count is reached.
|
|
57
|
+
*/
|
|
58
|
+
export declare class RnWebSocketAdapter implements SocketAdapter {
|
|
59
|
+
private ws;
|
|
60
|
+
private connectionOptions;
|
|
61
|
+
private readonly listeners;
|
|
62
|
+
private reconnectAttempts;
|
|
63
|
+
private reconnectTimeout;
|
|
64
|
+
private intentionalClose;
|
|
65
|
+
private readonly reconnect;
|
|
66
|
+
private readonly maxReconnectAttempts;
|
|
67
|
+
private readonly reconnectDelayMs;
|
|
68
|
+
private readonly maxReconnectDelayMs;
|
|
69
|
+
private readonly WebSocketCtor;
|
|
70
|
+
constructor(options?: RnWebSocketAdapterOptions);
|
|
71
|
+
connect(options: SocketConnectOptions): Promise<void>;
|
|
72
|
+
disconnect(): Promise<void>;
|
|
73
|
+
subscribe(eventName: string, handler: SocketEventHandler): () => void;
|
|
74
|
+
publish(eventName: string, payload: unknown): Promise<void>;
|
|
75
|
+
isConnected(): boolean;
|
|
76
|
+
private openConnection;
|
|
77
|
+
private handleMessage;
|
|
78
|
+
private scheduleReconnect;
|
|
79
|
+
private clearReconnectTimeout;
|
|
80
|
+
}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export
|
|
1
|
+
export * from './RnWebSocketAdapter';
|
|
2
|
+
export declare const OFPLATFORM_RN_SOCKET_ADAPTERS_READY = true;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { StorageAdapter } from 'ofcore';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal async storage backend interface.
|
|
4
|
+
*
|
|
5
|
+
* Compatible with `@react-native-async-storage/async-storage` and any other
|
|
6
|
+
* key-value store that exposes the same three async methods.
|
|
7
|
+
*/
|
|
8
|
+
export interface AsyncStorageBackend {
|
|
9
|
+
getItem(key: string): Promise<string | null>;
|
|
10
|
+
setItem(key: string, value: string): Promise<void>;
|
|
11
|
+
removeItem(key: string): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export interface AsyncStorageAdapterOptions {
|
|
14
|
+
/**
|
|
15
|
+
* The underlying async storage backend.
|
|
16
|
+
*
|
|
17
|
+
* Typically `AsyncStorage` from `@react-native-async-storage/async-storage`:
|
|
18
|
+
* ```ts
|
|
19
|
+
* import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
20
|
+
* const adapter = new AsyncStorageAdapter({ storage: AsyncStorage });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
storage: AsyncStorageBackend;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* React Native async storage adapter implementing the `StorageAdapter` contract.
|
|
27
|
+
*
|
|
28
|
+
* Delegates to any `AsyncStorageBackend`-compatible implementation, most
|
|
29
|
+
* commonly `@react-native-async-storage/async-storage`. The underlying package
|
|
30
|
+
* is NOT bundled here — the host application must install and provide it.
|
|
31
|
+
*
|
|
32
|
+
* All operations return `Promise<...>` as required by React Native's async
|
|
33
|
+
* storage model. The `StorageAdapter` interface permits async return types, so
|
|
34
|
+
* this is fully contract-compliant.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
39
|
+
* import { AsyncStorageAdapter } from 'ofplatform-rn';
|
|
40
|
+
*
|
|
41
|
+
* const storage = new AsyncStorageAdapter({ storage: AsyncStorage });
|
|
42
|
+
* await storage.setItem('session', JSON.stringify(sessionData));
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare class AsyncStorageAdapter implements StorageAdapter {
|
|
46
|
+
private readonly store;
|
|
47
|
+
constructor(options: AsyncStorageAdapterOptions);
|
|
48
|
+
getItem(key: string): Promise<string | null>;
|
|
49
|
+
setItem(key: string, value: string): Promise<void>;
|
|
50
|
+
removeItem(key: string): Promise<void>;
|
|
51
|
+
}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export
|
|
1
|
+
export * from './AsyncStorageAdapter';
|
|
2
|
+
export declare const OFPLATFORM_RN_STORAGE_ADAPTERS_READY = true;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export * from './adapters/db';
|
|
2
|
+
export * from './adapters/export';
|
|
2
3
|
export * from './adapters/http';
|
|
4
|
+
export * from './adapters/notification';
|
|
5
|
+
export * from './adapters/platform';
|
|
3
6
|
export * from './adapters/socket';
|
|
4
7
|
export * from './adapters/storage';
|
|
5
8
|
export interface ReactNativePlatformModuleStatus {
|
package/dist/index.esm.js
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
|
-
var t
|
|
1
|
+
var b=class{static escapeIdentifier(e){if(!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(e))throw new Error(`Invalid identifier: "${e}"`);return`"${e}"`}static escapeLiteral(e){return`'${e.replace(/'/g,"''")}'`}static normalizeValue(e){return e==null?null:typeof e=="boolean"?e?1:0:e instanceof Date?e.toISOString():Array.isArray(e)||typeof e=="object"&&e!==null?JSON.stringify(e):e}static buildValueExpr(e,t){if(e.type==="column"){let r=e.tableAlias||t;return{sql:`${this.escapeIdentifier(r)}.${this.escapeIdentifier(e.field)}`,params:[]}}return{sql:"?",params:[this.normalizeValue(e.value)]}}static buildFieldCondition(e,t){if("field"in e){let n=e.alias||t,s=`${this.escapeIdentifier(n)}.${this.escapeIdentifier(e.field)}`,a=e.operator||"=";if(a==="BETWEEN"){if(!Array.isArray(e.value)||e.value.length!==2)throw new Error("BETWEEN requires value tuple [min,max]");return{sql:`${s} BETWEEN ? AND ?`,params:[this.normalizeValue(e.value[0]),this.normalizeValue(e.value[1])]}}if(a==="IN"||a==="NOT IN"){if(!Array.isArray(e.value))throw new Error(`${a} requires array value`);return e.value.length===0?{sql:a==="IN"?"FALSE":"TRUE",params:[]}:{sql:`${s} ${a} (${e.value.map(()=>"?").join(", ")})`,params:e.value.map(p=>this.normalizeValue(p))}}if(a==="IS NULL"||a==="IS NOT NULL")return{sql:`${s} ${a}`,params:[]};if(a==="LIKE"){let c=("likeMode"in e?e.likeMode:"default")==="case-sensitive"?" COLLATE BINARY":"";return{sql:`${s} LIKE${c} ?`,params:[this.normalizeValue(e.value)]}}return{sql:`${s} ${a} ?`,params:[this.normalizeValue(e.value)]}}let r=this.buildValueExpr(e.left,t);if(e.operator==="IS NULL"||e.operator==="IS NOT NULL")return{sql:`${r.sql} ${e.operator}`,params:r.params};let i=this.buildValueExpr(e.right,t);if(e.operator==="IN"||e.operator==="NOT IN"){if(e.right.type!=="literal"||!Array.isArray(e.right.value))throw new Error(`${e.operator} requires literal array on right side`);return e.right.value.length===0?{sql:e.operator==="IN"?"FALSE":"TRUE",params:[]}:{sql:`${r.sql} ${e.operator} (${e.right.value.map(()=>"?").join(", ")})`,params:[...r.params,...e.right.value.map(n=>this.normalizeValue(n))]}}if(e.operator==="BETWEEN"){if(e.right.type!=="literal"||!Array.isArray(e.right.value)||e.right.value.length!==2)throw new Error("BETWEEN requires literal tuple [min,max] on right side");return{sql:`${r.sql} BETWEEN ? AND ?`,params:[...r.params,this.normalizeValue(e.right.value[0]),this.normalizeValue(e.right.value[1])]}}if(e.operator==="LIKE"){let n=e.likeMode==="case-sensitive"?" COLLATE BINARY":"";return{sql:`${r.sql} LIKE${n} ${i.sql}`,params:[...r.params,...i.params]}}return{sql:`${r.sql} ${e.operator} ${i.sql}`,params:[...r.params,...i.params]}}static buildFilter(e,t="t0"){let r=e.and;if(Array.isArray(r)){let n=r.map(s=>this.buildFilter(s,t));return{sql:n.map(s=>`(${s.sql})`).join(" AND "),params:n.flatMap(s=>s.params)}}let i=e.or;if(Array.isArray(i)){let n=i.map(s=>this.buildFilter(s,t));return{sql:n.map(s=>`(${s.sql})`).join(" OR "),params:n.flatMap(s=>s.params)}}if(e!==null&&typeof e=="object"&&!Array.isArray(e)){if("left"in e||"field"in e)return this.buildFieldCondition(e,t);let n=Object.entries(e);if(n.length===0)return{sql:"TRUE",params:[]};let s=n.map(([a,p])=>({sql:`${this.escapeIdentifier(t)}.${this.escapeIdentifier(a)} = ?`,params:[this.normalizeValue(p)]}));return{sql:s.map(a=>`(${a.sql})`).join(" AND "),params:s.flatMap(a=>a.params)}}throw new Error(`Invalid filter expression: ${JSON.stringify(e)}`)}static buildGroupBy(e,t="t0"){return!e||!e.length?"":` GROUP BY ${e.map(i=>{let n=typeof i.field=="string"?{tableAlias:t,field:i.field}:i.field;return`${this.escapeIdentifier(n.tableAlias)}.${this.escapeIdentifier(n.field)}`}).join(", ")}`}static buildSort(e,t="t0"){return!e||!e.length?"":e.map(r=>{let i=typeof r.field=="string"?{tableAlias:t,field:r.field}:r.field;return`${this.escapeIdentifier(i.tableAlias)}.${this.escapeIdentifier(i.field)} ${r.direction.toUpperCase()}`}).join(", ")}static buildSelect(e,t){var f;let r=t,i=(t==null?void 0:t.mainAlias)||"t0",n=(t==null?void 0:t.joins)||[],s=[],a=[],p=[];if(t!=null&&t.aggregates&&t.aggregates.length>0)t.groupBy&&t.groupBy.forEach(o=>{let u=typeof o.field=="string"?{tableAlias:i,field:o.field}:o.field;p.push(`${this.escapeIdentifier(u.tableAlias)}.${this.escapeIdentifier(u.field)} AS ${this.escapeIdentifier(`${u.tableAlias}_${u.field}`)}`)}),t.aggregates.forEach(o=>{let u=typeof o.field=="string"?{tableAlias:i,field:o.field}:o.field,m=`${this.escapeIdentifier(u.tableAlias)}.${this.escapeIdentifier(u.field)}`;p.push(`${o.function}(${m}) AS ${this.escapeIdentifier(o.as)}`)});else if(t!=null&&t.fields){let o=0;for(let[u,m]of Object.entries(t.fields))for(let h of m){let y=((f=r==null?void 0:r.resultShape)!=null?f:"flat")==="nested"?`__c${o++}`:`${u}_${h}`;a.push({key:y,alias:u,field:h}),p.push(`${this.escapeIdentifier(u)}.${this.escapeIdentifier(h)} AS ${this.escapeIdentifier(y)}`)}}else p.push(`${this.escapeIdentifier(i)}.*`);let c=`SELECT ${p.join(", ")} FROM ${this.escapeIdentifier(e)} AS ${this.escapeIdentifier(i)}`;if(n.forEach(o=>{let u=(o.type||"inner").toUpperCase();if(u==="RIGHT"||u==="FULL")throw new Error(`RnSqliteAdapter does not support ${u} JOIN`);let m=[];o.conditions.forEach(h=>{let d=this.buildValueExpr(h.left,i),y=this.buildValueExpr(h.right,i);m.push(`${d.sql} ${h.operator} ${y.sql}`),s.push(...d.params,...y.params)}),c+=` ${u} JOIN ${this.escapeIdentifier(o.table)} AS ${this.escapeIdentifier(o.alias)} ON ${m.join(" AND ")}`}),t!=null&&t.filters){let o=this.buildFilter(t.filters,i);o.sql&&o.sql!=="TRUE"&&(c+=` WHERE ${o.sql}`,s.push(...o.params))}if(t!=null&&t.aggregates&&t.aggregates.length>0){if(c+=this.buildGroupBy(t.groupBy,i),t.having){let o=this.buildFilter(t.having,i);o.sql&&o.sql!=="TRUE"&&(c+=` HAVING ${o.sql}`,s.push(...o.params))}return{sql:c,params:s,projectionMap:a.length?a:void 0}}if(c+=this.buildGroupBy(t==null?void 0:t.groupBy,i),t!=null&&t.having){let o=this.buildFilter(t.having,i);o.sql&&o.sql!=="TRUE"&&(c+=` HAVING ${o.sql}`,s.push(...o.params))}if(t!=null&&t.sort&&t.sort.length){let o=this.buildSort(t.sort,i);o&&(c+=` ORDER BY ${o}`)}return(t==null?void 0:t.limit)!==void 0&&(c+=" LIMIT ?",s.push(t.limit)),(t==null?void 0:t.offset)!==void 0&&(c+=" OFFSET ?",s.push(t.offset)),{sql:c,params:s,projectionMap:a.length?a:void 0}}static buildInsert(e,t){let r=Object.keys(t),i=r.map(a=>this.escapeIdentifier(a)).join(", "),n=r.map(()=>"?").join(", "),s=r.map(a=>this.normalizeValue(t[a]));return{sql:`INSERT INTO ${this.escapeIdentifier(e)} (${i}) VALUES (${n})`,params:s}}static buildUpdate(e,t,r){let i=Object.keys(r);if(!i.length)throw new Error("Cannot build UPDATE with empty updates");let n=i.map(a=>`${this.escapeIdentifier(a)} = ?`).join(", "),s=i.map(a=>this.normalizeValue(r[a]));return{sql:`UPDATE ${this.escapeIdentifier(e)} SET ${n} WHERE id = ?`,params:[...s,t]}}static buildDelete(e,t){return{sql:`DELETE FROM ${this.escapeIdentifier(e)} WHERE id = ?`,params:[t]}}};var E=class{constructor(e){this.savepointDepth=0;this.schemaMap=new Map;this.metaInitialized=!1;var t;this.driver=e.driver,this.tableNamePrefix=(t=e.tableNamePrefix)!=null?t:"",this.tableNameResolver=e.tableNameMapper}setTableNameResolver(e){this.tableNameResolver=e}resolveTableName(e){return this.tableNameResolver?String(this.tableNameResolver(e)||"").trim()||e:!this.tableNamePrefix||e.startsWith(this.tableNamePrefix)?e:`${this.tableNamePrefix}${e}`}async ensureMetaInit(){this.metaInitialized||(await this.driver.execute(`
|
|
2
|
+
CREATE TABLE IF NOT EXISTS _schema_version (
|
|
3
|
+
version INTEGER NOT NULL
|
|
4
|
+
)
|
|
5
|
+
`),this.metaInitialized=!0)}async getSchemaVersion(){await this.ensureMetaInit();let e=await this.driver.execute("SELECT version FROM _schema_version LIMIT 1");return e.rows.length?Number(e.rows[0].version):(await this.driver.execute("INSERT INTO _schema_version (version) VALUES (?)",[0]),0)}async setSchemaVersion(e){await this.ensureMetaInit(),(await this.driver.execute("SELECT 1 FROM _schema_version LIMIT 1")).rows.length?await this.driver.execute("UPDATE _schema_version SET version = ?",[e]):await this.driver.execute("INSERT INTO _schema_version (version) VALUES (?)",[e])}async addTable(e){let t=this.resolveTableName(e.name),r=e.columns.map(i=>{let n=`${g(i.name)} ${S(i.type)}`;return i.isOptional||(n+=" NOT NULL"),i.name==="id"&&(n+=" PRIMARY KEY"),n}).join(", ");await this.driver.execute(`CREATE TABLE IF NOT EXISTS ${g(t)} (${r})`);for(let i of e.columns){if(!i.isIndexed)continue;let n=`idx_${t}_${i.name}`;await this.driver.execute(`CREATE INDEX IF NOT EXISTS ${g(n)} ON ${g(t)}(${g(i.name)})`)}this.schemaMap.set(e.name,e)}async addColumn(e,t){let r=this.resolveTableName(e);if((await this.driver.execute(`SELECT name FROM pragma_table_info(${b.escapeLiteral(r)})`)).rows.some(a=>a.name===t.name))return;let n=`ALTER TABLE ${g(r)} ADD COLUMN ${g(t.name)} ${S(t.type)}`;if(!t.isOptional){let a=t.type==="boolean"?"0":t.type==="number"?"0.0":"''";n+=` DEFAULT ${a} NOT NULL`}if(await this.driver.execute(n),t.isIndexed){let a=`idx_${r}_${t.name}`;await this.driver.execute(`CREATE INDEX IF NOT EXISTS ${g(a)} ON ${g(r)}(${g(t.name)})`)}let s=this.schemaMap.get(e);s&&!s.columns.find(a=>a.name===t.name)&&(s.columns.push(t),this.schemaMap.set(e,s))}async get(e,t){var s;let r,i;return typeof t=="string"?i={id:t}:(r=t,i=r.filters),(s=(await this.query(e,{...r,filters:i,limit:1}))[0])!=null?s:null}async query(e,t){var u,m,h;let r=this.resolveTableName(e),i=(u=t==null?void 0:t.joins)!=null&&u.length?{...t,joins:t.joins.map(d=>({...d,table:this.resolveTableName(d.table)}))}:t,{sql:n,params:s,projectionMap:a}=b.buildSelect(r,i),c=(await this.driver.execute(n,s)).rows;if((m=t==null?void 0:t.aggregates)!=null&&m.length)return c;if(t!=null&&t.fields)return((h=t==null?void 0:t.resultShape)!=null?h:"flat")==="nested"&&(a!=null&&a.length)?c.map(y=>{let T={};for(let v of a)T[v.alias]||(T[v.alias]={}),T[v.alias][v.field]=y[v.key];return T}):c;let f=this.schemaMap.get(e);if(!f)return c;let o=Object.fromEntries(f.columns.map(d=>[d.name,d]));return c.map(d=>R(d,o))}async create(e,t){var o;let r=this.resolveTableName(e),{sql:i,params:n}=b.buildInsert(r,t),s=await this.driver.execute(i,n),a=(o=t==null?void 0:t.id)!=null?o:s.insertId!==void 0?String(s.insertId):void 0,p={...t};a!==void 0&&(p.id=a);let c=this.schemaMap.get(e);if(!c)return p;let f=Object.fromEntries(c.columns.map(u=>[u.name,u]));return R(p,f)}async update(e,t,r){if(!Object.keys(r).length){let p=await this.get(e,t);if(!p)throw new Error(`Record not found: ${e}#${t}`);return p}let i=this.resolveTableName(e),{sql:n,params:s}=b.buildUpdate(i,t,r);await this.driver.execute(n,s);let a=await this.get(e,t);if(!a)throw new Error(`Record not found after update: ${e}#${t}`);return a}async delete(e,t){let r=this.resolveTableName(e),{sql:i,params:n}=b.buildDelete(r,t);await this.driver.execute(i,n)}async bulkCreate(e,t){return t.length?this.transaction(async r=>{let i=[];for(let n of t)i.push(await r.create(e,n));return i}):[]}async bulkUpdate(e,t){return t.length?this.transaction(async r=>{let i=[];for(let n of t)Object.keys(n.updates).length&&i.push(await r.update(e,n.id,n.updates));return i}):[]}async transaction(e){let t=this.savepointDepth===0,r=t?"BEGIN":`SAVEPOINT sp${this.savepointDepth}`;await this.driver.execute(r),this.savepointDepth+=1;try{let i=await e(this),n=t?"COMMIT":`RELEASE SAVEPOINT sp${this.savepointDepth-1}`;return await this.driver.execute(n),i}catch(i){let n=t?"ROLLBACK":`ROLLBACK TO SAVEPOINT sp${this.savepointDepth-1}`;try{await this.driver.execute(n)}catch(s){}throw i}finally{this.savepointDepth-=1}}};function g(l){if(!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(l))throw new Error(`Invalid identifier: "${l}"`);return`"${l}"`}function S(l){switch(l){case"string":return"TEXT";case"string[]":return"TEXT";case"number":return"REAL";case"boolean":return"INTEGER";case"json":return"TEXT";case"date":return"TEXT";default:return"TEXT"}}function R(l,e){let t={};for(let[r,i]of Object.entries(l)){let n=e[r],s=i;if(i!=null&&n){if(n.type==="boolean")s=!!i;else if(n.type==="date"&&typeof i=="string"){let a=new Date(i);Number.isNaN(a.getTime())||(s=a)}else if((n.type==="json"||n.type==="string[]")&&typeof i=="string")try{s=JSON.parse(i)}catch{}}t[r]=s}return t}var M=!0;var A=class{constructor(e){this.documentsPath=e.documentsPath.replace(/\/$/,""),this.backend=e.backend}getDocumentPath(e){return`${this.documentsPath}/${e}`}async writeFile(e,t){await this.backend.writeFile(e,t)}async share(e){var t,r;await((r=(t=this.backend).share)==null?void 0:r.call(t,e))}};function N(l,e){if(!e||Object.keys(e).length===0)return l;let t=new URLSearchParams;for(let[i,n]of Object.entries(e))n!=null&&t.set(i,String(n));let r=t.toString();return r?l.includes("?")?`${l}&${r}`:`${l}?${r}`:l}var w=class{constructor(e={}){let t=e.fetch;if(t)this.fetchImpl=t;else{let r=globalThis.fetch;if(typeof r!="function")throw new Error("RnFetchHttpAdapter: globalThis.fetch is not available. Pass a fetch implementation via options.fetch.");this.fetchImpl=r}}async request(e){var n,s;let t=N(e.url,e.query),r=typeof AbortController!="undefined"?new AbortController:null,i=r&&e.timeoutMs?setTimeout(()=>r.abort(),e.timeoutMs):null;try{let a=e.body!==void 0&&e.body!==null,p={...(n=e.headers)!=null?n:{}};a&&!p["content-type"]&&!p["Content-Type"]&&(p["content-type"]="application/json");let c=await this.fetchImpl(t,{method:e.method,headers:p,body:a?JSON.stringify(e.body):void 0,signal:r==null?void 0:r.signal}),f={};c.headers.forEach((m,h)=>{f[h.toLowerCase()]=m});let u=((s=f["content-type"])!=null?s:"").includes("application/json")?await c.json():await c.text();return{status:c.status,headers:f,data:u}}finally{i!==null&&clearTimeout(i)}}};var U=!0;var $=class{constructor(e){this.backend=e.backend}configure(e){var t,r;(r=(t=this.backend).configure)==null||r.call(t,e)}createChannel(e){var t,r;(r=(t=this.backend).createChannel)==null||r.call(t,e)}localNotification(e){this.backend.localNotification(e)}};async function x(l){let t=new TextEncoder().encode(l),r=await crypto.subtle.digest("SHA-256",t);return Array.from(new Uint8Array(r)).map(n=>n.toString(16).padStart(2,"0")).join("")}var I=class{constructor(e={}){this.envVars=e.envVars,this.isOnlineFn=e.isOnline,this.hashPinFn=e.hashPin}getEnvVar(e){var r;if(this.envVars)return this.envVars[e];let t=globalThis.process;return(r=t==null?void 0:t.env)==null?void 0:r[e]}isOnline(){if(this.isOnlineFn)return this.isOnlineFn();let e=globalThis.navigator;return e&&typeof e.onLine=="boolean"?e.onLine:!0}async hashPin(e){var r;if(this.hashPinFn)return this.hashPinFn(e);if((r=globalThis.crypto)==null?void 0:r.subtle)return x(e);throw new Error("RnPlatformAdapter: crypto.subtle tidak tersedia di runtime ini. Injeksikan implementasi hashPin via options.hashPin (misalnya @noble/hashes atau expo-crypto).")}async verifyPin(e,t){return await this.hashPin(e)===t}};function P(l){let e=l.query;if(!e||Object.keys(e).length===0)return l.url;let r=new URLSearchParams(e).toString();return l.url.includes("?")?`${l.url}&${r}`:`${l.url}?${r}`}var O=class{constructor(e={}){this.ws=null;this.connectionOptions=null;this.listeners=new Map;this.reconnectAttempts=0;this.reconnectTimeout=null;this.intentionalClose=!1;var r,i,n,s,a;this.reconnect=(r=e.reconnect)!=null?r:!0,this.maxReconnectAttempts=Math.max(0,(i=e.maxReconnectAttempts)!=null?i:5),this.reconnectDelayMs=Math.max(0,(n=e.reconnectDelayMs)!=null?n:500),this.maxReconnectDelayMs=Math.max(this.reconnectDelayMs,(s=e.maxReconnectDelayMs)!=null?s:3e4);let t=(a=e.webSocket)!=null?a:globalThis.WebSocket;if(typeof t!="function")throw new Error("RnWebSocketAdapter: globalThis.WebSocket is not available. Pass a WebSocket constructor via options.webSocket.");this.WebSocketCtor=t}connect(e){return this.connectionOptions=e,this.intentionalClose=!1,this.reconnectAttempts=0,this.openConnection(e)}async disconnect(){this.intentionalClose=!0,this.clearReconnectTimeout(),this.ws&&(this.ws.close(1e3,"client disconnect"),this.ws=null)}subscribe(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),()=>{let r=this.listeners.get(e);r&&(r.delete(t),r.size===0&&this.listeners.delete(e))}}async publish(e,t){var i,n;if(!this.ws||this.ws.readyState!==this.ws.OPEN)throw new Error(`RnWebSocketAdapter: cannot publish "${e}" \u2014 not connected (readyState=${(n=(i=this.ws)==null?void 0:i.readyState)!=null?n:"null"}).`);let r={event:e,payload:t};this.ws.send(JSON.stringify(r))}isConnected(){return this.ws!==null&&this.ws.readyState===this.ws.OPEN}openConnection(e){return new Promise((t,r)=>{let i=P(e),n;try{n=new this.WebSocketCtor(i)}catch(s){r(s instanceof Error?s:new Error(String(s)));return}n.onopen=()=>{this.ws=n,this.reconnectAttempts=0,t()},n.onerror=()=>{this.ws||r(new Error("RnWebSocketAdapter: connection failed"))},n.onclose=()=>{this.ws===n&&(this.ws=null),!this.intentionalClose&&this.reconnect&&this.connectionOptions&&this.reconnectAttempts<this.maxReconnectAttempts&&this.scheduleReconnect()},n.onmessage=s=>{this.handleMessage(s.data)}})}handleMessage(e){if(typeof e!="string")return;let t;try{t=JSON.parse(e)}catch{return}if(typeof t!="object"||t===null||typeof t.event!="string")return;let{event:r,payload:i}=t,n=this.listeners.get(r);if(n)for(let s of n)try{s(i)}catch{}}scheduleReconnect(){this.clearReconnectTimeout();let e=++this.reconnectAttempts,t=Math.min(this.reconnectDelayMs*Math.pow(2,e-1),this.maxReconnectDelayMs);this.reconnectTimeout=setTimeout(()=>{!this.intentionalClose&&this.connectionOptions&&this.openConnection(this.connectionOptions).catch(()=>{})},t)}clearReconnectTimeout(){this.reconnectTimeout!==null&&(clearTimeout(this.reconnectTimeout),this.reconnectTimeout=null)}};var ee=!0;var k=class{constructor(e){this.store=e.storage}getItem(e){return this.store.getItem(e)}setItem(e,t){return this.store.setItem(e,t)}removeItem(e){return this.store.removeItem(e)}};var ne=!0;var oe={platform:"react-native",ready:!0,notes:["All adapter domains implemented:","RnSqliteAdapter (db, injectable driver),","RnExportAdapter (export, injectable backend),","RnFetchHttpAdapter (http),","RnNotificationAdapter (notification, injectable backend),","RnPlatformAdapter (platform, injectable hashPin/isOnline/envVars),","RnWebSocketAdapter (socket),","AsyncStorageAdapter (storage)."].join(" ")};export{k as AsyncStorageAdapter,M as OFPLATFORM_RN_DB_ADAPTERS_READY,U as OFPLATFORM_RN_HTTP_ADAPTERS_READY,ee as OFPLATFORM_RN_SOCKET_ADAPTERS_READY,oe as OFPLATFORM_RN_STATUS,ne as OFPLATFORM_RN_STORAGE_ADAPTERS_READY,A as RnExportAdapter,w as RnFetchHttpAdapter,$ as RnNotificationAdapter,I as RnPlatformAdapter,E as RnSqliteAdapter,b as RnSqliteQueryBuilder,O as RnWebSocketAdapter};
|
package/dist/index.js
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var E=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var P=Object.getOwnPropertyNames;var C=Object.prototype.hasOwnProperty;var L=(l,e)=>{for(var t in e)E(l,t,{get:e[t],enumerable:!0})},q=(l,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of P(e))!C.call(l,i)&&i!==t&&E(l,i,{get:()=>e[i],enumerable:!(r=x(e,i))||r.enumerable});return l};var M=l=>q(E({},"__esModule",{value:!0}),l);var z={};L(z,{AsyncStorageAdapter:()=>O,OFPLATFORM_RN_DB_ADAPTERS_READY:()=>D,OFPLATFORM_RN_HTTP_ADAPTERS_READY:()=>V,OFPLATFORM_RN_SOCKET_ADAPTERS_READY:()=>B,OFPLATFORM_RN_STATUS:()=>U,OFPLATFORM_RN_STORAGE_ADAPTERS_READY:()=>W,RnExportAdapter:()=>R,RnFetchHttpAdapter:()=>A,RnNotificationAdapter:()=>w,RnPlatformAdapter:()=>$,RnSqliteAdapter:()=>S,RnSqliteQueryBuilder:()=>b,RnWebSocketAdapter:()=>I});module.exports=M(z);var b=class{static escapeIdentifier(e){if(!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(e))throw new Error(`Invalid identifier: "${e}"`);return`"${e}"`}static escapeLiteral(e){return`'${e.replace(/'/g,"''")}'`}static normalizeValue(e){return e==null?null:typeof e=="boolean"?e?1:0:e instanceof Date?e.toISOString():Array.isArray(e)||typeof e=="object"&&e!==null?JSON.stringify(e):e}static buildValueExpr(e,t){if(e.type==="column"){let r=e.tableAlias||t;return{sql:`${this.escapeIdentifier(r)}.${this.escapeIdentifier(e.field)}`,params:[]}}return{sql:"?",params:[this.normalizeValue(e.value)]}}static buildFieldCondition(e,t){if("field"in e){let n=e.alias||t,s=`${this.escapeIdentifier(n)}.${this.escapeIdentifier(e.field)}`,a=e.operator||"=";if(a==="BETWEEN"){if(!Array.isArray(e.value)||e.value.length!==2)throw new Error("BETWEEN requires value tuple [min,max]");return{sql:`${s} BETWEEN ? AND ?`,params:[this.normalizeValue(e.value[0]),this.normalizeValue(e.value[1])]}}if(a==="IN"||a==="NOT IN"){if(!Array.isArray(e.value))throw new Error(`${a} requires array value`);return e.value.length===0?{sql:a==="IN"?"FALSE":"TRUE",params:[]}:{sql:`${s} ${a} (${e.value.map(()=>"?").join(", ")})`,params:e.value.map(p=>this.normalizeValue(p))}}if(a==="IS NULL"||a==="IS NOT NULL")return{sql:`${s} ${a}`,params:[]};if(a==="LIKE"){let c=("likeMode"in e?e.likeMode:"default")==="case-sensitive"?" COLLATE BINARY":"";return{sql:`${s} LIKE${c} ?`,params:[this.normalizeValue(e.value)]}}return{sql:`${s} ${a} ?`,params:[this.normalizeValue(e.value)]}}let r=this.buildValueExpr(e.left,t);if(e.operator==="IS NULL"||e.operator==="IS NOT NULL")return{sql:`${r.sql} ${e.operator}`,params:r.params};let i=this.buildValueExpr(e.right,t);if(e.operator==="IN"||e.operator==="NOT IN"){if(e.right.type!=="literal"||!Array.isArray(e.right.value))throw new Error(`${e.operator} requires literal array on right side`);return e.right.value.length===0?{sql:e.operator==="IN"?"FALSE":"TRUE",params:[]}:{sql:`${r.sql} ${e.operator} (${e.right.value.map(()=>"?").join(", ")})`,params:[...r.params,...e.right.value.map(n=>this.normalizeValue(n))]}}if(e.operator==="BETWEEN"){if(e.right.type!=="literal"||!Array.isArray(e.right.value)||e.right.value.length!==2)throw new Error("BETWEEN requires literal tuple [min,max] on right side");return{sql:`${r.sql} BETWEEN ? AND ?`,params:[...r.params,this.normalizeValue(e.right.value[0]),this.normalizeValue(e.right.value[1])]}}if(e.operator==="LIKE"){let n=e.likeMode==="case-sensitive"?" COLLATE BINARY":"";return{sql:`${r.sql} LIKE${n} ${i.sql}`,params:[...r.params,...i.params]}}return{sql:`${r.sql} ${e.operator} ${i.sql}`,params:[...r.params,...i.params]}}static buildFilter(e,t="t0"){let r=e.and;if(Array.isArray(r)){let n=r.map(s=>this.buildFilter(s,t));return{sql:n.map(s=>`(${s.sql})`).join(" AND "),params:n.flatMap(s=>s.params)}}let i=e.or;if(Array.isArray(i)){let n=i.map(s=>this.buildFilter(s,t));return{sql:n.map(s=>`(${s.sql})`).join(" OR "),params:n.flatMap(s=>s.params)}}if(e!==null&&typeof e=="object"&&!Array.isArray(e)){if("left"in e||"field"in e)return this.buildFieldCondition(e,t);let n=Object.entries(e);if(n.length===0)return{sql:"TRUE",params:[]};let s=n.map(([a,p])=>({sql:`${this.escapeIdentifier(t)}.${this.escapeIdentifier(a)} = ?`,params:[this.normalizeValue(p)]}));return{sql:s.map(a=>`(${a.sql})`).join(" AND "),params:s.flatMap(a=>a.params)}}throw new Error(`Invalid filter expression: ${JSON.stringify(e)}`)}static buildGroupBy(e,t="t0"){return!e||!e.length?"":` GROUP BY ${e.map(i=>{let n=typeof i.field=="string"?{tableAlias:t,field:i.field}:i.field;return`${this.escapeIdentifier(n.tableAlias)}.${this.escapeIdentifier(n.field)}`}).join(", ")}`}static buildSort(e,t="t0"){return!e||!e.length?"":e.map(r=>{let i=typeof r.field=="string"?{tableAlias:t,field:r.field}:r.field;return`${this.escapeIdentifier(i.tableAlias)}.${this.escapeIdentifier(i.field)} ${r.direction.toUpperCase()}`}).join(", ")}static buildSelect(e,t){var f;let r=t,i=(t==null?void 0:t.mainAlias)||"t0",n=(t==null?void 0:t.joins)||[],s=[],a=[],p=[];if(t!=null&&t.aggregates&&t.aggregates.length>0)t.groupBy&&t.groupBy.forEach(o=>{let u=typeof o.field=="string"?{tableAlias:i,field:o.field}:o.field;p.push(`${this.escapeIdentifier(u.tableAlias)}.${this.escapeIdentifier(u.field)} AS ${this.escapeIdentifier(`${u.tableAlias}_${u.field}`)}`)}),t.aggregates.forEach(o=>{let u=typeof o.field=="string"?{tableAlias:i,field:o.field}:o.field,m=`${this.escapeIdentifier(u.tableAlias)}.${this.escapeIdentifier(u.field)}`;p.push(`${o.function}(${m}) AS ${this.escapeIdentifier(o.as)}`)});else if(t!=null&&t.fields){let o=0;for(let[u,m]of Object.entries(t.fields))for(let h of m){let y=((f=r==null?void 0:r.resultShape)!=null?f:"flat")==="nested"?`__c${o++}`:`${u}_${h}`;a.push({key:y,alias:u,field:h}),p.push(`${this.escapeIdentifier(u)}.${this.escapeIdentifier(h)} AS ${this.escapeIdentifier(y)}`)}}else p.push(`${this.escapeIdentifier(i)}.*`);let c=`SELECT ${p.join(", ")} FROM ${this.escapeIdentifier(e)} AS ${this.escapeIdentifier(i)}`;if(n.forEach(o=>{let u=(o.type||"inner").toUpperCase();if(u==="RIGHT"||u==="FULL")throw new Error(`RnSqliteAdapter does not support ${u} JOIN`);let m=[];o.conditions.forEach(h=>{let d=this.buildValueExpr(h.left,i),y=this.buildValueExpr(h.right,i);m.push(`${d.sql} ${h.operator} ${y.sql}`),s.push(...d.params,...y.params)}),c+=` ${u} JOIN ${this.escapeIdentifier(o.table)} AS ${this.escapeIdentifier(o.alias)} ON ${m.join(" AND ")}`}),t!=null&&t.filters){let o=this.buildFilter(t.filters,i);o.sql&&o.sql!=="TRUE"&&(c+=` WHERE ${o.sql}`,s.push(...o.params))}if(t!=null&&t.aggregates&&t.aggregates.length>0){if(c+=this.buildGroupBy(t.groupBy,i),t.having){let o=this.buildFilter(t.having,i);o.sql&&o.sql!=="TRUE"&&(c+=` HAVING ${o.sql}`,s.push(...o.params))}return{sql:c,params:s,projectionMap:a.length?a:void 0}}if(c+=this.buildGroupBy(t==null?void 0:t.groupBy,i),t!=null&&t.having){let o=this.buildFilter(t.having,i);o.sql&&o.sql!=="TRUE"&&(c+=` HAVING ${o.sql}`,s.push(...o.params))}if(t!=null&&t.sort&&t.sort.length){let o=this.buildSort(t.sort,i);o&&(c+=` ORDER BY ${o}`)}return(t==null?void 0:t.limit)!==void 0&&(c+=" LIMIT ?",s.push(t.limit)),(t==null?void 0:t.offset)!==void 0&&(c+=" OFFSET ?",s.push(t.offset)),{sql:c,params:s,projectionMap:a.length?a:void 0}}static buildInsert(e,t){let r=Object.keys(t),i=r.map(a=>this.escapeIdentifier(a)).join(", "),n=r.map(()=>"?").join(", "),s=r.map(a=>this.normalizeValue(t[a]));return{sql:`INSERT INTO ${this.escapeIdentifier(e)} (${i}) VALUES (${n})`,params:s}}static buildUpdate(e,t,r){let i=Object.keys(r);if(!i.length)throw new Error("Cannot build UPDATE with empty updates");let n=i.map(a=>`${this.escapeIdentifier(a)} = ?`).join(", "),s=i.map(a=>this.normalizeValue(r[a]));return{sql:`UPDATE ${this.escapeIdentifier(e)} SET ${n} WHERE id = ?`,params:[...s,t]}}static buildDelete(e,t){return{sql:`DELETE FROM ${this.escapeIdentifier(e)} WHERE id = ?`,params:[t]}}};var S=class{constructor(e){this.savepointDepth=0;this.schemaMap=new Map;this.metaInitialized=!1;var t;this.driver=e.driver,this.tableNamePrefix=(t=e.tableNamePrefix)!=null?t:"",this.tableNameResolver=e.tableNameMapper}setTableNameResolver(e){this.tableNameResolver=e}resolveTableName(e){return this.tableNameResolver?String(this.tableNameResolver(e)||"").trim()||e:!this.tableNamePrefix||e.startsWith(this.tableNamePrefix)?e:`${this.tableNamePrefix}${e}`}async ensureMetaInit(){this.metaInitialized||(await this.driver.execute(`
|
|
2
|
+
CREATE TABLE IF NOT EXISTS _schema_version (
|
|
3
|
+
version INTEGER NOT NULL
|
|
4
|
+
)
|
|
5
|
+
`),this.metaInitialized=!0)}async getSchemaVersion(){await this.ensureMetaInit();let e=await this.driver.execute("SELECT version FROM _schema_version LIMIT 1");return e.rows.length?Number(e.rows[0].version):(await this.driver.execute("INSERT INTO _schema_version (version) VALUES (?)",[0]),0)}async setSchemaVersion(e){await this.ensureMetaInit(),(await this.driver.execute("SELECT 1 FROM _schema_version LIMIT 1")).rows.length?await this.driver.execute("UPDATE _schema_version SET version = ?",[e]):await this.driver.execute("INSERT INTO _schema_version (version) VALUES (?)",[e])}async addTable(e){let t=this.resolveTableName(e.name),r=e.columns.map(i=>{let n=`${g(i.name)} ${k(i.type)}`;return i.isOptional||(n+=" NOT NULL"),i.name==="id"&&(n+=" PRIMARY KEY"),n}).join(", ");await this.driver.execute(`CREATE TABLE IF NOT EXISTS ${g(t)} (${r})`);for(let i of e.columns){if(!i.isIndexed)continue;let n=`idx_${t}_${i.name}`;await this.driver.execute(`CREATE INDEX IF NOT EXISTS ${g(n)} ON ${g(t)}(${g(i.name)})`)}this.schemaMap.set(e.name,e)}async addColumn(e,t){let r=this.resolveTableName(e);if((await this.driver.execute(`SELECT name FROM pragma_table_info(${b.escapeLiteral(r)})`)).rows.some(a=>a.name===t.name))return;let n=`ALTER TABLE ${g(r)} ADD COLUMN ${g(t.name)} ${k(t.type)}`;if(!t.isOptional){let a=t.type==="boolean"?"0":t.type==="number"?"0.0":"''";n+=` DEFAULT ${a} NOT NULL`}if(await this.driver.execute(n),t.isIndexed){let a=`idx_${r}_${t.name}`;await this.driver.execute(`CREATE INDEX IF NOT EXISTS ${g(a)} ON ${g(r)}(${g(t.name)})`)}let s=this.schemaMap.get(e);s&&!s.columns.find(a=>a.name===t.name)&&(s.columns.push(t),this.schemaMap.set(e,s))}async get(e,t){var s;let r,i;return typeof t=="string"?i={id:t}:(r=t,i=r.filters),(s=(await this.query(e,{...r,filters:i,limit:1}))[0])!=null?s:null}async query(e,t){var u,m,h;let r=this.resolveTableName(e),i=(u=t==null?void 0:t.joins)!=null&&u.length?{...t,joins:t.joins.map(d=>({...d,table:this.resolveTableName(d.table)}))}:t,{sql:n,params:s,projectionMap:a}=b.buildSelect(r,i),c=(await this.driver.execute(n,s)).rows;if((m=t==null?void 0:t.aggregates)!=null&&m.length)return c;if(t!=null&&t.fields)return((h=t==null?void 0:t.resultShape)!=null?h:"flat")==="nested"&&(a!=null&&a.length)?c.map(y=>{let T={};for(let v of a)T[v.alias]||(T[v.alias]={}),T[v.alias][v.field]=y[v.key];return T}):c;let f=this.schemaMap.get(e);if(!f)return c;let o=Object.fromEntries(f.columns.map(d=>[d.name,d]));return c.map(d=>N(d,o))}async create(e,t){var o;let r=this.resolveTableName(e),{sql:i,params:n}=b.buildInsert(r,t),s=await this.driver.execute(i,n),a=(o=t==null?void 0:t.id)!=null?o:s.insertId!==void 0?String(s.insertId):void 0,p={...t};a!==void 0&&(p.id=a);let c=this.schemaMap.get(e);if(!c)return p;let f=Object.fromEntries(c.columns.map(u=>[u.name,u]));return N(p,f)}async update(e,t,r){if(!Object.keys(r).length){let p=await this.get(e,t);if(!p)throw new Error(`Record not found: ${e}#${t}`);return p}let i=this.resolveTableName(e),{sql:n,params:s}=b.buildUpdate(i,t,r);await this.driver.execute(n,s);let a=await this.get(e,t);if(!a)throw new Error(`Record not found after update: ${e}#${t}`);return a}async delete(e,t){let r=this.resolveTableName(e),{sql:i,params:n}=b.buildDelete(r,t);await this.driver.execute(i,n)}async bulkCreate(e,t){return t.length?this.transaction(async r=>{let i=[];for(let n of t)i.push(await r.create(e,n));return i}):[]}async bulkUpdate(e,t){return t.length?this.transaction(async r=>{let i=[];for(let n of t)Object.keys(n.updates).length&&i.push(await r.update(e,n.id,n.updates));return i}):[]}async transaction(e){let t=this.savepointDepth===0,r=t?"BEGIN":`SAVEPOINT sp${this.savepointDepth}`;await this.driver.execute(r),this.savepointDepth+=1;try{let i=await e(this),n=t?"COMMIT":`RELEASE SAVEPOINT sp${this.savepointDepth-1}`;return await this.driver.execute(n),i}catch(i){let n=t?"ROLLBACK":`ROLLBACK TO SAVEPOINT sp${this.savepointDepth-1}`;try{await this.driver.execute(n)}catch(s){}throw i}finally{this.savepointDepth-=1}}};function g(l){if(!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(l))throw new Error(`Invalid identifier: "${l}"`);return`"${l}"`}function k(l){switch(l){case"string":return"TEXT";case"string[]":return"TEXT";case"number":return"REAL";case"boolean":return"INTEGER";case"json":return"TEXT";case"date":return"TEXT";default:return"TEXT"}}function N(l,e){let t={};for(let[r,i]of Object.entries(l)){let n=e[r],s=i;if(i!=null&&n){if(n.type==="boolean")s=!!i;else if(n.type==="date"&&typeof i=="string"){let a=new Date(i);Number.isNaN(a.getTime())||(s=a)}else if((n.type==="json"||n.type==="string[]")&&typeof i=="string")try{s=JSON.parse(i)}catch{}}t[r]=s}return t}var D=!0;var R=class{constructor(e){this.documentsPath=e.documentsPath.replace(/\/$/,""),this.backend=e.backend}getDocumentPath(e){return`${this.documentsPath}/${e}`}async writeFile(e,t){await this.backend.writeFile(e,t)}async share(e){var t,r;await((r=(t=this.backend).share)==null?void 0:r.call(t,e))}};function F(l,e){if(!e||Object.keys(e).length===0)return l;let t=new URLSearchParams;for(let[i,n]of Object.entries(e))n!=null&&t.set(i,String(n));let r=t.toString();return r?l.includes("?")?`${l}&${r}`:`${l}?${r}`:l}var A=class{constructor(e={}){let t=e.fetch;if(t)this.fetchImpl=t;else{let r=globalThis.fetch;if(typeof r!="function")throw new Error("RnFetchHttpAdapter: globalThis.fetch is not available. Pass a fetch implementation via options.fetch.");this.fetchImpl=r}}async request(e){var n,s;let t=F(e.url,e.query),r=typeof AbortController!="undefined"?new AbortController:null,i=r&&e.timeoutMs?setTimeout(()=>r.abort(),e.timeoutMs):null;try{let a=e.body!==void 0&&e.body!==null,p={...(n=e.headers)!=null?n:{}};a&&!p["content-type"]&&!p["Content-Type"]&&(p["content-type"]="application/json");let c=await this.fetchImpl(t,{method:e.method,headers:p,body:a?JSON.stringify(e.body):void 0,signal:r==null?void 0:r.signal}),f={};c.headers.forEach((m,h)=>{f[h.toLowerCase()]=m});let u=((s=f["content-type"])!=null?s:"").includes("application/json")?await c.json():await c.text();return{status:c.status,headers:f,data:u}}finally{i!==null&&clearTimeout(i)}}};var V=!0;var w=class{constructor(e){this.backend=e.backend}configure(e){var t,r;(r=(t=this.backend).configure)==null||r.call(t,e)}createChannel(e){var t,r;(r=(t=this.backend).createChannel)==null||r.call(t,e)}localNotification(e){this.backend.localNotification(e)}};async function _(l){let t=new TextEncoder().encode(l),r=await crypto.subtle.digest("SHA-256",t);return Array.from(new Uint8Array(r)).map(n=>n.toString(16).padStart(2,"0")).join("")}var $=class{constructor(e={}){this.envVars=e.envVars,this.isOnlineFn=e.isOnline,this.hashPinFn=e.hashPin}getEnvVar(e){var r;if(this.envVars)return this.envVars[e];let t=globalThis.process;return(r=t==null?void 0:t.env)==null?void 0:r[e]}isOnline(){if(this.isOnlineFn)return this.isOnlineFn();let e=globalThis.navigator;return e&&typeof e.onLine=="boolean"?e.onLine:!0}async hashPin(e){var r;if(this.hashPinFn)return this.hashPinFn(e);if((r=globalThis.crypto)==null?void 0:r.subtle)return _(e);throw new Error("RnPlatformAdapter: crypto.subtle tidak tersedia di runtime ini. Injeksikan implementasi hashPin via options.hashPin (misalnya @noble/hashes atau expo-crypto).")}async verifyPin(e,t){return await this.hashPin(e)===t}};function j(l){let e=l.query;if(!e||Object.keys(e).length===0)return l.url;let r=new URLSearchParams(e).toString();return l.url.includes("?")?`${l.url}&${r}`:`${l.url}?${r}`}var I=class{constructor(e={}){this.ws=null;this.connectionOptions=null;this.listeners=new Map;this.reconnectAttempts=0;this.reconnectTimeout=null;this.intentionalClose=!1;var r,i,n,s,a;this.reconnect=(r=e.reconnect)!=null?r:!0,this.maxReconnectAttempts=Math.max(0,(i=e.maxReconnectAttempts)!=null?i:5),this.reconnectDelayMs=Math.max(0,(n=e.reconnectDelayMs)!=null?n:500),this.maxReconnectDelayMs=Math.max(this.reconnectDelayMs,(s=e.maxReconnectDelayMs)!=null?s:3e4);let t=(a=e.webSocket)!=null?a:globalThis.WebSocket;if(typeof t!="function")throw new Error("RnWebSocketAdapter: globalThis.WebSocket is not available. Pass a WebSocket constructor via options.webSocket.");this.WebSocketCtor=t}connect(e){return this.connectionOptions=e,this.intentionalClose=!1,this.reconnectAttempts=0,this.openConnection(e)}async disconnect(){this.intentionalClose=!0,this.clearReconnectTimeout(),this.ws&&(this.ws.close(1e3,"client disconnect"),this.ws=null)}subscribe(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),()=>{let r=this.listeners.get(e);r&&(r.delete(t),r.size===0&&this.listeners.delete(e))}}async publish(e,t){var i,n;if(!this.ws||this.ws.readyState!==this.ws.OPEN)throw new Error(`RnWebSocketAdapter: cannot publish "${e}" \u2014 not connected (readyState=${(n=(i=this.ws)==null?void 0:i.readyState)!=null?n:"null"}).`);let r={event:e,payload:t};this.ws.send(JSON.stringify(r))}isConnected(){return this.ws!==null&&this.ws.readyState===this.ws.OPEN}openConnection(e){return new Promise((t,r)=>{let i=j(e),n;try{n=new this.WebSocketCtor(i)}catch(s){r(s instanceof Error?s:new Error(String(s)));return}n.onopen=()=>{this.ws=n,this.reconnectAttempts=0,t()},n.onerror=()=>{this.ws||r(new Error("RnWebSocketAdapter: connection failed"))},n.onclose=()=>{this.ws===n&&(this.ws=null),!this.intentionalClose&&this.reconnect&&this.connectionOptions&&this.reconnectAttempts<this.maxReconnectAttempts&&this.scheduleReconnect()},n.onmessage=s=>{this.handleMessage(s.data)}})}handleMessage(e){if(typeof e!="string")return;let t;try{t=JSON.parse(e)}catch{return}if(typeof t!="object"||t===null||typeof t.event!="string")return;let{event:r,payload:i}=t,n=this.listeners.get(r);if(n)for(let s of n)try{s(i)}catch{}}scheduleReconnect(){this.clearReconnectTimeout();let e=++this.reconnectAttempts,t=Math.min(this.reconnectDelayMs*Math.pow(2,e-1),this.maxReconnectDelayMs);this.reconnectTimeout=setTimeout(()=>{!this.intentionalClose&&this.connectionOptions&&this.openConnection(this.connectionOptions).catch(()=>{})},t)}clearReconnectTimeout(){this.reconnectTimeout!==null&&(clearTimeout(this.reconnectTimeout),this.reconnectTimeout=null)}};var B=!0;var O=class{constructor(e){this.store=e.storage}getItem(e){return this.store.getItem(e)}setItem(e,t){return this.store.setItem(e,t)}removeItem(e){return this.store.removeItem(e)}};var W=!0;var U={platform:"react-native",ready:!0,notes:["All adapter domains implemented:","RnSqliteAdapter (db, injectable driver),","RnExportAdapter (export, injectable backend),","RnFetchHttpAdapter (http),","RnNotificationAdapter (notification, injectable backend),","RnPlatformAdapter (platform, injectable hashPin/isOnline/envVars),","RnWebSocketAdapter (socket),","AsyncStorageAdapter (storage)."].join(" ")};
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ofplatform-rn",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "React Native platform adapters for of* host apps.",
|
|
5
|
+
"description": "React Native platform adapters for of* host apps (SQLite, fetch, WebSocket, AsyncStorage).",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Agus Made",
|
|
8
8
|
"email": "krisnaparta@gmail.com"
|
|
@@ -24,17 +24,19 @@
|
|
|
24
24
|
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
25
25
|
"build": "npm run clean && tsc -p tsconfig.build.json && node esbuild.config.js",
|
|
26
26
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
27
|
-
"
|
|
27
|
+
"test": "npm run build && node --test ./tests/*.test.js",
|
|
28
|
+
"ci:check": "npm run typecheck && npm run test",
|
|
28
29
|
"prepublishOnly": "npm run ci:check",
|
|
29
30
|
"prepack": "npm run build",
|
|
30
31
|
"bundle": "node esbuild.config.js"
|
|
31
32
|
},
|
|
32
33
|
"dependencies": {
|
|
33
|
-
"ofcore": "0.
|
|
34
|
+
"ofcore": "0.2.0-alpha.0"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
|
-
"
|
|
37
|
-
"
|
|
37
|
+
"esbuild": "^0.25.11",
|
|
38
|
+
"sql.js": "^1.14.1",
|
|
39
|
+
"typescript": "^5.9.3"
|
|
38
40
|
},
|
|
39
41
|
"publishConfig": {
|
|
40
42
|
"access": "public"
|