@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.
- package/LICENSE +201 -0
- package/NarsilReactNative.podspec +57 -0
- package/README.md +46 -2
- package/android/CMakeLists.txt +48 -0
- package/android/build.gradle +151 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/AndroidEnvironment.cpp +44 -0
- package/android/src/main/cpp/AndroidEnvironment.hpp +11 -0
- package/android/src/main/cpp/cpp-adapter.cpp +11 -0
- package/android/src/main/java/com/margelo/nitro/narsil/NarsilAndroidEnvironment.kt +29 -0
- package/android/src/main/java/com/margelo/nitro/narsil/NarsilReactNativePackage.kt +22 -0
- package/app.plugin.js +25 -0
- package/cpp/AttachedField.cpp +234 -0
- package/cpp/AttachedField.hpp +61 -0
- package/cpp/Compression.cpp +113 -0
- package/cpp/Compression.hpp +16 -0
- package/cpp/GraphHeldAlone.cpp +29 -0
- package/cpp/GraphHeldAlone.hpp +25 -0
- package/cpp/HostMemory.cpp +123 -0
- package/cpp/HostMemory.hpp +53 -0
- package/cpp/HybridNarsilCoreImpl.cpp +390 -0
- package/cpp/HybridNarsilCoreImpl.hpp +68 -0
- package/cpp/HybridNarsilFilesImpl.cpp +245 -0
- package/cpp/HybridNarsilFilesImpl.hpp +34 -0
- package/cpp/HybridNarsilHostImpl.cpp +126 -0
- package/cpp/HybridNarsilHostImpl.hpp +42 -0
- package/cpp/NativeAtomics.cpp +110 -0
- package/cpp/NativeAtomics.hpp +11 -0
- package/cpp/ProcessInfo.cpp +83 -0
- package/cpp/ProcessInfo.hpp +11 -0
- package/cpp/ThreadPriority.cpp +77 -0
- package/cpp/ThreadPriority.hpp +8 -0
- package/cpp/WorkletRuntimeDispatcher.cpp +61 -0
- package/cpp/WorkletRuntimeDispatcher.hpp +9 -0
- package/cpp/core/include/narsil_core.h +208 -0
- package/cpp/core/src/connect.c +213 -0
- package/cpp/core/src/distance.c +103 -0
- package/cpp/core/src/files.c +99 -0
- package/cpp/core/src/heap.c +84 -0
- package/cpp/core/src/internal.h +284 -0
- package/cpp/core/src/kernels_code.c +209 -0
- package/cpp/core/src/kernels_float.c +197 -0
- package/cpp/core/src/kernels_pair.c +133 -0
- package/cpp/core/src/last_links.c +145 -0
- package/cpp/core/src/links.c +195 -0
- package/cpp/core/src/locks.c +168 -0
- package/cpp/core/src/nearest_links.c +147 -0
- package/cpp/core/src/place.c +215 -0
- package/cpp/core/src/quantize.c +226 -0
- package/cpp/core/src/records.c +97 -0
- package/cpp/core/src/remove.c +205 -0
- package/cpp/core/src/vectors.c +139 -0
- package/cpp/core/src/walk.c +231 -0
- package/cpp/core/src/workspace.c +167 -0
- package/ios/PrivacyInfo.xcprivacy +23 -0
- package/lib/client.d.ts +19 -0
- package/lib/client.js +41 -0
- package/lib/constants.d.ts +9 -0
- package/lib/constants.js +9 -0
- package/lib/engine/byte-files.d.ts +5 -0
- package/lib/engine/byte-files.js +97 -0
- package/lib/engine/core.d.ts +3 -0
- package/lib/engine/core.js +113 -0
- package/lib/engine/files.d.ts +3 -0
- package/lib/engine/files.js +188 -0
- package/lib/engine/host.d.ts +13 -0
- package/lib/engine/host.js +102 -0
- package/lib/engine/install.d.ts +4 -0
- package/lib/engine/install.js +29 -0
- package/lib/engine/native-error.d.ts +1 -0
- package/lib/engine/native-error.js +12 -0
- package/lib/engine/stand-ins/atomics.d.ts +5 -0
- package/lib/engine/stand-ins/atomics.js +236 -0
- package/lib/engine/stand-ins/crypto.d.ts +2 -0
- package/lib/engine/stand-ins/crypto.js +65 -0
- package/lib/engine/stand-ins/process.d.ts +2 -0
- package/lib/engine/stand-ins/process.js +22 -0
- package/lib/engine/stand-ins/streams.d.ts +1 -0
- package/lib/engine/stand-ins/streams.js +31 -0
- package/lib/engine/stand-ins/structured-clone.d.ts +1 -0
- package/lib/engine/stand-ins/structured-clone.js +121 -0
- package/lib/engine/streamed.d.ts +6 -0
- package/lib/engine/streamed.js +29 -0
- package/lib/engine/transfer.d.ts +3 -0
- package/lib/engine/transfer.js +77 -0
- package/lib/errors.d.ts +25 -0
- package/lib/errors.js +59 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +2 -0
- package/lib/methods.d.ts +5 -0
- package/lib/methods.js +47 -0
- package/lib/native-objects.d.ts +2 -0
- package/lib/native-objects.js +24 -0
- package/lib/specs/core.nitro.d.ts +79 -0
- package/lib/specs/core.nitro.js +1 -0
- package/lib/specs/files.nitro.d.ts +36 -0
- package/lib/specs/files.nitro.js +1 -0
- package/lib/specs/host.nitro.d.ts +24 -0
- package/lib/specs/host.nitro.js +1 -0
- package/lib/transferred.d.ts +15 -0
- package/lib/transferred.js +6 -0
- package/lib/types.d.ts +136 -0
- package/lib/types.js +1 -0
- package/lib/worklets/app-turns.d.ts +1 -0
- package/lib/worklets/app-turns.js +44 -0
- package/lib/worklets/client.d.ts +30 -0
- package/lib/worklets/client.js +144 -0
- package/lib/worklets/index.d.ts +4 -0
- package/lib/worklets/index.js +2 -0
- package/lib/worklets/instances.d.ts +15 -0
- package/lib/worklets/instances.js +194 -0
- package/lib/worklets/json-part.d.ts +1 -0
- package/lib/worklets/json-part.js +42 -0
- package/lib/worklets/runtime.d.ts +25 -0
- package/lib/worklets/runtime.js +80 -0
- package/lib/worklets/transfer-queue.d.ts +7 -0
- package/lib/worklets/transfer-queue.js +79 -0
- package/lib/worklets/types.d.ts +26 -0
- package/lib/worklets/types.js +1 -0
- package/nitro.json +32 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/NarsilReactNative+autolinking.cmake +85 -0
- package/nitrogen/generated/android/NarsilReactNative+autolinking.gradle +27 -0
- package/nitrogen/generated/android/NarsilReactNativeOnLoad.cpp +69 -0
- package/nitrogen/generated/android/NarsilReactNativeOnLoad.hpp +34 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/narsil/NarsilReactNativeOnLoad.kt +35 -0
- package/nitrogen/generated/ios/NarsilReactNative+autolinking.rb +62 -0
- package/nitrogen/generated/ios/NarsilReactNative-Swift-Cxx-Bridge.cpp +17 -0
- package/nitrogen/generated/ios/NarsilReactNative-Swift-Cxx-Bridge.hpp +27 -0
- package/nitrogen/generated/ios/NarsilReactNative-Swift-Cxx-Umbrella.hpp +38 -0
- package/nitrogen/generated/ios/NarsilReactNativeAutolinking.mm +55 -0
- package/nitrogen/generated/ios/NarsilReactNativeAutolinking.swift +16 -0
- package/nitrogen/generated/shared/c++/DirectoryEntry.hpp +91 -0
- package/nitrogen/generated/shared/c++/FileStatus.hpp +103 -0
- package/nitrogen/generated/shared/c++/GraphMemory.hpp +120 -0
- package/nitrogen/generated/shared/c++/HybridNarsilAttachedGraphSpec.cpp +21 -0
- package/nitrogen/generated/shared/c++/HybridNarsilAttachedGraphSpec.hpp +62 -0
- package/nitrogen/generated/shared/c++/HybridNarsilAttachedStoreSpec.cpp +21 -0
- package/nitrogen/generated/shared/c++/HybridNarsilAttachedStoreSpec.hpp +62 -0
- package/nitrogen/generated/shared/c++/HybridNarsilCoreSpec.cpp +35 -0
- package/nitrogen/generated/shared/c++/HybridNarsilCoreSpec.hpp +100 -0
- package/nitrogen/generated/shared/c++/HybridNarsilFilesSpec.cpp +37 -0
- package/nitrogen/generated/shared/c++/HybridNarsilFilesSpec.hpp +86 -0
- package/nitrogen/generated/shared/c++/HybridNarsilHostSpec.cpp +34 -0
- package/nitrogen/generated/shared/c++/HybridNarsilHostSpec.hpp +78 -0
- package/nitrogen/generated/shared/c++/MemoryView.hpp +91 -0
- package/nitrogen/generated/shared/c++/NativeConnectOutcome.hpp +87 -0
- package/nitrogen/generated/shared/c++/NativeScores.hpp +87 -0
- package/nitrogen/generated/shared/c++/NativeSearchHits.hpp +91 -0
- package/nitrogen/generated/shared/c++/ProcessMemory.hpp +87 -0
- package/nitrogen/generated/shared/c++/StoreMemory.hpp +142 -0
- package/package.json +87 -3
- package/react-native.config.js +8 -0
- package/src/client.ts +44 -0
- package/src/constants.ts +9 -0
- package/src/engine/byte-files.ts +99 -0
- package/src/engine/core.ts +162 -0
- package/src/engine/files.ts +186 -0
- package/src/engine/host.ts +111 -0
- package/src/engine/install.ts +31 -0
- package/src/engine/native-error.ts +10 -0
- package/src/engine/stand-ins/atomics.ts +288 -0
- package/src/engine/stand-ins/crypto.ts +81 -0
- package/src/engine/stand-ins/process.ts +30 -0
- package/src/engine/stand-ins/streams.ts +45 -0
- package/src/engine/stand-ins/structured-clone.ts +118 -0
- package/src/engine/streamed.ts +40 -0
- package/src/engine/transfer.ts +72 -0
- package/src/errors.ts +73 -0
- package/src/index.ts +3 -0
- package/src/methods.ts +52 -0
- package/src/native-objects.ts +30 -0
- package/src/specs/core.nitro.ts +140 -0
- package/src/specs/files.nitro.ts +42 -0
- package/src/specs/host.nitro.ts +30 -0
- package/src/transferred.ts +18 -0
- package/src/types.ts +164 -0
- package/src/worklets/app-turns.ts +48 -0
- package/src/worklets/client.ts +165 -0
- package/src/worklets/index.ts +4 -0
- package/src/worklets/instances.ts +231 -0
- package/src/worklets/json-part.ts +38 -0
- package/src/worklets/runtime.ts +190 -0
- package/src/worklets/transfer-queue.ts +98 -0
- 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.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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
|
}
|
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
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -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
|
+
}
|