@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.
Files changed (128) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +283 -0
  3. package/build/angular/index.d.ts +2 -0
  4. package/build/angular/index.js +2 -0
  5. package/build/angular/sqlite.service.d.ts +41 -0
  6. package/build/angular/sqlite.service.js +127 -0
  7. package/build/core/index.d.ts +10 -0
  8. package/build/core/index.js +6 -0
  9. package/build/core/native-database.d.ts +65 -0
  10. package/build/core/native-database.js +13 -0
  11. package/build/core/native-module.d.ts +25 -0
  12. package/build/core/native-module.js +17 -0
  13. package/build/core/native-session.d.ts +20 -0
  14. package/build/core/native-session.js +1 -0
  15. package/build/core/native-statement.d.ts +45 -0
  16. package/build/core/native-statement.js +6 -0
  17. package/build/core/param-utils.d.ts +28 -0
  18. package/build/core/param-utils.js +126 -0
  19. package/build/core/path-utils.d.ts +6 -0
  20. package/build/core/path-utils.js +29 -0
  21. package/build/core/query-utils.d.ts +17 -0
  22. package/build/core/query-utils.js +37 -0
  23. package/build/core/sqlite-database.d.ts +246 -0
  24. package/build/core/sqlite-database.js +427 -0
  25. package/build/core/sqlite-session.d.ts +84 -0
  26. package/build/core/sqlite-session.js +121 -0
  27. package/build/core/sqlite-statement.d.ts +96 -0
  28. package/build/core/sqlite-statement.js +321 -0
  29. package/build/core/sqlite-tagged-query.d.ts +73 -0
  30. package/build/core/sqlite-tagged-query.js +119 -0
  31. package/build/core/storage.d.ts +107 -0
  32. package/build/core/storage.js +382 -0
  33. package/build/core/types.d.ts +30 -0
  34. package/build/core/types.js +1 -0
  35. package/build/react/index.d.ts +2 -0
  36. package/build/react/index.js +2 -0
  37. package/build/react/sqlite-context.d.ts +25 -0
  38. package/build/react/sqlite-context.js +129 -0
  39. package/build/solid/index.d.ts +2 -0
  40. package/build/solid/index.js +2 -0
  41. package/build/solid/sqlite-context.d.ts +23 -0
  42. package/build/solid/sqlite-context.js +106 -0
  43. package/build/svelte/SQLiteProvider.svelte +88 -0
  44. package/build/svelte/SQLiteProvider.svelte.d.ts +13 -0
  45. package/build/svelte/index.d.ts +3 -0
  46. package/build/svelte/index.js +5 -0
  47. package/build/svelte/sqlite-context.d.ts +11 -0
  48. package/build/svelte/sqlite-context.js +23 -0
  49. package/build/vue/index.d.ts +2 -0
  50. package/build/vue/index.js +2 -0
  51. package/build/vue/sqlite-context.d.ts +21 -0
  52. package/build/vue/sqlite-context.js +69 -0
  53. package/build-ngc/angular/index.d.ts +2 -0
  54. package/build-ngc/angular/index.js +3 -0
  55. package/build-ngc/angular/index.js.map +1 -0
  56. package/build-ngc/angular/sqlite.service.d.ts +44 -0
  57. package/build-ngc/angular/sqlite.service.js +85 -0
  58. package/build-ngc/angular/sqlite.service.js.map +1 -0
  59. package/build-ngc/core/index.d.ts +10 -0
  60. package/build-ngc/core/index.js +7 -0
  61. package/build-ngc/core/index.js.map +1 -0
  62. package/build-ngc/core/native-database.d.ts +65 -0
  63. package/build-ngc/core/native-database.js +14 -0
  64. package/build-ngc/core/native-database.js.map +1 -0
  65. package/build-ngc/core/native-module.d.ts +25 -0
  66. package/build-ngc/core/native-module.js +18 -0
  67. package/build-ngc/core/native-module.js.map +1 -0
  68. package/build-ngc/core/native-session.d.ts +20 -0
  69. package/build-ngc/core/native-session.js +2 -0
  70. package/build-ngc/core/native-session.js.map +1 -0
  71. package/build-ngc/core/native-statement.d.ts +45 -0
  72. package/build-ngc/core/native-statement.js +7 -0
  73. package/build-ngc/core/native-statement.js.map +1 -0
  74. package/build-ngc/core/param-utils.d.ts +28 -0
  75. package/build-ngc/core/param-utils.js +127 -0
  76. package/build-ngc/core/param-utils.js.map +1 -0
  77. package/build-ngc/core/path-utils.d.ts +6 -0
  78. package/build-ngc/core/path-utils.js +30 -0
  79. package/build-ngc/core/path-utils.js.map +1 -0
  80. package/build-ngc/core/query-utils.d.ts +17 -0
  81. package/build-ngc/core/query-utils.js +38 -0
  82. package/build-ngc/core/query-utils.js.map +1 -0
  83. package/build-ngc/core/sqlite-database.d.ts +246 -0
  84. package/build-ngc/core/sqlite-database.js +428 -0
  85. package/build-ngc/core/sqlite-database.js.map +1 -0
  86. package/build-ngc/core/sqlite-session.d.ts +84 -0
  87. package/build-ngc/core/sqlite-session.js +122 -0
  88. package/build-ngc/core/sqlite-session.js.map +1 -0
  89. package/build-ngc/core/sqlite-statement.d.ts +96 -0
  90. package/build-ngc/core/sqlite-statement.js +322 -0
  91. package/build-ngc/core/sqlite-statement.js.map +1 -0
  92. package/build-ngc/core/sqlite-tagged-query.d.ts +73 -0
  93. package/build-ngc/core/sqlite-tagged-query.js +120 -0
  94. package/build-ngc/core/sqlite-tagged-query.js.map +1 -0
  95. package/build-ngc/core/storage.d.ts +107 -0
  96. package/build-ngc/core/storage.js +383 -0
  97. package/build-ngc/core/storage.js.map +1 -0
  98. package/build-ngc/core/types.d.ts +30 -0
  99. package/build-ngc/core/types.js +2 -0
  100. package/build-ngc/core/types.js.map +1 -0
  101. package/native-link.json +12 -0
  102. package/package.json +141 -0
  103. package/src/angular/index.ts +2 -0
  104. package/src/angular/sqlite.service.ts +114 -0
  105. package/src/core/index.ts +40 -0
  106. package/src/core/native-database.ts +118 -0
  107. package/src/core/native-module.ts +57 -0
  108. package/src/core/native-session.ts +64 -0
  109. package/src/core/native-statement.ts +85 -0
  110. package/src/core/param-utils.ts +163 -0
  111. package/src/core/path-utils.ts +38 -0
  112. package/src/core/query-utils.ts +45 -0
  113. package/src/core/sqlite-database.ts +676 -0
  114. package/src/core/sqlite-session.ts +165 -0
  115. package/src/core/sqlite-statement.ts +578 -0
  116. package/src/core/sqlite-tagged-query.ts +160 -0
  117. package/src/core/storage.ts +492 -0
  118. package/src/core/types.ts +33 -0
  119. package/src/react/index.ts +2 -0
  120. package/src/react/sqlite-context.tsx +244 -0
  121. package/src/solid/index.ts +2 -0
  122. package/src/solid/sqlite-context.ts +156 -0
  123. package/src/svelte/SQLiteProvider.svelte +88 -0
  124. package/src/svelte/index.ts +6 -0
  125. package/src/svelte/sqlite-context.ts +32 -0
  126. package/src/svelte/svelte-compile.test-helper.ts +135 -0
  127. package/src/vue/index.ts +2 -0
  128. 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
+ }