tywrap 0.8.0 → 0.9.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/README.md +22 -7
- package/SECURITY.md +39 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +8 -0
- package/dist/config/index.js.map +1 -1
- package/dist/core/annotation-parser.d.ts +2 -1
- package/dist/core/annotation-parser.d.ts.map +1 -1
- package/dist/core/annotation-parser.js +6 -3
- package/dist/core/annotation-parser.js.map +1 -1
- package/dist/core/generator.d.ts +23 -4
- package/dist/core/generator.d.ts.map +1 -1
- package/dist/core/generator.js +243 -170
- package/dist/core/generator.js.map +1 -1
- package/dist/core/mapper.d.ts +3 -2
- package/dist/core/mapper.d.ts.map +1 -1
- package/dist/core/mapper.js +5 -5
- package/dist/core/mapper.js.map +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/runtime/base-bridge.d.ts +3 -7
- package/dist/runtime/base-bridge.d.ts.map +1 -1
- package/dist/runtime/base-bridge.js +4 -17
- package/dist/runtime/base-bridge.js.map +1 -1
- package/dist/runtime/bounded-context.d.ts +3 -22
- package/dist/runtime/bounded-context.d.ts.map +1 -1
- package/dist/runtime/bounded-context.js +13 -53
- package/dist/runtime/bounded-context.js.map +1 -1
- package/dist/runtime/bridge-codec.d.ts +1 -1
- package/dist/runtime/bridge-codec.d.ts.map +1 -1
- package/dist/runtime/bridge-codec.js +78 -55
- package/dist/runtime/bridge-codec.js.map +1 -1
- package/dist/runtime/errors.d.ts +16 -0
- package/dist/runtime/errors.d.ts.map +1 -1
- package/dist/runtime/errors.js +17 -0
- package/dist/runtime/errors.js.map +1 -1
- package/dist/runtime/http-transport.d.ts +1 -1
- package/dist/runtime/http-transport.d.ts.map +1 -1
- package/dist/runtime/http-transport.js +1 -1
- package/dist/runtime/http-transport.js.map +1 -1
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/node.d.ts +7 -36
- package/dist/runtime/node.d.ts.map +1 -1
- package/dist/runtime/node.js +2 -80
- package/dist/runtime/node.js.map +1 -1
- package/dist/runtime/pooled-transport.d.ts +120 -59
- package/dist/runtime/pooled-transport.d.ts.map +1 -1
- package/dist/runtime/pooled-transport.js +345 -78
- 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 +1 -1
- package/dist/runtime/pyodide-transport.d.ts.map +1 -1
- package/dist/runtime/pyodide-transport.js +2 -3
- package/dist/runtime/pyodide-transport.js.map +1 -1
- package/dist/runtime/rpc-client.d.ts +7 -36
- package/dist/runtime/rpc-client.d.ts.map +1 -1
- package/dist/runtime/rpc-client.js +20 -102
- package/dist/runtime/rpc-client.js.map +1 -1
- package/dist/runtime/subprocess-transport.d.ts +30 -69
- package/dist/runtime/subprocess-transport.d.ts.map +1 -1
- package/dist/runtime/subprocess-transport.js +179 -236
- package/dist/runtime/subprocess-transport.js.map +1 -1
- package/dist/runtime/timed-out-request-tracker.d.ts +2 -1
- package/dist/runtime/timed-out-request-tracker.d.ts.map +1 -1
- package/dist/runtime/transport.d.ts +9 -19
- package/dist/runtime/transport.d.ts.map +1 -1
- package/dist/runtime/transport.js +1 -1
- package/dist/runtime/transport.js.map +1 -1
- package/dist/runtime/validators.d.ts +49 -0
- package/dist/runtime/validators.d.ts.map +1 -1
- package/dist/runtime/validators.js +152 -0
- package/dist/runtime/validators.js.map +1 -1
- package/dist/types/index.d.ts +14 -39
- package/dist/types/index.d.ts.map +1 -1
- package/dist/tywrap.d.ts +3 -2
- package/dist/tywrap.d.ts.map +1 -1
- package/dist/tywrap.js +140 -13
- package/dist/tywrap.js.map +1 -1
- package/dist/utils/cache.d.ts +3 -16
- package/dist/utils/cache.d.ts.map +1 -1
- package/dist/utils/codec.d.ts +1 -0
- package/dist/utils/codec.d.ts.map +1 -1
- package/dist/utils/codec.js +13 -13
- package/dist/utils/codec.js.map +1 -1
- package/dist/utils/ir-cache.d.ts +2 -1
- package/dist/utils/ir-cache.d.ts.map +1 -1
- package/dist/utils/runtime.d.ts +0 -29
- package/dist/utils/runtime.d.ts.map +1 -1
- package/dist/utils/runtime.js +16 -107
- package/dist/utils/runtime.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -6
- package/runtime/frame_codec.py +7 -1
- package/runtime/python_bridge.py +85 -125
- package/runtime/tywrap_bridge_core.py +133 -150
- package/src/config/index.ts +11 -0
- package/src/core/annotation-parser.ts +7 -4
- package/src/core/generator.ts +315 -205
- package/src/core/mapper.ts +8 -8
- package/src/index.ts +7 -4
- package/src/runtime/base-bridge.ts +5 -30
- package/src/runtime/bounded-context.ts +12 -67
- package/src/runtime/bridge-codec.ts +94 -65
- package/src/runtime/errors.ts +21 -0
- package/src/runtime/http-transport.ts +6 -1
- package/src/runtime/index.ts +6 -0
- package/src/runtime/node.ts +9 -120
- package/src/runtime/pooled-transport.ts +424 -90
- package/src/runtime/pyodide-bootstrap-core.generated.ts +1 -1
- package/src/runtime/pyodide-transport.ts +7 -3
- package/src/runtime/rpc-client.ts +37 -139
- package/src/runtime/subprocess-transport.ts +210 -285
- package/src/runtime/timed-out-request-tracker.ts +1 -1
- package/src/runtime/transport.ts +15 -23
- package/src/runtime/validators.ts +204 -0
- package/src/types/index.ts +21 -60
- package/src/tywrap.ts +157 -22
- package/src/utils/cache.ts +3 -3
- package/src/utils/codec.ts +21 -13
- package/src/utils/ir-cache.ts +1 -1
- package/src/utils/runtime.ts +17 -128
- package/src/version.ts +1 -1
- package/dist/core/discovery.d.ts +0 -103
- package/dist/core/discovery.d.ts.map +0 -1
- package/dist/core/discovery.js +0 -380
- package/dist/core/discovery.js.map +0 -1
- package/dist/core/validation.d.ts +0 -102
- package/dist/core/validation.d.ts.map +0 -1
- package/dist/core/validation.js +0 -490
- package/dist/core/validation.js.map +0 -1
- package/dist/runtime/base.d.ts +0 -22
- package/dist/runtime/base.d.ts.map +0 -1
- package/dist/runtime/base.js +0 -23
- package/dist/runtime/base.js.map +0 -1
- package/dist/runtime/transport-pool.d.ts +0 -196
- package/dist/runtime/transport-pool.d.ts.map +0 -1
- package/dist/runtime/transport-pool.js +0 -418
- package/dist/runtime/transport-pool.js.map +0 -1
- package/runtime/__pycache__/_tywrap_conformance_chunking_fixtures.cpython-311.pyc +0 -0
- package/runtime/__pycache__/_tywrap_member_fixtures.cpython-311.pyc +0 -0
- package/runtime/__pycache__/_tywrap_w4_chunking_fixture.cpython-311.pyc +0 -0
- package/runtime/__pycache__/_tywrap_w5_request_chunking_fixture.cpython-311.pyc +0 -0
- package/runtime/__pycache__/_tywrap_w6_pool_chunking_fixture.cpython-311.pyc +0 -0
- package/runtime/__pycache__/frame_codec.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/safe_codec.py +0 -352
- package/src/core/discovery.ts +0 -477
- package/src/core/validation.ts +0 -729
- package/src/runtime/base.ts +0 -24
- package/src/runtime/transport-pool.ts +0 -538
package/src/tywrap.ts
CHANGED
|
@@ -16,13 +16,15 @@ import type {
|
|
|
16
16
|
Parameter,
|
|
17
17
|
PythonType,
|
|
18
18
|
PythonModuleConfig,
|
|
19
|
+
IrContract,
|
|
19
20
|
} from './types/index.js';
|
|
20
21
|
import { fsUtils, pathUtils, processUtils, isWindows } from './utils/runtime.js';
|
|
21
22
|
import { globalCache } from './utils/cache.js';
|
|
22
23
|
import { resolvePythonExecutable } from './utils/python.js';
|
|
23
24
|
import { computeIrCacheFilename } from './utils/ir-cache.js';
|
|
25
|
+
import { logger } from './utils/logger.js';
|
|
24
26
|
|
|
25
|
-
const TYWRAP_IR_VERSION = '0.
|
|
27
|
+
const TYWRAP_IR_VERSION = '0.4.0';
|
|
26
28
|
|
|
27
29
|
// Collect unknown typing constructs encountered during annotation parsing (per-generate run)
|
|
28
30
|
let unknownTypeNamesCollector: Map<string, number> = new Map();
|
|
@@ -34,7 +36,7 @@ function recordUnknown(name: string): void {
|
|
|
34
36
|
/**
|
|
35
37
|
* Main tywrap function
|
|
36
38
|
*/
|
|
37
|
-
|
|
39
|
+
interface TywrapInstance {
|
|
38
40
|
mapper: TypeMapper;
|
|
39
41
|
generator: CodeGenerator;
|
|
40
42
|
options: Partial<TywrapOptions>;
|
|
@@ -42,7 +44,9 @@ export interface TywrapInstance {
|
|
|
42
44
|
|
|
43
45
|
export async function tywrap(options: Partial<TywrapOptions> = {}): Promise<TywrapInstance> {
|
|
44
46
|
const mapper = new TypeMapper({ presets: options.types?.presets });
|
|
45
|
-
const generator = new CodeGenerator(mapper
|
|
47
|
+
const generator = new CodeGenerator(mapper, {
|
|
48
|
+
onTypeDegrade: typeName => recordUnknown(`unresolvable generated type ${typeName}`),
|
|
49
|
+
});
|
|
46
50
|
|
|
47
51
|
globalCache.setDebug(options.debug ?? false);
|
|
48
52
|
|
|
@@ -63,7 +67,7 @@ export interface GenerateRunOptions {
|
|
|
63
67
|
|
|
64
68
|
export interface GenerateFailure {
|
|
65
69
|
module: string;
|
|
66
|
-
code: 'ir-unavailable';
|
|
70
|
+
code: 'ir-unavailable' | 'ir-version-mismatch' | 'contract-invalid';
|
|
67
71
|
message: string;
|
|
68
72
|
}
|
|
69
73
|
|
|
@@ -83,6 +87,68 @@ function normalizeForComparison(text: string): string {
|
|
|
83
87
|
return text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
84
88
|
}
|
|
85
89
|
|
|
90
|
+
function stableJson(value: unknown): unknown {
|
|
91
|
+
if (Array.isArray(value)) {
|
|
92
|
+
return value.map(stableJson);
|
|
93
|
+
}
|
|
94
|
+
if (value !== null && typeof value === 'object') {
|
|
95
|
+
return Object.fromEntries(
|
|
96
|
+
Object.entries(value as Record<string, unknown>)
|
|
97
|
+
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
|
|
98
|
+
.map(([key, child]) => [key, stableJson(child)])
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function serializeContract(ir: IrContract): string {
|
|
105
|
+
const contract = { ...ir };
|
|
106
|
+
delete contract.metadata;
|
|
107
|
+
return `${JSON.stringify(stableJson(contract), null, 2)}\n`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function validateIrVersion(
|
|
111
|
+
ir: unknown,
|
|
112
|
+
source: string
|
|
113
|
+
): { ir: IrContract | null; error?: string; code?: GenerateFailure['code'] } {
|
|
114
|
+
if (ir === null || typeof ir !== 'object' || Array.isArray(ir)) {
|
|
115
|
+
return { ir: null, code: 'contract-invalid', error: `${source} is not a JSON IR object.` };
|
|
116
|
+
}
|
|
117
|
+
const contract = ir as IrContract;
|
|
118
|
+
if (typeof contract.ir_version !== 'string') {
|
|
119
|
+
return {
|
|
120
|
+
ir: null,
|
|
121
|
+
code: 'contract-invalid',
|
|
122
|
+
error: `${source} is missing its string ir_version.`,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (contract.ir_version !== TYWRAP_IR_VERSION) {
|
|
126
|
+
return {
|
|
127
|
+
ir: null,
|
|
128
|
+
code: 'ir-version-mismatch',
|
|
129
|
+
error: `IR version mismatch: TypeScript expects ${TYWRAP_IR_VERSION}, but ${source} declares ${contract.ir_version}. Regenerate the contract with a matching tywrap_ir.`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (typeof contract.module !== 'string' || contract.module.length === 0) {
|
|
133
|
+
return { ir: null, code: 'contract-invalid', error: `${source} is missing its module name.` };
|
|
134
|
+
}
|
|
135
|
+
const requiredArrays: ReadonlyArray<readonly [string, unknown]> = [
|
|
136
|
+
['functions', contract.functions],
|
|
137
|
+
['classes', contract.classes],
|
|
138
|
+
['type_aliases', contract.type_aliases],
|
|
139
|
+
];
|
|
140
|
+
for (const [field, value] of requiredArrays) {
|
|
141
|
+
if (!Array.isArray(value)) {
|
|
142
|
+
return {
|
|
143
|
+
ir: null,
|
|
144
|
+
code: 'contract-invalid',
|
|
145
|
+
error: `${source} is missing required array field ${field}.`,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return { ir: contract };
|
|
150
|
+
}
|
|
151
|
+
|
|
86
152
|
async function safeReadFile(path: string): Promise<string | null> {
|
|
87
153
|
try {
|
|
88
154
|
return await fsUtils.readFile(path);
|
|
@@ -106,13 +172,17 @@ async function fetchAndCacheIr(
|
|
|
106
172
|
cacheKey: string,
|
|
107
173
|
caching: boolean,
|
|
108
174
|
checkMode: boolean
|
|
109
|
-
): Promise<{ ir:
|
|
175
|
+
): Promise<{ ir: IrContract | null; error?: string; code?: GenerateFailure['code'] }> {
|
|
110
176
|
let ir: unknown | null = null;
|
|
111
177
|
let irError: string | undefined;
|
|
112
178
|
if (caching && fsUtils.isAvailable() && !checkMode) {
|
|
113
179
|
try {
|
|
114
180
|
const cached = await fsUtils.readFile(pathUtils.join(CACHE_DIR, cacheKey));
|
|
115
|
-
|
|
181
|
+
const cachedValidation = validateIrVersion(
|
|
182
|
+
JSON.parse(cached),
|
|
183
|
+
`Cached Python IR for ${moduleKey}`
|
|
184
|
+
);
|
|
185
|
+
ir = cachedValidation.ir;
|
|
116
186
|
} catch {
|
|
117
187
|
ir = null;
|
|
118
188
|
}
|
|
@@ -124,13 +194,60 @@ async function fetchAndCacheIr(
|
|
|
124
194
|
});
|
|
125
195
|
ir = fetchResult.ir;
|
|
126
196
|
irError = fetchResult.error;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
197
|
+
}
|
|
198
|
+
if (!ir) {
|
|
199
|
+
return { ir: null, error: irError, code: 'ir-unavailable' };
|
|
200
|
+
}
|
|
201
|
+
const validated = validateIrVersion(ir, `Python IR for ${moduleKey}`);
|
|
202
|
+
if (!validated.ir) {
|
|
203
|
+
return {
|
|
204
|
+
ir: null,
|
|
205
|
+
error: irError ?? validated.error,
|
|
206
|
+
code: validated.code ?? 'ir-unavailable',
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (caching && fsUtils.isAvailable() && !checkMode) {
|
|
210
|
+
try {
|
|
211
|
+
await fsUtils.writeFile(pathUtils.join(CACHE_DIR, cacheKey), JSON.stringify(validated.ir));
|
|
212
|
+
} catch {}
|
|
213
|
+
}
|
|
214
|
+
return { ir: validated.ir };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function readContractInput(
|
|
218
|
+
moduleKey: string,
|
|
219
|
+
contractInput: TywrapOptions['contractInput']
|
|
220
|
+
): Promise<{ ir: IrContract | null; error?: string; code?: GenerateFailure['code'] }> {
|
|
221
|
+
const inputPath = typeof contractInput === 'string' ? contractInput : contractInput?.[moduleKey];
|
|
222
|
+
if (!inputPath) {
|
|
223
|
+
return {
|
|
224
|
+
ir: null,
|
|
225
|
+
code: 'contract-invalid',
|
|
226
|
+
error: `No contractInput path configured for module ${moduleKey}.`,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
const parsed = JSON.parse(await fsUtils.readFile(inputPath)) as unknown;
|
|
231
|
+
const validated = validateIrVersion(parsed, `Contract ${inputPath}`);
|
|
232
|
+
if (!validated.ir) {
|
|
233
|
+
return { ir: null, error: validated.error, code: validated.code };
|
|
131
234
|
}
|
|
235
|
+
if (validated.ir.module !== moduleKey) {
|
|
236
|
+
return {
|
|
237
|
+
ir: null,
|
|
238
|
+
code: 'contract-invalid',
|
|
239
|
+
error: `Contract ${inputPath} is for module ${validated.ir.module}, not ${moduleKey}.`,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
return { ir: validated.ir };
|
|
243
|
+
} catch (error: unknown) {
|
|
244
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
245
|
+
return {
|
|
246
|
+
ir: null,
|
|
247
|
+
code: 'contract-invalid',
|
|
248
|
+
error: `Failed to read contract ${inputPath}: ${message}`,
|
|
249
|
+
};
|
|
132
250
|
}
|
|
133
|
-
return { ir, error: irError };
|
|
134
251
|
}
|
|
135
252
|
|
|
136
253
|
/**
|
|
@@ -320,25 +437,39 @@ export async function generate(
|
|
|
320
437
|
unknownTypeNamesCollector = new Map();
|
|
321
438
|
// Resolve module IR via the Python extractor (with optional cache)
|
|
322
439
|
const cacheKey = await computeCacheKey(moduleKey, resolvedOptions);
|
|
323
|
-
const
|
|
324
|
-
moduleKey,
|
|
325
|
-
resolvedOptions,
|
|
326
|
-
|
|
327
|
-
cacheKey,
|
|
328
|
-
caching,
|
|
329
|
-
checkMode
|
|
330
|
-
);
|
|
440
|
+
const irResult = resolvedOptions.contractInput
|
|
441
|
+
? await readContractInput(moduleKey, resolvedOptions.contractInput)
|
|
442
|
+
: await fetchAndCacheIr(moduleKey, resolvedOptions, pythonPath, cacheKey, caching, checkMode);
|
|
443
|
+
const { ir, error: irError } = irResult;
|
|
331
444
|
if (!ir) {
|
|
332
445
|
const message = `No IR produced for module ${moduleKey}${irError ? `: ${irError}` : ''}`;
|
|
333
446
|
warnings.push(message);
|
|
334
447
|
failures.push({
|
|
335
448
|
module: moduleKey,
|
|
336
|
-
code: 'ir-unavailable',
|
|
449
|
+
code: irResult.code ?? 'ir-unavailable',
|
|
337
450
|
message,
|
|
338
451
|
});
|
|
339
452
|
continue;
|
|
340
453
|
}
|
|
341
454
|
|
|
455
|
+
const irWarnings = Array.isArray((ir as Record<string, unknown>).warnings)
|
|
456
|
+
? ((ir as Record<string, unknown>).warnings as unknown[])
|
|
457
|
+
: [];
|
|
458
|
+
for (const warning of irWarnings) {
|
|
459
|
+
if (typeof warning !== 'string') {
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
// Only type-honesty degrades feed --fail-on-warn. Environment capability
|
|
463
|
+
// notices (e.g. typing.get_overloads missing on Python < 3.11) describe
|
|
464
|
+
// the analyzing interpreter, not the generated types, and must not fail
|
|
465
|
+
// an otherwise clean build.
|
|
466
|
+
if (warning.startsWith('Return annotation for ')) {
|
|
467
|
+
recordUnknown(`Python IR warning: ${warning}`);
|
|
468
|
+
} else {
|
|
469
|
+
logger.info(`Python IR notice: ${warning}`, { component: 'Generate' });
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
342
473
|
const moduleModel = transformIrToTsModel(ir);
|
|
343
474
|
|
|
344
475
|
// Apply module-level export filtering (functions/classes + excludes).
|
|
@@ -351,6 +482,10 @@ export async function generate(
|
|
|
351
482
|
const baseName = moduleModel.name || 'module';
|
|
352
483
|
const filesToEmit: Array<{ path: string; content: string }> = [
|
|
353
484
|
{ path: pathUtils.join(outputDir, `${baseName}.generated.ts`), content: gen.typescript },
|
|
485
|
+
{
|
|
486
|
+
path: pathUtils.join(outputDir, `${baseName}.contract.json`),
|
|
487
|
+
content: serializeContract(ir),
|
|
488
|
+
},
|
|
354
489
|
];
|
|
355
490
|
|
|
356
491
|
// Optional .d.ts emission (header-only declarations mirroring exports)
|
|
@@ -441,7 +576,7 @@ async function fetchPythonIr(
|
|
|
441
576
|
try {
|
|
442
577
|
const primary = await execAndParseIr(
|
|
443
578
|
pythonPath,
|
|
444
|
-
['-m', 'tywrap_ir', '--module', moduleName, '--
|
|
579
|
+
['-m', 'tywrap_ir', '--module', moduleName, '--no-pretty'],
|
|
445
580
|
execOptions,
|
|
446
581
|
'tywrap_ir output'
|
|
447
582
|
);
|
|
@@ -476,7 +611,7 @@ async function fetchPythonIr(
|
|
|
476
611
|
|
|
477
612
|
const fallback = await execAndParseIr(
|
|
478
613
|
pythonPath,
|
|
479
|
-
[localMain, '--module', moduleName, '--
|
|
614
|
+
[localMain, '--module', moduleName, '--no-pretty'],
|
|
480
615
|
execOptions,
|
|
481
616
|
'tywrap_ir fallback output'
|
|
482
617
|
);
|
package/src/utils/cache.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { getComponentLogger } from './logger.js';
|
|
|
12
12
|
|
|
13
13
|
const log = getComponentLogger('Cache');
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
interface CacheEntry<T = unknown> {
|
|
16
16
|
key: string;
|
|
17
17
|
data: T;
|
|
18
18
|
timestamp: number;
|
|
@@ -27,7 +27,7 @@ export interface CacheEntry<T = unknown> {
|
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
interface CacheStats {
|
|
31
31
|
totalEntries: number;
|
|
32
32
|
totalSize: number;
|
|
33
33
|
hitRate: number;
|
|
@@ -36,7 +36,7 @@ export interface CacheStats {
|
|
|
36
36
|
memoryUsage: number;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
interface CacheConfig {
|
|
40
40
|
baseDir: string;
|
|
41
41
|
maxSize: number; // Maximum cache size in bytes
|
|
42
42
|
maxAge: number; // Maximum age in milliseconds
|
package/src/utils/codec.ts
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* Sklearn estimators: { "__tywrap__": "sklearn.estimator", "encoding": "json", ... }
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { tagDecodedShape } from '../runtime/validators.js';
|
|
13
|
+
|
|
12
14
|
// Avoid hard dependency on apache-arrow types at compile time to keep install optional.
|
|
13
15
|
export type ArrowTable = { readonly numCols?: number; readonly numRows?: number } & Record<
|
|
14
16
|
string,
|
|
@@ -68,6 +70,7 @@ export type ValueEnvelope =
|
|
|
68
70
|
readonly b64?: string; // when encoding=arrow
|
|
69
71
|
readonly data?: unknown; // when encoding=json
|
|
70
72
|
readonly shape?: readonly number[];
|
|
73
|
+
readonly dtype?: string | null;
|
|
71
74
|
}
|
|
72
75
|
| {
|
|
73
76
|
readonly __tywrap__: 'scipy.sparse';
|
|
@@ -442,13 +445,16 @@ function decodeArrowOrJsonEnvelope<T>(
|
|
|
442
445
|
throw new Error(`Invalid ${typeTag} envelope: missing b64`);
|
|
443
446
|
}
|
|
444
447
|
const bytes = fromBase64(b64);
|
|
445
|
-
|
|
448
|
+
const decoded = decodeArrow(bytes);
|
|
449
|
+
return isPromiseLike(decoded)
|
|
450
|
+
? decoded.then(item => tagDecodedShape(item, { marker: typeTag as 'dataframe' | 'series' }))
|
|
451
|
+
: tagDecodedShape(decoded, { marker: typeTag as 'dataframe' | 'series' });
|
|
446
452
|
}
|
|
447
453
|
if (encoding === 'json') {
|
|
448
454
|
if (!('data' in value)) {
|
|
449
455
|
throw new Error(`Invalid ${typeTag} envelope: missing data`);
|
|
450
456
|
}
|
|
451
|
-
return value.data;
|
|
457
|
+
return tagDecodedShape(value.data, { marker: typeTag as 'dataframe' | 'series' });
|
|
452
458
|
}
|
|
453
459
|
throw new Error(`Invalid ${typeTag} envelope: unsupported encoding ${String(encoding)}`);
|
|
454
460
|
}
|
|
@@ -463,6 +469,8 @@ const decodeNdarrayEnvelope: EnvelopeHandler = (value, decodeArrow) => {
|
|
|
463
469
|
const encoding = value.encoding;
|
|
464
470
|
const shapeValue = value.shape;
|
|
465
471
|
const shape = isNumberArray(shapeValue) ? shapeValue : undefined;
|
|
472
|
+
const dtype = typeof value.dtype === 'string' ? value.dtype : undefined;
|
|
473
|
+
const metadata = { marker: 'ndarray' as const, dims: shape?.length, dtype };
|
|
466
474
|
|
|
467
475
|
if (encoding === 'arrow') {
|
|
468
476
|
const b64 = value.b64;
|
|
@@ -480,30 +488,30 @@ const decodeNdarrayEnvelope: EnvelopeHandler = (value, decodeArrow) => {
|
|
|
480
488
|
return decoded.then(data => {
|
|
481
489
|
const values = extractArrowValues(data);
|
|
482
490
|
if (!values) {
|
|
483
|
-
return data; // Fallback:
|
|
491
|
+
return tagDecodedShape(data, metadata); // Fallback: keep provenance on raw data.
|
|
484
492
|
}
|
|
485
493
|
// Reshape scalars and multi-dimensional arrays, but not 1D
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
494
|
+
return tagDecodedShape(
|
|
495
|
+
shape && shape.length !== 1 ? reshapeArray(values, shape) : values,
|
|
496
|
+
metadata
|
|
497
|
+
);
|
|
490
498
|
});
|
|
491
499
|
}
|
|
492
500
|
const values = extractArrowValues(decoded);
|
|
493
501
|
if (!values) {
|
|
494
|
-
return decoded; // Fallback:
|
|
502
|
+
return tagDecodedShape(decoded, metadata); // Fallback: keep provenance on raw data.
|
|
495
503
|
}
|
|
496
504
|
// Reshape scalars and multi-dimensional arrays, but not 1D
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
505
|
+
return tagDecodedShape(
|
|
506
|
+
shape && shape.length !== 1 ? reshapeArray(values, shape) : values,
|
|
507
|
+
metadata
|
|
508
|
+
);
|
|
501
509
|
}
|
|
502
510
|
if (encoding === 'json') {
|
|
503
511
|
if (!('data' in value)) {
|
|
504
512
|
throw new Error('Invalid ndarray envelope: missing data');
|
|
505
513
|
}
|
|
506
|
-
return value.data;
|
|
514
|
+
return tagDecodedShape(value.data, metadata);
|
|
507
515
|
}
|
|
508
516
|
throw new Error(`Invalid ndarray envelope: unsupported encoding ${String(encoding)}`);
|
|
509
517
|
};
|
package/src/utils/ir-cache.ts
CHANGED
package/src/utils/runtime.ts
CHANGED
|
@@ -23,13 +23,16 @@ interface RuntimeCapabilities {
|
|
|
23
23
|
|
|
24
24
|
// Cache for runtime detection to avoid repeated environment checks
|
|
25
25
|
let runtimeCache: RuntimeInfo | null = null;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
let runtimeCacheSignature: string | undefined;
|
|
27
|
+
|
|
28
|
+
function currentRuntimeSignature(): string {
|
|
29
|
+
return [
|
|
30
|
+
typeof Deno !== 'undefined' && Deno !== null,
|
|
31
|
+
typeof Bun !== 'undefined' && Bun !== null,
|
|
32
|
+
typeof process !== 'undefined' && process.versions?.node,
|
|
33
|
+
typeof window !== 'undefined' ? window.isSecureContext : undefined,
|
|
34
|
+
typeof self !== 'undefined' ? self.isSecureContext : undefined,
|
|
35
|
+
].join('|');
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
/**
|
|
@@ -37,7 +40,8 @@ export function clearRuntimeCache(): void {
|
|
|
37
40
|
* Results are cached and frozen to prevent external mutation
|
|
38
41
|
*/
|
|
39
42
|
export function detectRuntime(): RuntimeInfo {
|
|
40
|
-
|
|
43
|
+
const signature = currentRuntimeSignature();
|
|
44
|
+
if (runtimeCache && runtimeCacheSignature === signature) {
|
|
41
45
|
return runtimeCache;
|
|
42
46
|
}
|
|
43
47
|
// Deno detection (must come before Node.js check)
|
|
@@ -56,6 +60,7 @@ export function detectRuntime(): RuntimeInfo {
|
|
|
56
60
|
capabilities: Object.freeze(capabilities),
|
|
57
61
|
};
|
|
58
62
|
runtimeCache = Object.freeze(result) as RuntimeInfo;
|
|
63
|
+
runtimeCacheSignature = signature;
|
|
59
64
|
return runtimeCache;
|
|
60
65
|
}
|
|
61
66
|
|
|
@@ -75,6 +80,7 @@ export function detectRuntime(): RuntimeInfo {
|
|
|
75
80
|
capabilities: Object.freeze(capabilities),
|
|
76
81
|
};
|
|
77
82
|
runtimeCache = Object.freeze(result) as RuntimeInfo;
|
|
83
|
+
runtimeCacheSignature = signature;
|
|
78
84
|
return runtimeCache;
|
|
79
85
|
}
|
|
80
86
|
|
|
@@ -94,6 +100,7 @@ export function detectRuntime(): RuntimeInfo {
|
|
|
94
100
|
capabilities: Object.freeze(capabilities),
|
|
95
101
|
};
|
|
96
102
|
runtimeCache = Object.freeze(result) as RuntimeInfo;
|
|
103
|
+
runtimeCacheSignature = signature;
|
|
97
104
|
return runtimeCache;
|
|
98
105
|
}
|
|
99
106
|
|
|
@@ -119,6 +126,7 @@ export function detectRuntime(): RuntimeInfo {
|
|
|
119
126
|
capabilities: Object.freeze(capabilities),
|
|
120
127
|
};
|
|
121
128
|
runtimeCache = Object.freeze(result) as RuntimeInfo;
|
|
129
|
+
runtimeCacheSignature = signature;
|
|
122
130
|
return runtimeCache;
|
|
123
131
|
}
|
|
124
132
|
|
|
@@ -137,6 +145,7 @@ export function detectRuntime(): RuntimeInfo {
|
|
|
137
145
|
|
|
138
146
|
// Cache and freeze the result to prevent external mutation
|
|
139
147
|
runtimeCache = Object.freeze(result) as RuntimeInfo;
|
|
148
|
+
runtimeCacheSignature = signature;
|
|
140
149
|
return runtimeCache;
|
|
141
150
|
}
|
|
142
151
|
|
|
@@ -183,52 +192,6 @@ export function hasCapability(capability: keyof RuntimeCapabilities): boolean {
|
|
|
183
192
|
return getRuntimeCapabilities()[capability];
|
|
184
193
|
}
|
|
185
194
|
|
|
186
|
-
/**
|
|
187
|
-
* Get the best runtime strategy for Python execution
|
|
188
|
-
*/
|
|
189
|
-
export function getBestPythonRuntime(): 'node' | 'pyodide' | 'http' {
|
|
190
|
-
const runtime = detectRuntime();
|
|
191
|
-
|
|
192
|
-
if (runtime.name === 'browser') {
|
|
193
|
-
return 'pyodide';
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (runtime.capabilities.subprocess) {
|
|
197
|
-
return 'node'; // Works for Node.js, Deno, and Bun
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Fallback to HTTP bridge
|
|
201
|
-
return 'http';
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
type PathModule = typeof import('node:path');
|
|
205
|
-
|
|
206
|
-
// Cache for lazy-loaded path module
|
|
207
|
-
let pathModule: PathModule | null = null;
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Lazy load Node.js path module on demand
|
|
211
|
-
*/
|
|
212
|
-
async function loadPathModule(): Promise<PathModule | null> {
|
|
213
|
-
if (pathModule) {
|
|
214
|
-
return pathModule;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const runtime = detectRuntime();
|
|
218
|
-
if (runtime.name === 'node') {
|
|
219
|
-
try {
|
|
220
|
-
pathModule = await import('node:path');
|
|
221
|
-
return pathModule;
|
|
222
|
-
} catch {
|
|
223
|
-
// Fallback for older Node.js versions
|
|
224
|
-
pathModule = await import('path');
|
|
225
|
-
return pathModule;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return null;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
195
|
/**
|
|
233
196
|
* Apply a single path segment to the accumulated normalized segments,
|
|
234
197
|
* resolving '..' and dropping '.'/empty segments in place.
|
|
@@ -283,30 +246,6 @@ export const pathUtils = {
|
|
|
283
246
|
return normalizePath(joined);
|
|
284
247
|
},
|
|
285
248
|
|
|
286
|
-
/**
|
|
287
|
-
* Join paths asynchronously with enhanced Node.js support
|
|
288
|
-
*/
|
|
289
|
-
async joinAsync(...segments: string[]): Promise<string> {
|
|
290
|
-
const runtime = detectRuntime();
|
|
291
|
-
|
|
292
|
-
// For Node.js, use the real path module when available
|
|
293
|
-
if (runtime.name === 'node') {
|
|
294
|
-
const pathMod = await loadPathModule();
|
|
295
|
-
if (pathMod?.posix) {
|
|
296
|
-
return pathMod.posix.join(...segments);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Fallback implementation with normalization
|
|
301
|
-
const joined = segments
|
|
302
|
-
.filter(Boolean)
|
|
303
|
-
.join('/')
|
|
304
|
-
.replace(/\/+/g, '/') // Replace multiple slashes with single slash
|
|
305
|
-
.replace(/\\/g, '/'); // Normalize backslashes to forward slashes
|
|
306
|
-
|
|
307
|
-
return normalizePath(joined);
|
|
308
|
-
},
|
|
309
|
-
|
|
310
249
|
/**
|
|
311
250
|
* Resolve absolute path in a cross-runtime way (synchronous)
|
|
312
251
|
*/
|
|
@@ -329,27 +268,6 @@ export const pathUtils = {
|
|
|
329
268
|
// Fallback: normalize and return as-is for relative paths
|
|
330
269
|
return normalizePath(path);
|
|
331
270
|
},
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Resolve absolute path in a cross-runtime way (asynchronous)
|
|
335
|
-
*/
|
|
336
|
-
async resolveAsync(path: string): Promise<string> {
|
|
337
|
-
const runtime = detectRuntime();
|
|
338
|
-
|
|
339
|
-
if (runtime.name === 'node') {
|
|
340
|
-
const pathMod = await loadPathModule();
|
|
341
|
-
if (pathMod) {
|
|
342
|
-
return pathMod.resolve(path);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (runtime.name === 'browser') {
|
|
347
|
-
return new URL(path, location.href).href;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// Fallback: normalize and return as-is
|
|
351
|
-
return normalizePath(path);
|
|
352
|
-
},
|
|
353
271
|
};
|
|
354
272
|
|
|
355
273
|
/**
|
|
@@ -642,28 +560,6 @@ export function isWindows(): boolean {
|
|
|
642
560
|
return deno?.build?.os === 'windows';
|
|
643
561
|
}
|
|
644
562
|
|
|
645
|
-
/**
|
|
646
|
-
* Check if running on macOS
|
|
647
|
-
*/
|
|
648
|
-
export function isMacOS(): boolean {
|
|
649
|
-
if (typeof process !== 'undefined' && process.platform) {
|
|
650
|
-
return process.platform === 'darwin';
|
|
651
|
-
}
|
|
652
|
-
const deno = (globalThis as unknown as { Deno?: { build?: { os?: string } } }).Deno;
|
|
653
|
-
return deno?.build?.os === 'darwin';
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
/**
|
|
657
|
-
* Check if running on Linux
|
|
658
|
-
*/
|
|
659
|
-
export function isLinux(): boolean {
|
|
660
|
-
if (typeof process !== 'undefined' && process.platform) {
|
|
661
|
-
return process.platform === 'linux';
|
|
662
|
-
}
|
|
663
|
-
const deno = (globalThis as unknown as { Deno?: { build?: { os?: string } } }).Deno;
|
|
664
|
-
return deno?.build?.os === 'linux';
|
|
665
|
-
}
|
|
666
|
-
|
|
667
563
|
/**
|
|
668
564
|
* Check if a path is absolute
|
|
669
565
|
*/
|
|
@@ -676,13 +572,6 @@ export function isAbsolutePath(path: string): boolean {
|
|
|
676
572
|
return /^[A-Za-z]:[\\/]/.test(path);
|
|
677
573
|
}
|
|
678
574
|
|
|
679
|
-
/**
|
|
680
|
-
* Get the path separator for the current platform
|
|
681
|
-
*/
|
|
682
|
-
export function getPathSeparator(): string {
|
|
683
|
-
return isWindows() ? '\\' : '/';
|
|
684
|
-
}
|
|
685
|
-
|
|
686
575
|
/**
|
|
687
576
|
* Get the default Python executable name for the current platform
|
|
688
577
|
*/
|
package/src/version.ts
CHANGED