crossbind 0.0.1 → 2.0.0-beta.50

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 (95) hide show
  1. package/AGENTS.md +92 -0
  2. package/CHANGELOG.md +145 -0
  3. package/LICENSE +21 -0
  4. package/README.md +133 -0
  5. package/package.json +52 -4
  6. package/src/actions/buildBinTools.js +0 -0
  7. package/src/actions/buildCargo.js +88 -0
  8. package/src/actions/buildDependencies.js +157 -0
  9. package/src/actions/buildExternal.js +57 -0
  10. package/src/actions/buildJs.js +93 -0
  11. package/src/actions/buildLib.js +115 -0
  12. package/src/actions/buildTypes.js +33 -0
  13. package/src/actions/buildWasiCommand.js +101 -0
  14. package/src/actions/buildWasm.js +322 -0
  15. package/src/actions/createInterface.js +172 -0
  16. package/src/actions/createLib.js +249 -0
  17. package/src/actions/createXCFramework.js +82 -0
  18. package/src/actions/extensions.js +7 -0
  19. package/src/actions/getAllBridges.js +20 -0
  20. package/src/actions/getCmakeParameters.js +66 -0
  21. package/src/actions/getData.js +46 -0
  22. package/src/actions/getDependLibs.js +25 -0
  23. package/src/actions/isSourceNewer.js +35 -0
  24. package/src/actions/licenses.js +156 -0
  25. package/src/actions/run.js +305 -0
  26. package/src/actions/target.js +54 -0
  27. package/src/assets/cmake/CMakeLists.txt +61 -0
  28. package/src/assets/cmake/dist.cmake +57 -0
  29. package/src/assets/cmake/ios.toolchain.cmake +1176 -0
  30. package/src/assets/cpp-runtime/browser.cpp +12 -0
  31. package/src/assets/cpp-runtime/commonBridges.cpp +17 -0
  32. package/src/assets/cpp-runtime/crossbindEmptySource.cpp +9 -0
  33. package/src/assets/cpp-runtime/node.cpp +19 -0
  34. package/src/assets/js-runtime/adapters/exception-decode.js +79 -0
  35. package/src/assets/js-runtime/adapters/fs-browser.js +173 -0
  36. package/src/assets/js-runtime/adapters/fs-node.js +28 -0
  37. package/src/assets/js-runtime/adapters/path-fs.js +6 -0
  38. package/src/assets/js-runtime/adapters/path-url.js +8 -0
  39. package/src/assets/js-runtime/adapters/vector-coercion.js +209 -0
  40. package/src/assets/js-runtime/adapters/worker-comlink.js +309 -0
  41. package/src/assets/js-runtime/browser.js +16 -0
  42. package/src/assets/js-runtime/core.js +193 -0
  43. package/src/assets/js-runtime/edge.js +18 -0
  44. package/src/assets/js-runtime/node.js +20 -0
  45. package/src/assets/packaging/crossbind-package.podspec +17 -0
  46. package/src/assets/wasi-runtime/stubs.c +84 -0
  47. package/src/bin.js +416 -0
  48. package/src/index.js +19 -0
  49. package/src/integration/getCrossbindScript.js +180 -0
  50. package/src/integration/getDependFilePath.js +132 -0
  51. package/src/runtime/wasiRun.mjs +118 -0
  52. package/src/state/calculateDependencyParameters.js +97 -0
  53. package/src/state/index.js +128 -0
  54. package/src/state/loadConfig.js +215 -0
  55. package/src/utils/appRustCrates.js +106 -0
  56. package/src/utils/binTools.js +76 -0
  57. package/src/utils/bridgeAsyncGuard.js +37 -0
  58. package/src/utils/cargoTarget.js +15 -0
  59. package/src/utils/cppDts.js +201 -0
  60. package/src/utils/cppFieldBindings.js +23 -0
  61. package/src/utils/dependencyRebuild.js +99 -0
  62. package/src/utils/dirLock.js +71 -0
  63. package/src/utils/downloadAndExtractFile.js +102 -0
  64. package/src/utils/familyManifest.js +17 -0
  65. package/src/utils/findFiles.js +5 -0
  66. package/src/utils/fixPackageName.js +7 -0
  67. package/src/utils/flattenConfigForTable.js +75 -0
  68. package/src/utils/getAbsolutePath.js +14 -0
  69. package/src/utils/getCMakeListsFilePath.js +17 -0
  70. package/src/utils/getOsUserAndGroupId.js +11 -0
  71. package/src/utils/getParentPath.js +12 -0
  72. package/src/utils/hash.js +33 -0
  73. package/src/utils/inputStamp.js +28 -0
  74. package/src/utils/licenseReport.js +94 -0
  75. package/src/utils/linkLayout.js +34 -0
  76. package/src/utils/loadJs.js +31 -0
  77. package/src/utils/loadJson.js +16 -0
  78. package/src/utils/logger.js +110 -0
  79. package/src/utils/ociArtifact.js +124 -0
  80. package/src/utils/overrideDependency.js +99 -0
  81. package/src/utils/provenance.js +54 -0
  82. package/src/utils/pullDockerImage.js +105 -0
  83. package/src/utils/replaceBasePathForDocker.js +18 -0
  84. package/src/utils/resolveEmbindRust.js +66 -0
  85. package/src/utils/runCargo.js +228 -0
  86. package/src/utils/rustBridgeGen.js +1013 -0
  87. package/src/utils/rustMt.js +76 -0
  88. package/src/utils/rustSysroot.js +171 -0
  89. package/src/utils/rustSysrootPin.js +11 -0
  90. package/src/utils/systemKeys.js +29 -0
  91. package/src/utils/targets.js +109 -0
  92. package/src/utils/wasiToolchain.js +47 -0
  93. package/src/utils/writeIfChanged.js +8 -0
  94. package/src/utils/writeJson.js +9 -0
  95. package/vitest.config.js +40 -0
@@ -0,0 +1,12 @@
1
+ #include <emscripten.h>
2
+ #include <emscripten/bind.h>
3
+ #include <emscripten/wasmfs.h>
4
+
5
+ void crossbind_init_opfs() {
6
+ backend_t opfs_backend = wasmfs_create_opfs_backend();
7
+ wasmfs_create_directory("/opfs", 0777, opfs_backend);
8
+ }
9
+
10
+ EMSCRIPTEN_BINDINGS(Browser) {
11
+ emscripten::function("crossbind_init_opfs", &crossbind_init_opfs);
12
+ };
@@ -0,0 +1,17 @@
1
+ #include <emscripten/bind.h>
2
+
3
+ EMSCRIPTEN_BINDINGS(CommonBridges) {
4
+ emscripten::register_vector<char>("VectorChar");
5
+ emscripten::register_vector<unsigned char>("VectorUChar");
6
+ emscripten::register_vector<short>("VectorShort");
7
+ emscripten::register_vector<int>("VectorInt");
8
+ emscripten::register_vector<int64_t>("VectorInt64");
9
+ emscripten::register_vector<float>("VectorFloat");
10
+ emscripten::register_vector<double>("VectorDouble");
11
+ emscripten::register_vector<std::string>("VectorString");
12
+
13
+ emscripten::register_map<int, int>("MapIntInt");
14
+ emscripten::register_map<int, std::string>("MapIntString");
15
+ emscripten::register_map<std::string, std::string>("MapStringString");
16
+ emscripten::register_map<std::string, int>("MapStringInt");
17
+ };
@@ -0,0 +1,9 @@
1
+ // Placeholder TU so the static archive always exports at least one symbol;
2
+ // otherwise ranlib emits "has no symbols" warnings.
3
+ //
4
+ // `inline` gives the variable C++17 vague/COMDAT linkage so the same file
5
+ // compiled into multiple libraries can be linked into one binary without
6
+ // ODR conflicts. `[[gnu::used]]` (clang/gcc) forces the compiler to emit
7
+ // the symbol even though nothing references it; without it, the optimizer
8
+ // drops the inline variable and the warning persists.
9
+ [[maybe_unused, gnu::used]] inline int crossbind_empty_source_marker = 0;
@@ -0,0 +1,19 @@
1
+ #include <emscripten.h>
2
+ #include <emscripten/wasmfs.h>
3
+ #include <limits.h>
4
+ #include <unistd.h>
5
+
6
+ backend_t wasmfs_create_root_dir(void) {
7
+ return wasmfs_create_node_backend("/");
8
+ }
9
+
10
+ void wasmfs_before_preload(void) {
11
+ backend_t mem = wasmfs_create_memory_backend();
12
+ wasmfs_create_directory("/memfs", 0777, mem);
13
+ }
14
+
15
+ __attribute__((constructor)) static void setup_cwd(void) {
16
+ char cwd[PATH_MAX];
17
+ EM_ASM({ stringToUTF8(process.cwd(), $0, $1); }, cwd, (int)sizeof(cwd));
18
+ chdir(cwd);
19
+ }
@@ -0,0 +1,79 @@
1
+ // === wasm-EH exception decoding (shared by the worker and direct runtimes) ===
2
+ // Builds link with -fwasm-exceptions, so an uncaught C++ exception reaches JS as a raw
3
+ // WebAssembly.Exception with no .message. The glue exports getExceptionMessage (via
4
+ // -sEXPORT_EXCEPTION_HANDLING_HELPERS), which decodes it back into [type, what()]. This
5
+ // adapter turns such values into real JS Errors at the binding boundary; anything else
6
+ // passes through untouched, so it is safe to apply on every call path.
7
+ let decodeModule = null;
8
+
9
+ export function setExceptionDecodeModule(m) {
10
+ decodeModule = m;
11
+ }
12
+
13
+ export function decodeCppException(value) {
14
+ const m = decodeModule;
15
+ if (m == null
16
+ || typeof m.getExceptionMessage !== 'function'
17
+ || typeof WebAssembly === 'undefined'
18
+ || typeof WebAssembly.Exception !== 'function'
19
+ || !(value instanceof WebAssembly.Exception)) {
20
+ return value;
21
+ }
22
+ try {
23
+ const [type, message] = m.getExceptionMessage(value);
24
+ const error = new Error(message ? `${type}: ${message}` : `${type}`);
25
+ error.cppType = type;
26
+ error.cppMessage = message;
27
+ return error;
28
+ } catch (decodeFailure) {
29
+ return value;
30
+ }
31
+ }
32
+
33
+ const STATIC_SKIP = new Set(['prototype', 'name', 'length', 'caller', 'arguments', 'argCount']);
34
+
35
+ function wrapOwnMethod(holder, name) {
36
+ const desc = Object.getOwnPropertyDescriptor(holder, name);
37
+ if (!desc || typeof desc.value !== 'function' || desc.writable === false) return;
38
+ const original = desc.value;
39
+ function decoded(...args) {
40
+ let result;
41
+ try {
42
+ result = original.apply(this, args);
43
+ } catch (e) {
44
+ throw decodeCppException(e);
45
+ }
46
+ if (result && typeof result.then === 'function') {
47
+ // Async (JSPI-style) binding: decode the rejection reason as well.
48
+ return result.then(undefined, (e) => { throw decodeCppException(e); });
49
+ }
50
+ return result;
51
+ }
52
+ // Overload dispatch reads properties (e.g. overloadTable) off the exposed function.
53
+ Object.assign(decoded, original);
54
+ Object.defineProperty(holder, name, { ...desc, value: decoded });
55
+ }
56
+
57
+ // Direct (non-worker) mode exposes embind class instances raw, so a thrown C++ exception in
58
+ // an instance or static method never crosses a wrapper. Patching each bound class's
59
+ // prototype and statics once at module-ready keeps object identity intact (embind checks
60
+ // prototypes, not method identity) while every call site gains the decode.
61
+ export function patchModuleForExceptionDecode(m) {
62
+ setExceptionDecodeModule(m);
63
+ if (m == null || typeof m !== 'object') return m;
64
+ for (const key of Object.keys(m)) {
65
+ const Cls = m[key];
66
+ if (typeof Cls !== 'function') continue;
67
+ if (Cls.prototype) {
68
+ // Static-only classes are common (pure class_function surfaces), so statics are
69
+ // wrapped below regardless of whether the prototype carries any method.
70
+ Object.getOwnPropertyNames(Cls.prototype)
71
+ .filter((n) => n !== 'constructor')
72
+ .forEach((n) => wrapOwnMethod(Cls.prototype, n));
73
+ }
74
+ Object.getOwnPropertyNames(Cls)
75
+ .filter((n) => !STATIC_SKIP.has(n))
76
+ .forEach((n) => wrapOwnMethod(Cls, n));
77
+ }
78
+ return m;
79
+ }
@@ -0,0 +1,173 @@
1
+ /* global WorkerGlobalScope */
2
+ import * as Comlink from 'comlink';
3
+
4
+ const STATIC_PATHS = {
5
+ opfs: '/opfs',
6
+ memfs: '/memfs',
7
+ };
8
+
9
+ const isWorkerScope = typeof WorkerGlobalScope !== 'undefined'
10
+ && typeof self !== 'undefined'
11
+ && self instanceof WorkerGlobalScope;
12
+
13
+ const isBrowserSupportOPFS = typeof navigator !== 'undefined'
14
+ && navigator.storage
15
+ && navigator.storage.getDirectory;
16
+
17
+ // Presence of the API does not guarantee a working backend: Playwright's WebKit
18
+ // rejects getDirectory() with UnknownError, and storage-restricted contexts
19
+ // reject too. false only after the onModuleReady preflight actually failed.
20
+ let isOpfsFunctional = true;
21
+
22
+ export default {
23
+ extendModule(m, config) {
24
+ // Hand the pthread spawn script to the glue via a crossbind-specific key. Using emscripten's
25
+ // own mainScriptUrlOrBlob aborts emsdk 6 debug builds ("supplied but not included in
26
+ // INCOMING_MODULE_JS_API") on single-thread targets; a custom key is not validated, and
27
+ // buildWasm rewrites the pthread bootstrap to read it (falling back to _scriptName).
28
+ const pthreadScript = config.paths.worker || config.paths.js;
29
+ if (pthreadScript) {
30
+ m.crossbindMainScript = m.locateFile(pthreadScript);
31
+ }
32
+
33
+ m.getDefaultPath = () => STATIC_PATHS[(config.fs?.opfs !== false && isOpfsFunctional) ? 'opfs' : 'memfs'];
34
+
35
+ m.getFinalPath = function getFinalPath(path) {
36
+ let returnPath = path;
37
+ if (!path.startsWith('/')) {
38
+ throw new Error(`Path ${path} does not start with /`);
39
+ }
40
+
41
+ const opfsConfigured = config.fs?.opfs !== false;
42
+
43
+ let backend;
44
+ Object.entries(STATIC_PATHS).forEach(([key, value]) => {
45
+ if (path.startsWith(value)) {
46
+ backend = key;
47
+ }
48
+ });
49
+ if (!backend) {
50
+ throw new Error(`Path ${path} does not start with any of the static paths: ${Object.values(STATIC_PATHS).join(', ')}`);
51
+ }
52
+ if (backend === 'opfs' && !opfsConfigured) {
53
+ throw new Error(`Path ${path} starts with ${STATIC_PATHS.opfs} but OPFS is disabled. Enable fs.opfs in config to mount under ${STATIC_PATHS.opfs}/.`);
54
+ }
55
+ if (backend === 'opfs' && !isWorkerScope) {
56
+ throw new Error(`Path ${path} starts with ${STATIC_PATHS.opfs} but OPFS is only available inside a Worker scope. Enable useWorker or mount under ${STATIC_PATHS.memfs}/ instead. Falling back to ${STATIC_PATHS.memfs}/.`);
57
+ }
58
+ if (backend === 'opfs' && (!isBrowserSupportOPFS || !isOpfsFunctional)) {
59
+ console.error(`Path ${path} starts with ${STATIC_PATHS.opfs} but OPFS is not usable in this browser. Falling back to ${STATIC_PATHS.memfs}/.`);
60
+ returnPath = returnPath.replace(STATIC_PATHS.opfs, STATIC_PATHS.memfs);
61
+ }
62
+
63
+ return returnPath;
64
+ };
65
+
66
+ m.getRandomPath = function getRandomPath(startPath = m.getDefaultPath()) {
67
+ const appName = config.general?.name;
68
+ const rand = Math.floor(Math.random() * 1000000);
69
+ const path = m.getFinalPath(`${startPath}/${appName}/automounted/${rand}`);
70
+ m.FS.mkdirTree(path);
71
+ return path;
72
+ };
73
+
74
+ m.autoMountFiles = function autoMountFiles(files, parentPath = null) {
75
+ return new Promise((resolve, reject) => {
76
+ if (files.length === 0) {
77
+ resolve([]);
78
+ return;
79
+ }
80
+
81
+ const mountPath = parentPath ? m.getFinalPath(parentPath) : m.getRandomPath();
82
+ m.FS.mkdirTree(mountPath);
83
+
84
+ (async () => {
85
+ try {
86
+ const paths = [];
87
+ for (const f of files) {
88
+ const filePath = `${mountPath}/${f.name}`;
89
+ const stream = m.FS.open(filePath, 'w');
90
+ const reader = f.stream().getReader();
91
+ let offset = 0;
92
+
93
+ while (true) {
94
+ const { done, value } = await reader.read();
95
+ if (done) break;
96
+ m.FS.write(stream, value, 0, value.length, offset);
97
+ offset += value.length;
98
+ }
99
+
100
+ m.FS.close(stream);
101
+ paths.push(filePath);
102
+ }
103
+ resolve(paths);
104
+ } catch (e) {
105
+ reject(e);
106
+ }
107
+ })();
108
+ });
109
+ };
110
+
111
+ m.getFileBytes = function getFileBytes(path) {
112
+ if (!path) return new Uint8Array();
113
+ return m.FS.readFile(path, { encoding: 'binary' });
114
+ };
115
+
116
+ m.getFileList = function getFileList(path = m.getDefaultPath()) {
117
+ const fileList = [];
118
+ for (const name of m.FS.readdir(path)) {
119
+ if (name === '.' || name === '..') continue;
120
+ const fullPath = path === '/' ? `/${name}` : `${path}/${name}`;
121
+ const stat = m.FS.stat(fullPath);
122
+ const type = stat.mode & 0o170000;
123
+ if (type === 0o040000) {
124
+ fileList.push(...m.getFileList(fullPath));
125
+ } else if (type === 0o100000) {
126
+ fileList.push({ path: fullPath, size: stat.size });
127
+ }
128
+ }
129
+ return fileList;
130
+ };
131
+
132
+ m._createVector = function _createVector(className, array = []) {
133
+ const vector = m.toVector(className, array);
134
+ return Comlink.proxy(vector);
135
+ };
136
+ },
137
+
138
+ async onModuleReady(m, config) {
139
+ const appName = config.general?.name;
140
+ try {
141
+ m.FS.mkdirTree(`${STATIC_PATHS.memfs}/${appName}/automounted`);
142
+ } catch (e) {
143
+ console.error(e);
144
+ }
145
+
146
+ if (!(isWorkerScope && isBrowserSupportOPFS && config.fs?.opfs !== false && typeof m.crossbind_init_opfs === 'function')) {
147
+ return;
148
+ }
149
+
150
+ // crossbind_init_opfs() blocks until the OPFS root opens; if the backend is
151
+ // broken, that rejection is swallowed inside the WASMFS proxy pthread and
152
+ // init deadlocks with no signal. Probe the root here first instead.
153
+ try {
154
+ await navigator.storage.getDirectory();
155
+ } catch (error) {
156
+ isOpfsFunctional = false;
157
+ console.error(`OPFS is present but not functional in this environment (${error}). Falling back to ${STATIC_PATHS.memfs}/.`);
158
+ return;
159
+ }
160
+
161
+ m.crossbind_init_opfs();
162
+ if (appName) {
163
+ const appDir = `${STATIC_PATHS.opfs}/${appName}/automounted`;
164
+ try {
165
+ m.FS.mkdirTree(appDir);
166
+ } catch (e) {
167
+ console.error(e);
168
+ }
169
+ }
170
+ },
171
+ };
172
+
173
+ export { STATIC_PATHS, isWorkerScope, isBrowserSupportOPFS };
@@ -0,0 +1,28 @@
1
+ export default {
2
+ extendModule(m) {
3
+ m.getPreloadedPackage = function getPreloadedPackage(packageName) {
4
+ return require('fs').readFileSync(`./${packageName}`, { flag: 'r' }).buffer;
5
+ };
6
+
7
+ m.getFileBytes = function getFileBytes(path) {
8
+ if (!path) return new Uint8Array();
9
+ return m.FS.readFile(path, { encoding: 'binary' });
10
+ };
11
+
12
+ m.getFileList = function getFileList(path = '/memfs') {
13
+ const fileList = [];
14
+ for (const name of m.FS.readdir(path)) {
15
+ if (name === '.' || name === '..') continue;
16
+ const fullPath = path === '/' ? `/${name}` : `${path}/${name}`;
17
+ const stat = m.FS.stat(fullPath);
18
+ const type = stat.mode & 0o170000;
19
+ if (type === 0o040000) {
20
+ fileList.push(...m.getFileList(fullPath));
21
+ } else if (type === 0o100000) {
22
+ fileList.push({ path: fullPath, size: stat.size });
23
+ }
24
+ }
25
+ return fileList;
26
+ };
27
+ },
28
+ };
@@ -0,0 +1,6 @@
1
+ export default function pathFs({ defaultPathPrefix, dataPath }) {
2
+ return {
3
+ getDefaultPathPrefix: () => defaultPathPrefix,
4
+ getDataPath: () => dataPath,
5
+ };
6
+ }
@@ -0,0 +1,8 @@
1
+ export default {
2
+ finalizePath(output) {
3
+ if (output.substring(0, 4) !== 'http' && output[0] !== '/') {
4
+ return `/${output}`;
5
+ }
6
+ return output;
7
+ },
8
+ };
@@ -0,0 +1,209 @@
1
+ // === Plain array -> embind vector coercion (shared by the worker and direct runtimes) ===
2
+ // Vectors RETURNED across the worker boundary arrive as plain arrays, so callers naturally
3
+ // try to pass plain arrays back as vector PARAMETERS - which embind rejects with
4
+ // 'Cannot pass "..." as a VectorX'. Coercion catches that error, converts the first
5
+ // remaining plain-array argument with Module.toVector, and retries, so mixed signatures
6
+ // (VectorDataset + VectorString, ...) resolve left to right, matching embind's argument
7
+ // order. embind passes std::vector parameters by value, so the temporaries are deleted
8
+ // after the call instead of leaking wasm memory.
9
+ //
10
+ // This is a REACTIVE strategy: embind does not expose parameter types to JS, so we can
11
+ // only learn that an argument should have been a vector from the failed call. See
12
+ // EXPECTED_VECTOR_RE below - the coercion silently disengages (rethrows) if embind ever
13
+ // changes that message, which the canary test in vectorCoercion.test.js guards against.
14
+ import { decodeCppException } from './exception-decode.js';
15
+
16
+ let coercionModule = null;
17
+
18
+ // Matches embind's "Cannot pass <value> as a VectorX" BindingError message.
19
+ const EXPECTED_VECTOR_RE = /as a (Vector\w+)\b/;
20
+
21
+ export function setCoercionModule(m) {
22
+ coercionModule = m;
23
+ }
24
+
25
+ // Proxies created by the coercion wrappers, mapped back to their raw targets. embind
26
+ // rejects a proxied `this` or argument (its class checks compare $$.ptrType.registeredClass
27
+ // by identity), so every invocation unwraps them back to the raw objects first.
28
+ const coercionProxies = new WeakMap();
29
+
30
+ export function unwrapCoercionProxy(value) {
31
+ const raw = coercionProxies.get(value);
32
+ return raw === undefined ? value : raw;
33
+ }
34
+
35
+ // The retry-on-vector-error loop shared by call and construct. `invoke(args)` performs the
36
+ // actual apply/construct; on a 'Cannot pass ... as a VectorX' error that names a registered
37
+ // Vector class while a plain-array argument remains, the first remaining array is converted
38
+ // with Module.toVector and the invocation retried. Each retry converts exactly one array, so
39
+ // the array count strictly decreases and the loop always terminates.
40
+ function retryWithCoercion(invoke, args) {
41
+ const temp = [];
42
+ const cleanup = () => {
43
+ for (const v of temp) {
44
+ try {
45
+ v.delete();
46
+ } catch (e) { /* already deleted by the callee */ }
47
+ }
48
+ };
49
+ const current = args.slice();
50
+ for (;;) {
51
+ let result;
52
+ try {
53
+ result = invoke(current);
54
+ } catch (rawError) {
55
+ const e = decodeCppException(rawError);
56
+ const match = EXPECTED_VECTOR_RE.exec(String((e && e.message) || e));
57
+ const VectorClass = match && coercionModule ? coercionModule[match[1]] : undefined;
58
+ const index = current.findIndex((a) => Array.isArray(a));
59
+ if (typeof VectorClass !== 'function' || index < 0) {
60
+ cleanup();
61
+ throw e;
62
+ }
63
+ let vector;
64
+ try {
65
+ vector = coercionModule.toVector(VectorClass, current[index]);
66
+ } catch (convError) {
67
+ cleanup();
68
+ throw e;
69
+ }
70
+ temp.push(vector);
71
+ current[index] = vector;
72
+ continue;
73
+ }
74
+ if (temp.length && result && typeof result.then === 'function') {
75
+ // Async (JSPI-style) binding: arguments were already converted synchronously,
76
+ // but hold the temporaries until it settles.
77
+ return result.then(
78
+ (value) => {
79
+ cleanup();
80
+ return value;
81
+ },
82
+ (err) => {
83
+ cleanup();
84
+ throw decodeCppException(err);
85
+ },
86
+ );
87
+ }
88
+ cleanup();
89
+ return result;
90
+ }
91
+ }
92
+
93
+ export function callWithVectorCoercion(fn, thisArg, args) {
94
+ return retryWithCoercion((current) => Reflect.apply(fn, thisArg, current), args);
95
+ }
96
+
97
+ export function constructWithVectorCoercion(Class, args, newTarget) {
98
+ return retryWithCoercion((current) => Reflect.construct(Class, current, newTarget || Class), args);
99
+ }
100
+
101
+ // Deep wrapper (WORKER mode): every method reached through the wrapped object - at any depth
102
+ // (Module.Gdal.openEx, dataset.translate, ...) - is invoked through callWithVectorCoercion,
103
+ // and every `new` through the construct trap below. Safe here because the worker world is
104
+ // entirely opaque Comlink proxies - nothing accesses raw memory or object identity through it.
105
+ export function wrapWithVectorCoercion(value, thisArg) {
106
+ if (value == null || (typeof value !== 'object' && typeof value !== 'function')) {
107
+ return value;
108
+ }
109
+ const proxy = new Proxy(value, {
110
+ get(target, prop) {
111
+ const member = Reflect.get(target, prop);
112
+ // Comlink invokes exposed methods as rawValue.apply(parent, args) with parent
113
+ // being the WRAPPED object. Handle Function.prototype apply/call here so `this`
114
+ // and the argument list reach callWithVectorCoercion unwrapped and flat (retries
115
+ // depend on seeing the real argument positions).
116
+ if (typeof target === 'function' && member === Function.prototype.apply) {
117
+ return (applyThis, applyArgs) => callWithVectorCoercion(
118
+ target,
119
+ applyThis === undefined && thisArg !== undefined
120
+ ? thisArg
121
+ : unwrapCoercionProxy(applyThis),
122
+ applyArgs ? Array.prototype.map.call(applyArgs, unwrapCoercionProxy) : [],
123
+ );
124
+ }
125
+ if (typeof target === 'function' && member === Function.prototype.call) {
126
+ return (callThis, ...callArgs) => callWithVectorCoercion(
127
+ target,
128
+ callThis === undefined && thisArg !== undefined
129
+ ? thisArg
130
+ : unwrapCoercionProxy(callThis),
131
+ callArgs.map(unwrapCoercionProxy),
132
+ );
133
+ }
134
+ if (typeof member === 'function' || (member !== null && typeof member === 'object')) {
135
+ // Proxy invariant: non-configurable, non-writable data properties (a class's
136
+ // .prototype, notably) must be returned as-is, or the engine throws on access.
137
+ const desc = Reflect.getOwnPropertyDescriptor(target, prop);
138
+ if (desc && desc.configurable === false && desc.writable === false) {
139
+ return member;
140
+ }
141
+ return wrapWithVectorCoercion(member, target);
142
+ }
143
+ return member;
144
+ },
145
+ set(target, prop, value) {
146
+ // 3-arg Reflect.set keeps the RAW object as receiver: embind property setters
147
+ // identity-check `this`, and the default (proxy receiver) fails that check.
148
+ return Reflect.set(target, prop, unwrapCoercionProxy(value));
149
+ },
150
+ apply(target, applyThis, args) {
151
+ const boundThis = thisArg !== undefined ? thisArg : unwrapCoercionProxy(applyThis);
152
+ return callWithVectorCoercion(target, boundThis, args.map(unwrapCoercionProxy));
153
+ },
154
+ construct(target, args) {
155
+ // Comlink's CONSTRUCT handler does `new rawValue(...args)` on the wrapped
156
+ // class. Without this trap the construction falls through with the PROXY as
157
+ // newTarget, whose get trap hands out a wrapped .prototype (embind classes
158
+ // are plain functions, so their prototype is writable and the proxy
159
+ // invariant early-return does not apply) - and embind's prototype-identity
160
+ // check rejects the instance with "Use 'new' to construct X". Constructing
161
+ // against the raw class keeps the real prototype and adds the same
162
+ // plain-array vector coercion that method calls get.
163
+ return constructWithVectorCoercion(target, args.map(unwrapCoercionProxy));
164
+ },
165
+ });
166
+ coercionProxies.set(proxy, value);
167
+ return proxy;
168
+ }
169
+
170
+ // Narrow wrapper (DIRECT / non-worker mode): only the module's own function calls and
171
+ // constructions are coerced. Data members (HEAP*, FS, typed arrays) and returned objects
172
+ // stay raw, because direct-mode code accesses those by index and by identity - a blanket
173
+ // proxy (like the worker wrapper above) would break them. Returned embind objects are
174
+ // already real vectors/objects in direct mode (they are not flattened to plain arrays the
175
+ // way the worker boundary does), so module-level coercion covers the plain-array-literal
176
+ // case without changing any object's identity.
177
+ export function wrapModuleForCoercion(m) {
178
+ if (m == null || (typeof m !== 'object' && typeof m !== 'function')) {
179
+ return m;
180
+ }
181
+ const wrappedFns = new Map();
182
+ const moduleProxy = new Proxy(m, {
183
+ get(target, prop) {
184
+ const member = Reflect.get(target, prop);
185
+ if (typeof member !== 'function') {
186
+ return member; // HEAP*, FS, typed arrays and other data pass through raw
187
+ }
188
+ let wrapped = wrappedFns.get(prop);
189
+ if (!wrapped) {
190
+ wrapped = new Proxy(member, {
191
+ apply(fn, thisArg, args) {
192
+ return callWithVectorCoercion(
193
+ fn,
194
+ unwrapCoercionProxy(thisArg),
195
+ args.map(unwrapCoercionProxy),
196
+ );
197
+ },
198
+ construct(Class, args) {
199
+ return constructWithVectorCoercion(Class, args.map(unwrapCoercionProxy));
200
+ },
201
+ });
202
+ wrappedFns.set(prop, wrapped);
203
+ }
204
+ return wrapped;
205
+ },
206
+ });
207
+ coercionProxies.set(moduleProxy, m);
208
+ return moduleProxy;
209
+ }