tywrap 0.1.1 → 0.2.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 +32 -5
- package/dist/cli.js +70 -3
- package/dist/cli.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +47 -11
- package/dist/config/index.js.map +1 -1
- package/dist/core/generator.d.ts.map +1 -1
- package/dist/core/generator.js +6 -2
- package/dist/core/generator.js.map +1 -1
- package/dist/core/mapper.d.ts.map +1 -1
- package/dist/core/mapper.js +16 -4
- package/dist/core/mapper.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/runtime/bridge-core.d.ts +64 -0
- package/dist/runtime/bridge-core.d.ts.map +1 -0
- package/dist/runtime/bridge-core.js +344 -0
- package/dist/runtime/bridge-core.js.map +1 -0
- package/dist/runtime/node.d.ts +161 -15
- package/dist/runtime/node.d.ts.map +1 -1
- package/dist/runtime/node.js +542 -218
- package/dist/runtime/node.js.map +1 -1
- package/dist/runtime/optimized-node.d.ts +19 -128
- package/dist/runtime/optimized-node.d.ts.map +1 -1
- package/dist/runtime/optimized-node.js +19 -627
- package/dist/runtime/optimized-node.js.map +1 -1
- package/dist/runtime/timed-out-request-tracker.d.ts +20 -0
- package/dist/runtime/timed-out-request-tracker.d.ts.map +1 -0
- package/dist/runtime/timed-out-request-tracker.js +48 -0
- package/dist/runtime/timed-out-request-tracker.js.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/tywrap.d.ts +17 -4
- package/dist/tywrap.d.ts.map +1 -1
- package/dist/tywrap.js +68 -35
- package/dist/tywrap.js.map +1 -1
- package/dist/utils/codec.d.ts +22 -0
- package/dist/utils/codec.d.ts.map +1 -1
- package/dist/utils/codec.js +245 -55
- package/dist/utils/codec.js.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +1 -0
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/python.d.ts.map +1 -1
- package/dist/utils/python.js.map +1 -1
- package/package.json +11 -2
- package/runtime/python_bridge.py +450 -69
- package/src/cli.ts +75 -3
- package/src/config/index.ts +58 -15
- package/src/core/generator.ts +6 -2
- package/src/core/mapper.ts +16 -4
- package/src/index.ts +2 -1
- package/src/runtime/bridge-core.ts +454 -0
- package/src/runtime/node.ts +725 -253
- package/src/runtime/optimized-node.ts +19 -844
- package/src/runtime/timed-out-request-tracker.ts +58 -0
- package/src/types/index.ts +3 -0
- package/src/tywrap.ts +91 -36
- package/src/utils/codec.ts +299 -82
- package/src/utils/logger.ts +1 -0
- package/src/utils/python.ts +0 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export interface TimedOutRequestTrackerOptions {
|
|
2
|
+
ttlMs: number;
|
|
3
|
+
maxSize?: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Track request IDs that timed out on the JS side so late Python responses can be
|
|
8
|
+
* safely ignored instead of treated as protocol errors.
|
|
9
|
+
*/
|
|
10
|
+
export class TimedOutRequestTracker {
|
|
11
|
+
private readonly ttlMs: number;
|
|
12
|
+
private readonly maxSize: number;
|
|
13
|
+
private readonly items = new Map<number, number>();
|
|
14
|
+
|
|
15
|
+
constructor(options: TimedOutRequestTrackerOptions) {
|
|
16
|
+
this.ttlMs = options.ttlMs;
|
|
17
|
+
this.maxSize = options.maxSize ?? 1000;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
clear(): void {
|
|
21
|
+
this.items.clear();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
mark(id: number): void {
|
|
25
|
+
const now = Date.now();
|
|
26
|
+
this.pruneOld(now);
|
|
27
|
+
this.items.set(id, now);
|
|
28
|
+
this.pruneMax();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
consume(id: number): boolean {
|
|
32
|
+
if (!this.items.has(id)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
this.items.delete(id);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private pruneOld(now: number): void {
|
|
40
|
+
const cutoff = now - this.ttlMs;
|
|
41
|
+
for (const [key, ts] of this.items) {
|
|
42
|
+
if (ts >= cutoff) {
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
this.items.delete(key);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private pruneMax(): void {
|
|
50
|
+
while (this.items.size > this.maxSize) {
|
|
51
|
+
const oldest = this.items.keys().next();
|
|
52
|
+
if (oldest.done) {
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
this.items.delete(oldest.value);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
package/src/types/index.ts
CHANGED
package/src/tywrap.ts
CHANGED
|
@@ -72,16 +72,50 @@ expectType<Record<string, unknown>>(mod);
|
|
|
72
72
|
await writeFile(filePath, content, 'utf-8');
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
export interface GenerateRunOptions {
|
|
76
|
+
/**
|
|
77
|
+
* If true, do not write files; instead compare generated output to what's on disk.
|
|
78
|
+
* Intended for CI to ensure wrappers are checked in and up to date.
|
|
79
|
+
*/
|
|
80
|
+
check?: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface GenerateResult {
|
|
84
|
+
written: string[];
|
|
85
|
+
warnings: string[];
|
|
86
|
+
/**
|
|
87
|
+
* Only set when `GenerateRunOptions.check === true`.
|
|
88
|
+
* Lists files that are missing or differ from what would be generated.
|
|
89
|
+
*/
|
|
90
|
+
outOfDate?: string[];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function normalizeForComparison(text: string): string {
|
|
94
|
+
// Avoid false positives from CRLF vs LF (e.g. Windows checkouts)
|
|
95
|
+
return text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function safeReadFile(path: string): Promise<string | null> {
|
|
99
|
+
try {
|
|
100
|
+
return await fsUtils.readFile(path);
|
|
101
|
+
} catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
75
106
|
/**
|
|
76
107
|
* Generate TypeScript wrappers for configured Python modules.
|
|
77
108
|
* Minimal MVP implementation: resolves module file, analyzes, generates TS, writes to output.dir
|
|
78
109
|
*/
|
|
79
110
|
export async function generate(
|
|
80
|
-
options: Partial<TywrapOptions
|
|
81
|
-
|
|
111
|
+
options: Partial<TywrapOptions>,
|
|
112
|
+
runOptions: GenerateRunOptions = {}
|
|
113
|
+
): Promise<GenerateResult> {
|
|
114
|
+
const checkMode = runOptions.check === true;
|
|
82
115
|
const resolvedOptions = createConfig(options);
|
|
83
116
|
const instance = await tywrap(resolvedOptions);
|
|
84
117
|
const written: string[] = [];
|
|
118
|
+
const outOfDate: string[] = [];
|
|
85
119
|
const warnings: string[] = [];
|
|
86
120
|
const outputDir = resolvedOptions.output.dir;
|
|
87
121
|
const caching = resolvedOptions.performance.caching;
|
|
@@ -92,11 +126,13 @@ export async function generate(
|
|
|
92
126
|
});
|
|
93
127
|
|
|
94
128
|
// Ensure directory exists (Node-only best-effort)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
129
|
+
if (!checkMode) {
|
|
130
|
+
try {
|
|
131
|
+
const modFs = await import('fs/promises');
|
|
132
|
+
await modFs.mkdir(outputDir, { recursive: true });
|
|
133
|
+
} catch {
|
|
134
|
+
// ignore in non-node or if already exists
|
|
135
|
+
}
|
|
100
136
|
}
|
|
101
137
|
|
|
102
138
|
const modules = resolvedOptions.pythonModules ?? {};
|
|
@@ -109,10 +145,6 @@ export async function generate(
|
|
|
109
145
|
let ir: unknown | null = null;
|
|
110
146
|
let irError: string | undefined;
|
|
111
147
|
if (caching && fsUtils.isAvailable()) {
|
|
112
|
-
try {
|
|
113
|
-
const modFs = await import('fs/promises');
|
|
114
|
-
await modFs.mkdir(cacheDir, { recursive: true });
|
|
115
|
-
} catch {}
|
|
116
148
|
try {
|
|
117
149
|
const cached = await fsUtils.readFile(pathUtils.join(cacheDir, cacheKey));
|
|
118
150
|
ir = JSON.parse(cached);
|
|
@@ -124,7 +156,7 @@ export async function generate(
|
|
|
124
156
|
const fetchResult = await fetchPythonIr(moduleKey, pythonPath);
|
|
125
157
|
ir = fetchResult.ir;
|
|
126
158
|
irError = fetchResult.error;
|
|
127
|
-
if (ir && caching && fsUtils.isAvailable()) {
|
|
159
|
+
if (ir && caching && fsUtils.isAvailable() && !checkMode) {
|
|
128
160
|
try {
|
|
129
161
|
await fsUtils.writeFile(pathUtils.join(cacheDir, cacheKey), JSON.stringify(ir));
|
|
130
162
|
} catch {}
|
|
@@ -141,26 +173,43 @@ export async function generate(
|
|
|
141
173
|
const annotatedJSDoc = Boolean(resolvedOptions.output?.annotatedJSDoc);
|
|
142
174
|
const gen = instance.generator.generateModuleDefinition(moduleModel, annotatedJSDoc);
|
|
143
175
|
|
|
144
|
-
// Write file
|
|
145
176
|
const baseName = moduleModel.name || 'module';
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
177
|
+
const filesToEmit: Array<{ path: string; content: string }> = [
|
|
178
|
+
{ path: pathUtils.join(outputDir, `${baseName}.generated.ts`), content: gen.typescript },
|
|
179
|
+
];
|
|
149
180
|
|
|
150
181
|
// Optional .d.ts emission (header-only declarations mirroring exports)
|
|
151
182
|
if (resolvedOptions.output?.declaration) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
183
|
+
filesToEmit.push({
|
|
184
|
+
path: pathUtils.join(outputDir, `${baseName}.generated.d.ts`),
|
|
185
|
+
content: renderDts(gen.typescript),
|
|
186
|
+
});
|
|
156
187
|
}
|
|
157
188
|
|
|
158
189
|
// Optional source map emission (placeholder mapping for now)
|
|
159
190
|
if (resolvedOptions.output?.sourceMap) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
191
|
+
filesToEmit.push({
|
|
192
|
+
path: pathUtils.join(outputDir, `${baseName}.generated.ts.map`),
|
|
193
|
+
content: renderSourceMapPlaceholder(moduleModel.name),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (checkMode) {
|
|
198
|
+
for (const file of filesToEmit) {
|
|
199
|
+
const existing = await safeReadFile(file.path);
|
|
200
|
+
if (existing === null) {
|
|
201
|
+
outOfDate.push(file.path);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (normalizeForComparison(existing) !== normalizeForComparison(file.content)) {
|
|
205
|
+
outOfDate.push(file.path);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
for (const file of filesToEmit) {
|
|
210
|
+
await fsUtils.writeFile(file.path, file.content);
|
|
211
|
+
written.push(file.path);
|
|
212
|
+
}
|
|
164
213
|
}
|
|
165
214
|
|
|
166
215
|
// Emit warning summary of unknown typing constructs (best-effort)
|
|
@@ -174,22 +223,28 @@ export async function generate(
|
|
|
174
223
|
`Module ${baseName}: unknown typing constructs encountered: ${unkList}${entries.length > 25 ? '…' : ''}`
|
|
175
224
|
);
|
|
176
225
|
// Write JSON report
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
226
|
+
if (!checkMode) {
|
|
227
|
+
try {
|
|
228
|
+
const reportsDir = pathUtils.join('.tywrap', 'reports');
|
|
229
|
+
await fsUtils.writeFile(
|
|
230
|
+
pathUtils.join(reportsDir, `${baseName}.json`),
|
|
231
|
+
JSON.stringify({
|
|
232
|
+
module: baseName,
|
|
233
|
+
unknowns: Object.fromEntries(entries),
|
|
234
|
+
generatedAt: new Date().toISOString(),
|
|
235
|
+
})
|
|
236
|
+
);
|
|
237
|
+
} catch {
|
|
238
|
+
// ignore
|
|
239
|
+
}
|
|
189
240
|
}
|
|
190
241
|
}
|
|
191
242
|
}
|
|
192
243
|
|
|
244
|
+
if (checkMode) {
|
|
245
|
+
return { written: [], warnings, outOfDate };
|
|
246
|
+
}
|
|
247
|
+
|
|
193
248
|
return { written, warnings };
|
|
194
249
|
}
|
|
195
250
|
|