tywrap 0.6.1 → 0.8.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 +15 -5
- package/dist/core/annotation-parser.d.ts.map +1 -1
- package/dist/core/annotation-parser.js.map +1 -1
- package/dist/core/emit-call.d.ts.map +1 -1
- package/dist/core/emit-call.js +1 -1
- package/dist/core/emit-call.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/dev.d.ts.map +1 -1
- package/dist/dev.js +1 -3
- package/dist/dev.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.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/frame-codec.d.ts +111 -0
- package/dist/runtime/frame-codec.d.ts.map +1 -0
- package/dist/runtime/frame-codec.js +352 -0
- package/dist/runtime/frame-codec.js.map +1 -0
- 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 +2 -2
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/node.d.ts +25 -19
- package/dist/runtime/node.d.ts.map +1 -1
- package/dist/runtime/node.js +19 -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 +14 -1
- package/dist/runtime/rpc-client.d.ts.map +1 -1
- package/dist/runtime/rpc-client.js +68 -6
- package/dist/runtime/rpc-client.js.map +1 -1
- package/dist/runtime/subprocess-transport.d.ts +177 -3
- package/dist/runtime/subprocess-transport.d.ts.map +1 -1
- package/dist/runtime/subprocess-transport.js +526 -26
- package/dist/runtime/subprocess-transport.js.map +1 -1
- package/dist/runtime/transport.d.ts +141 -0
- package/dist/runtime/transport.d.ts.map +1 -1
- package/dist/runtime/transport.js +21 -0
- package/dist/runtime/transport.js.map +1 -1
- package/dist/types/index.d.ts +59 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/tywrap.d.ts.map +1 -1
- package/dist/tywrap.js +204 -149
- package/dist/tywrap.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 +205 -6
- package/dist/utils/codec.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -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/frame_codec.py +424 -0
- package/runtime/python_bridge.py +241 -42
- package/runtime/tywrap_bridge_core.py +152 -13
- package/src/core/annotation-parser.ts +2 -1
- package/src/core/emit-call.ts +1 -7
- package/src/core/generator.ts +50 -11
- package/src/dev.ts +1 -3
- package/src/index.ts +1 -0
- package/src/runtime/base-bridge.ts +106 -0
- package/src/runtime/frame-codec.ts +469 -0
- package/src/runtime/http-transport.ts +21 -1
- package/src/runtime/http.ts +7 -51
- package/src/runtime/index.ts +2 -6
- package/src/runtime/node.ts +42 -53
- 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 +91 -7
- package/src/runtime/subprocess-transport.ts +629 -30
- package/src/runtime/transport.ts +169 -0
- package/src/types/index.ts +62 -0
- package/src/tywrap.ts +265 -162
- package/src/utils/codec.ts +245 -7
- package/src/version.ts +1 -1
package/src/runtime/transport.ts
CHANGED
|
@@ -26,6 +26,28 @@ export const PROTOCOL_ID = 'tywrap/1';
|
|
|
26
26
|
*/
|
|
27
27
|
export const TYWRAP_PROTOCOL_VERSION = Number.parseInt(PROTOCOL_ID.split('/')[1] ?? '', 10);
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Framing protocol identifier for chunked large-payload transport.
|
|
31
|
+
*
|
|
32
|
+
* This is DISTINCT from {@link PROTOCOL_ID}: the logical RPC stays `tywrap/1`,
|
|
33
|
+
* while `tywrap-frame/1` describes a separate layer (below {@link Transport.send})
|
|
34
|
+
* that fragments one logical message across multiple wire frames and reassembles
|
|
35
|
+
* it. An old bridge rejects any non-`tywrap/1` request, so the logical protocol
|
|
36
|
+
* must NOT be bumped to negotiate chunking — a separate framing protocol,
|
|
37
|
+
* advertised through a `tywrap/1` `meta` extension, is used instead.
|
|
38
|
+
*
|
|
39
|
+
* Subprocess-only for 0.8.0 (it is the only backend with a real frame ceiling —
|
|
40
|
+
* the JSONL line-length limit). See docs/transport-framing.md.
|
|
41
|
+
*/
|
|
42
|
+
export const FRAME_PROTOCOL_ID = 'tywrap-frame/1';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Numeric framing-protocol version. Derived from the trailing number of
|
|
46
|
+
* {@link FRAME_PROTOCOL_ID} so the two cannot drift — bump FRAME_PROTOCOL_ID
|
|
47
|
+
* alone and this follows (same pattern as {@link TYWRAP_PROTOCOL_VERSION}).
|
|
48
|
+
*/
|
|
49
|
+
export const FRAME_PROTOCOL_VERSION = Number.parseInt(FRAME_PROTOCOL_ID.split('/')[1] ?? '', 10);
|
|
50
|
+
|
|
29
51
|
// =============================================================================
|
|
30
52
|
// PROTOCOL TYPES
|
|
31
53
|
// =============================================================================
|
|
@@ -102,6 +124,142 @@ export interface ProtocolResponse {
|
|
|
102
124
|
};
|
|
103
125
|
}
|
|
104
126
|
|
|
127
|
+
// =============================================================================
|
|
128
|
+
// CHUNK FRAMING (tywrap-frame/1)
|
|
129
|
+
// =============================================================================
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Per-frame encoding for a {@link ChunkFrame}'s `data` field.
|
|
133
|
+
*
|
|
134
|
+
* - `utf8-slice` (the chosen default for 0.8.0): `data` is a raw substring of the
|
|
135
|
+
* complete logical JSON message, split on UTF-8 codepoint boundaries. Because
|
|
136
|
+
* the logical payload is already valid-UTF-8 JSON, the slices reassemble by
|
|
137
|
+
* simple concatenation — no inflation, no extra decode. See
|
|
138
|
+
* docs/transport-framing.md for the rationale (decision #6).
|
|
139
|
+
* - `utf8-base64`: `data` is a base64-encoded chunk of the UTF-8 bytes, safe for
|
|
140
|
+
* arbitrary byte splits but ~33% larger on the wire with a memory-amplification
|
|
141
|
+
* cost. Reserved as an alternative; not emitted by tywrap in 0.8.0.
|
|
142
|
+
*/
|
|
143
|
+
export type ChunkFrameEncoding = 'utf8-base64' | 'utf8-slice';
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* A single wire frame of the `tywrap-frame/1` framing protocol.
|
|
147
|
+
*
|
|
148
|
+
* A frame envelope is DISTINCT from the logical {@link ProtocolMessage} /
|
|
149
|
+
* {@link ProtocolResponse}: it carries a slice of the bytes of ONE complete
|
|
150
|
+
* logical JSON message (a request or a response), fragmented because the payload
|
|
151
|
+
* exceeds the transport's frame ceiling. The framing layer reassembles all
|
|
152
|
+
* frames for a given {@link ChunkFrame.id} back into the single logical message
|
|
153
|
+
* before the JSON/codec path ever sees it.
|
|
154
|
+
*
|
|
155
|
+
* Correlation reuses the existing RPC `id`. `seq` is zero-based; `total` and
|
|
156
|
+
* `totalBytes` are repeated on every frame so the receiver can validate the
|
|
157
|
+
* stream is complete (no missing/duplicate `seq`, exact frame count, exact
|
|
158
|
+
* reassembled byte length) before decoding.
|
|
159
|
+
*
|
|
160
|
+
* @see docs/transport-framing.md
|
|
161
|
+
*/
|
|
162
|
+
export interface ChunkFrame {
|
|
163
|
+
/**
|
|
164
|
+
* Frame-envelope discriminator.
|
|
165
|
+
* - `'chunk'`: a normal data-carrying frame.
|
|
166
|
+
* - `'error'`: a framing-layer error (e.g. the sender could not continue the
|
|
167
|
+
* stream); carries no further data frames for this `id`.
|
|
168
|
+
*/
|
|
169
|
+
__tywrap_frame__: 'chunk' | 'error';
|
|
170
|
+
|
|
171
|
+
/** Framing protocol identifier (must equal {@link FRAME_PROTOCOL_ID}). */
|
|
172
|
+
frameProtocol: string;
|
|
173
|
+
|
|
174
|
+
/** Which logical stream this frame belongs to. */
|
|
175
|
+
stream: 'request' | 'response';
|
|
176
|
+
|
|
177
|
+
/** RPC correlation id, shared with the logical {@link ProtocolMessage.id}. */
|
|
178
|
+
id: number;
|
|
179
|
+
|
|
180
|
+
/** Zero-based sequence index of this frame within its stream. */
|
|
181
|
+
seq: number;
|
|
182
|
+
|
|
183
|
+
/** Total number of frames in this stream (repeated on every frame). */
|
|
184
|
+
total: number;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Total byte length of the complete reassembled logical message (repeated on
|
|
188
|
+
* every frame). Used to validate the reassembled payload exactly.
|
|
189
|
+
*/
|
|
190
|
+
totalBytes: number;
|
|
191
|
+
|
|
192
|
+
/** Per-frame payload encoding (see {@link ChunkFrameEncoding}). */
|
|
193
|
+
encoding: ChunkFrameEncoding;
|
|
194
|
+
|
|
195
|
+
/** This frame's slice of the logical message, encoded per {@link encoding}. */
|
|
196
|
+
data: string;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// =============================================================================
|
|
200
|
+
// TRANSPORT CAPABILITIES
|
|
201
|
+
// =============================================================================
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Static, transport-level capability descriptor.
|
|
205
|
+
*
|
|
206
|
+
* Each backend exposes one of these via {@link Transport.capabilities} so callers
|
|
207
|
+
* can reason about what the wire channel can carry WITHOUT round-tripping to
|
|
208
|
+
* Python. These flags describe the transport itself (what bytes it can move and
|
|
209
|
+
* how it frames them) — they are deliberately separate from the bridge's runtime
|
|
210
|
+
* `meta` report ({@link BridgeInfo}), which describes the *Python environment*
|
|
211
|
+
* (which optional libraries happen to be importable). The transport descriptor is
|
|
212
|
+
* authoritative for transport-level flags; the meta report is authoritative for
|
|
213
|
+
* library availability.
|
|
214
|
+
*
|
|
215
|
+
* Honest for TODAY's behavior: as of 0.8.0 `supportsChunking` is implemented for
|
|
216
|
+
* the subprocess backend and reports the *configured* `tywrap-frame/1` path
|
|
217
|
+
* (`enableChunking`) — static and lifecycle-independent, like `supportsArrow`;
|
|
218
|
+
* the per-bridge negotiated fact lives on {@link BridgeInfo}'s `transport` block.
|
|
219
|
+
* HTTP and Pyodide stay `false`. `supportsStreaming` is `false` on every backend
|
|
220
|
+
* (not implemented in 0.8.0). See docs/transport-capabilities.md for the full
|
|
221
|
+
* matrix.
|
|
222
|
+
*/
|
|
223
|
+
export interface TransportCapabilities {
|
|
224
|
+
/** Which backend this transport drives. */
|
|
225
|
+
readonly backend: 'subprocess' | 'http' | 'pyodide';
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Whether the transport can carry Arrow-encoded payloads (binary IPC frames)
|
|
229
|
+
* on the wire. Pyodide is JSON-only (pyarrow is unavailable in WASM), so it is
|
|
230
|
+
* `false` there; subprocess and HTTP can move Arrow bytes.
|
|
231
|
+
*/
|
|
232
|
+
readonly supportsArrow: boolean;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Whether the transport can carry arbitrary binary data (e.g. Python `bytes`).
|
|
236
|
+
* All current backends carry binary via base64 envelopes, so this is `true`
|
|
237
|
+
* everywhere.
|
|
238
|
+
*/
|
|
239
|
+
readonly supportsBinary: boolean;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Whether the transport splits a single logical message across multiple wire
|
|
243
|
+
* frames. Not implemented on any backend yet (planned for 0.8.0) — always
|
|
244
|
+
* `false`.
|
|
245
|
+
*/
|
|
246
|
+
readonly supportsChunking: boolean;
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Whether the transport can stream incremental results for a single request.
|
|
250
|
+
* Not implemented on any backend yet (planned for 0.8.0) — always `false`.
|
|
251
|
+
*/
|
|
252
|
+
readonly supportsStreaming: boolean;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Maximum size, in bytes, of a single wire frame the transport will accept.
|
|
256
|
+
* `Number.POSITIVE_INFINITY` means the transport imposes no frame ceiling of
|
|
257
|
+
* its own (a higher layer — e.g. the codec's payload limit — may still cap the
|
|
258
|
+
* size). For the subprocess backend this is the JSONL line-length limit.
|
|
259
|
+
*/
|
|
260
|
+
readonly maxFrameBytes: number;
|
|
261
|
+
}
|
|
262
|
+
|
|
105
263
|
// =============================================================================
|
|
106
264
|
// TRANSPORT INTERFACE
|
|
107
265
|
// =============================================================================
|
|
@@ -177,6 +335,16 @@ export interface Transport extends Disposable {
|
|
|
177
335
|
* Returns `false` in all other states.
|
|
178
336
|
*/
|
|
179
337
|
readonly isReady: boolean;
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Static, transport-level capability descriptor.
|
|
341
|
+
*
|
|
342
|
+
* Returns what this transport can carry and how it frames messages, with
|
|
343
|
+
* honest values for the transport's current behavior (see
|
|
344
|
+
* {@link TransportCapabilities}). It does NOT depend on lifecycle state — it is
|
|
345
|
+
* safe to call before `init()` and after `dispose()`.
|
|
346
|
+
*/
|
|
347
|
+
capabilities(): TransportCapabilities;
|
|
180
348
|
}
|
|
181
349
|
|
|
182
350
|
// =============================================================================
|
|
@@ -228,6 +396,7 @@ export function isTransport(value: unknown): value is Transport {
|
|
|
228
396
|
typeof (value as Transport).init === 'function' &&
|
|
229
397
|
typeof (value as Transport).send === 'function' &&
|
|
230
398
|
typeof (value as Transport).dispose === 'function' &&
|
|
399
|
+
typeof (value as Transport).capabilities === 'function' &&
|
|
231
400
|
'isReady' in value
|
|
232
401
|
);
|
|
233
402
|
}
|
package/src/types/index.ts
CHANGED
|
@@ -13,6 +13,16 @@ export interface PythonModule {
|
|
|
13
13
|
exports: string[];
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* How a callable is bound on its owning class.
|
|
18
|
+
*
|
|
19
|
+
* Mirrors `IRFunction.method_kind` from the Python IR (tywrap_ir 0.3.0):
|
|
20
|
+
* `'instance'` (default, keeps `self`), `'class'` (keeps `cls`), or
|
|
21
|
+
* `'static'` (no implicit first parameter). Module-level functions retain the
|
|
22
|
+
* `'instance'` default; it is only meaningful for class members.
|
|
23
|
+
*/
|
|
24
|
+
export type PythonMethodKind = 'instance' | 'class' | 'static';
|
|
25
|
+
|
|
16
26
|
export interface PythonFunction {
|
|
17
27
|
name: string;
|
|
18
28
|
signature: FunctionSignature;
|
|
@@ -23,6 +33,28 @@ export interface PythonFunction {
|
|
|
23
33
|
typeParameters?: PythonGenericParameter[];
|
|
24
34
|
returnType: PythonType;
|
|
25
35
|
parameters: Parameter[];
|
|
36
|
+
/**
|
|
37
|
+
* Binding of this callable on its owning class. Defaults to `'instance'`.
|
|
38
|
+
* @see PythonMethodKind
|
|
39
|
+
*/
|
|
40
|
+
methodKind?: PythonMethodKind;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A `@property` or `functools.cached_property` exposed on a class.
|
|
45
|
+
*
|
|
46
|
+
* Mirrors `IRAccessor` from the Python IR (tywrap_ir 0.3.0). Distinct from
|
|
47
|
+
* {@link PythonClass.properties} (which model TypedDict/NamedTuple/dataclass
|
|
48
|
+
* data shapes): accessors are computed attributes backed by a getter.
|
|
49
|
+
*/
|
|
50
|
+
export interface PythonAccessor {
|
|
51
|
+
name: string;
|
|
52
|
+
type: PythonType;
|
|
53
|
+
docstring?: string;
|
|
54
|
+
/** True when there is no setter. `undefined` when undeterminable. */
|
|
55
|
+
readOnly?: boolean;
|
|
56
|
+
/** True for `functools.cached_property`. */
|
|
57
|
+
isCached: boolean;
|
|
26
58
|
}
|
|
27
59
|
|
|
28
60
|
export interface PythonClass {
|
|
@@ -30,6 +62,11 @@ export interface PythonClass {
|
|
|
30
62
|
bases: string[];
|
|
31
63
|
methods: PythonFunction[];
|
|
32
64
|
properties: Property[];
|
|
65
|
+
/**
|
|
66
|
+
* `@property` / `functools.cached_property` accessors, emitted as TS getters.
|
|
67
|
+
* @see PythonAccessor
|
|
68
|
+
*/
|
|
69
|
+
accessors?: PythonAccessor[];
|
|
33
70
|
docstring?: string;
|
|
34
71
|
decorators: string[];
|
|
35
72
|
kind?: 'class' | 'protocol' | 'typed_dict' | 'namedtuple' | 'dataclass' | 'pydantic';
|
|
@@ -376,6 +413,25 @@ export interface TypeMappingConfig {
|
|
|
376
413
|
/** Known bridge backends. Each speaks the identical "tywrap/1" protocol. */
|
|
377
414
|
export type BridgeBackend = 'python-subprocess' | 'pyodide' | 'http';
|
|
378
415
|
|
|
416
|
+
/**
|
|
417
|
+
* Optional chunked-transport negotiation block in {@link BridgeInfo}.
|
|
418
|
+
*
|
|
419
|
+
* Reported by a bridge that understands the `tywrap-frame/1` framing protocol
|
|
420
|
+
* (subprocess only, 0.8.0). It lets the JS side learn, via the `meta` probe,
|
|
421
|
+
* whether the bridge can reassemble chunked frames and the maximum single-frame
|
|
422
|
+
* size it will accept. Absent on old bridges (and on HTTP/Pyodide, which stay
|
|
423
|
+
* single-frame in 0.8.0) — absence means "no chunking", which is backward
|
|
424
|
+
* compatible. See docs/transport-framing.md.
|
|
425
|
+
*/
|
|
426
|
+
export interface BridgeTransportInfo {
|
|
427
|
+
/** Framing protocol the bridge speaks (e.g. `'tywrap-frame/1'`). */
|
|
428
|
+
frameProtocol: string;
|
|
429
|
+
/** Whether the bridge can fragment/reassemble chunked frames. */
|
|
430
|
+
supportsChunking: boolean;
|
|
431
|
+
/** Maximum size, in bytes, of a single wire frame the bridge will accept. */
|
|
432
|
+
maxFrameBytes: number;
|
|
433
|
+
}
|
|
434
|
+
|
|
379
435
|
export interface BridgeInfo {
|
|
380
436
|
protocol: string;
|
|
381
437
|
protocolVersion: number;
|
|
@@ -389,6 +445,12 @@ export interface BridgeInfo {
|
|
|
389
445
|
torchAvailable: boolean;
|
|
390
446
|
sklearnAvailable: boolean;
|
|
391
447
|
instances: number;
|
|
448
|
+
/**
|
|
449
|
+
* Optional chunked-transport negotiation block. Present only when the bridge
|
|
450
|
+
* advertises `tywrap-frame/1` framing; absent on old bridges and on
|
|
451
|
+
* HTTP/Pyodide (single-frame in 0.8.0). See {@link BridgeTransportInfo}.
|
|
452
|
+
*/
|
|
453
|
+
transport?: BridgeTransportInfo;
|
|
392
454
|
}
|
|
393
455
|
|
|
394
456
|
// Analysis and generation results
|