tywrap 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/annotation-parser.d.ts.map +1 -1
- package/dist/core/annotation-parser.js +19 -14
- package/dist/core/annotation-parser.js.map +1 -1
- package/dist/core/discovery.d.ts +17 -0
- package/dist/core/discovery.d.ts.map +1 -1
- package/dist/core/discovery.js +59 -49
- package/dist/core/discovery.js.map +1 -1
- package/dist/core/generator.d.ts.map +1 -1
- package/dist/core/generator.js +40 -9
- package/dist/core/generator.js.map +1 -1
- package/dist/core/validation.d.ts +23 -0
- package/dist/core/validation.d.ts.map +1 -1
- package/dist/core/validation.js +52 -48
- package/dist/core/validation.js.map +1 -1
- package/dist/dev.d.ts.map +1 -1
- package/dist/dev.js +175 -133
- package/dist/dev.js.map +1 -1
- package/dist/runtime/base-bridge.d.ts +57 -0
- package/dist/runtime/base-bridge.d.ts.map +1 -0
- package/dist/runtime/base-bridge.js +72 -0
- package/dist/runtime/base-bridge.js.map +1 -0
- package/dist/runtime/bridge-codec.d.ts +15 -0
- package/dist/runtime/bridge-codec.d.ts.map +1 -1
- package/dist/runtime/bridge-codec.js +45 -48
- package/dist/runtime/bridge-codec.js.map +1 -1
- package/dist/runtime/http-transport.d.ts +11 -1
- package/dist/runtime/http-transport.d.ts.map +1 -1
- package/dist/runtime/http-transport.js +19 -0
- package/dist/runtime/http-transport.js.map +1 -1
- package/dist/runtime/http.d.ts +5 -12
- package/dist/runtime/http.d.ts.map +1 -1
- package/dist/runtime/http.js +6 -29
- package/dist/runtime/http.js.map +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/node.d.ts +12 -19
- package/dist/runtime/node.d.ts.map +1 -1
- package/dist/runtime/node.js +14 -34
- package/dist/runtime/node.js.map +1 -1
- package/dist/runtime/pooled-transport.d.ts +21 -2
- package/dist/runtime/pooled-transport.d.ts.map +1 -1
- package/dist/runtime/pooled-transport.js +16 -0
- package/dist/runtime/pooled-transport.js.map +1 -1
- package/dist/runtime/pyodide-bootstrap-core.generated.d.ts.map +1 -1
- package/dist/runtime/pyodide-bootstrap-core.generated.js +1 -1
- package/dist/runtime/pyodide-bootstrap-core.generated.js.map +1 -1
- package/dist/runtime/pyodide-transport.d.ts +12 -1
- package/dist/runtime/pyodide-transport.d.ts.map +1 -1
- package/dist/runtime/pyodide-transport.js +20 -0
- package/dist/runtime/pyodide-transport.js.map +1 -1
- package/dist/runtime/pyodide.d.ts +5 -12
- package/dist/runtime/pyodide.d.ts.map +1 -1
- package/dist/runtime/pyodide.js +6 -29
- package/dist/runtime/pyodide.js.map +1 -1
- package/dist/runtime/rpc-client.d.ts +32 -1
- package/dist/runtime/rpc-client.d.ts.map +1 -1
- package/dist/runtime/rpc-client.js +45 -24
- package/dist/runtime/rpc-client.js.map +1 -1
- package/dist/runtime/subprocess-transport.d.ts +26 -1
- package/dist/runtime/subprocess-transport.d.ts.map +1 -1
- package/dist/runtime/subprocess-transport.js +72 -35
- package/dist/runtime/subprocess-transport.js.map +1 -1
- package/dist/runtime/transport.d.ts +59 -0
- package/dist/runtime/transport.d.ts.map +1 -1
- package/dist/runtime/transport.js +1 -0
- package/dist/runtime/transport.js.map +1 -1
- package/dist/types/index.d.ts +35 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/tywrap.d.ts +0 -5
- package/dist/tywrap.d.ts.map +1 -1
- package/dist/tywrap.js +212 -169
- package/dist/tywrap.js.map +1 -1
- package/dist/utils/cache.d.ts +11 -0
- package/dist/utils/cache.d.ts.map +1 -1
- package/dist/utils/cache.js +50 -58
- package/dist/utils/cache.js.map +1 -1
- package/dist/utils/codec.d.ts +2 -0
- package/dist/utils/codec.d.ts.map +1 -1
- package/dist/utils/codec.js +53 -2
- package/dist/utils/codec.js.map +1 -1
- package/dist/utils/python.d.ts.map +1 -1
- package/dist/utils/python.js +28 -17
- package/dist/utils/python.js.map +1 -1
- package/dist/utils/runtime.d.ts.map +1 -1
- package/dist/utils/runtime.js +22 -16
- package/dist/utils/runtime.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -1
- package/runtime/__pycache__/_tywrap_member_fixtures.cpython-311.pyc +0 -0
- package/runtime/__pycache__/safe_codec.cpython-311.pyc +0 -0
- package/runtime/__pycache__/tywrap_bridge_core.cpython-311.pyc +0 -0
- package/runtime/tywrap_bridge_core.py +55 -3
- package/src/core/annotation-parser.ts +23 -18
- package/src/core/discovery.ts +70 -54
- package/src/core/generator.ts +50 -11
- package/src/core/validation.ts +84 -48
- package/src/dev.ts +237 -153
- package/src/runtime/base-bridge.ts +106 -0
- package/src/runtime/bridge-codec.ts +58 -70
- package/src/runtime/http-transport.ts +21 -1
- package/src/runtime/http.ts +7 -51
- package/src/runtime/index.ts +1 -0
- package/src/runtime/node.ts +17 -52
- package/src/runtime/pooled-transport.ts +25 -2
- package/src/runtime/pyodide-bootstrap-core.generated.ts +1 -1
- package/src/runtime/pyodide-transport.ts +22 -0
- package/src/runtime/pyodide.ts +7 -52
- package/src/runtime/rpc-client.ts +56 -32
- package/src/runtime/subprocess-transport.ts +89 -40
- package/src/runtime/transport.ts +71 -0
- package/src/types/index.ts +37 -0
- package/src/tywrap.ts +274 -187
- package/src/utils/cache.ts +59 -61
- package/src/utils/codec.ts +61 -4
- package/src/utils/python.ts +33 -20
- package/src/utils/runtime.ts +24 -13
- package/src/version.ts +1 -1
package/src/utils/codec.ts
CHANGED
|
@@ -111,6 +111,24 @@ export type DecodedValue =
|
|
|
111
111
|
|
|
112
112
|
let arrowTableFrom: ((bytes: Uint8Array) => ArrowTable | Uint8Array) | undefined;
|
|
113
113
|
|
|
114
|
+
// Why: lazy auto-registration (on first Arrow decode) imports apache-arrow at most
|
|
115
|
+
// once per process. We cache the in-flight/settled attempt so concurrent decodes
|
|
116
|
+
// share a single dynamic import, and a missing module is not re-probed on every call.
|
|
117
|
+
let lazyRegistration: Promise<boolean> | undefined;
|
|
118
|
+
|
|
119
|
+
// Why: the lazy decode path imports apache-arrow through the default Node loader, which
|
|
120
|
+
// is hard to simulate-as-absent in a test env where the dependency IS installed. This
|
|
121
|
+
// internal seam lets the unit suite exercise the "apache-arrow missing" clear-failure
|
|
122
|
+
// branch deterministically. It is intentionally NOT re-exported from the package root
|
|
123
|
+
// (see test/api_surface.test.ts) and is reset by clearArrowDecoder().
|
|
124
|
+
let lazyArrowLoaderOverride: ArrowModuleLoader | undefined;
|
|
125
|
+
|
|
126
|
+
/** @internal Test-only: override the loader used by lazy auto-registration. */
|
|
127
|
+
export const _setLazyArrowLoaderForTesting = (loader: ArrowModuleLoader | undefined): void => {
|
|
128
|
+
lazyArrowLoaderOverride = loader;
|
|
129
|
+
lazyRegistration = undefined;
|
|
130
|
+
};
|
|
131
|
+
|
|
114
132
|
export function registerArrowDecoder(
|
|
115
133
|
decoder: (bytes: Uint8Array) => ArrowTable | Uint8Array
|
|
116
134
|
): void {
|
|
@@ -119,6 +137,10 @@ export function registerArrowDecoder(
|
|
|
119
137
|
|
|
120
138
|
export function clearArrowDecoder(): void {
|
|
121
139
|
arrowTableFrom = undefined;
|
|
140
|
+
// Why: reset the cached import attempt so tests (and reload helpers) can exercise
|
|
141
|
+
// the auto-registration path again from a clean slate.
|
|
142
|
+
lazyRegistration = undefined;
|
|
143
|
+
lazyArrowLoaderOverride = undefined;
|
|
122
144
|
}
|
|
123
145
|
|
|
124
146
|
/**
|
|
@@ -186,6 +208,12 @@ export async function autoRegisterArrowDecoder(
|
|
|
186
208
|
}
|
|
187
209
|
try {
|
|
188
210
|
const arrowModule = await loader();
|
|
211
|
+
// Another path may have registered a decoder while the import was in flight
|
|
212
|
+
// (e.g. an explicit registerArrowDecoder() during concurrent startup/reload).
|
|
213
|
+
// Don't clobber it — the explicit registration wins.
|
|
214
|
+
if (hasArrowDecoder()) {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
189
217
|
registerArrowDecoderFromModule(arrowModule as { tableFromIPC?: unknown });
|
|
190
218
|
return true;
|
|
191
219
|
} catch {
|
|
@@ -214,17 +242,46 @@ function fromBase64(b64: string): Uint8Array {
|
|
|
214
242
|
throw new Error('Base64 decoding is not available in this runtime');
|
|
215
243
|
}
|
|
216
244
|
|
|
245
|
+
// Why: a single, actionable message for both decode paths so users always know the two
|
|
246
|
+
// supported remedies — install the optional dependency, or opt into the lossy JSON fallback.
|
|
247
|
+
const ARROW_MISSING_MESSAGE =
|
|
248
|
+
'Received an Arrow-encoded payload but no Arrow decoder is available. ' +
|
|
249
|
+
'Install the optional dependency with `npm install apache-arrow`, or set ' +
|
|
250
|
+
'TYWRAP_CODEC_FALLBACK=json on the Python side to receive JSON instead ' +
|
|
251
|
+
'(lossy for dtype/NA fidelity). tywrap never silently downgrades Arrow payloads.';
|
|
252
|
+
|
|
217
253
|
function requireArrowDecoder(): (bytes: Uint8Array) => ArrowTable | Uint8Array {
|
|
218
254
|
if (!arrowTableFrom) {
|
|
219
|
-
throw new Error(
|
|
220
|
-
|
|
221
|
-
|
|
255
|
+
throw new Error(ARROW_MISSING_MESSAGE);
|
|
256
|
+
}
|
|
257
|
+
return arrowTableFrom;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Ensure an Arrow decoder is registered, lazily importing apache-arrow on first use.
|
|
262
|
+
*
|
|
263
|
+
* Why: keep apache-arrow optional and zero-config. The first Arrow-encoded payload
|
|
264
|
+
* triggers a single best-effort dynamic import; if it succeeds the decoder is cached
|
|
265
|
+
* for the rest of the process. If apache-arrow is absent we throw a clear, actionable
|
|
266
|
+
* error rather than silently producing wrong data.
|
|
267
|
+
*/
|
|
268
|
+
async function ensureArrowDecoder(): Promise<(bytes: Uint8Array) => ArrowTable | Uint8Array> {
|
|
269
|
+
if (arrowTableFrom) {
|
|
270
|
+
return arrowTableFrom;
|
|
271
|
+
}
|
|
272
|
+
// Reuse a single import attempt across concurrent decodes.
|
|
273
|
+
lazyRegistration ??= autoRegisterArrowDecoder(
|
|
274
|
+
lazyArrowLoaderOverride ? { loader: lazyArrowLoaderOverride } : {}
|
|
275
|
+
);
|
|
276
|
+
await lazyRegistration;
|
|
277
|
+
if (!arrowTableFrom) {
|
|
278
|
+
throw new Error(ARROW_MISSING_MESSAGE);
|
|
222
279
|
}
|
|
223
280
|
return arrowTableFrom;
|
|
224
281
|
}
|
|
225
282
|
|
|
226
283
|
async function tryDecodeArrowTable(bytes: Uint8Array): Promise<ArrowTable | Uint8Array> {
|
|
227
|
-
const decoder =
|
|
284
|
+
const decoder = await ensureArrowDecoder();
|
|
228
285
|
try {
|
|
229
286
|
return decoder(bytes);
|
|
230
287
|
} catch (err) {
|
package/src/utils/python.ts
CHANGED
|
@@ -44,30 +44,43 @@ export function getDefaultPythonPath(): string {
|
|
|
44
44
|
return getPythonExecutableName();
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Whether the configured pythonPath is the default interpreter (or unset),
|
|
49
|
+
* meaning a virtual environment lookup should take precedence.
|
|
50
|
+
*/
|
|
51
|
+
function usesDefaultPython(pythonPath: string | undefined): boolean {
|
|
52
|
+
return !pythonPath || pythonPath === 'python3' || pythonPath === 'python';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Resolve the Python executable inside a virtual environment, preferring the
|
|
57
|
+
* Node path module when available and falling back to cross-runtime pathUtils.
|
|
58
|
+
*/
|
|
59
|
+
async function resolveVenvPython(virtualEnv: string, cwd: string): Promise<string> {
|
|
60
|
+
const binDir = getVenvBinDir();
|
|
61
|
+
const exe = getVenvPythonExe();
|
|
62
|
+
const pathMod = await loadNodePathModule();
|
|
63
|
+
|
|
64
|
+
if (pathMod) {
|
|
65
|
+
const venvRoot = pathMod.resolve(cwd, virtualEnv);
|
|
66
|
+
return pathMod.join(venvRoot, binDir, exe);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const venvRoot = isAbsolutePath(virtualEnv)
|
|
70
|
+
? pathUtils.join(virtualEnv)
|
|
71
|
+
: pathUtils.join(cwd, virtualEnv);
|
|
72
|
+
return pathUtils.join(venvRoot, binDir, exe);
|
|
73
|
+
}
|
|
74
|
+
|
|
47
75
|
export async function resolvePythonExecutable(options: PythonResolveOptions = {}): Promise<string> {
|
|
48
76
|
const pythonPath = options.pythonPath?.trim();
|
|
49
77
|
const virtualEnv = options.virtualEnv?.trim();
|
|
50
78
|
|
|
51
|
-
if (virtualEnv) {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
(typeof process !== 'undefined' && typeof process.cwd === 'function' ? process.cwd() : '.');
|
|
57
|
-
const binDir = getVenvBinDir();
|
|
58
|
-
const exe = getVenvPythonExe();
|
|
59
|
-
const pathMod = await loadNodePathModule();
|
|
60
|
-
|
|
61
|
-
if (pathMod) {
|
|
62
|
-
const venvRoot = pathMod.resolve(cwd, virtualEnv);
|
|
63
|
-
return pathMod.join(venvRoot, binDir, exe);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const venvRoot = isAbsolutePath(virtualEnv)
|
|
67
|
-
? pathUtils.join(virtualEnv)
|
|
68
|
-
: pathUtils.join(cwd, virtualEnv);
|
|
69
|
-
return pathUtils.join(venvRoot, binDir, exe);
|
|
70
|
-
}
|
|
79
|
+
if (virtualEnv && usesDefaultPython(pythonPath)) {
|
|
80
|
+
const cwd =
|
|
81
|
+
options.cwd ??
|
|
82
|
+
(typeof process !== 'undefined' && typeof process.cwd === 'function' ? process.cwd() : '.');
|
|
83
|
+
return resolveVenvPython(virtualEnv, cwd);
|
|
71
84
|
}
|
|
72
85
|
|
|
73
86
|
if (pythonPath) {
|
package/src/utils/runtime.ts
CHANGED
|
@@ -229,26 +229,37 @@ async function loadPathModule(): Promise<PathModule | null> {
|
|
|
229
229
|
return null;
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Apply a single path segment to the accumulated normalized segments,
|
|
234
|
+
* resolving '..' and dropping '.'/empty segments in place.
|
|
235
|
+
*/
|
|
236
|
+
function applyPathSegment(normalized: string[], segment: string, isAbsolute: boolean): void {
|
|
237
|
+
if (segment === '.' || segment === '') {
|
|
238
|
+
return; // Skip current directory and empty segments
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (segment !== '..') {
|
|
242
|
+
normalized.push(segment);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// '..' segment: go up one directory when possible.
|
|
247
|
+
if (normalized.length > 0 && normalized[normalized.length - 1] !== '..') {
|
|
248
|
+
normalized.pop();
|
|
249
|
+
} else if (!isAbsolute) {
|
|
250
|
+
normalized.push(segment); // Keep '..' if not absolute and at root
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
232
254
|
/**
|
|
233
255
|
* Normalize path by stripping '.' and resolving '..' components
|
|
234
256
|
*/
|
|
235
257
|
function normalizePath(path: string): string {
|
|
236
258
|
const isAbsolute = path.startsWith('/');
|
|
237
|
-
const segments = path.split('/');
|
|
238
259
|
const normalized: string[] = [];
|
|
239
260
|
|
|
240
|
-
for (const segment of
|
|
241
|
-
|
|
242
|
-
continue; // Skip current directory and empty segments
|
|
243
|
-
} else if (segment === '..') {
|
|
244
|
-
if (normalized.length > 0 && normalized[normalized.length - 1] !== '..') {
|
|
245
|
-
normalized.pop(); // Go up one directory
|
|
246
|
-
} else if (!isAbsolute) {
|
|
247
|
-
normalized.push(segment); // Keep '..' if not absolute and at root
|
|
248
|
-
}
|
|
249
|
-
} else {
|
|
250
|
-
normalized.push(segment);
|
|
251
|
-
}
|
|
261
|
+
for (const segment of path.split('/')) {
|
|
262
|
+
applyPathSegment(normalized, segment, isAbsolute);
|
|
252
263
|
}
|
|
253
264
|
|
|
254
265
|
const result = normalized.join('/');
|
package/src/version.ts
CHANGED