@delali/narsil-react-native 0.0.0-stage → 0.3.1-next.151

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 (186) hide show
  1. package/LICENSE +201 -0
  2. package/NarsilReactNative.podspec +57 -0
  3. package/README.md +46 -2
  4. package/android/CMakeLists.txt +48 -0
  5. package/android/build.gradle +151 -0
  6. package/android/gradle.properties +5 -0
  7. package/android/src/main/AndroidManifest.xml +2 -0
  8. package/android/src/main/cpp/AndroidEnvironment.cpp +44 -0
  9. package/android/src/main/cpp/AndroidEnvironment.hpp +11 -0
  10. package/android/src/main/cpp/cpp-adapter.cpp +11 -0
  11. package/android/src/main/java/com/margelo/nitro/narsil/NarsilAndroidEnvironment.kt +29 -0
  12. package/android/src/main/java/com/margelo/nitro/narsil/NarsilReactNativePackage.kt +22 -0
  13. package/app.plugin.js +25 -0
  14. package/cpp/AttachedField.cpp +234 -0
  15. package/cpp/AttachedField.hpp +61 -0
  16. package/cpp/Compression.cpp +113 -0
  17. package/cpp/Compression.hpp +16 -0
  18. package/cpp/GraphHeldAlone.cpp +29 -0
  19. package/cpp/GraphHeldAlone.hpp +25 -0
  20. package/cpp/HostMemory.cpp +123 -0
  21. package/cpp/HostMemory.hpp +53 -0
  22. package/cpp/HybridNarsilCoreImpl.cpp +390 -0
  23. package/cpp/HybridNarsilCoreImpl.hpp +68 -0
  24. package/cpp/HybridNarsilFilesImpl.cpp +245 -0
  25. package/cpp/HybridNarsilFilesImpl.hpp +34 -0
  26. package/cpp/HybridNarsilHostImpl.cpp +126 -0
  27. package/cpp/HybridNarsilHostImpl.hpp +42 -0
  28. package/cpp/NativeAtomics.cpp +110 -0
  29. package/cpp/NativeAtomics.hpp +11 -0
  30. package/cpp/ProcessInfo.cpp +83 -0
  31. package/cpp/ProcessInfo.hpp +11 -0
  32. package/cpp/ThreadPriority.cpp +77 -0
  33. package/cpp/ThreadPriority.hpp +8 -0
  34. package/cpp/WorkletRuntimeDispatcher.cpp +61 -0
  35. package/cpp/WorkletRuntimeDispatcher.hpp +9 -0
  36. package/cpp/core/include/narsil_core.h +208 -0
  37. package/cpp/core/src/connect.c +213 -0
  38. package/cpp/core/src/distance.c +103 -0
  39. package/cpp/core/src/files.c +99 -0
  40. package/cpp/core/src/heap.c +84 -0
  41. package/cpp/core/src/internal.h +284 -0
  42. package/cpp/core/src/kernels_code.c +209 -0
  43. package/cpp/core/src/kernels_float.c +197 -0
  44. package/cpp/core/src/kernels_pair.c +133 -0
  45. package/cpp/core/src/last_links.c +145 -0
  46. package/cpp/core/src/links.c +195 -0
  47. package/cpp/core/src/locks.c +168 -0
  48. package/cpp/core/src/nearest_links.c +147 -0
  49. package/cpp/core/src/place.c +215 -0
  50. package/cpp/core/src/quantize.c +226 -0
  51. package/cpp/core/src/records.c +97 -0
  52. package/cpp/core/src/remove.c +205 -0
  53. package/cpp/core/src/vectors.c +139 -0
  54. package/cpp/core/src/walk.c +231 -0
  55. package/cpp/core/src/workspace.c +167 -0
  56. package/ios/PrivacyInfo.xcprivacy +23 -0
  57. package/lib/client.d.ts +19 -0
  58. package/lib/client.js +41 -0
  59. package/lib/constants.d.ts +9 -0
  60. package/lib/constants.js +9 -0
  61. package/lib/engine/byte-files.d.ts +5 -0
  62. package/lib/engine/byte-files.js +97 -0
  63. package/lib/engine/core.d.ts +3 -0
  64. package/lib/engine/core.js +113 -0
  65. package/lib/engine/files.d.ts +3 -0
  66. package/lib/engine/files.js +188 -0
  67. package/lib/engine/host.d.ts +13 -0
  68. package/lib/engine/host.js +102 -0
  69. package/lib/engine/install.d.ts +4 -0
  70. package/lib/engine/install.js +29 -0
  71. package/lib/engine/native-error.d.ts +1 -0
  72. package/lib/engine/native-error.js +12 -0
  73. package/lib/engine/stand-ins/atomics.d.ts +5 -0
  74. package/lib/engine/stand-ins/atomics.js +236 -0
  75. package/lib/engine/stand-ins/crypto.d.ts +2 -0
  76. package/lib/engine/stand-ins/crypto.js +65 -0
  77. package/lib/engine/stand-ins/process.d.ts +2 -0
  78. package/lib/engine/stand-ins/process.js +22 -0
  79. package/lib/engine/stand-ins/streams.d.ts +1 -0
  80. package/lib/engine/stand-ins/streams.js +31 -0
  81. package/lib/engine/stand-ins/structured-clone.d.ts +1 -0
  82. package/lib/engine/stand-ins/structured-clone.js +121 -0
  83. package/lib/engine/streamed.d.ts +6 -0
  84. package/lib/engine/streamed.js +29 -0
  85. package/lib/engine/transfer.d.ts +3 -0
  86. package/lib/engine/transfer.js +77 -0
  87. package/lib/errors.d.ts +25 -0
  88. package/lib/errors.js +59 -0
  89. package/lib/index.d.ts +3 -0
  90. package/lib/index.js +2 -0
  91. package/lib/methods.d.ts +5 -0
  92. package/lib/methods.js +47 -0
  93. package/lib/native-objects.d.ts +2 -0
  94. package/lib/native-objects.js +24 -0
  95. package/lib/specs/core.nitro.d.ts +79 -0
  96. package/lib/specs/core.nitro.js +1 -0
  97. package/lib/specs/files.nitro.d.ts +36 -0
  98. package/lib/specs/files.nitro.js +1 -0
  99. package/lib/specs/host.nitro.d.ts +24 -0
  100. package/lib/specs/host.nitro.js +1 -0
  101. package/lib/transferred.d.ts +15 -0
  102. package/lib/transferred.js +6 -0
  103. package/lib/types.d.ts +136 -0
  104. package/lib/types.js +1 -0
  105. package/lib/worklets/app-turns.d.ts +1 -0
  106. package/lib/worklets/app-turns.js +44 -0
  107. package/lib/worklets/client.d.ts +30 -0
  108. package/lib/worklets/client.js +144 -0
  109. package/lib/worklets/index.d.ts +4 -0
  110. package/lib/worklets/index.js +2 -0
  111. package/lib/worklets/instances.d.ts +15 -0
  112. package/lib/worklets/instances.js +194 -0
  113. package/lib/worklets/json-part.d.ts +1 -0
  114. package/lib/worklets/json-part.js +42 -0
  115. package/lib/worklets/runtime.d.ts +25 -0
  116. package/lib/worklets/runtime.js +80 -0
  117. package/lib/worklets/transfer-queue.d.ts +7 -0
  118. package/lib/worklets/transfer-queue.js +79 -0
  119. package/lib/worklets/types.d.ts +26 -0
  120. package/lib/worklets/types.js +1 -0
  121. package/nitro.json +32 -0
  122. package/nitrogen/generated/.gitattributes +1 -0
  123. package/nitrogen/generated/android/NarsilReactNative+autolinking.cmake +85 -0
  124. package/nitrogen/generated/android/NarsilReactNative+autolinking.gradle +27 -0
  125. package/nitrogen/generated/android/NarsilReactNativeOnLoad.cpp +69 -0
  126. package/nitrogen/generated/android/NarsilReactNativeOnLoad.hpp +34 -0
  127. package/nitrogen/generated/android/kotlin/com/margelo/nitro/narsil/NarsilReactNativeOnLoad.kt +35 -0
  128. package/nitrogen/generated/ios/NarsilReactNative+autolinking.rb +62 -0
  129. package/nitrogen/generated/ios/NarsilReactNative-Swift-Cxx-Bridge.cpp +17 -0
  130. package/nitrogen/generated/ios/NarsilReactNative-Swift-Cxx-Bridge.hpp +27 -0
  131. package/nitrogen/generated/ios/NarsilReactNative-Swift-Cxx-Umbrella.hpp +38 -0
  132. package/nitrogen/generated/ios/NarsilReactNativeAutolinking.mm +55 -0
  133. package/nitrogen/generated/ios/NarsilReactNativeAutolinking.swift +16 -0
  134. package/nitrogen/generated/shared/c++/DirectoryEntry.hpp +91 -0
  135. package/nitrogen/generated/shared/c++/FileStatus.hpp +103 -0
  136. package/nitrogen/generated/shared/c++/GraphMemory.hpp +120 -0
  137. package/nitrogen/generated/shared/c++/HybridNarsilAttachedGraphSpec.cpp +21 -0
  138. package/nitrogen/generated/shared/c++/HybridNarsilAttachedGraphSpec.hpp +62 -0
  139. package/nitrogen/generated/shared/c++/HybridNarsilAttachedStoreSpec.cpp +21 -0
  140. package/nitrogen/generated/shared/c++/HybridNarsilAttachedStoreSpec.hpp +62 -0
  141. package/nitrogen/generated/shared/c++/HybridNarsilCoreSpec.cpp +35 -0
  142. package/nitrogen/generated/shared/c++/HybridNarsilCoreSpec.hpp +100 -0
  143. package/nitrogen/generated/shared/c++/HybridNarsilFilesSpec.cpp +37 -0
  144. package/nitrogen/generated/shared/c++/HybridNarsilFilesSpec.hpp +86 -0
  145. package/nitrogen/generated/shared/c++/HybridNarsilHostSpec.cpp +34 -0
  146. package/nitrogen/generated/shared/c++/HybridNarsilHostSpec.hpp +78 -0
  147. package/nitrogen/generated/shared/c++/MemoryView.hpp +91 -0
  148. package/nitrogen/generated/shared/c++/NativeConnectOutcome.hpp +87 -0
  149. package/nitrogen/generated/shared/c++/NativeScores.hpp +87 -0
  150. package/nitrogen/generated/shared/c++/NativeSearchHits.hpp +91 -0
  151. package/nitrogen/generated/shared/c++/ProcessMemory.hpp +87 -0
  152. package/nitrogen/generated/shared/c++/StoreMemory.hpp +142 -0
  153. package/package.json +87 -3
  154. package/react-native.config.js +8 -0
  155. package/src/client.ts +44 -0
  156. package/src/constants.ts +9 -0
  157. package/src/engine/byte-files.ts +99 -0
  158. package/src/engine/core.ts +162 -0
  159. package/src/engine/files.ts +186 -0
  160. package/src/engine/host.ts +111 -0
  161. package/src/engine/install.ts +31 -0
  162. package/src/engine/native-error.ts +10 -0
  163. package/src/engine/stand-ins/atomics.ts +288 -0
  164. package/src/engine/stand-ins/crypto.ts +81 -0
  165. package/src/engine/stand-ins/process.ts +30 -0
  166. package/src/engine/stand-ins/streams.ts +45 -0
  167. package/src/engine/stand-ins/structured-clone.ts +118 -0
  168. package/src/engine/streamed.ts +40 -0
  169. package/src/engine/transfer.ts +72 -0
  170. package/src/errors.ts +73 -0
  171. package/src/index.ts +3 -0
  172. package/src/methods.ts +52 -0
  173. package/src/native-objects.ts +30 -0
  174. package/src/specs/core.nitro.ts +140 -0
  175. package/src/specs/files.nitro.ts +42 -0
  176. package/src/specs/host.nitro.ts +30 -0
  177. package/src/transferred.ts +18 -0
  178. package/src/types.ts +164 -0
  179. package/src/worklets/app-turns.ts +48 -0
  180. package/src/worklets/client.ts +165 -0
  181. package/src/worklets/index.ts +4 -0
  182. package/src/worklets/instances.ts +231 -0
  183. package/src/worklets/json-part.ts +38 -0
  184. package/src/worklets/runtime.ts +190 -0
  185. package/src/worklets/transfer-queue.ts +98 -0
  186. package/src/worklets/types.ts +37 -0
package/package.json CHANGED
@@ -1,6 +1,90 @@
1
1
  {
2
2
  "name": "@delali/narsil-react-native",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.3.1-next.151",
4
+ "description": "Narsil for React Native: keyword and vector search on the phone, with the index kept in durable files.",
5
+ "author": "Delali (https://sondelali.com)",
6
+ "license": "Apache-2.0",
7
+ "homepage": "https://narsil.sondelali.com",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/assetcorp/narsil.git",
11
+ "directory": "packages/react-native"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/assetcorp/narsil/issues"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "main": "./lib/index.js",
20
+ "types": "./lib/index.d.ts",
21
+ "react-native": "./src/index.ts",
22
+ "source": "./src/index.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./lib/index.d.ts",
26
+ "react-native": "./src/index.ts",
27
+ "default": "./lib/index.js"
28
+ },
29
+ "./worklets": {
30
+ "types": "./lib/worklets/index.d.ts",
31
+ "react-native": "./src/worklets/index.ts",
32
+ "default": "./lib/worklets/index.js"
33
+ },
34
+ "./app.plugin.js": "./app.plugin.js",
35
+ "./package.json": "./package.json"
36
+ },
37
+ "files": [
38
+ "src/",
39
+ "lib/",
40
+ "cpp/",
41
+ "nitrogen/",
42
+ "android/build.gradle",
43
+ "android/gradle.properties",
44
+ "android/CMakeLists.txt",
45
+ "android/src/",
46
+ "ios/PrivacyInfo.xcprivacy",
47
+ "NarsilReactNative.podspec",
48
+ "nitro.json",
49
+ "app.plugin.js",
50
+ "react-native.config.js",
51
+ "LICENSE",
52
+ "README.md"
53
+ ],
54
+ "sideEffects": false,
55
+ "peerDependencies": {
56
+ "@delali/narsil": "0.3.1-next.151",
57
+ "expo": "*",
58
+ "react-native": "*",
59
+ "react-native-nitro-modules": "*",
60
+ "react-native-worklets": "*"
61
+ },
62
+ "peerDependenciesMeta": {
63
+ "expo": {
64
+ "optional": true
65
+ },
66
+ "react-native-worklets": {
67
+ "optional": true
68
+ }
69
+ },
70
+ "devDependencies": {
71
+ "@biomejs/biome": "2.4.4",
72
+ "expo": "58.0.21",
73
+ "nitrogen": "0.37.1",
74
+ "react-native": "0.88.1",
75
+ "react-native-nitro-modules": "0.37.1",
76
+ "react-native-worklets": "0.13.0",
77
+ "typescript": "5.9.3",
78
+ "@delali/narsil": "0.3.1-next.151"
79
+ },
80
+ "dependencies": {
81
+ "web-streams-polyfill": "4.3.0"
82
+ },
83
+ "scripts": {
84
+ "build": "node scripts/copy-core.mjs && tsc -p tsconfig.build.json",
85
+ "copy-core": "node scripts/copy-core.mjs",
86
+ "specs": "nitrogen --logLevel=\"warning\"",
87
+ "lint": "biome check src/ scripts/ app.plugin.js react-native.config.js",
88
+ "typecheck": "tsc --noEmit"
89
+ }
6
90
  }
@@ -0,0 +1,8 @@
1
+ module.exports = {
2
+ dependency: {
3
+ platforms: {
4
+ ios: {},
5
+ android: {},
6
+ },
7
+ },
8
+ }
package/src/client.ts ADDED
@@ -0,0 +1,44 @@
1
+ import { createNarsil as createEngine, type Narsil, type ReactNativeHost } from '@delali/narsil'
2
+ import { installEngineHost, installedHost } from './engine/install'
3
+ import { callStreamed, withPhoneDurability } from './engine/streamed'
4
+ import { FORWARDED_METHODS, STREAMED_METHODS } from './methods'
5
+ import { loadNativeObjects } from './native-objects'
6
+ import type { PhoneNarsil, PhoneNarsilConfig } from './types'
7
+
8
+ function phoneNarsil(narsil: Narsil, host: ReactNativeHost): PhoneNarsil {
9
+ const client: Record<string, unknown> = {}
10
+ for (const method of FORWARDED_METHODS) {
11
+ const target = narsil[method] as (...values: unknown[]) => unknown
12
+ client[method] = async (...args: unknown[]) => target.apply(narsil, args)
13
+ }
14
+ for (const method of STREAMED_METHODS) {
15
+ client[method] = async (...args: unknown[]) => callStreamed(narsil, host, method, args)
16
+ }
17
+ client.on = narsil.on.bind(narsil)
18
+ client.off = narsil.off.bind(narsil)
19
+ client.shutdown = async () => narsil.shutdown()
20
+ return client as unknown as PhoneNarsil
21
+ }
22
+
23
+ /**
24
+ * Starts a Narsil engine on the app's own JS runtime and resolves with a
25
+ * client whose methods each return a promise. The engine takes the same
26
+ * settings as on Node.js, function settings such as embedding adapters,
27
+ * plugins, and an id generator included. File reads, file writes, file
28
+ * syncs, and vector searches run on native threads, and a long import or
29
+ * search yields to the app between steps.
30
+ *
31
+ * @param config - The engine settings. `durability` defaults to a
32
+ * write-ahead log in the `narsil` directory of the app's private storage,
33
+ * and `null` keeps every index in memory alone.
34
+ * @returns The client, once the engine has started.
35
+ * @throws A `NarsilError` with `NATIVE_DEPENDENCY_MISSING` when the app's
36
+ * build lacks the native module, as in Expo Go.
37
+ *
38
+ * @public
39
+ */
40
+ export async function createNarsil(config: PhoneNarsilConfig = {}): Promise<PhoneNarsil> {
41
+ installEngineHost(await loadNativeObjects(), true)
42
+ const narsil = await createEngine(withPhoneDurability(config))
43
+ return phoneNarsil(narsil, installedHost())
44
+ }
@@ -0,0 +1,9 @@
1
+ export const FILE_CHUNK_BYTES = 1_048_576
2
+ export const DEFAULT_DURABILITY_DIRECTORY = 'narsil'
3
+ export const ENGINE_RUNTIME_NAME = 'narsil-engine'
4
+ export const WORKLET_TRANSFER_TURN_BUDGET_MS = 8
5
+ export const WORKLET_TRANSFER_PART_TARGET_MS = 4
6
+ export const WORKLET_TRANSFER_FIRST_PART_ITEMS = 32
7
+ export const WORKLET_TRANSFER_LARGEST_PART_ITEMS = 4_096
8
+ export const WORKLET_JSON_PART_MAX_DEPTH = 64
9
+ export const APP_TIMER_WAIT_BEFORE_RUN_LOOP_HOP_MS = 100
@@ -0,0 +1,99 @@
1
+ import type { ReactNativeFiles, ReactNativeHost } from '@delali/narsil'
2
+ import { FILE_CHUNK_BYTES } from '../constants'
3
+
4
+ const CURRENT_POSITION = -1
5
+
6
+ function invalidPath(message: string): Error {
7
+ return Object.assign(new TypeError(message), { code: 'EINVAL' })
8
+ }
9
+
10
+ export function resolveAppPath(host: ReactNativeHost, path: string): string {
11
+ if (typeof path !== 'string' || path.length === 0) throw invalidPath('A file path must be a non-empty string')
12
+ if (path.includes('\0')) throw invalidPath('A file path cannot hold a NUL character')
13
+ if (path.startsWith('/')) return path
14
+ const directory = host.appStorageDirectory()
15
+ return directory.endsWith('/') ? `${directory}${path}` : `${directory}/${path}`
16
+ }
17
+
18
+ async function writeAll(files: ReactNativeFiles, descriptor: number, chunk: Uint8Array): Promise<void> {
19
+ let written = 0
20
+ while (written < chunk.byteLength) {
21
+ const end = Math.min(chunk.byteLength, written + FILE_CHUNK_BYTES)
22
+ const count = await files.writeAsync(descriptor, chunk.subarray(written, end), CURRENT_POSITION)
23
+ if (count <= 0) throw Object.assign(new Error('The file accepted no more bytes'), { code: 'EIO' })
24
+ written += count
25
+ }
26
+ }
27
+
28
+ function temporaryNameFor(path: string): string {
29
+ const suffix = new Uint8Array(8)
30
+ globalThis.crypto.getRandomValues(suffix)
31
+ let text = ''
32
+ for (const byte of suffix) text += byte.toString(16).padStart(2, '0')
33
+ return `${path}.partial-${text}`
34
+ }
35
+
36
+ export async function writeStreamToFile(
37
+ files: ReactNativeFiles,
38
+ stream: ReadableStream<Uint8Array>,
39
+ path: string,
40
+ ): Promise<number> {
41
+ const temporary = temporaryNameFor(path)
42
+ const descriptor = files.open(temporary, 'wx')
43
+ const reader = stream.getReader()
44
+ let total = 0
45
+ try {
46
+ for (;;) {
47
+ const { done, value } = await reader.read()
48
+ if (done) break
49
+ await writeAll(files, descriptor, value)
50
+ total += value.byteLength
51
+ }
52
+ await files.syncAsync(descriptor)
53
+ } catch (error) {
54
+ await reader.cancel(error).catch(() => undefined)
55
+ files.close(descriptor)
56
+ try {
57
+ files.unlink(temporary)
58
+ } catch {}
59
+ throw error
60
+ }
61
+ files.close(descriptor)
62
+ files.rename(temporary, path)
63
+ return total
64
+ }
65
+
66
+ export async function* fileChunks(files: ReactNativeFiles, path: string): AsyncGenerator<Uint8Array> {
67
+ const descriptor = files.open(path, 'r')
68
+ try {
69
+ let position = 0
70
+ for (;;) {
71
+ const chunk = new Uint8Array(FILE_CHUNK_BYTES)
72
+ const count = await files.readAsync(descriptor, chunk, position)
73
+ if (count === 0) return
74
+ position += count
75
+ yield count === chunk.byteLength ? chunk : chunk.subarray(0, count)
76
+ }
77
+ } finally {
78
+ files.close(descriptor)
79
+ }
80
+ }
81
+
82
+ export async function collectStream(stream: ReadableStream<Uint8Array>): Promise<Uint8Array> {
83
+ const reader = stream.getReader()
84
+ const chunks: Uint8Array[] = []
85
+ let total = 0
86
+ for (;;) {
87
+ const { done, value } = await reader.read()
88
+ if (done) break
89
+ chunks.push(value)
90
+ total += value.byteLength
91
+ }
92
+ const bytes = new Uint8Array(total)
93
+ let offset = 0
94
+ for (const chunk of chunks) {
95
+ bytes.set(chunk, offset)
96
+ offset += chunk.byteLength
97
+ }
98
+ return bytes
99
+ }
@@ -0,0 +1,162 @@
1
+ import type {
2
+ NativeCore,
3
+ NativeGraphHandle,
4
+ NativeGraphMemory,
5
+ NativeStoreHandle,
6
+ NativeStoreMemory,
7
+ } from '@delali/narsil'
8
+ import type {
9
+ GraphMemory,
10
+ MemoryView,
11
+ NarsilAttachedGraph,
12
+ NarsilAttachedStore,
13
+ NarsilCore,
14
+ StoreMemory,
15
+ } from '../specs/core.nitro'
16
+ import { withNativeCode } from './native-error'
17
+
18
+ const ABSENT_REGION: MemoryView = { buffer: new ArrayBuffer(0), byteOffset: 0, byteLength: 0 }
19
+
20
+ function viewOf(array: ArrayBufferView): MemoryView {
21
+ return { buffer: array.buffer as ArrayBuffer, byteOffset: array.byteOffset, byteLength: array.byteLength }
22
+ }
23
+
24
+ function graphMemoryOf(memory: NativeGraphMemory): GraphMemory {
25
+ return {
26
+ graphHeader: viewOf(memory.graphHeader),
27
+ nodeLevels: viewOf(memory.nodeLevels),
28
+ level0: viewOf(memory.level0),
29
+ upperBase: viewOf(memory.upperBase),
30
+ upper: viewOf(memory.upper),
31
+ locks: viewOf(memory.locks),
32
+ tombstones: viewOf(memory.tombstones),
33
+ incoming: viewOf(memory.incoming),
34
+ nearestLinkDue: viewOf(memory.nearestLinkDue),
35
+ heldLocks: viewOf(memory.heldLocks),
36
+ }
37
+ }
38
+
39
+ function storeMemoryOf(memory: NativeStoreMemory): StoreMemory {
40
+ return {
41
+ storeHeader: viewOf(memory.storeHeader),
42
+ dimension: memory.dimension,
43
+ bits: memory.bits,
44
+ recordsPerBlock: memory.recordsPerBlock,
45
+ vectorsPerBlock: memory.vectorsPerBlock,
46
+ vectorStrideBytes: memory.vectorStrideBytes,
47
+ codePresent: viewOf(memory.codePresent),
48
+ centroid: viewOf(memory.centroid),
49
+ magnitudes: viewOf(memory.magnitudes),
50
+ present: viewOf(memory.present),
51
+ vectorFile: viewOf(memory.vectorFile),
52
+ vectorOffset: viewOf(memory.vectorOffset),
53
+ vectorFiles: memory.vectorFiles,
54
+ codeBlocks: memory.codeBlocks.map(viewOf),
55
+ vectorBlocks: memory.vectorBlocks.map(block => (block === null ? ABSENT_REGION : viewOf(block))),
56
+ }
57
+ }
58
+
59
+ function graphOf(handle: NativeGraphHandle): NarsilAttachedGraph {
60
+ return handle as NarsilAttachedGraph
61
+ }
62
+
63
+ function storeOf(handle: NativeStoreHandle): NarsilAttachedStore {
64
+ return handle as NarsilAttachedStore
65
+ }
66
+
67
+ function backgroundCalls(core: NarsilCore): Pick<NativeCore, 'searchAsync' | 'scoreAsync' | 'connectUnreachedAsync'> {
68
+ return {
69
+ async connectUnreachedAsync(graph, store, metric, nearby, threadSlot) {
70
+ try {
71
+ const outcome = await core.connectUnreachedAsync(
72
+ graphOf(graph),
73
+ storeOf(store),
74
+ metric,
75
+ viewOf(nearby),
76
+ threadSlot,
77
+ )
78
+ return { status: outcome.status, wakes: new Int32Array(outcome.wakes) }
79
+ } catch (error) {
80
+ throw withNativeCode(error)
81
+ }
82
+ },
83
+ async searchAsync(graph, store, query, metric, candidateCount, threadSlot, accepted) {
84
+ try {
85
+ const hits = await core.searchAsync(
86
+ graphOf(graph),
87
+ storeOf(store),
88
+ viewOf(query),
89
+ metric,
90
+ candidateCount,
91
+ threadSlot,
92
+ accepted === null ? ABSENT_REGION : viewOf(accepted),
93
+ accepted !== null,
94
+ )
95
+ return {
96
+ count: hits.count,
97
+ ordinals: new Int32Array(hits.ordinals),
98
+ distances: new Float64Array(hits.distances),
99
+ }
100
+ } catch (error) {
101
+ throw withNativeCode(error)
102
+ }
103
+ },
104
+ async scoreAsync(store, query, metric, ordinals, count, threadSlot) {
105
+ try {
106
+ const scores = await core.scoreAsync(storeOf(store), viewOf(query), metric, viewOf(ordinals), count, threadSlot)
107
+ return { status: scores.status, distances: new Float64Array(scores.distances) }
108
+ } catch (error) {
109
+ throw withNativeCode(error)
110
+ }
111
+ },
112
+ }
113
+ }
114
+
115
+ export function adaptCore(core: NarsilCore, nativeThreads: boolean): NativeCore {
116
+ return {
117
+ abiVersion: () => core.abiVersion(),
118
+ workspaceBytes: () => core.workspaceBytes(),
119
+ attachGraph(memory) {
120
+ try {
121
+ return core.attachGraph(graphMemoryOf(memory))
122
+ } catch (error) {
123
+ throw withNativeCode(error)
124
+ }
125
+ },
126
+ attachStore(memory) {
127
+ try {
128
+ return core.attachStore(storeMemoryOf(memory))
129
+ } catch (error) {
130
+ throw withNativeCode(error)
131
+ }
132
+ },
133
+ detachStore: store => storeOf(store).detach(),
134
+ search(graph, store, query, metric, candidateCount, threadSlot, ordinals, distances, accepted) {
135
+ return core.search(
136
+ graphOf(graph),
137
+ storeOf(store),
138
+ viewOf(query),
139
+ metric,
140
+ candidateCount,
141
+ threadSlot,
142
+ viewOf(ordinals),
143
+ viewOf(distances),
144
+ accepted === null ? ABSENT_REGION : viewOf(accepted),
145
+ accepted !== null,
146
+ )
147
+ },
148
+ score: (store, query, metric, ordinals, count, distances) =>
149
+ core.score(storeOf(store), viewOf(query), metric, viewOf(ordinals), count, viewOf(distances)),
150
+ ...(nativeThreads ? backgroundCalls(core) : {}),
151
+ place: (graph, store, metric, ordinal, topLayer, graphHeldAlone, threadSlot, wakes) =>
152
+ core.place(graphOf(graph), storeOf(store), metric, ordinal, topLayer, graphHeldAlone, threadSlot, viewOf(wakes)),
153
+ remove: (graph, ordinal, threadSlot, wakes) => core.remove(graphOf(graph), ordinal, threadSlot, viewOf(wakes)),
154
+ compact: (graph, store, metric, threadSlot, wakes) =>
155
+ core.compact(graphOf(graph), storeOf(store), metric, threadSlot, viewOf(wakes)),
156
+ connectUnreached: (graph, store, metric, nearby, threadSlot, wakes) =>
157
+ core.connectUnreached(graphOf(graph), storeOf(store), metric, viewOf(nearby), threadSlot, viewOf(wakes)),
158
+ measure: (store, metric, ordinals, count, totals) =>
159
+ core.measure(storeOf(store), metric, viewOf(ordinals), count, viewOf(totals)),
160
+ quantise: (store, metric, ordinals, count) => core.quantise(storeOf(store), metric, viewOf(ordinals), count),
161
+ }
162
+ }
@@ -0,0 +1,186 @@
1
+ import type { ReactNativeDirectoryEntry, ReactNativeFileStatus, ReactNativeFiles } from '@delali/narsil'
2
+ import type { NarsilFiles } from '../specs/files.nitro'
3
+ import { withNativeCode } from './native-error'
4
+
5
+ type BlockingFiles = Omit<ReactNativeFiles, 'readAsync' | 'writeAsync' | 'syncAsync'>
6
+
7
+ function rejectedWithNativeCode(error: unknown): Promise<never> {
8
+ return Promise.reject(withNativeCode(error))
9
+ }
10
+
11
+ function blockingFiles(files: NarsilFiles): BlockingFiles {
12
+ return {
13
+ open(path: string, flags: string): number {
14
+ try {
15
+ return files.open(path, flags)
16
+ } catch (error) {
17
+ throw withNativeCode(error)
18
+ }
19
+ },
20
+ close(descriptor: number): void {
21
+ try {
22
+ files.close(descriptor)
23
+ } catch (error) {
24
+ throw withNativeCode(error)
25
+ }
26
+ },
27
+ read(descriptor: number, target: Uint8Array, position: number): number {
28
+ try {
29
+ return files.read(descriptor, target.buffer as ArrayBuffer, target.byteOffset, target.byteLength, position)
30
+ } catch (error) {
31
+ throw withNativeCode(error)
32
+ }
33
+ },
34
+ write(descriptor: number, source: Uint8Array, position: number): number {
35
+ try {
36
+ return files.write(descriptor, source.buffer as ArrayBuffer, source.byteOffset, source.byteLength, position)
37
+ } catch (error) {
38
+ throw withNativeCode(error)
39
+ }
40
+ },
41
+ sync(descriptor: number): void {
42
+ try {
43
+ files.sync(descriptor)
44
+ } catch (error) {
45
+ throw withNativeCode(error)
46
+ }
47
+ },
48
+ truncate(descriptor: number, byteLength: number): void {
49
+ try {
50
+ files.truncate(descriptor, byteLength)
51
+ } catch (error) {
52
+ throw withNativeCode(error)
53
+ }
54
+ },
55
+ descriptorStatus(descriptor: number): ReactNativeFileStatus {
56
+ try {
57
+ return files.descriptorStatus(descriptor)
58
+ } catch (error) {
59
+ throw withNativeCode(error)
60
+ }
61
+ },
62
+ pathStatus(path: string): ReactNativeFileStatus {
63
+ try {
64
+ return files.pathStatus(path)
65
+ } catch (error) {
66
+ throw withNativeCode(error)
67
+ }
68
+ },
69
+ makeDirectory(path: string): void {
70
+ try {
71
+ files.makeDirectory(path)
72
+ } catch (error) {
73
+ throw withNativeCode(error)
74
+ }
75
+ },
76
+ readDirectory(path: string): ReactNativeDirectoryEntry[] {
77
+ try {
78
+ return files.readDirectory(path)
79
+ } catch (error) {
80
+ throw withNativeCode(error)
81
+ }
82
+ },
83
+ rename(from: string, to: string): void {
84
+ try {
85
+ files.rename(from, to)
86
+ } catch (error) {
87
+ throw withNativeCode(error)
88
+ }
89
+ },
90
+ unlink(path: string): void {
91
+ try {
92
+ files.unlink(path)
93
+ } catch (error) {
94
+ throw withNativeCode(error)
95
+ }
96
+ },
97
+ removeDirectory(path: string): void {
98
+ try {
99
+ files.removeDirectory(path)
100
+ } catch (error) {
101
+ throw withNativeCode(error)
102
+ }
103
+ },
104
+ link(existingPath: string, newPath: string): void {
105
+ try {
106
+ files.link(existingPath, newPath)
107
+ } catch (error) {
108
+ throw withNativeCode(error)
109
+ }
110
+ },
111
+ }
112
+ }
113
+
114
+ function onTheCallingThread(blocking: BlockingFiles): ReactNativeFiles {
115
+ return {
116
+ ...blocking,
117
+ readAsync: async (descriptor, target, position) => blocking.read(descriptor, target, position),
118
+ writeAsync: async (descriptor, source, position) => blocking.write(descriptor, source, position),
119
+ syncAsync: async descriptor => blocking.sync(descriptor),
120
+ }
121
+ }
122
+
123
+ function onNativeThreads(files: NarsilFiles, blocking: BlockingFiles): ReactNativeFiles {
124
+ const pendingByDescriptor = new Map<number, Promise<unknown>>()
125
+
126
+ function inOrder<T>(descriptor: number, run: () => Promise<T>): Promise<T> {
127
+ const previous = pendingByDescriptor.get(descriptor)
128
+ let started: Promise<T>
129
+ if (previous !== undefined) {
130
+ started = previous.then(run, run)
131
+ } else {
132
+ try {
133
+ started = run()
134
+ } catch (error) {
135
+ started = Promise.reject(error)
136
+ }
137
+ }
138
+ const settled = started.then(
139
+ () => {},
140
+ () => {},
141
+ )
142
+ pendingByDescriptor.set(descriptor, settled)
143
+ settled.then(() => {
144
+ if (pendingByDescriptor.get(descriptor) === settled) pendingByDescriptor.delete(descriptor)
145
+ })
146
+ return started.catch(rejectedWithNativeCode)
147
+ }
148
+
149
+ return {
150
+ ...blocking,
151
+ close(descriptor: number): void {
152
+ const pending = pendingByDescriptor.get(descriptor)
153
+ if (pending === undefined) {
154
+ blocking.close(descriptor)
155
+ return
156
+ }
157
+ pendingByDescriptor.delete(descriptor)
158
+ pending.then(() => {
159
+ try {
160
+ files.close(descriptor)
161
+ } catch {}
162
+ })
163
+ },
164
+ readAsync(descriptor: number, target: Uint8Array, position: number): Promise<number> {
165
+ return inOrder(descriptor, async () => {
166
+ const bytes = await files.readAsync(descriptor, target.byteLength, position)
167
+ target.set(new Uint8Array(bytes))
168
+ return bytes.byteLength
169
+ })
170
+ },
171
+ writeAsync(descriptor: number, source: Uint8Array, position: number): Promise<number> {
172
+ const bytes = pendingByDescriptor.has(descriptor) ? source.slice() : source
173
+ return inOrder(descriptor, () =>
174
+ files.writeAsync(descriptor, bytes.buffer as ArrayBuffer, bytes.byteOffset, bytes.byteLength, position),
175
+ )
176
+ },
177
+ syncAsync(descriptor: number): Promise<void> {
178
+ return inOrder(descriptor, () => files.syncAsync(descriptor))
179
+ },
180
+ }
181
+ }
182
+
183
+ export function adaptFiles(files: NarsilFiles, nativeThreads: boolean): ReactNativeFiles {
184
+ const blocking = blockingFiles(files)
185
+ return nativeThreads ? onNativeThreads(files, blocking) : onTheCallingThread(blocking)
186
+ }