@polyengine/wasi 0.1.0-pre.g633468a → 0.2.0-pre.g2125a06

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/esm/http.js CHANGED
@@ -66,7 +66,12 @@
66
66
  // Fetch failures are TypeErrors with prose; a small sniff table maps the
67
67
  // recognizable ones and everything else is `internal-error(message)`.
68
68
  import { ComponentException } from "@polyengine/runtime/embedder";
69
- /** The compatibility track the fragment registers on by default. */
69
+ /**
70
+ * The compatibility track the fragment registers on by default.
71
+ *
72
+ * @internal — test-only export; wasi/tests/http_test.ts pins the default
73
+ * track literally, but the public entry point is `http()`.
74
+ */
70
75
  export const HTTP_TRACK = "0.3";
71
76
  const OK = { kind: "ok" };
72
77
  function httpError(payload, detail) {
@@ -75,7 +80,12 @@ function httpError(payload, detail) {
75
80
  function headerError(kind, detail) {
76
81
  return new ComponentException({ kind }, `wasi:http/types: ${detail}`);
77
82
  }
78
- /** Map a fetch failure onto `error-code` (sniff table + honest catch-all). */
83
+ /**
84
+ * Map a fetch failure onto `error-code` (sniff table + honest catch-all).
85
+ *
86
+ * @internal — used only inside this module's `send` implementation; no
87
+ * importer outside wasi/src/http.ts. The public entry point is `http()`.
88
+ */
79
89
  export function mapFetchError(e) {
80
90
  // Deno/undici wrap the transport detail in the `cause` chain; sniff the
81
91
  // whole chain, report the top-level message.
@@ -133,6 +143,7 @@ const decoder = new TextDecoder();
133
143
  export function http(options = {}) {
134
144
  const onCall = options.onCall ?? (() => { });
135
145
  const v = options.version ?? HTTP_TRACK;
146
+ const allowRequest = options.allowRequest;
136
147
  // --- fields -----------------------------------------------------------------
137
148
  class Fields {
138
149
  /** Entries in original casing and insertion order. */
@@ -608,6 +619,42 @@ export function http(options = {}) {
608
619
  throw httpError({ kind: "internal-error", value: `header '${name}' refused by the platform` }, `client.send: ${e}`);
609
620
  }
610
621
  }
622
+ // Name-level egress policy (allowRequest). Only parse `url` when a
623
+ // policy is actually configured — with no policy or `allowRequest:
624
+ // true`, this path is byte-for-byte today's behavior (same string
625
+ // handed to fetch below, `URL.href` never substituted in). Placed
626
+ // after headers, before collectBody: refusing must not drain the
627
+ // guest's body stream.
628
+ if (allowRequest !== undefined && allowRequest !== true) {
629
+ if (allowRequest === false) {
630
+ request.settleTransmission({ kind: "err", value: { kind: "HTTP-request-denied" } });
631
+ throw httpError({ kind: "HTTP-request-denied" }, "client.send: request denied (allowRequest: false)");
632
+ }
633
+ let parsed;
634
+ try {
635
+ parsed = new URL(url);
636
+ }
637
+ catch {
638
+ // A malformed authority is not a policy decision — the existing
639
+ // HTTP-request-URI-invalid case at line ~858 covers this.
640
+ throw httpError({ kind: "HTTP-request-URI-invalid" }, "client.send: url could not be parsed for policy check");
641
+ }
642
+ let allowed;
643
+ try {
644
+ allowed = await allowRequest({ url: parsed, method, headers });
645
+ }
646
+ catch (e) {
647
+ // Fail closed: a throwing/rejecting predicate denies. The thrown
648
+ // message goes into the ComponentException's DETAIL string only,
649
+ // never the WIT payload.
650
+ request.settleTransmission({ kind: "err", value: { kind: "HTTP-request-denied" } });
651
+ throw httpError({ kind: "HTTP-request-denied" }, `client.send: allowRequest threw: ${e instanceof Error ? e.message : String(e)}`);
652
+ }
653
+ if (!allowed) {
654
+ request.settleTransmission({ kind: "err", value: { kind: "HTTP-request-denied" } });
655
+ throw httpError({ kind: "HTTP-request-denied" }, "client.send: request denied by allowRequest");
656
+ }
657
+ }
611
658
  // Buffered request body (module header divergence), then the trailers
612
659
  // future decides whether the request may be transmitted at all.
613
660
  let body;
@@ -19,7 +19,12 @@ export function componentError(payload, detail) {
19
19
  return new ComponentException(payload, `wasi:sockets/types@0.3: ${detail}`);
20
20
  }
21
21
  // --- address codec ------------------------------------------------------------
22
- /** Render the address part of `addr` as a Deno hostname string. */
22
+ /**
23
+ * Render the address part of `addr` as a Deno hostname string.
24
+ *
25
+ * @internal — shared by the node/Deno socket backends; the public entry
26
+ * point is `sockets()`.
27
+ */
23
28
  export function ipHostname(addr) {
24
29
  if (addr.kind === "ipv4")
25
30
  return addr.value.address.join(".");
@@ -35,6 +40,9 @@ export function ipHostname(addr) {
35
40
  * when it is numeric and drops to 0 otherwise (interface names are not
36
41
  * representable in the WIT shape); flow-info is not observable and is
37
42
  * always 0.
43
+ *
44
+ * @internal — shared by the node/Deno socket backends; the public entry
45
+ * point is `sockets()`.
38
46
  */
39
47
  export function parseNetAddr(addr) {
40
48
  const host = addr.hostname;
@@ -131,6 +139,9 @@ function isDeprecatedV4CompatibleV6(groups) {
131
139
  /**
132
140
  * Whether `addr` may cross this socket's family boundary: same family, and
133
141
  * never an IPv4-mapped or deprecated IPv4-compatible IPv6 address.
142
+ *
143
+ * @internal — shared by the node/Deno socket backends; the public entry
144
+ * point is `sockets()`.
134
145
  */
135
146
  export function isValidAddressFamily(family, addr) {
136
147
  if (family === "ipv4")
@@ -139,12 +150,21 @@ export function isValidAddressFamily(family, addr) {
139
150
  !isV4MappedV6(addr.value.address) &&
140
151
  !isDeprecatedV4CompatibleV6(addr.value.address);
141
152
  }
153
+ /**
154
+ * @internal — shared by the node/Deno socket backends; the public entry
155
+ * point is `sockets()`.
156
+ */
142
157
  export function isUnspecified(addr) {
143
158
  if (addr.kind === "ipv4")
144
159
  return addr.value.address.every((o) => o === 0);
145
160
  return addr.value.address.every((g) => g === 0);
146
161
  }
147
- /** Same endpoint: family, address, and port (udp connected-mode filter). */
162
+ /**
163
+ * Same endpoint: family, address, and port (udp connected-mode filter).
164
+ *
165
+ * @internal — shared by the node/Deno socket backends; the public entry
166
+ * point is `sockets()`.
167
+ */
148
168
  export function sameSocketAddress(a, b) {
149
169
  if (a.kind !== b.kind || a.value.port !== b.value.port)
150
170
  return false;
@@ -203,6 +223,9 @@ const CODE_ERRORS = {
203
223
  * capability re-detection throw branded errors from inside the same try
204
224
  * blocks that guard the platform calls, and re-wrapping one would demote
205
225
  * its payload to `other`.
226
+ *
227
+ * @internal — shared by the node/Deno socket backends; the public entry
228
+ * point is `sockets()`.
206
229
  */
207
230
  export function mapPlatformError(e, what) {
208
231
  if (e instanceof ComponentException)
@@ -257,7 +280,12 @@ export const RESULT_INVALID_STATE = {
257
280
  export function resultErrOf(e, what) {
258
281
  return { kind: "err", value: mapPlatformError(e, what).payload };
259
282
  }
260
- /** The family's wildcard address, port 0 (tcp listen's implicit bind). */
283
+ /**
284
+ * The family's wildcard address, port 0 (tcp listen's implicit bind).
285
+ *
286
+ * @internal — shared by the node/Deno socket backends; the public entry
287
+ * point is `sockets()`.
288
+ */
261
289
  export function wildcardAddress(family) {
262
290
  return family === "ipv4"
263
291
  ? { kind: "ipv4", value: { port: 0, address: [0, 0, 0, 0] } }
package/esm/io.js CHANGED
@@ -366,7 +366,10 @@ let OutputStream = (() => {
366
366
  export { OutputStream };
367
367
  /** Default high-water mark for the async-backed streams below: how many
368
368
  * buffered bytes pause a `FedInputStream`'s feed, and the byte budget a
369
- * `SinkOutputStream`'s `check-write` reports. */
369
+ * `SinkOutputStream`'s `check-write` reports.
370
+ *
371
+ * @internal — a tuning constant for this module's stream implementations;
372
+ * not part of any consumer-facing option surface. */
370
373
  export const STREAM_HIGH_WATER = 65536;
371
374
  /**
372
375
  * The p2 `input-stream` surface over an asynchronously-fed buffer: the
@@ -377,6 +380,11 @@ export const STREAM_HIGH_WATER = 65536;
377
380
  * registered `InputStream`, the marks relay from that prototype), and
378
381
  * EOF-with-drained-buffer is the `closed` stream-error. The feed pauses
379
382
  * past the high-water mark (no unbounded buffering).
383
+ *
384
+ * @internal — an implementation detail wired internally by `cli_stdio.ts`,
385
+ * `filesystem_node.ts`/`filesystem_web.ts`, and the sockets fragments; never
386
+ * surfaced as a public field/return type (`CliStdio.imports` and
387
+ * `FilesystemFragment.imports` are opaque `Record<string, unknown>`).
380
388
  */
381
389
  let FedInputStream = (() => {
382
390
  let _instanceExtraInitializers = [];
@@ -508,6 +516,10 @@ export { FedInputStream };
508
516
  * drained everything (A14/A2 mark relay), `subscribe` wakes when budget
509
517
  * frees. A sink failure surfaces as the `last-operation-failed`
510
518
  * stream-error carrying an `IoError`.
519
+ *
520
+ * @internal — an implementation detail wired internally by `cli_stdio.ts`,
521
+ * `filesystem_node.ts`/`filesystem_web.ts`, and the sockets fragments; never
522
+ * surfaced as a public field/return type.
511
523
  */
512
524
  let SinkOutputStream = (() => {
513
525
  let _instanceExtraInitializers = [];
package/esm/sockets.js CHANGED
@@ -148,6 +148,10 @@ export { ipHostname, isUnspecified, isValidAddressFamily, mapPlatformError, MAX_
148
148
  // The 0.2 track's public pieces: the enum error vocabulary, the io-error
149
149
  // resource `network-error-code` downcasts, and the opaque network.
150
150
  export { Network, SocketIoError, } from "./internal/sockets_02.js";
151
+ /**
152
+ * @internal — test-only export; wasi/tests/sockets_test.ts pins the
153
+ * literal track key. The public entry point is `sockets()`.
154
+ */
151
155
  export const SOCKETS_TYPES_INTERFACE = "wasi:sockets/types@0.3";
152
156
  /**
153
157
  * The `wasi:sockets` provider fragment, BOTH tracks (module header):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polyengine/wasi",
3
- "version": "0.1.0-pre.g633468a",
3
+ "version": "0.2.0-pre.g2125a06",
4
4
  "description": "WASI providers for polyengine hosts: the p2 baseline and p3 clocks, one module per semver track.",
5
5
  "homepage": "https://github.com/polymorph-components/polyengine#readme",
6
6
  "repository": {
@@ -91,8 +91,8 @@
91
91
  "access": "public"
92
92
  },
93
93
  "dependencies": {
94
- "@polyengine/protocol": "0.1.0-pre.g633468a",
95
- "@polyengine/runtime": "0.1.0-pre.g633468a"
94
+ "@polyengine/protocol": "0.2.0-pre.g2125a06",
95
+ "@polyengine/runtime": "0.2.0-pre.g2125a06"
96
96
  },
97
97
  "_generatedBy": "dnt@0.43.2"
98
98
  }
package/types/http.d.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  import { Stream } from "@polyengine/runtime/embedder";
2
- /** The compatibility track the fragment registers on by default. */
2
+ /**
3
+ * The compatibility track the fragment registers on by default.
4
+ *
5
+ * @internal — test-only export; wasi/tests/http_test.ts pins the default
6
+ * track literally, but the public entry point is `http()`.
7
+ */
3
8
  export declare const HTTP_TRACK = "0.3";
4
9
  /** `method` — case names verbatim (A10). */
5
10
  export type Method = {
@@ -77,7 +82,12 @@ export type HttpResult = {
77
82
  kind: "err";
78
83
  value: ErrorCode;
79
84
  };
80
- /** Map a fetch failure onto `error-code` (sniff table + honest catch-all). */
85
+ /**
86
+ * Map a fetch failure onto `error-code` (sniff table + honest catch-all).
87
+ *
88
+ * @internal — used only inside this module's `send` implementation; no
89
+ * importer outside wasi/src/http.ts. The public entry point is `http()`.
90
+ */
81
91
  export declare function mapFetchError(e: unknown): ErrorCode;
82
92
  /** What body params accept: the lifted handle, or any natural byte producer. */
83
93
  export type BodySource = Stream<number> | AsyncIterable<Uint8Array | number[]> | Iterable<Uint8Array | number[]>;
@@ -93,6 +103,34 @@ export interface HttpOptions {
93
103
  version?: string;
94
104
  /** Observe every entry point the guest reaches (see sockets' onCall). */
95
105
  onCall?: (call: string) => void;
106
+ /**
107
+ * Name-level egress policy, evaluated in `client.send` after the outgoing
108
+ * request is assembled (method, headers) but before its body is
109
+ * collected — so a refused request never drains the guest's body
110
+ * stream. `true`/default preserves today's behavior exactly (unscoped
111
+ * egress); `false` denies every request without dispatching `fetch`; a
112
+ * callback decides per request from the parsed `url`, `method`, and
113
+ * `headers`.
114
+ *
115
+ * This is the name-level half only: it sees the URL a guest asked for,
116
+ * matching what a `fetch`-based host can express (no resolved address
117
+ * is ever observed here, unlike a native socket layer). The
118
+ * address-level half (`socket_addr_check`-shaped, e.g. blocking a
119
+ * name that resolves to a loopback/link-local address) is issue #200
120
+ * and is out of scope for this fragment; `sockets()` is unaffected.
121
+ *
122
+ * A callback that throws, or whose returned promise rejects, DENIES
123
+ * (fail closed) — a security predicate must never fail open. Denials
124
+ * refuse with the WIT `HTTP-request-denied` error-code case
125
+ * (examples/guests/http-fetch/wit/deps/wasi-http/types.wit); this is
126
+ * distinct from `destination-IP-prohibited`, which names an address
127
+ * judgement this fragment cannot make.
128
+ */
129
+ allowRequest?: boolean | ((request: {
130
+ url: URL;
131
+ method: string;
132
+ headers: Headers;
133
+ }) => boolean | Promise<boolean>);
96
134
  }
97
135
  /** What `http()` returns: the imports fragment plus the fragment's classes. */
98
136
  export interface HttpFragment {
@@ -50,6 +50,12 @@ export interface Opened<H> {
50
50
  handle: H;
51
51
  type: DescriptorType;
52
52
  }
53
+ /**
54
+ * @internal — used only in the (unexported) `FsBackend` seam interface;
55
+ * re-exported from `filesystem_node.ts`/`filesystem_web.ts` but never part
56
+ * of `FilesystemFragment`'s public shape (`{ imports: Record<string,
57
+ * unknown> }`), so no importer outside wasi/src references it.
58
+ */
53
59
  export type MaybeAsync<T> = T | Promise<T>;
54
60
  /**
55
61
  * The backend seam. `isSync: true` promises every op returns a plain
@@ -1,4 +1,10 @@
1
- /** The address shape the socket backends speak. */
1
+ /**
2
+ * The address shape the socket backends speak.
3
+ *
4
+ * @internal — a Deno/node platform backend type; its only non-backend
5
+ * reference was `parseNetAddr`'s parameter, itself internal. The public
6
+ * entry point is `sockets()`.
7
+ */
2
8
  export interface NetAddr {
3
9
  transport?: string;
4
10
  hostname: string;
@@ -106,7 +106,12 @@ export declare const MAX_UDP_DATAGRAM_SIZE = 65535;
106
106
  * guest-visible err.
107
107
  */
108
108
  export declare function componentError(payload: SocketErrorCode, detail: string): ComponentException<SocketErrorCode>;
109
- /** Render the address part of `addr` as a Deno hostname string. */
109
+ /**
110
+ * Render the address part of `addr` as a Deno hostname string.
111
+ *
112
+ * @internal — shared by the node/Deno socket backends; the public entry
113
+ * point is `sockets()`.
114
+ */
110
115
  export declare function ipHostname(addr: IpSocketAddress): string;
111
116
  /**
112
117
  * Parse a Deno `NetAddr` back into a WIT `ip-socket-address`.
@@ -117,15 +122,30 @@ export declare function ipHostname(addr: IpSocketAddress): string;
117
122
  * when it is numeric and drops to 0 otherwise (interface names are not
118
123
  * representable in the WIT shape); flow-info is not observable and is
119
124
  * always 0.
125
+ *
126
+ * @internal — shared by the node/Deno socket backends; the public entry
127
+ * point is `sockets()`.
120
128
  */
121
129
  export declare function parseNetAddr(addr: NetAddr): IpSocketAddress;
122
130
  /**
123
131
  * Whether `addr` may cross this socket's family boundary: same family, and
124
132
  * never an IPv4-mapped or deprecated IPv4-compatible IPv6 address.
133
+ *
134
+ * @internal — shared by the node/Deno socket backends; the public entry
135
+ * point is `sockets()`.
125
136
  */
126
137
  export declare function isValidAddressFamily(family: IpAddressFamily, addr: IpSocketAddress): boolean;
138
+ /**
139
+ * @internal — shared by the node/Deno socket backends; the public entry
140
+ * point is `sockets()`.
141
+ */
127
142
  export declare function isUnspecified(addr: IpSocketAddress): boolean;
128
- /** Same endpoint: family, address, and port (udp connected-mode filter). */
143
+ /**
144
+ * Same endpoint: family, address, and port (udp connected-mode filter).
145
+ *
146
+ * @internal — shared by the node/Deno socket backends; the public entry
147
+ * point is `sockets()`.
148
+ */
129
149
  export declare function sameSocketAddress(a: IpSocketAddress, b: IpSocketAddress): boolean;
130
150
  /** `e instanceof Deno.errors[name]`, tolerating hosts/versions lacking the class. */
131
151
  export declare function isDenoError(e: unknown, name: string): boolean;
@@ -138,6 +158,9 @@ export declare function isDenoError(e: unknown, name: string): boolean;
138
158
  * capability re-detection throw branded errors from inside the same try
139
159
  * blocks that guard the platform calls, and re-wrapping one would demote
140
160
  * its payload to `other`.
161
+ *
162
+ * @internal — shared by the node/Deno socket backends; the public entry
163
+ * point is `sockets()`.
141
164
  */
142
165
  export declare function mapPlatformError(e: unknown, what: string): ComponentException<SocketErrorCode>;
143
166
  /** `result<_, error-code>` as a value (the payload of tcp send/receive futures). */
@@ -252,5 +275,10 @@ export interface TcpSocket {
252
275
  export interface TcpSocketClass {
253
276
  create(addressFamily: IpAddressFamily): TcpSocket;
254
277
  }
255
- /** The family's wildcard address, port 0 (tcp listen's implicit bind). */
278
+ /**
279
+ * The family's wildcard address, port 0 (tcp listen's implicit bind).
280
+ *
281
+ * @internal — shared by the node/Deno socket backends; the public entry
282
+ * point is `sockets()`.
283
+ */
256
284
  export declare function wildcardAddress(family: IpAddressFamily): IpSocketAddress;
package/types/io.d.ts CHANGED
@@ -1,4 +1,9 @@
1
- /** A p2 `stream-error` value (variant): `closed` or `last-operation-failed`. */
1
+ /**
2
+ * A p2 `stream-error` value (variant): `closed` or `last-operation-failed`.
3
+ *
4
+ * @internal — only used as `closedError()`'s local payload type; not part
5
+ * of any exported class's public signature (`IoError` never surfaces it).
6
+ */
2
7
  export type StreamErrorValue = {
3
8
  kind: "closed";
4
9
  } | {
@@ -116,7 +121,10 @@ export declare class OutputStream {
116
121
  }
117
122
  /** Default high-water mark for the async-backed streams below: how many
118
123
  * buffered bytes pause a `FedInputStream`'s feed, and the byte budget a
119
- * `SinkOutputStream`'s `check-write` reports. */
124
+ * `SinkOutputStream`'s `check-write` reports.
125
+ *
126
+ * @internal — a tuning constant for this module's stream implementations;
127
+ * not part of any consumer-facing option surface. */
120
128
  export declare const STREAM_HIGH_WATER = 65536;
121
129
  /** An async byte sink; the returned promise settling = the chunk drained. */
122
130
  export type ByteSink = (chunk: Uint8Array) => void | Promise<void>;
@@ -129,6 +137,11 @@ export type ByteSink = (chunk: Uint8Array) => void | Promise<void>;
129
137
  * registered `InputStream`, the marks relay from that prototype), and
130
138
  * EOF-with-drained-buffer is the `closed` stream-error. The feed pauses
131
139
  * past the high-water mark (no unbounded buffering).
140
+ *
141
+ * @internal — an implementation detail wired internally by `cli_stdio.ts`,
142
+ * `filesystem_node.ts`/`filesystem_web.ts`, and the sockets fragments; never
143
+ * surfaced as a public field/return type (`CliStdio.imports` and
144
+ * `FilesystemFragment.imports` are opaque `Record<string, unknown>`).
132
145
  */
133
146
  export declare class FedInputStream {
134
147
  #private;
@@ -149,6 +162,10 @@ export declare class FedInputStream {
149
162
  * drained everything (A14/A2 mark relay), `subscribe` wakes when budget
150
163
  * frees. A sink failure surfaces as the `last-operation-failed`
151
164
  * stream-error carrying an `IoError`.
165
+ *
166
+ * @internal — an implementation detail wired internally by `cli_stdio.ts`,
167
+ * `filesystem_node.ts`/`filesystem_web.ts`, and the sockets fragments; never
168
+ * surfaced as a public field/return type.
152
169
  */
153
170
  export declare class SinkOutputStream {
154
171
  #private;
@@ -1,6 +1,10 @@
1
1
  import type { IpAddress, SocketsOptions, TcpSocketClass, UdpSocketClass } from "./internal/sockets_shared.js";
2
2
  export { type IpAddress, type IpAddressFamily, ipHostname, type IpSocketAddress, type Ipv4Address, type Ipv4SocketAddress, type Ipv6Address, type Ipv6SocketAddress, isUnspecified, isValidAddressFamily, mapPlatformError, MAX_UDP_DATAGRAM_SIZE, type NameLookupErrorCode, type NetAddr, parseNetAddr, sameSocketAddress, type SocketErrorCode, type SocketResult, type SocketsOptions, type TcpAcceptStream, type TcpByteStream, type TcpSendSource, type TcpSocket, type TcpSocketClass, type UdpSocket, type UdpSocketClass, wildcardAddress, } from "./internal/sockets_shared.js";
3
3
  export { Network, type SocketErrorCode02, SocketIoError, } from "./internal/sockets_02.js";
4
+ /**
5
+ * @internal — test-only export; wasi/tests/sockets_test.ts pins the
6
+ * literal track key. The public entry point is `sockets()`.
7
+ */
4
8
  export declare const SOCKETS_TYPES_INTERFACE = "wasi:sockets/types@0.3";
5
9
  /** What `sockets()` returns: the imports fragment plus the fragment's classes. */
6
10
  export interface SocketsShim {