@symbiote-native/sqlite 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/README.md +283 -0
- package/build/angular/index.d.ts +2 -0
- package/build/angular/index.js +2 -0
- package/build/angular/sqlite.service.d.ts +41 -0
- package/build/angular/sqlite.service.js +127 -0
- package/build/core/index.d.ts +10 -0
- package/build/core/index.js +6 -0
- package/build/core/native-database.d.ts +65 -0
- package/build/core/native-database.js +13 -0
- package/build/core/native-module.d.ts +25 -0
- package/build/core/native-module.js +17 -0
- package/build/core/native-session.d.ts +20 -0
- package/build/core/native-session.js +1 -0
- package/build/core/native-statement.d.ts +45 -0
- package/build/core/native-statement.js +6 -0
- package/build/core/param-utils.d.ts +28 -0
- package/build/core/param-utils.js +126 -0
- package/build/core/path-utils.d.ts +6 -0
- package/build/core/path-utils.js +29 -0
- package/build/core/query-utils.d.ts +17 -0
- package/build/core/query-utils.js +37 -0
- package/build/core/sqlite-database.d.ts +246 -0
- package/build/core/sqlite-database.js +427 -0
- package/build/core/sqlite-session.d.ts +84 -0
- package/build/core/sqlite-session.js +121 -0
- package/build/core/sqlite-statement.d.ts +96 -0
- package/build/core/sqlite-statement.js +321 -0
- package/build/core/sqlite-tagged-query.d.ts +73 -0
- package/build/core/sqlite-tagged-query.js +119 -0
- package/build/core/storage.d.ts +107 -0
- package/build/core/storage.js +382 -0
- package/build/core/types.d.ts +30 -0
- package/build/core/types.js +1 -0
- package/build/react/index.d.ts +2 -0
- package/build/react/index.js +2 -0
- package/build/react/sqlite-context.d.ts +25 -0
- package/build/react/sqlite-context.js +129 -0
- package/build/solid/index.d.ts +2 -0
- package/build/solid/index.js +2 -0
- package/build/solid/sqlite-context.d.ts +23 -0
- package/build/solid/sqlite-context.js +106 -0
- package/build/svelte/SQLiteProvider.svelte +88 -0
- package/build/svelte/SQLiteProvider.svelte.d.ts +13 -0
- package/build/svelte/index.d.ts +3 -0
- package/build/svelte/index.js +5 -0
- package/build/svelte/sqlite-context.d.ts +11 -0
- package/build/svelte/sqlite-context.js +23 -0
- package/build/vue/index.d.ts +2 -0
- package/build/vue/index.js +2 -0
- package/build/vue/sqlite-context.d.ts +21 -0
- package/build/vue/sqlite-context.js +69 -0
- package/build-ngc/angular/index.d.ts +2 -0
- package/build-ngc/angular/index.js +3 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/angular/sqlite.service.d.ts +44 -0
- package/build-ngc/angular/sqlite.service.js +85 -0
- package/build-ngc/angular/sqlite.service.js.map +1 -0
- package/build-ngc/core/index.d.ts +10 -0
- package/build-ngc/core/index.js +7 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/native-database.d.ts +65 -0
- package/build-ngc/core/native-database.js +14 -0
- package/build-ngc/core/native-database.js.map +1 -0
- package/build-ngc/core/native-module.d.ts +25 -0
- package/build-ngc/core/native-module.js +18 -0
- package/build-ngc/core/native-module.js.map +1 -0
- package/build-ngc/core/native-session.d.ts +20 -0
- package/build-ngc/core/native-session.js +2 -0
- package/build-ngc/core/native-session.js.map +1 -0
- package/build-ngc/core/native-statement.d.ts +45 -0
- package/build-ngc/core/native-statement.js +7 -0
- package/build-ngc/core/native-statement.js.map +1 -0
- package/build-ngc/core/param-utils.d.ts +28 -0
- package/build-ngc/core/param-utils.js +127 -0
- package/build-ngc/core/param-utils.js.map +1 -0
- package/build-ngc/core/path-utils.d.ts +6 -0
- package/build-ngc/core/path-utils.js +30 -0
- package/build-ngc/core/path-utils.js.map +1 -0
- package/build-ngc/core/query-utils.d.ts +17 -0
- package/build-ngc/core/query-utils.js +38 -0
- package/build-ngc/core/query-utils.js.map +1 -0
- package/build-ngc/core/sqlite-database.d.ts +246 -0
- package/build-ngc/core/sqlite-database.js +428 -0
- package/build-ngc/core/sqlite-database.js.map +1 -0
- package/build-ngc/core/sqlite-session.d.ts +84 -0
- package/build-ngc/core/sqlite-session.js +122 -0
- package/build-ngc/core/sqlite-session.js.map +1 -0
- package/build-ngc/core/sqlite-statement.d.ts +96 -0
- package/build-ngc/core/sqlite-statement.js +322 -0
- package/build-ngc/core/sqlite-statement.js.map +1 -0
- package/build-ngc/core/sqlite-tagged-query.d.ts +73 -0
- package/build-ngc/core/sqlite-tagged-query.js +120 -0
- package/build-ngc/core/sqlite-tagged-query.js.map +1 -0
- package/build-ngc/core/storage.d.ts +107 -0
- package/build-ngc/core/storage.js +383 -0
- package/build-ngc/core/storage.js.map +1 -0
- package/build-ngc/core/types.d.ts +30 -0
- package/build-ngc/core/types.js +2 -0
- package/build-ngc/core/types.js.map +1 -0
- package/native-link.json +12 -0
- package/package.json +141 -0
- package/src/angular/index.ts +2 -0
- package/src/angular/sqlite.service.ts +114 -0
- package/src/core/index.ts +40 -0
- package/src/core/native-database.ts +118 -0
- package/src/core/native-module.ts +57 -0
- package/src/core/native-session.ts +64 -0
- package/src/core/native-statement.ts +85 -0
- package/src/core/param-utils.ts +163 -0
- package/src/core/path-utils.ts +38 -0
- package/src/core/query-utils.ts +45 -0
- package/src/core/sqlite-database.ts +676 -0
- package/src/core/sqlite-session.ts +165 -0
- package/src/core/sqlite-statement.ts +578 -0
- package/src/core/sqlite-tagged-query.ts +160 -0
- package/src/core/storage.ts +492 -0
- package/src/core/types.ts +33 -0
- package/src/react/index.ts +2 -0
- package/src/react/sqlite-context.tsx +244 -0
- package/src/solid/index.ts +2 -0
- package/src/solid/sqlite-context.ts +156 -0
- package/src/svelte/SQLiteProvider.svelte +88 -0
- package/src/svelte/index.ts +6 -0
- package/src/svelte/sqlite-context.ts +32 -0
- package/src/svelte/svelte-compile.test-helper.ts +135 -0
- package/src/vue/index.ts +2 -0
- package/src/vue/sqlite-context.ts +105 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Angular's DI twin of upstream's `<SQLiteProvider>`/`useSQLiteContext`
|
|
2
|
+
// (.vendors/expo@sdk-57 packages/expo-sqlite/src/hooks.tsx): ONE database instance app-wide by
|
|
3
|
+
// default, opened once and read through DI instead of a React context.
|
|
4
|
+
//
|
|
5
|
+
// Declarative config goes through `provideSqliteDatabase()` — the
|
|
6
|
+
// `providers: [{ provide: TOKEN, useValue }]` pattern this repo already uses for
|
|
7
|
+
// gate-demand.ts's `provideGateDemand()` — rather than a scoping directive like navigation's
|
|
8
|
+
// `symbioteNavigationScope`. That directive pattern deliberately mints a FRESH service instance
|
|
9
|
+
// per usage (one per route); wrong here, since a database is a singleton to open once, not
|
|
10
|
+
// something to re-scope per screen.
|
|
11
|
+
//
|
|
12
|
+
// Async state rides Angular's own `resource()` (adapters/angular/src/render/index.ts already
|
|
13
|
+
// leans on it for TransferState) rather than a hand-rolled Promise/Observable — it is the
|
|
14
|
+
// signals-first primitive this Angular adapter already uses for "async value + error + loading",
|
|
15
|
+
// so `database`/`error`/`isLoading` come straight off it instead of a second, parallel state
|
|
16
|
+
// machine. Its own contract already matches upstream's non-Suspense provider: a failure PARKS in
|
|
17
|
+
// `error()` rather than throwing, exactly like `SQLiteProviderNonSuspense`'s `onError` branch
|
|
18
|
+
// parks it in React state instead of re-throwing.
|
|
19
|
+
//
|
|
20
|
+
// Deliberately excluded, same as `../core`: `assetSource` (needs `expo-asset`, out of scope).
|
|
21
|
+
// No Suspense equivalent — Angular has none.
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
Injectable,
|
|
25
|
+
InjectionToken,
|
|
26
|
+
effect,
|
|
27
|
+
inject,
|
|
28
|
+
resource,
|
|
29
|
+
signal,
|
|
30
|
+
type OnDestroy,
|
|
31
|
+
type Provider,
|
|
32
|
+
} from '@angular/core';
|
|
33
|
+
import { openDatabaseAsync } from '../core';
|
|
34
|
+
import type { IOpenDatabaseOptions } from '../core';
|
|
35
|
+
|
|
36
|
+
export interface ISqliteConfig {
|
|
37
|
+
/** The name of the database file to open. */
|
|
38
|
+
databaseName: string;
|
|
39
|
+
/** The directory the database file is located in. Defaults to `defaultDatabaseDirectory`. */
|
|
40
|
+
directory?: string;
|
|
41
|
+
/** Open options — `onInit` runs once, right after the native handle opens. */
|
|
42
|
+
options?: IOpenDatabaseOptions;
|
|
43
|
+
/**
|
|
44
|
+
* Called instead of the failure sitting quietly in `SqliteService#error` — mirrors upstream's
|
|
45
|
+
* `SQLiteProviderProps.onError`. Omit it to read the failure off `error()` instead.
|
|
46
|
+
*/
|
|
47
|
+
onError?: (error: Error) => void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const SQLITE_CONFIG = new InjectionToken<ISqliteConfig>(
|
|
51
|
+
'symbiote.sqlite-config',
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Declarative config for `SqliteService`'s app-wide database — pass to `bootstrapApplication`'s
|
|
56
|
+
* (or any environment injector's) `providers`, mirroring mounting `<SQLiteProvider>` once near
|
|
57
|
+
* the app root.
|
|
58
|
+
*/
|
|
59
|
+
export function provideSqliteDatabase(config: ISqliteConfig): Provider {
|
|
60
|
+
return { provide: SQLITE_CONFIG, useValue: config };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Injectable({ providedIn: 'root' })
|
|
64
|
+
export class SqliteService implements OnDestroy {
|
|
65
|
+
private readonly config = signal<ISqliteConfig | undefined>(undefined);
|
|
66
|
+
private readonly onErrorHandler = signal<
|
|
67
|
+
((error: Error) => void) | undefined
|
|
68
|
+
>(undefined);
|
|
69
|
+
|
|
70
|
+
private readonly dbResource = resource({
|
|
71
|
+
params: () => this.config(),
|
|
72
|
+
loader: ({ params }) =>
|
|
73
|
+
openDatabaseAsync(params.databaseName, params.options, params.directory),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The resolved database, or `undefined` before `open()` has resolved. `resource()`'s own
|
|
78
|
+
* contract: reading this while `error()` is set RE-THROWS that error instead of returning —
|
|
79
|
+
* this IS the "propagates" half of upstream's `onError` contract for a caller that reads it
|
|
80
|
+
* directly (e.g. from a template's `@if (sqlite.database(); as db)`).
|
|
81
|
+
*/
|
|
82
|
+
readonly database = this.dbResource.value;
|
|
83
|
+
/** The open failure, if any — reading `error()` itself never throws, only `database()` does. */
|
|
84
|
+
readonly error = this.dbResource.error;
|
|
85
|
+
readonly isLoading = this.dbResource.isLoading;
|
|
86
|
+
|
|
87
|
+
constructor() {
|
|
88
|
+
const injected = inject(SQLITE_CONFIG, { optional: true });
|
|
89
|
+
if (injected) this.open(injected);
|
|
90
|
+
|
|
91
|
+
effect(() => {
|
|
92
|
+
const raw = this.dbResource.error();
|
|
93
|
+
if (raw === undefined) return;
|
|
94
|
+
const error = raw instanceof Error ? raw : new Error(String(raw));
|
|
95
|
+
this.onErrorHandler()?.(error);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Opens (or re-opens, for a new config) the app-wide database. */
|
|
100
|
+
open(config: ISqliteConfig): void {
|
|
101
|
+
this.onErrorHandler.set(config.onError);
|
|
102
|
+
this.config.set(config);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ponytail: closes only the CURRENTLY resolved handle, on the service's own teardown
|
|
106
|
+
// (providedIn: 'root' → root injector destruction). Re-opening with a different config does
|
|
107
|
+
// not close the previous handle first, unlike upstream's `getDatabaseAsync` memo — add that if
|
|
108
|
+
// an app actually calls `open()` more than once per lifetime.
|
|
109
|
+
ngOnDestroy(): void {
|
|
110
|
+
// `hasValue()` never throws, unlike reading `.value()` directly on an errored resource.
|
|
111
|
+
if (!this.dbResource.hasValue()) return;
|
|
112
|
+
void this.dbResource.value().closeAsync();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export {
|
|
2
|
+
SQLiteDatabase,
|
|
3
|
+
defaultDatabaseDirectory,
|
|
4
|
+
bundledExtensions,
|
|
5
|
+
openDatabaseAsync,
|
|
6
|
+
openDatabaseSync,
|
|
7
|
+
deserializeDatabaseAsync,
|
|
8
|
+
deserializeDatabaseSync,
|
|
9
|
+
deleteDatabaseAsync,
|
|
10
|
+
deleteDatabaseSync,
|
|
11
|
+
backupDatabaseAsync,
|
|
12
|
+
backupDatabaseSync,
|
|
13
|
+
addDatabaseChangeListener,
|
|
14
|
+
} from './sqlite-database';
|
|
15
|
+
export type {
|
|
16
|
+
ISQLiteOpenOptions,
|
|
17
|
+
IDatabaseChangeEvent,
|
|
18
|
+
IOnInitCallback,
|
|
19
|
+
IOpenDatabaseOptions,
|
|
20
|
+
} from './sqlite-database';
|
|
21
|
+
|
|
22
|
+
export { SQLiteStatement } from './sqlite-statement';
|
|
23
|
+
export type {
|
|
24
|
+
ISQLiteBindParams,
|
|
25
|
+
ISQLiteBindValue,
|
|
26
|
+
ISQLiteExecuteAsyncResult,
|
|
27
|
+
ISQLiteExecuteSyncResult,
|
|
28
|
+
ISQLiteRunResult,
|
|
29
|
+
ISQLiteVariadicBindParams,
|
|
30
|
+
} from './sqlite-statement';
|
|
31
|
+
|
|
32
|
+
export { SQLiteSession } from './sqlite-session';
|
|
33
|
+
export type { IChangeset } from './sqlite-session';
|
|
34
|
+
|
|
35
|
+
export { SQLiteTaggedQuery } from './sqlite-tagged-query';
|
|
36
|
+
|
|
37
|
+
export { parseSQLQuery } from './query-utils';
|
|
38
|
+
export type { ISQLParsedInfo } from './query-utils';
|
|
39
|
+
|
|
40
|
+
export { createDatabasePath, basename } from './path-utils';
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Ported from expo-sqlite's NativeDatabase.ts (.vendors/expo @ origin/sdk-57,
|
|
2
|
+
// packages/expo-sqlite/src/NativeDatabase.ts), renamed with this repo's `I`-prefix convention.
|
|
3
|
+
//
|
|
4
|
+
// `NativeDatabase` is a PLAIN native class, not a `SharedObject` — expo-sqlite instantiates it
|
|
5
|
+
// directly (`new ExpoSQLite.NativeDatabase(path, options)`), unlike expo-audio's JSI-backed
|
|
6
|
+
// shared objects. See the package README for why that makes this port simpler than
|
|
7
|
+
// `@symbiote-native/audio`'s.
|
|
8
|
+
import type { NativeSession } from './native-session';
|
|
9
|
+
import type { NativeStatement } from './native-statement';
|
|
10
|
+
|
|
11
|
+
/** An instance of the native SQLite database handle. */
|
|
12
|
+
export declare class NativeDatabase {
|
|
13
|
+
constructor(
|
|
14
|
+
databasePath: string,
|
|
15
|
+
options?: ISQLiteOpenOptions,
|
|
16
|
+
serializedData?: Uint8Array,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
//#region Asynchronous API
|
|
20
|
+
|
|
21
|
+
public initAsync(): Promise<void>;
|
|
22
|
+
public isInTransactionAsync(): Promise<boolean>;
|
|
23
|
+
public closeAsync(): Promise<void>;
|
|
24
|
+
public execAsync(source: string): Promise<void>;
|
|
25
|
+
public serializeAsync(databaseName: string): Promise<Uint8Array>;
|
|
26
|
+
public prepareAsync(
|
|
27
|
+
nativeStatement: NativeStatement,
|
|
28
|
+
source: string,
|
|
29
|
+
): Promise<NativeStatement>;
|
|
30
|
+
public createSessionAsync(
|
|
31
|
+
nativeSession: NativeSession,
|
|
32
|
+
dbName: string,
|
|
33
|
+
): Promise<NativeSession>;
|
|
34
|
+
public loadExtensionAsync(
|
|
35
|
+
libPath: string,
|
|
36
|
+
entryPoint?: string,
|
|
37
|
+
): Promise<void>;
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
|
|
41
|
+
//#region Synchronous API
|
|
42
|
+
|
|
43
|
+
public initSync(): void;
|
|
44
|
+
public isInTransactionSync(): boolean;
|
|
45
|
+
public closeSync(): void;
|
|
46
|
+
public execSync(source: string): void;
|
|
47
|
+
public serializeSync(databaseName: string): Uint8Array;
|
|
48
|
+
public prepareSync(
|
|
49
|
+
nativeStatement: NativeStatement,
|
|
50
|
+
source: string,
|
|
51
|
+
): NativeStatement;
|
|
52
|
+
public createSessionSync(
|
|
53
|
+
nativeSession: NativeSession,
|
|
54
|
+
dbName: string,
|
|
55
|
+
): NativeSession;
|
|
56
|
+
public loadExtensionSync(libPath: string, entryPoint?: string): void;
|
|
57
|
+
|
|
58
|
+
//#endregion
|
|
59
|
+
|
|
60
|
+
/** Only available when the native module was built against a libSQL-backed SQLite. */
|
|
61
|
+
public syncLibSQL(): Promise<void>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Options for opening a database. */
|
|
65
|
+
export type ISQLiteOpenOptions = {
|
|
66
|
+
/**
|
|
67
|
+
* Whether to call the [`sqlite3_update_hook()`](https://www.sqlite.org/c3ref/update_hook.html)
|
|
68
|
+
* function and enable `onDatabaseChange` events.
|
|
69
|
+
* @default false
|
|
70
|
+
*/
|
|
71
|
+
enableChangeListener?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Whether to create a new connection even if a connection with the same database name exists
|
|
74
|
+
* in cache.
|
|
75
|
+
* @default false
|
|
76
|
+
*/
|
|
77
|
+
useNewConnection?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Finalize unclosed statements automatically when the database is closed.
|
|
80
|
+
* @default true
|
|
81
|
+
* @hidden
|
|
82
|
+
*/
|
|
83
|
+
finalizeUnusedStatementsBeforeClosing?: boolean;
|
|
84
|
+
/** Options for libSQL integration. */
|
|
85
|
+
libSQLOptions?: {
|
|
86
|
+
/** The URL of the libSQL server. */
|
|
87
|
+
url: string;
|
|
88
|
+
/** The auth token for the libSQL server. */
|
|
89
|
+
authToken: string;
|
|
90
|
+
/**
|
|
91
|
+
* Whether to use remote-only without syncing to a local database.
|
|
92
|
+
* @default false
|
|
93
|
+
*/
|
|
94
|
+
remoteOnly?: boolean;
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
type IFlattenedOpenOptions = Omit<ISQLiteOpenOptions, 'libSQLOptions'> & {
|
|
99
|
+
libSQLUrl?: string;
|
|
100
|
+
libSQLAuthToken?: string;
|
|
101
|
+
libSQLRemoteOnly?: boolean;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/** Flattens `ISQLiteOpenOptions` into the shape the native module expects. */
|
|
105
|
+
export function flattenOpenOptions(
|
|
106
|
+
options: ISQLiteOpenOptions,
|
|
107
|
+
): IFlattenedOpenOptions {
|
|
108
|
+
const { libSQLOptions, ...restOptions } = options;
|
|
109
|
+
const result: IFlattenedOpenOptions = { ...restOptions };
|
|
110
|
+
if (libSQLOptions) {
|
|
111
|
+
Object.assign(result, {
|
|
112
|
+
libSQLUrl: libSQLOptions.url,
|
|
113
|
+
libSQLAuthToken: libSQLOptions.authToken,
|
|
114
|
+
libSQLRemoteOnly: libSQLOptions.remoteOnly,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Resolves expo-sqlite's native module by the same name its own `ExpoSQLite.ts` one-liner uses
|
|
2
|
+
// (`.vendors/expo` @ origin/sdk-57, packages/expo-sqlite/src/ExpoSQLite.ts:
|
|
3
|
+
// `requireNativeModule('ExpoSQLite')`) — through `expo-modules-core` directly, never the `expo`
|
|
4
|
+
// meta-package (root CLAUDE.md's dependency-scope invariant). Verified against the Android
|
|
5
|
+
// module's `Name("ExpoSQLite")` declaration
|
|
6
|
+
// (packages/expo-sqlite/android/.../SQLiteModule.kt) — see this package's `native-link.json`.
|
|
7
|
+
//
|
|
8
|
+
// `NativeDatabase`/`NativeStatement`/`NativeSession` are plain constructors exposed as CLASS
|
|
9
|
+
// PROPERTIES on the native module (`new ExpoSQLite.NativeDatabase(...)`), not `SharedObject`
|
|
10
|
+
// subclasses — see native-database.ts's header for why that makes this simpler than
|
|
11
|
+
// `@symbiote-native/audio`'s port.
|
|
12
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
13
|
+
import type { EventSubscription } from 'expo-modules-core';
|
|
14
|
+
|
|
15
|
+
import { NativeDatabase } from './native-database';
|
|
16
|
+
import { NativeSession } from './native-session';
|
|
17
|
+
import { NativeStatement } from './native-statement';
|
|
18
|
+
import type { IDatabaseChangeEvent } from './types';
|
|
19
|
+
|
|
20
|
+
const EXPO_SQLITE_MODULE_NAME = 'ExpoSQLite';
|
|
21
|
+
|
|
22
|
+
export type INativeSQLiteModule = {
|
|
23
|
+
readonly NativeDatabase: typeof NativeDatabase;
|
|
24
|
+
readonly NativeStatement: typeof NativeStatement;
|
|
25
|
+
readonly NativeSession: typeof NativeSession;
|
|
26
|
+
/** The directory new databases are created in when no explicit directory is given. */
|
|
27
|
+
readonly defaultDatabaseDirectory: string;
|
|
28
|
+
/** Pre-bundled SQLite extensions (e.g. `sqlite-vec`), keyed by extension name. */
|
|
29
|
+
readonly bundledExtensions: Record<
|
|
30
|
+
string,
|
|
31
|
+
{ libPath: string; entryPoint: string } | undefined
|
|
32
|
+
>;
|
|
33
|
+
ensureDatabasePathExistsAsync(databasePath: string): Promise<void>;
|
|
34
|
+
ensureDatabasePathExistsSync(databasePath: string): void;
|
|
35
|
+
deleteDatabaseAsync(databasePath: string): Promise<void>;
|
|
36
|
+
deleteDatabaseSync(databasePath: string): void;
|
|
37
|
+
backupDatabaseAsync(
|
|
38
|
+
destDatabase: NativeDatabase,
|
|
39
|
+
destDatabaseName: string,
|
|
40
|
+
sourceDatabase: NativeDatabase,
|
|
41
|
+
sourceDatabaseName: string,
|
|
42
|
+
): Promise<void>;
|
|
43
|
+
backupDatabaseSync(
|
|
44
|
+
destDatabase: NativeDatabase,
|
|
45
|
+
destDatabaseName: string,
|
|
46
|
+
sourceDatabase: NativeDatabase,
|
|
47
|
+
sourceDatabaseName: string,
|
|
48
|
+
): void;
|
|
49
|
+
addListener(
|
|
50
|
+
eventName: 'onDatabaseChange',
|
|
51
|
+
listener: (event: IDatabaseChangeEvent) => void,
|
|
52
|
+
): EventSubscription;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const expoSQLite = requireNativeModule<INativeSQLiteModule>(
|
|
56
|
+
EXPO_SQLITE_MODULE_NAME,
|
|
57
|
+
);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Ported from expo-sqlite's NativeSession.ts (.vendors/expo @ origin/sdk-57,
|
|
2
|
+
// packages/expo-sqlite/src/NativeSession.ts), renamed with this repo's `I`-prefix convention.
|
|
3
|
+
//
|
|
4
|
+
// A plain native class wrapping SQLite's session extension
|
|
5
|
+
// (https://www.sqlite.org/sessionintro.html), instantiated directly
|
|
6
|
+
// (`new ExpoSQLite.NativeSession()`) and mutated in place by
|
|
7
|
+
// `NativeDatabase.createSessionAsync`/`createSessionSync` — see `sqlite-database.ts`.
|
|
8
|
+
import type { ISQLiteAnyDatabase } from './native-statement';
|
|
9
|
+
|
|
10
|
+
/** A changeset produced by the session extension. */
|
|
11
|
+
export type IChangeset = Uint8Array;
|
|
12
|
+
export type INativeChangeset = ArrayBuffer;
|
|
13
|
+
|
|
14
|
+
export declare class NativeSession {
|
|
15
|
+
//#region Asynchronous API
|
|
16
|
+
|
|
17
|
+
public attachAsync(
|
|
18
|
+
database: ISQLiteAnyDatabase,
|
|
19
|
+
table: string | null,
|
|
20
|
+
): Promise<void>;
|
|
21
|
+
public enableAsync(
|
|
22
|
+
database: ISQLiteAnyDatabase,
|
|
23
|
+
enabled: boolean,
|
|
24
|
+
): Promise<void>;
|
|
25
|
+
public closeAsync(database: ISQLiteAnyDatabase): Promise<void>;
|
|
26
|
+
|
|
27
|
+
public createChangesetAsync(
|
|
28
|
+
database: ISQLiteAnyDatabase,
|
|
29
|
+
): Promise<INativeChangeset>;
|
|
30
|
+
public createInvertedChangesetAsync(
|
|
31
|
+
database: ISQLiteAnyDatabase,
|
|
32
|
+
): Promise<INativeChangeset>;
|
|
33
|
+
public applyChangesetAsync(
|
|
34
|
+
database: ISQLiteAnyDatabase,
|
|
35
|
+
changeset: IChangeset | INativeChangeset,
|
|
36
|
+
): Promise<void>;
|
|
37
|
+
public invertChangesetAsync(
|
|
38
|
+
database: ISQLiteAnyDatabase,
|
|
39
|
+
changeset: IChangeset | INativeChangeset,
|
|
40
|
+
): Promise<INativeChangeset>;
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
|
|
44
|
+
//#region Synchronous API
|
|
45
|
+
|
|
46
|
+
public attachSync(database: ISQLiteAnyDatabase, table: string | null): void;
|
|
47
|
+
public enableSync(database: ISQLiteAnyDatabase, enabled: boolean): void;
|
|
48
|
+
public closeSync(database: ISQLiteAnyDatabase): void;
|
|
49
|
+
|
|
50
|
+
public createChangesetSync(database: ISQLiteAnyDatabase): INativeChangeset;
|
|
51
|
+
public createInvertedChangesetSync(
|
|
52
|
+
database: ISQLiteAnyDatabase,
|
|
53
|
+
): INativeChangeset;
|
|
54
|
+
public applyChangesetSync(
|
|
55
|
+
database: ISQLiteAnyDatabase,
|
|
56
|
+
changeset: IChangeset | INativeChangeset,
|
|
57
|
+
): void;
|
|
58
|
+
public invertChangesetSync(
|
|
59
|
+
database: ISQLiteAnyDatabase,
|
|
60
|
+
changeset: IChangeset | INativeChangeset,
|
|
61
|
+
): INativeChangeset;
|
|
62
|
+
|
|
63
|
+
//#endregion
|
|
64
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Ported from expo-sqlite's NativeStatement.ts (.vendors/expo @ origin/sdk-57,
|
|
2
|
+
// packages/expo-sqlite/src/NativeStatement.ts), renamed with this repo's `I`-prefix convention.
|
|
3
|
+
//
|
|
4
|
+
// A plain native class, instantiated directly (`new ExpoSQLite.NativeStatement()`) and then
|
|
5
|
+
// mutated in place by `NativeDatabase.prepareAsync`/`prepareSync` — see `sqlite-database.ts`.
|
|
6
|
+
|
|
7
|
+
/** A result returned by `SQLiteStatement.executeAsync()`/`executeSync()`. */
|
|
8
|
+
export type ISQLiteRunResult = {
|
|
9
|
+
/**
|
|
10
|
+
* The last inserted row ID, from
|
|
11
|
+
* [`sqlite3_last_insert_rowid()`](https://www.sqlite.org/c3ref/last_insert_rowid.html).
|
|
12
|
+
*/
|
|
13
|
+
lastInsertRowId: number;
|
|
14
|
+
/**
|
|
15
|
+
* The number of rows affected, from
|
|
16
|
+
* [`sqlite3_changes()`](https://www.sqlite.org/c3ref/changes.html).
|
|
17
|
+
*/
|
|
18
|
+
changes: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Bind parameters to a prepared statement — a single array/variadic args for unnamed
|
|
23
|
+
* parameters (`?`), or a single object for named parameters (`:VVV`, `@VVV`, `$VVV`).
|
|
24
|
+
*/
|
|
25
|
+
export type ISQLiteBindValue =
|
|
26
|
+
string | number | null | boolean | ISQLiteBindBlobValue;
|
|
27
|
+
export type ISQLiteBindParams =
|
|
28
|
+
Record<string, ISQLiteBindValue> | ISQLiteBindValue[];
|
|
29
|
+
export type ISQLiteVariadicBindParams = ISQLiteBindValue[];
|
|
30
|
+
export type ISQLiteBindBlobValue = Uint8Array | ArrayBuffer;
|
|
31
|
+
|
|
32
|
+
export type ISQLiteBindPrimitiveParams = Record<
|
|
33
|
+
string,
|
|
34
|
+
Exclude<ISQLiteBindValue, ISQLiteBindBlobValue>
|
|
35
|
+
>;
|
|
36
|
+
export type ISQLiteBindBlobParams = Record<string, ISQLiteBindBlobValue>;
|
|
37
|
+
export type ISQLiteColumnNames = string[];
|
|
38
|
+
// `any`: native bridge payload — a prepared statement's row shape is only known to the caller of
|
|
39
|
+
// `executeAsync<T>`/`executeSync<T>`.
|
|
40
|
+
export type ISQLiteColumnValues = any[];
|
|
41
|
+
// `any`: `NativeDatabase` and its `Transaction` subclass (sqlite-database.ts) both satisfy this,
|
|
42
|
+
// kept untyped here (matching upstream) to avoid a circular import between the two native
|
|
43
|
+
// declaration files.
|
|
44
|
+
export type ISQLiteAnyDatabase = any;
|
|
45
|
+
|
|
46
|
+
/** An instance of a prepared SQLite statement. */
|
|
47
|
+
export declare class NativeStatement {
|
|
48
|
+
//#region Asynchronous API
|
|
49
|
+
|
|
50
|
+
public runAsync(
|
|
51
|
+
database: ISQLiteAnyDatabase,
|
|
52
|
+
bindParams: ISQLiteBindPrimitiveParams,
|
|
53
|
+
bindBlobParams: ISQLiteBindBlobParams,
|
|
54
|
+
shouldPassAsArray: boolean,
|
|
55
|
+
): Promise<ISQLiteRunResult & { firstRowValues: ISQLiteColumnValues }>;
|
|
56
|
+
public stepAsync(
|
|
57
|
+
database: ISQLiteAnyDatabase,
|
|
58
|
+
): Promise<ISQLiteColumnValues | null | undefined>;
|
|
59
|
+
public getAllAsync(
|
|
60
|
+
database: ISQLiteAnyDatabase,
|
|
61
|
+
): Promise<ISQLiteColumnValues[]>;
|
|
62
|
+
public resetAsync(database: ISQLiteAnyDatabase): Promise<void>;
|
|
63
|
+
public getColumnNamesAsync(): Promise<ISQLiteColumnNames>;
|
|
64
|
+
public finalizeAsync(database: ISQLiteAnyDatabase): Promise<void>;
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
|
|
68
|
+
//#region Synchronous API
|
|
69
|
+
|
|
70
|
+
public runSync(
|
|
71
|
+
database: ISQLiteAnyDatabase,
|
|
72
|
+
bindParams: ISQLiteBindPrimitiveParams,
|
|
73
|
+
bindBlobParams: ISQLiteBindBlobParams,
|
|
74
|
+
shouldPassAsArray: boolean,
|
|
75
|
+
): ISQLiteRunResult & { firstRowValues: ISQLiteColumnValues };
|
|
76
|
+
public stepSync(
|
|
77
|
+
database: ISQLiteAnyDatabase,
|
|
78
|
+
): ISQLiteColumnValues | null | undefined;
|
|
79
|
+
public getAllSync(database: ISQLiteAnyDatabase): ISQLiteColumnValues[];
|
|
80
|
+
public resetSync(database: ISQLiteAnyDatabase): void;
|
|
81
|
+
public getColumnNamesSync(): string[];
|
|
82
|
+
public finalizeSync(database: ISQLiteAnyDatabase): void;
|
|
83
|
+
|
|
84
|
+
//#endregion
|
|
85
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// Ported verbatim (logic unchanged) from expo-sqlite's paramUtils.ts (.vendors/expo @
|
|
2
|
+
// origin/sdk-57, packages/expo-sqlite/src/paramUtils.ts), renamed with this repo's `I`-prefix
|
|
3
|
+
// convention.
|
|
4
|
+
import type {
|
|
5
|
+
ISQLiteBindBlobParams,
|
|
6
|
+
ISQLiteBindBlobValue,
|
|
7
|
+
ISQLiteBindPrimitiveParams,
|
|
8
|
+
ISQLiteBindValue,
|
|
9
|
+
ISQLiteColumnNames,
|
|
10
|
+
ISQLiteColumnValues,
|
|
11
|
+
} from './native-statement';
|
|
12
|
+
|
|
13
|
+
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
14
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Normalizes bind params into `[primitiveParams, blobParams, shouldPassAsArray]` for the
|
|
19
|
+
* native module. `params` is untyped because the public overloads
|
|
20
|
+
* (`ISQLiteBindParams` vs. variadic `ISQLiteVariadicBindParams`) already constrain what a
|
|
21
|
+
* type-checked caller can pass here — this only has to survive a caller that bypasses them.
|
|
22
|
+
* @hidden
|
|
23
|
+
*/
|
|
24
|
+
export function normalizeParams(
|
|
25
|
+
...params: unknown[]
|
|
26
|
+
): [ISQLiteBindPrimitiveParams, ISQLiteBindBlobParams, boolean] {
|
|
27
|
+
const rawParams: unknown = params.length > 1 ? params : params[0];
|
|
28
|
+
let bindParams: unknown = rawParams ?? [];
|
|
29
|
+
|
|
30
|
+
if (
|
|
31
|
+
typeof bindParams !== 'object' ||
|
|
32
|
+
bindParams === null ||
|
|
33
|
+
bindParams instanceof ArrayBuffer ||
|
|
34
|
+
ArrayBuffer.isView(bindParams)
|
|
35
|
+
) {
|
|
36
|
+
bindParams = [bindParams];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const shouldPassAsArray = Array.isArray(bindParams);
|
|
40
|
+
const entries: [string, unknown][] = Array.isArray(bindParams)
|
|
41
|
+
? bindParams.map((value, index): [string, unknown] => [
|
|
42
|
+
String(index),
|
|
43
|
+
value,
|
|
44
|
+
])
|
|
45
|
+
: isPlainRecord(bindParams)
|
|
46
|
+
? Object.entries(bindParams)
|
|
47
|
+
: [];
|
|
48
|
+
|
|
49
|
+
const primitiveParams: ISQLiteBindPrimitiveParams = {};
|
|
50
|
+
const blobParams: ISQLiteBindBlobParams = {};
|
|
51
|
+
for (const [key, value] of entries) {
|
|
52
|
+
if (value instanceof Uint8Array || value instanceof ArrayBuffer) {
|
|
53
|
+
blobParams[key] = value;
|
|
54
|
+
} else if (typeof value === 'boolean') {
|
|
55
|
+
primitiveParams[key] = value ? 1 : 0;
|
|
56
|
+
} else if (
|
|
57
|
+
value === null ||
|
|
58
|
+
typeof value === 'string' ||
|
|
59
|
+
typeof value === 'number'
|
|
60
|
+
) {
|
|
61
|
+
primitiveParams[key] = value;
|
|
62
|
+
} else {
|
|
63
|
+
// Not a recognized ISQLiteBindValue at this point (undefined, an object, an array, a
|
|
64
|
+
// function...) — keep it as-is, matching upstream's `value ?? null`, which only guards
|
|
65
|
+
// null/undefined and otherwise passes the value through untouched. Binding an invalid
|
|
66
|
+
// value is the native bridge's problem at bind time; this function only reshapes params.
|
|
67
|
+
// I/O edge: `value` is genuinely unknown here — a caller that bypassed the public
|
|
68
|
+
// `ISQLiteBindParams` overloads is the only way to reach this branch.
|
|
69
|
+
primitiveParams[key] = (value ?? null) as Exclude<
|
|
70
|
+
ISQLiteBindValue,
|
|
71
|
+
ISQLiteBindBlobValue
|
|
72
|
+
>;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return [primitiveParams, blobParams, shouldPassAsArray];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Composes `columnNames` and `columnValues` into a row object.
|
|
81
|
+
* @hidden
|
|
82
|
+
*/
|
|
83
|
+
export function composeRow<T>(
|
|
84
|
+
columnNames: ISQLiteColumnNames,
|
|
85
|
+
columnValues: ISQLiteColumnValues,
|
|
86
|
+
): T {
|
|
87
|
+
const row: Record<string, unknown> = {};
|
|
88
|
+
if (columnNames.length !== columnValues.length) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Column names and values count mismatch. Names: ${columnNames.length}, Values: ${columnValues.length}`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
for (let i = 0; i < columnNames.length; i++) {
|
|
94
|
+
const columnName = columnNames[i];
|
|
95
|
+
if (columnName != null) {
|
|
96
|
+
row[columnName] = columnValues[i];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// I/O edge: T is the caller-specified row shape (SQLiteStatement.executeAsync<T>, etc.) — the
|
|
100
|
+
// native bridge has no way to describe it, so it is asserted here, at the narrowest point
|
|
101
|
+
// native data enters the type system.
|
|
102
|
+
return row as T;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Composes `columnNames` and `columnValuesList` into an array of row objects.
|
|
107
|
+
* @hidden
|
|
108
|
+
*/
|
|
109
|
+
export function composeRows<T>(
|
|
110
|
+
columnNames: ISQLiteColumnNames,
|
|
111
|
+
columnValuesList: ISQLiteColumnValues[],
|
|
112
|
+
): T[] {
|
|
113
|
+
const firstRow = columnValuesList[0];
|
|
114
|
+
if (firstRow == null) {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
if (columnNames.length !== firstRow.length) {
|
|
118
|
+
// Only the first row is checked — SQLite returns the same column count for every row. A
|
|
119
|
+
// shorter LATER row is not an error case (unlike composeRow's own check, which this
|
|
120
|
+
// deliberately does NOT delegate to) — a missing trailing value just composes as
|
|
121
|
+
// `undefined`, matching upstream's inline (non-composeRow) row construction here.
|
|
122
|
+
throw new Error(
|
|
123
|
+
`Column names and values count mismatch. Names: ${columnNames.length}, Values: ${firstRow.length}`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
const results: T[] = [];
|
|
127
|
+
for (const columnValues of columnValuesList) {
|
|
128
|
+
const row: Record<string, unknown> = {};
|
|
129
|
+
for (let i = 0; i < columnNames.length; i++) {
|
|
130
|
+
const columnName = columnNames[i];
|
|
131
|
+
if (columnName != null) {
|
|
132
|
+
row[columnName] = columnValues[i];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// I/O edge, same as composeRow above.
|
|
136
|
+
results.push(row as T);
|
|
137
|
+
}
|
|
138
|
+
return results;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Normalizes the index for `SQLiteStorage.getKeyByIndexAsync`/`getKeyByIndexSync`. `index` is
|
|
143
|
+
* `unknown` (upstream types it `any`) because the function coerces defensively via `Number()` —
|
|
144
|
+
* a caller reaching this from untyped host data (e.g. a `web` `Storage` polyfill) may hand it
|
|
145
|
+
* anything.
|
|
146
|
+
* @returns The normalized index, or `null` when out of bounds.
|
|
147
|
+
* @hidden
|
|
148
|
+
*/
|
|
149
|
+
export function normalizeStorageIndex(index: unknown): number | null {
|
|
150
|
+
const value = Math.floor(Number(index));
|
|
151
|
+
|
|
152
|
+
if (Object.is(value, -0)) {
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
if (!Number.isSafeInteger(value)) {
|
|
156
|
+
// Chromium uses a zero index when the index is out of bounds.
|
|
157
|
+
return 0;
|
|
158
|
+
}
|
|
159
|
+
if (value < 0) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
return value;
|
|
163
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Ported verbatim (logic unchanged) from expo-sqlite's pathUtils.ts (.vendors/expo @
|
|
2
|
+
// origin/sdk-57, packages/expo-sqlite/src/pathUtils.ts).
|
|
3
|
+
import { expoSQLite } from './native-module';
|
|
4
|
+
|
|
5
|
+
function resolveDbDirectory(directory: string | undefined): string {
|
|
6
|
+
const resolvedDirectory = directory ?? expoSQLite.defaultDatabaseDirectory;
|
|
7
|
+
if (resolvedDirectory == null) {
|
|
8
|
+
throw new Error(
|
|
9
|
+
'Both provided directory and defaultDatabaseDirectory are null.',
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
return resolvedDirectory;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates a normalized database path by combining the directory and database name — no
|
|
17
|
+
* trailing slash on the directory, no leading slash on the name, so the join never doubles up.
|
|
18
|
+
*/
|
|
19
|
+
export function createDatabasePath(
|
|
20
|
+
databaseName: string,
|
|
21
|
+
directory?: string,
|
|
22
|
+
): string {
|
|
23
|
+
if (databaseName === ':memory:') return databaseName;
|
|
24
|
+
const resolvedDirectory = resolveDbDirectory(directory);
|
|
25
|
+
|
|
26
|
+
function removeTrailingSlash(path: string): string {
|
|
27
|
+
return path.replace(/\/*$/, '');
|
|
28
|
+
}
|
|
29
|
+
function removeLeadingSlash(path: string): string {
|
|
30
|
+
return path.replace(/^\/+/, '');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return `${removeTrailingSlash(resolvedDirectory)}/${removeLeadingSlash(databaseName)}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function basename(path: string): string {
|
|
37
|
+
return path.substring(path.lastIndexOf('/') + 1);
|
|
38
|
+
}
|