@localpreview/protocol 0.1.0 → 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 ADDED
@@ -0,0 +1,161 @@
1
+ # @localpreview/protocol
2
+
3
+ Shared protocol contract for LocalPreview packages.
4
+
5
+ This package contains the stable shapes and helpers used by the CLI,
6
+ control-plane, and relay. Keep runtime-specific code out of this package; it
7
+ should stay small, dependency-light, and safe to import from every LocalPreview
8
+ runtime.
9
+
10
+ ## What Lives Here
11
+
12
+ - Public LocalPreview origin constants.
13
+ - Tunnel creation and relay registration response/request shapes.
14
+ - Relay WebSocket message encoding and validation.
15
+ - Shared error codes and error response shape.
16
+ - Header filtering helpers for proxy boundaries.
17
+ - Target and requested subdomain validation.
18
+ - Protocol and relay snapshot version constants.
19
+
20
+ ## Relay Message Flow
21
+
22
+ The relay protocol multiplexes browser HTTP requests over the CLI WebSocket.
23
+ Messages are JSON strings and every message belongs to a `requestId`.
24
+
25
+ ```txt
26
+ browser request
27
+ -> relay sends request-start/request-chunk/request-end to CLI
28
+ -> CLI forwards to local target
29
+ -> CLI sends response-start/response-chunk/response-end to relay
30
+ -> relay streams response back to browser
31
+ ```
32
+
33
+ Server-to-client messages are decoded with `decodeServerRelayMessage`.
34
+ Client-to-server messages are decoded with `decodeClientRelayMessage`.
35
+
36
+ ```ts
37
+ import {
38
+ decodeServerRelayMessage,
39
+ encodeRelayMessage,
40
+ type ServerMessage,
41
+ } from "@localpreview/protocol";
42
+
43
+ const message: ServerMessage = {
44
+ type: "request-start",
45
+ requestId: "req_1",
46
+ method: "GET",
47
+ path: "/hello",
48
+ headers: [["host", "example.test"]],
49
+ };
50
+
51
+ const encoded = encodeRelayMessage(message);
52
+ const decoded = decodeServerRelayMessage(encoded);
53
+ ```
54
+
55
+ Decoders return `{ ok: true, message }` or `{ ok: false, error }`. They do not
56
+ throw on invalid JSON or invalid message shapes.
57
+
58
+ Request and response body chunks are sent as base64 strings in `chunkBase64`.
59
+ Headers are represented as ordered `[name, value]` pairs rather than an object
60
+ map so duplicate headers such as `set-cookie` are preserved.
61
+
62
+ ## Targets
63
+
64
+ `parseTarget` normalizes CLI target input into a `TunnelTarget`.
65
+
66
+ ```ts
67
+ import { parseTarget } from "@localpreview/protocol";
68
+
69
+ parseTarget("4000");
70
+ // { ok: true, target: { protocol: "http", hostname: "127.0.0.1", port: 4000 } }
71
+
72
+ parseTarget("https://localhost:4000");
73
+ // { ok: true, target: { protocol: "https", hostname: "localhost", port: 4000 } }
74
+ ```
75
+
76
+ Rules:
77
+
78
+ - A bare port means `http://127.0.0.1:<port>`.
79
+ - URL targets default to `http` when no protocol is provided.
80
+ - URL targets must use `http` or `https`.
81
+ - URL targets must include an explicit port.
82
+ - Ports must be integers from `1` to `65535`.
83
+
84
+ ## Requested Subdomains
85
+
86
+ `validateRequestedSubdomain` trims and lowercases user input, rejects reserved
87
+ names, and enforces the public subdomain format.
88
+
89
+ ```ts
90
+ import { validateRequestedSubdomain } from "@localpreview/protocol";
91
+
92
+ validateRequestedSubdomain("Proyecto-API");
93
+ // { valid: true, subdomain: "proyecto-api" }
94
+ ```
95
+
96
+ Valid requested subdomains:
97
+
98
+ - Start and end with a lowercase letter or number after normalization.
99
+ - May contain lowercase letters, numbers, and hyphens.
100
+ - Must not use reserved names such as `api`, `app`, `auth`, `dashboard`, or
101
+ `www`.
102
+
103
+ ## Headers
104
+
105
+ Proxy boundaries should remove hop-by-hop headers and internal LocalPreview
106
+ headers before forwarding requests across trust boundaries.
107
+
108
+ ```ts
109
+ import {
110
+ filterEndToEndHeaderPairs,
111
+ filterInternalLocalPreviewHeaderPairs,
112
+ } from "@localpreview/protocol";
113
+
114
+ const publicHeaders = filterInternalLocalPreviewHeaderPairs(
115
+ filterEndToEndHeaderPairs(headers),
116
+ );
117
+ ```
118
+
119
+ Use `flattenHeaderPairs` when an HTTP adapter expects alternating
120
+ `name, value, name, value` entries.
121
+
122
+ ## Errors
123
+
124
+ `LOCALPREVIEW_ERROR_CODES` is the shared code registry for CLI, control-plane,
125
+ and relay failures. API responses should use the `LocalPreviewError` shape so
126
+ clients can branch on `error.code` instead of parsing messages.
127
+
128
+ ```ts
129
+ import { LOCALPREVIEW_ERROR_CODES, makeLocalPreviewError } from "@localpreview/protocol";
130
+
131
+ const error = makeLocalPreviewError({
132
+ code: LOCALPREVIEW_ERROR_CODES.TUNNEL_NOT_FOUND,
133
+ message: "Tunnel not found.",
134
+ tunnelId: "tun_123",
135
+ });
136
+ ```
137
+
138
+ ## Versioning
139
+
140
+ `LOCALPREVIEW_PROTOCOL_VERSION` identifies the wire and HTTP contract expected
141
+ by all LocalPreview packages.
142
+
143
+ `LOCALPREVIEW_RELAY_SNAPSHOT_VERSION` identifies the relay snapshot build
144
+ contract used by production sandboxes.
145
+
146
+ Change `LOCALPREVIEW_PROTOCOL_VERSION` when a deployed CLI, control-plane, or
147
+ relay would no longer understand the same request/response/message shapes.
148
+ Change `LOCALPREVIEW_RELAY_SNAPSHOT_VERSION` when the relay snapshot must be
149
+ rebuilt or promoted even if the protocol version stays compatible.
150
+
151
+ See `../../docs/relay-snapshot.md` for the production snapshot runbook.
152
+
153
+ ## Development
154
+
155
+ From the repo root:
156
+
157
+ ```sh
158
+ pnpm --filter @localpreview/protocol test
159
+ pnpm --filter @localpreview/protocol typecheck
160
+ pnpm --filter @localpreview/protocol build
161
+ ```
@@ -0,0 +1,37 @@
1
+ import type { TunnelTarget } from "./target.js";
2
+ /** Local backend endpoint registered via `--capture host:port`. */
3
+ export type CaptureTarget = {
4
+ readonly hostname: string;
5
+ readonly port: number;
6
+ };
7
+ type ParseCaptureResult = {
8
+ readonly capture: CaptureTarget;
9
+ readonly ok: true;
10
+ } | {
11
+ readonly message: string;
12
+ readonly ok: false;
13
+ };
14
+ /**
15
+ * Returns true when `hostname` is an allowed loopback host for capture.
16
+ *
17
+ * Allowed values: `localhost`, `127.0.0.1`, `[::1]` / `::1`, and `*.localhost`.
18
+ */
19
+ export declare const isLoopbackHost: (hostname: string) => boolean;
20
+ /**
21
+ * Parses `--capture host:port` input into a normalized capture target.
22
+ *
23
+ * The host must be loopback-only. Port must be between 1 and 65535.
24
+ */
25
+ export declare const parseCaptureHostPort: (input: string) => ParseCaptureResult;
26
+ /** Canonicalizes loopback hostnames for stable allowlist matching. */
27
+ export declare const normalizeLoopbackHostname: (hostname: string) => string;
28
+ /** Returns true when the capture matches the given target host and port. */
29
+ export declare const captureMatchesTarget: (capture: CaptureTarget, hostname: string, port: number) => boolean;
30
+ /** Finds an allowlisted capture for the given host and port. */
31
+ export declare const findCapture: (captures: ReadonlyArray<CaptureTarget>, hostname: string, port: number) => CaptureTarget | undefined;
32
+ /** Formats a capture target as `host:port` for CLI output. */
33
+ export declare const formatCaptureOrigin: (capture: CaptureTarget) => string;
34
+ /** Converts a capture target into a tunnel target using HTTP by default. */
35
+ export declare const captureToTunnelTarget: (capture: CaptureTarget, protocol: TunnelTarget["protocol"]) => TunnelTarget;
36
+ export {};
37
+ //# sourceMappingURL=capture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,mEAAmE;AACnE,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,KAAK,kBAAkB,GACnB;IACE,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;CACnB,GACD;IACE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;CACpB,CAAC;AAEN;;;;GAIG;AACH,eAAO,MAAM,cAAc,GAAI,UAAU,MAAM,KAAG,OAYjD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAAI,OAAO,MAAM,KAAG,kBAiCpD,CAAC;AAEF,sEAAsE;AACtE,eAAO,MAAM,yBAAyB,GAAI,UAAU,MAAM,KAAG,MAQ5D,CAAC;AAEF,4EAA4E;AAC5E,eAAO,MAAM,oBAAoB,GAC/B,SAAS,aAAa,EACtB,UAAU,MAAM,EAChB,MAAM,MAAM,KACX,OAEoB,CAAC;AAExB,gEAAgE;AAChE,eAAO,MAAM,WAAW,GACtB,UAAU,aAAa,CAAC,aAAa,CAAC,EACtC,UAAU,MAAM,EAChB,MAAM,MAAM,KACX,aAAa,GAAG,SACwD,CAAC;AAE5E,8DAA8D;AAC9D,eAAO,MAAM,mBAAmB,GAAI,SAAS,aAAa,KAAG,MACtB,CAAC;AAExC,4EAA4E;AAC5E,eAAO,MAAM,qBAAqB,GAChC,SAAS,aAAa,EACtB,UAAU,YAAY,CAAC,UAAU,CAAC,KACjC,YAID,CAAC"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Returns true when `hostname` is an allowed loopback host for capture.
3
+ *
4
+ * Allowed values: `localhost`, `127.0.0.1`, `[::1]` / `::1`, and `*.localhost`.
5
+ */
6
+ export const isLoopbackHost = (hostname) => {
7
+ const lower = hostname.toLowerCase();
8
+ if (lower === "localhost" || lower === "127.0.0.1") {
9
+ return true;
10
+ }
11
+ if (lower === "[::1]" || lower === "::1") {
12
+ return true;
13
+ }
14
+ return lower.endsWith(".localhost");
15
+ };
16
+ /**
17
+ * Parses `--capture host:port` input into a normalized capture target.
18
+ *
19
+ * The host must be loopback-only. Port must be between 1 and 65535.
20
+ */
21
+ export const parseCaptureHostPort = (input) => {
22
+ const trimmed = input.trim();
23
+ if (trimmed.length === 0) {
24
+ return { ok: false, message: "Capture must be host:port, like localhost:4000." };
25
+ }
26
+ const parsed = splitHostPort(trimmed);
27
+ if (!parsed.ok) {
28
+ return parsed;
29
+ }
30
+ const { hostname, port } = parsed;
31
+ if (!isLoopbackHost(hostname)) {
32
+ return {
33
+ ok: false,
34
+ message: `Capture host "${hostname}" must be loopback (localhost, 127.0.0.1, [::1], or *.localhost).`,
35
+ };
36
+ }
37
+ if (!Number.isInteger(port) || port < 1 || port > 65_535) {
38
+ return { ok: false, message: "Capture port must be between 1 and 65535." };
39
+ }
40
+ return {
41
+ ok: true,
42
+ capture: {
43
+ hostname: normalizeLoopbackHostname(hostname),
44
+ port,
45
+ },
46
+ };
47
+ };
48
+ /** Canonicalizes loopback hostnames for stable allowlist matching. */
49
+ export const normalizeLoopbackHostname = (hostname) => {
50
+ const lower = hostname.toLowerCase();
51
+ if (lower === "::1") {
52
+ return "[::1]";
53
+ }
54
+ return lower;
55
+ };
56
+ /** Returns true when the capture matches the given target host and port. */
57
+ export const captureMatchesTarget = (capture, hostname, port) => normalizeLoopbackHostname(hostname) === normalizeLoopbackHostname(capture.hostname) &&
58
+ capture.port === port;
59
+ /** Finds an allowlisted capture for the given host and port. */
60
+ export const findCapture = (captures, hostname, port) => captures.find((capture) => captureMatchesTarget(capture, hostname, port));
61
+ /** Formats a capture target as `host:port` for CLI output. */
62
+ export const formatCaptureOrigin = (capture) => `${capture.hostname}:${capture.port}`;
63
+ /** Converts a capture target into a tunnel target using HTTP by default. */
64
+ export const captureToTunnelTarget = (capture, protocol) => ({
65
+ hostname: capture.hostname,
66
+ port: capture.port,
67
+ protocol,
68
+ });
69
+ const splitHostPort = (input) => {
70
+ if (input.startsWith("[")) {
71
+ const closingBracket = input.indexOf("]");
72
+ if (closingBracket === -1 || input[closingBracket + 1] !== ":") {
73
+ return { ok: false, message: "Capture must be host:port, like [::1]:4000." };
74
+ }
75
+ const hostname = input.slice(0, closingBracket + 1);
76
+ const portText = input.slice(closingBracket + 2);
77
+ if (portText.length === 0) {
78
+ return { ok: false, message: "Capture must include a port." };
79
+ }
80
+ const port = Number(portText);
81
+ if (!/^\d+$/.test(portText)) {
82
+ return { ok: false, message: "Capture port must be a number." };
83
+ }
84
+ return { ok: true, hostname, port };
85
+ }
86
+ const separator = input.lastIndexOf(":");
87
+ if (separator <= 0 || separator === input.length - 1) {
88
+ return { ok: false, message: "Capture must be host:port, like localhost:4000." };
89
+ }
90
+ const hostname = input.slice(0, separator);
91
+ const portText = input.slice(separator + 1);
92
+ const port = Number(portText);
93
+ if (!/^\d+$/.test(portText)) {
94
+ return { ok: false, message: "Capture port must be a number." };
95
+ }
96
+ return { ok: true, hostname, port };
97
+ };
package/dist/errors.d.ts CHANGED
@@ -1,13 +1,43 @@
1
1
  import { Schema } from "effect";
2
- export declare const ErrorCodeSchema: Schema.Literals<readonly ["BODY_TOO_LARGE", "INVALID_SUBDOMAIN", "REQUEST_TIMEOUT", "REQUEST_FAILED", "RELAY_REGISTRATION_FAILED", "SANDBOX_CREATE_FAILED", "SUBDOMAIN_TAKEN", "TUNNEL_NOT_CONNECTED", "TUNNEL_NOT_FOUND", "UNAUTHORIZED"]>;
3
- export type ErrorCode = Schema.Schema.Type<typeof ErrorCodeSchema>;
4
- export declare const LocalPreviewErrorSchema: Schema.Struct<{
5
- readonly code: Schema.Literals<readonly ["BODY_TOO_LARGE", "INVALID_SUBDOMAIN", "REQUEST_TIMEOUT", "REQUEST_FAILED", "RELAY_REGISTRATION_FAILED", "SANDBOX_CREATE_FAILED", "SUBDOMAIN_TAKEN", "TUNNEL_NOT_CONNECTED", "TUNNEL_NOT_FOUND", "UNAUTHORIZED"]>;
2
+ /** Shared machine-readable error codes used across LocalPreview packages. */
3
+ export declare const LOCALPREVIEW_ERROR_CODES: {
4
+ readonly BODY_TOO_LARGE: "BODY_TOO_LARGE";
5
+ readonly BULK_CLEANUP_REQUIRES_FORCE: "BULK_CLEANUP_REQUIRES_FORCE";
6
+ readonly CLEANUP_DISABLED: "CLEANUP_DISABLED";
7
+ readonly CONFIG_ERROR: "CONFIG_ERROR";
8
+ readonly INTERNAL_ERROR: "INTERNAL_ERROR";
9
+ readonly INVALID_JSON: "INVALID_JSON";
10
+ readonly INVALID_SUBDOMAIN: "INVALID_SUBDOMAIN";
11
+ readonly RELAY_HEALTH_CHECK_FAILED: "RELAY_HEALTH_CHECK_FAILED";
12
+ readonly RELAY_REGISTRATION_FAILED: "RELAY_REGISTRATION_FAILED";
13
+ readonly REQUEST_FAILED: "REQUEST_FAILED";
14
+ readonly REQUEST_TIMEOUT: "REQUEST_TIMEOUT";
15
+ readonly SANDBOX_CREATE_FAILED: "SANDBOX_CREATE_FAILED";
16
+ readonly SANDBOX_STOP_FAILED: "SANDBOX_STOP_FAILED";
17
+ readonly STORE_FAILED: "STORE_FAILED";
18
+ readonly SUBDOMAIN_ACTIVE: "SUBDOMAIN_ACTIVE";
19
+ readonly SUBDOMAIN_TAKEN: "SUBDOMAIN_TAKEN";
20
+ readonly TUNNEL_HOST_NOT_FOUND: "TUNNEL_HOST_NOT_FOUND";
21
+ readonly TUNNEL_NOT_CONNECTED: "TUNNEL_NOT_CONNECTED";
22
+ readonly TUNNEL_NOT_FOUND: "TUNNEL_NOT_FOUND";
23
+ readonly UNAUTHORIZED: "UNAUTHORIZED";
24
+ };
25
+ declare const LocalPreviewErrorSchema: Schema.Struct<{
26
+ readonly code: Schema.Literals<readonly ["BODY_TOO_LARGE", "BULK_CLEANUP_REQUIRES_FORCE", "CLEANUP_DISABLED", "CONFIG_ERROR", "INTERNAL_ERROR", "INVALID_JSON", "INVALID_SUBDOMAIN", "RELAY_HEALTH_CHECK_FAILED", "RELAY_REGISTRATION_FAILED", "REQUEST_FAILED", "REQUEST_TIMEOUT", "SANDBOX_CREATE_FAILED", "SANDBOX_STOP_FAILED", "STORE_FAILED", "SUBDOMAIN_ACTIVE", "SUBDOMAIN_TAKEN", "TUNNEL_HOST_NOT_FOUND", "TUNNEL_NOT_CONNECTED", "TUNNEL_NOT_FOUND", "UNAUTHORIZED"]>;
6
27
  readonly message: Schema.String;
7
28
  readonly requestId: Schema.optional<Schema.String>;
8
29
  readonly subdomain: Schema.optional<Schema.String>;
30
+ readonly tunnelId: Schema.optional<Schema.String>;
9
31
  readonly limitBytes: Schema.optional<Schema.Number>;
10
32
  }>;
33
+ /**
34
+ * Shared API error shape.
35
+ *
36
+ * Callers should branch on `code` and treat `message` as human-readable context.
37
+ * Optional fields add request-specific identifiers or limits when available.
38
+ */
11
39
  export type LocalPreviewError = Schema.Schema.Type<typeof LocalPreviewErrorSchema>;
40
+ /** Preserves literal error-code inference when constructing LocalPreview errors. */
12
41
  export declare const makeLocalPreviewError: (error: LocalPreviewError) => LocalPreviewError;
42
+ export {};
13
43
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,eAAO,MAAM,eAAe,6OAWjB,CAAC;AAEZ,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,eAAe,CAAC,CAAC;AAEnE,eAAO,MAAM,uBAAuB;;;;;;EAMlC,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAEnF,eAAO,MAAM,qBAAqB,GAAI,OAAO,iBAAiB,KAAG,iBAA0B,CAAC"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,6EAA6E;AAC7E,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;CAqB3B,CAAC;AAyBX,QAAA,MAAM,uBAAuB;;;;;;;EAO3B,CAAC;AAEH;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAEnF,oFAAoF;AACpF,eAAO,MAAM,qBAAqB,GAAI,OAAO,iBAAiB,KAAG,iBAA0B,CAAC"}
package/dist/errors.js CHANGED
@@ -1,21 +1,56 @@
1
1
  import { Schema } from "effect";
2
- export const ErrorCodeSchema = Schema.Literals([
3
- "BODY_TOO_LARGE",
4
- "INVALID_SUBDOMAIN",
5
- "REQUEST_TIMEOUT",
6
- "REQUEST_FAILED",
7
- "RELAY_REGISTRATION_FAILED",
8
- "SANDBOX_CREATE_FAILED",
9
- "SUBDOMAIN_TAKEN",
10
- "TUNNEL_NOT_CONNECTED",
11
- "TUNNEL_NOT_FOUND",
12
- "UNAUTHORIZED",
2
+ /** Shared machine-readable error codes used across LocalPreview packages. */
3
+ export const LOCALPREVIEW_ERROR_CODES = {
4
+ BODY_TOO_LARGE: "BODY_TOO_LARGE",
5
+ BULK_CLEANUP_REQUIRES_FORCE: "BULK_CLEANUP_REQUIRES_FORCE",
6
+ CLEANUP_DISABLED: "CLEANUP_DISABLED",
7
+ CONFIG_ERROR: "CONFIG_ERROR",
8
+ INTERNAL_ERROR: "INTERNAL_ERROR",
9
+ INVALID_JSON: "INVALID_JSON",
10
+ INVALID_SUBDOMAIN: "INVALID_SUBDOMAIN",
11
+ RELAY_HEALTH_CHECK_FAILED: "RELAY_HEALTH_CHECK_FAILED",
12
+ RELAY_REGISTRATION_FAILED: "RELAY_REGISTRATION_FAILED",
13
+ REQUEST_FAILED: "REQUEST_FAILED",
14
+ REQUEST_TIMEOUT: "REQUEST_TIMEOUT",
15
+ SANDBOX_CREATE_FAILED: "SANDBOX_CREATE_FAILED",
16
+ SANDBOX_STOP_FAILED: "SANDBOX_STOP_FAILED",
17
+ STORE_FAILED: "STORE_FAILED",
18
+ SUBDOMAIN_ACTIVE: "SUBDOMAIN_ACTIVE",
19
+ SUBDOMAIN_TAKEN: "SUBDOMAIN_TAKEN",
20
+ TUNNEL_HOST_NOT_FOUND: "TUNNEL_HOST_NOT_FOUND",
21
+ TUNNEL_NOT_CONNECTED: "TUNNEL_NOT_CONNECTED",
22
+ TUNNEL_NOT_FOUND: "TUNNEL_NOT_FOUND",
23
+ UNAUTHORIZED: "UNAUTHORIZED",
24
+ };
25
+ const ErrorCodeSchema = Schema.Literals([
26
+ LOCALPREVIEW_ERROR_CODES.BODY_TOO_LARGE,
27
+ LOCALPREVIEW_ERROR_CODES.BULK_CLEANUP_REQUIRES_FORCE,
28
+ LOCALPREVIEW_ERROR_CODES.CLEANUP_DISABLED,
29
+ LOCALPREVIEW_ERROR_CODES.CONFIG_ERROR,
30
+ LOCALPREVIEW_ERROR_CODES.INTERNAL_ERROR,
31
+ LOCALPREVIEW_ERROR_CODES.INVALID_JSON,
32
+ LOCALPREVIEW_ERROR_CODES.INVALID_SUBDOMAIN,
33
+ LOCALPREVIEW_ERROR_CODES.RELAY_HEALTH_CHECK_FAILED,
34
+ LOCALPREVIEW_ERROR_CODES.RELAY_REGISTRATION_FAILED,
35
+ LOCALPREVIEW_ERROR_CODES.REQUEST_FAILED,
36
+ LOCALPREVIEW_ERROR_CODES.REQUEST_TIMEOUT,
37
+ LOCALPREVIEW_ERROR_CODES.SANDBOX_CREATE_FAILED,
38
+ LOCALPREVIEW_ERROR_CODES.SANDBOX_STOP_FAILED,
39
+ LOCALPREVIEW_ERROR_CODES.STORE_FAILED,
40
+ LOCALPREVIEW_ERROR_CODES.SUBDOMAIN_ACTIVE,
41
+ LOCALPREVIEW_ERROR_CODES.SUBDOMAIN_TAKEN,
42
+ LOCALPREVIEW_ERROR_CODES.TUNNEL_HOST_NOT_FOUND,
43
+ LOCALPREVIEW_ERROR_CODES.TUNNEL_NOT_CONNECTED,
44
+ LOCALPREVIEW_ERROR_CODES.TUNNEL_NOT_FOUND,
45
+ LOCALPREVIEW_ERROR_CODES.UNAUTHORIZED,
13
46
  ]);
14
- export const LocalPreviewErrorSchema = Schema.Struct({
47
+ const LocalPreviewErrorSchema = Schema.Struct({
15
48
  code: ErrorCodeSchema,
16
49
  message: Schema.String,
17
50
  requestId: Schema.optional(Schema.String),
18
51
  subdomain: Schema.optional(Schema.String),
52
+ tunnelId: Schema.optional(Schema.String),
19
53
  limitBytes: Schema.optional(Schema.Number),
20
54
  });
55
+ /** Preserves literal error-code inference when constructing LocalPreview errors. */
21
56
  export const makeLocalPreviewError = (error) => error;
package/dist/headers.d.ts CHANGED
@@ -1,19 +1,28 @@
1
+ /** Header used to authenticate administrative control-plane operations. */
1
2
  export declare const LOCALPREVIEW_ADMIN_TOKEN_HEADER = "x-localpreview-admin-token";
3
+ /** Header used by trusted proxies to preserve the original public host. */
2
4
  export declare const LOCALPREVIEW_FORWARDED_HOST_HEADER = "x-localpreview-forwarded-host";
5
+ /** Header used by trusted proxies to preserve the original public protocol. */
3
6
  export declare const LOCALPREVIEW_FORWARDED_PROTO_HEADER = "x-localpreview-forwarded-proto";
7
+ /** Header used to authenticate relay-to-control-plane proxy requests. */
4
8
  export declare const LOCALPREVIEW_PROXY_TOKEN_HEADER = "x-localpreview-proxy-token";
5
- export type HeaderMap = Readonly<Record<string, string>>;
9
+ /**
10
+ * Ordered HTTP header pair.
11
+ *
12
+ * Headers are represented as pairs instead of object maps so duplicate headers,
13
+ * especially `set-cookie`, survive relay and proxy boundaries.
14
+ */
6
15
  export type HeaderPair = readonly [name: string, value: string];
16
+ /** Ordered HTTP header list that may contain duplicate header names. */
7
17
  export type HeaderPairs = ReadonlyArray<HeaderPair>;
18
+ /** Returns true when a header must not be forwarded across proxy hops. */
8
19
  export declare const isHopByHopHeader: (name: string) => boolean;
20
+ /** Returns true when a header is internal LocalPreview control metadata. */
9
21
  export declare const isInternalLocalPreviewHeader: (name: string) => boolean;
10
- export declare const filterEndToEndHeaders: (headers: HeaderMap) => HeaderMap;
11
- export declare const filterInternalLocalPreviewHeaders: (headers: HeaderMap) => HeaderMap;
22
+ /** Removes hop-by-hop headers while preserving original header order. */
12
23
  export declare const filterEndToEndHeaderPairs: (headers: HeaderPairs) => HeaderPairs;
24
+ /** Removes LocalPreview internal headers before forwarding to untrusted targets. */
13
25
  export declare const filterInternalLocalPreviewHeaderPairs: (headers: HeaderPairs) => HeaderPairs;
14
- export declare const headerPairsToMap: (headers: HeaderPairs) => HeaderMap;
15
- export declare const headerMapToPairs: (headers: HeaderMap) => HeaderPairs;
26
+ /** Converts header pairs to alternating name/value entries for HTTP adapters. */
16
27
  export declare const flattenHeaderPairs: (headers: HeaderPairs) => Array<string>;
17
- export declare const headersToMap: (headers: Headers) => HeaderMap;
18
- export declare const recordToHeaders: (headers: HeaderMap) => Headers;
19
28
  //# sourceMappingURL=headers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,+BAA+B,+BAA+B,CAAC;AAC5E,eAAO,MAAM,kCAAkC,kCAAkC,CAAC;AAClF,eAAO,MAAM,mCAAmC,mCAAmC,CAAC;AACpF,eAAO,MAAM,+BAA+B,+BAA+B,CAAC;AAS5E,MAAM,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AACzD,MAAM,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;AAChE,MAAM,MAAM,WAAW,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;AAEpD,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,KAAG,OAAkD,CAAC;AAEnG,eAAO,MAAM,4BAA4B,GAAI,MAAM,MAAM,KAAG,OACP,CAAC;AAEtD,eAAO,MAAM,qBAAqB,GAAI,SAAS,SAAS,KAAG,SAC8B,CAAC;AAE1F,eAAO,MAAM,iCAAiC,GAAI,SAAS,SAAS,KAAG,SAGpE,CAAC;AAEJ,eAAO,MAAM,yBAAyB,GAAI,SAAS,WAAW,KAAG,WACZ,CAAC;AAEtD,eAAO,MAAM,qCAAqC,GAAI,SAAS,WAAW,KAAG,WACZ,CAAC;AAElE,eAAO,MAAM,gBAAgB,GAAI,SAAS,WAAW,KAAG,SAAwC,CAAC;AAEjG,eAAO,MAAM,gBAAgB,GAAI,SAAS,SAAS,KAAG,WAAsC,CAAC;AAE7F,eAAO,MAAM,kBAAkB,GAAI,SAAS,WAAW,KAAG,KAAK,CAAC,MAAM,CACnB,CAAC;AAEpD,eAAO,MAAM,YAAY,GAAI,SAAS,OAAO,KAAG,SAAkD,CAAC;AAEnG,eAAO,MAAM,eAAe,GAAI,SAAS,SAAS,KAAG,OAQpD,CAAC"}
1
+ {"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"AAWA,2EAA2E;AAC3E,eAAO,MAAM,+BAA+B,+BAA+B,CAAC;AAE5E,2EAA2E;AAC3E,eAAO,MAAM,kCAAkC,kCAAkC,CAAC;AAElF,+EAA+E;AAC/E,eAAO,MAAM,mCAAmC,mCAAmC,CAAC;AAEpF,yEAAyE;AACzE,eAAO,MAAM,+BAA+B,+BAA+B,CAAC;AAS5E;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;AAEhE,wEAAwE;AACxE,MAAM,MAAM,WAAW,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;AAEpD,0EAA0E;AAC1E,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,KAAG,OAAkD,CAAC;AAEnG,4EAA4E;AAC5E,eAAO,MAAM,4BAA4B,GAAI,MAAM,MAAM,KAAG,OACP,CAAC;AAEtD,yEAAyE;AACzE,eAAO,MAAM,yBAAyB,GAAI,SAAS,WAAW,KAAG,WACZ,CAAC;AAEtD,oFAAoF;AACpF,eAAO,MAAM,qCAAqC,GAAI,SAAS,WAAW,KAAG,WACZ,CAAC;AAElE,iFAAiF;AACjF,eAAO,MAAM,kBAAkB,GAAI,SAAS,WAAW,KAAG,KAAK,CAAC,MAAM,CACnB,CAAC"}
package/dist/headers.js CHANGED
@@ -8,9 +8,13 @@ const hopByHopHeaders = new Set([
8
8
  "transfer-encoding",
9
9
  "upgrade",
10
10
  ]);
11
+ /** Header used to authenticate administrative control-plane operations. */
11
12
  export const LOCALPREVIEW_ADMIN_TOKEN_HEADER = "x-localpreview-admin-token";
13
+ /** Header used by trusted proxies to preserve the original public host. */
12
14
  export const LOCALPREVIEW_FORWARDED_HOST_HEADER = "x-localpreview-forwarded-host";
15
+ /** Header used by trusted proxies to preserve the original public protocol. */
13
16
  export const LOCALPREVIEW_FORWARDED_PROTO_HEADER = "x-localpreview-forwarded-proto";
17
+ /** Header used to authenticate relay-to-control-plane proxy requests. */
14
18
  export const LOCALPREVIEW_PROXY_TOKEN_HEADER = "x-localpreview-proxy-token";
15
19
  const internalLocalPreviewHeaders = new Set([
16
20
  LOCALPREVIEW_ADMIN_TOKEN_HEADER,
@@ -18,20 +22,13 @@ const internalLocalPreviewHeaders = new Set([
18
22
  LOCALPREVIEW_FORWARDED_PROTO_HEADER,
19
23
  LOCALPREVIEW_PROXY_TOKEN_HEADER,
20
24
  ]);
25
+ /** Returns true when a header must not be forwarded across proxy hops. */
21
26
  export const isHopByHopHeader = (name) => hopByHopHeaders.has(name.toLowerCase());
27
+ /** Returns true when a header is internal LocalPreview control metadata. */
22
28
  export const isInternalLocalPreviewHeader = (name) => internalLocalPreviewHeaders.has(name.toLowerCase());
23
- export const filterEndToEndHeaders = (headers) => Object.fromEntries(Object.entries(headers).filter(([name]) => !isHopByHopHeader(name)));
24
- export const filterInternalLocalPreviewHeaders = (headers) => Object.fromEntries(Object.entries(headers).filter(([name]) => !isInternalLocalPreviewHeader(name)));
29
+ /** Removes hop-by-hop headers while preserving original header order. */
25
30
  export const filterEndToEndHeaderPairs = (headers) => headers.filter(([name]) => !isHopByHopHeader(name));
31
+ /** Removes LocalPreview internal headers before forwarding to untrusted targets. */
26
32
  export const filterInternalLocalPreviewHeaderPairs = (headers) => headers.filter(([name]) => !isInternalLocalPreviewHeader(name));
27
- export const headerPairsToMap = (headers) => Object.fromEntries(headers);
28
- export const headerMapToPairs = (headers) => Object.entries(headers);
33
+ /** Converts header pairs to alternating name/value entries for HTTP adapters. */
29
34
  export const flattenHeaderPairs = (headers) => headers.flatMap(([name, value]) => [name, value]);
30
- export const headersToMap = (headers) => Object.fromEntries(headers.entries());
31
- export const recordToHeaders = (headers) => {
32
- const result = new Headers();
33
- for (const [name, value] of Object.entries(headers)) {
34
- result.set(name, value);
35
- }
36
- return result;
37
- };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from "./capture.js";
1
2
  export * from "./errors.js";
2
3
  export * from "./headers.js";
3
4
  export * from "./messages.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export * from "./capture.js";
1
2
  export * from "./errors.js";
2
3
  export * from "./headers.js";
3
4
  export * from "./messages.js";
@@ -1,114 +1,14 @@
1
1
  import { Schema } from "effect";
2
2
  import type { HeaderPairs } from "./headers.js";
3
- export declare const RequestIdSchema: Schema.String;
4
- export type RequestId = Schema.Schema.Type<typeof RequestIdSchema>;
5
- export declare const RequestStartMessageSchema: Schema.Struct<{
6
- readonly headers: Schema.$Array<Schema.Tuple<readonly [Schema.String, Schema.String]>>;
7
- readonly method: Schema.String;
8
- readonly path: Schema.String;
9
- readonly requestId: Schema.String;
10
- readonly type: Schema.Literal<"request-start">;
11
- }>;
12
- export declare const RequestChunkMessageSchema: Schema.Struct<{
13
- readonly chunkBase64: Schema.String;
14
- readonly requestId: Schema.String;
15
- readonly type: Schema.Literal<"request-chunk">;
16
- }>;
17
- export declare const RequestEndMessageSchema: Schema.Struct<{
18
- readonly requestId: Schema.String;
19
- readonly type: Schema.Literal<"request-end">;
20
- }>;
21
- export declare const CancelMessageSchema: Schema.Struct<{
22
- readonly requestId: Schema.String;
23
- readonly type: Schema.Literal<"cancel">;
24
- }>;
25
- export declare const ResponseStartMessageSchema: Schema.Struct<{
26
- readonly headers: Schema.$Array<Schema.Tuple<readonly [Schema.String, Schema.String]>>;
27
- readonly requestId: Schema.String;
28
- readonly status: Schema.Number;
29
- readonly type: Schema.Literal<"response-start">;
30
- }>;
31
- export declare const ResponseChunkMessageSchema: Schema.Struct<{
32
- readonly chunkBase64: Schema.String;
33
- readonly requestId: Schema.String;
34
- readonly type: Schema.Literal<"response-chunk">;
35
- }>;
36
- export declare const ResponseEndMessageSchema: Schema.Struct<{
37
- readonly requestId: Schema.String;
38
- readonly type: Schema.Literal<"response-end">;
39
- }>;
40
- export declare const ResponseErrorMessageSchema: Schema.Struct<{
41
- readonly message: Schema.String;
42
- readonly requestId: Schema.String;
43
- readonly type: Schema.Literal<"response-error">;
44
- }>;
45
- export declare const ServerMessageSchema: Schema.Union<readonly [Schema.Struct<{
46
- readonly headers: Schema.$Array<Schema.Tuple<readonly [Schema.String, Schema.String]>>;
47
- readonly method: Schema.String;
48
- readonly path: Schema.String;
49
- readonly requestId: Schema.String;
50
- readonly type: Schema.Literal<"request-start">;
51
- }>, Schema.Struct<{
52
- readonly chunkBase64: Schema.String;
53
- readonly requestId: Schema.String;
54
- readonly type: Schema.Literal<"request-chunk">;
55
- }>, Schema.Struct<{
56
- readonly requestId: Schema.String;
57
- readonly type: Schema.Literal<"request-end">;
58
- }>, Schema.Struct<{
59
- readonly requestId: Schema.String;
60
- readonly type: Schema.Literal<"cancel">;
61
- }>]>;
62
- export declare const ClientMessageSchema: Schema.Union<readonly [Schema.Struct<{
63
- readonly headers: Schema.$Array<Schema.Tuple<readonly [Schema.String, Schema.String]>>;
64
- readonly requestId: Schema.String;
65
- readonly status: Schema.Number;
66
- readonly type: Schema.Literal<"response-start">;
67
- }>, Schema.Struct<{
68
- readonly chunkBase64: Schema.String;
69
- readonly requestId: Schema.String;
70
- readonly type: Schema.Literal<"response-chunk">;
71
- }>, Schema.Struct<{
72
- readonly requestId: Schema.String;
73
- readonly type: Schema.Literal<"response-end">;
74
- }>, Schema.Struct<{
75
- readonly message: Schema.String;
76
- readonly requestId: Schema.String;
77
- readonly type: Schema.Literal<"response-error">;
78
- }>]>;
79
- export declare const RelayMessageSchema: Schema.Union<readonly [Schema.Union<readonly [Schema.Struct<{
80
- readonly headers: Schema.$Array<Schema.Tuple<readonly [Schema.String, Schema.String]>>;
81
- readonly method: Schema.String;
82
- readonly path: Schema.String;
83
- readonly requestId: Schema.String;
84
- readonly type: Schema.Literal<"request-start">;
85
- }>, Schema.Struct<{
86
- readonly chunkBase64: Schema.String;
87
- readonly requestId: Schema.String;
88
- readonly type: Schema.Literal<"request-chunk">;
89
- }>, Schema.Struct<{
90
- readonly requestId: Schema.String;
91
- readonly type: Schema.Literal<"request-end">;
92
- }>, Schema.Struct<{
93
- readonly requestId: Schema.String;
94
- readonly type: Schema.Literal<"cancel">;
95
- }>]>, Schema.Union<readonly [Schema.Struct<{
96
- readonly headers: Schema.$Array<Schema.Tuple<readonly [Schema.String, Schema.String]>>;
97
- readonly requestId: Schema.String;
98
- readonly status: Schema.Number;
99
- readonly type: Schema.Literal<"response-start">;
100
- }>, Schema.Struct<{
101
- readonly chunkBase64: Schema.String;
102
- readonly requestId: Schema.String;
103
- readonly type: Schema.Literal<"response-chunk">;
104
- }>, Schema.Struct<{
105
- readonly requestId: Schema.String;
106
- readonly type: Schema.Literal<"response-end">;
107
- }>, Schema.Struct<{
108
- readonly message: Schema.String;
109
- readonly requestId: Schema.String;
110
- readonly type: Schema.Literal<"response-error">;
111
- }>]>]>;
3
+ declare const RequestIdSchema: Schema.String;
4
+ type RequestId = Schema.Schema.Type<typeof RequestIdSchema>;
5
+ /**
6
+ * Relay messages sent by the relay server to the CLI client.
7
+ *
8
+ * These messages describe the inbound browser request that the CLI must forward
9
+ * to the local target. Body chunks are base64 strings so the wire format can
10
+ * stay JSON while preserving arbitrary bytes.
11
+ */
112
12
  export type ServerMessage = {
113
13
  readonly type: "request-start";
114
14
  readonly requestId: RequestId;
@@ -126,7 +26,13 @@ export type ServerMessage = {
126
26
  readonly type: "cancel";
127
27
  readonly requestId: RequestId;
128
28
  };
129
- export type ClientMessage = {
29
+ /**
30
+ * Relay messages sent by the CLI client back to the relay server.
31
+ *
32
+ * These messages describe the local target response, stream response bytes, or
33
+ * report a request-scoped forwarding failure.
34
+ */
35
+ type ClientMessage = {
130
36
  readonly type: "response-start";
131
37
  readonly requestId: RequestId;
132
38
  readonly status: number;
@@ -143,17 +49,30 @@ export type ClientMessage = {
143
49
  readonly requestId: RequestId;
144
50
  readonly message: string;
145
51
  };
146
- export type RelayMessage = ServerMessage | ClientMessage;
52
+ type RelayMessage = ServerMessage | ClientMessage;
53
+ /** Encodes a relay message as the JSON string sent over the WebSocket. */
147
54
  export declare const encodeRelayMessage: (message: RelayMessage) => string;
148
- export declare const decodeRelayMessage: (input: string) => RelayMessage;
149
- export type RelayMessageDecodeResult<A> = {
55
+ type RelayMessageDecodeResult<A> = {
150
56
  readonly ok: true;
151
57
  readonly message: A;
152
58
  } | {
153
59
  readonly error: unknown;
154
60
  readonly ok: false;
155
61
  };
62
+ /**
63
+ * Decodes a relay-to-CLI WebSocket message.
64
+ *
65
+ * Returns a result object instead of throwing for invalid JSON or invalid
66
+ * message shapes, which lets callers close or ignore malformed connections
67
+ * using their own error policy.
68
+ */
156
69
  export declare const decodeServerRelayMessage: (input: string) => RelayMessageDecodeResult<ServerMessage>;
70
+ /**
71
+ * Decodes a CLI-to-relay WebSocket message.
72
+ *
73
+ * Response headers must be ordered pairs, not collapsed maps, so duplicate
74
+ * response headers remain representable.
75
+ */
157
76
  export declare const decodeClientRelayMessage: (input: string) => RelayMessageDecodeResult<ClientMessage>;
158
- export declare const decodeTypedRelayMessage: (input: string) => RelayMessageDecodeResult<RelayMessage>;
77
+ export {};
159
78
  //# sourceMappingURL=messages.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,eAAO,MAAM,eAAe,eAAgB,CAAC;AAC7C,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,eAAe,CAAC,CAAC;AAInE,eAAO,MAAM,yBAAyB;;;;;;EAMpC,CAAC;AAEH,eAAO,MAAM,yBAAyB;;;;EAIpC,CAAC;AAEH,eAAO,MAAM,uBAAuB;;;EAGlC,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;EAG9B,CAAC;AAEH,eAAO,MAAM,0BAA0B;;;;;EAKrC,CAAC;AAEH,eAAO,MAAM,0BAA0B;;;;EAIrC,CAAC;AAEH,eAAO,MAAM,wBAAwB;;;EAGnC,CAAC;AAEH,eAAO,MAAM,0BAA0B;;;;EAIrC,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;IAKrB,CAAC;AAEZ,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;IAKrB,CAAC;AAEZ,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAAoE,CAAC;AAEpG,MAAM,MAAM,aAAa,GACrB;IACE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;CAC/B,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;CAC/B,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;CAC/B,CAAC;AAEN,MAAM,MAAM,aAAa,GACrB;IACE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;CAC/B,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;CAC/B,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEN,MAAM,MAAM,YAAY,GAAG,aAAa,GAAG,aAAa,CAAC;AAEzD,eAAO,MAAM,kBAAkB,GAAI,SAAS,YAAY,KAAG,MAAiC,CAAC;AAE7F,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,KAAG,YAChB,CAAC;AAEpC,MAAM,MAAM,wBAAwB,CAAC,CAAC,IAClC;IACE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;CACrB,GACD;IACE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;CACpB,CAAC;AAEN,eAAO,MAAM,wBAAwB,GAAI,OAAO,MAAM,KAAG,wBAAwB,CAAC,aAAa,CACjD,CAAC;AAE/C,eAAO,MAAM,wBAAwB,GAAI,OAAO,MAAM,KAAG,wBAAwB,CAAC,aAAa,CACjD,CAAC;AAE/C,eAAO,MAAM,uBAAuB,GAAI,OAAO,MAAM,KAAG,wBAAwB,CAAC,YAAY,CAChD,CAAC"}
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,QAAA,MAAM,eAAe,eAAgB,CAAC;AACtC,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,eAAe,CAAC,CAAC;AAkE5D;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GACrB;IACE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;CAC/B,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;CAC/B,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;CAC/B,CAAC;AAEN;;;;;GAKG;AACH,KAAK,aAAa,GACd;IACE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;CAC/B,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;CAC/B,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEN,KAAK,YAAY,GAAG,aAAa,GAAG,aAAa,CAAC;AAElD,0EAA0E;AAC1E,eAAO,MAAM,kBAAkB,GAAI,SAAS,YAAY,KAAG,MAAiC,CAAC;AAE7F,KAAK,wBAAwB,CAAC,CAAC,IAC3B;IACE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;CACrB,GACD;IACE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;CACpB,CAAC;AAEN;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,GAAI,OAAO,MAAM,KAAG,wBAAwB,CAAC,aAAa,CACjD,CAAC;AAE/C;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,GAAI,OAAO,MAAM,KAAG,wBAAwB,CAAC,aAAa,CACjD,CAAC"}
package/dist/messages.js CHANGED
@@ -1,64 +1,75 @@
1
1
  import { Result, Schema } from "effect";
2
- export const RequestIdSchema = Schema.String;
2
+ const RequestIdSchema = Schema.String;
3
3
  const HeaderPairsSchema = Schema.Array(Schema.Tuple([Schema.String, Schema.String]));
4
- export const RequestStartMessageSchema = Schema.Struct({
4
+ const RequestStartMessageSchema = Schema.Struct({
5
5
  headers: HeaderPairsSchema,
6
6
  method: Schema.String,
7
7
  path: Schema.String,
8
8
  requestId: RequestIdSchema,
9
9
  type: Schema.Literal("request-start"),
10
10
  });
11
- export const RequestChunkMessageSchema = Schema.Struct({
11
+ const RequestChunkMessageSchema = Schema.Struct({
12
12
  chunkBase64: Schema.String,
13
13
  requestId: RequestIdSchema,
14
14
  type: Schema.Literal("request-chunk"),
15
15
  });
16
- export const RequestEndMessageSchema = Schema.Struct({
16
+ const RequestEndMessageSchema = Schema.Struct({
17
17
  requestId: RequestIdSchema,
18
18
  type: Schema.Literal("request-end"),
19
19
  });
20
- export const CancelMessageSchema = Schema.Struct({
20
+ const CancelMessageSchema = Schema.Struct({
21
21
  requestId: RequestIdSchema,
22
22
  type: Schema.Literal("cancel"),
23
23
  });
24
- export const ResponseStartMessageSchema = Schema.Struct({
24
+ const ResponseStartMessageSchema = Schema.Struct({
25
25
  headers: HeaderPairsSchema,
26
26
  requestId: RequestIdSchema,
27
27
  status: Schema.Number,
28
28
  type: Schema.Literal("response-start"),
29
29
  });
30
- export const ResponseChunkMessageSchema = Schema.Struct({
30
+ const ResponseChunkMessageSchema = Schema.Struct({
31
31
  chunkBase64: Schema.String,
32
32
  requestId: RequestIdSchema,
33
33
  type: Schema.Literal("response-chunk"),
34
34
  });
35
- export const ResponseEndMessageSchema = Schema.Struct({
35
+ const ResponseEndMessageSchema = Schema.Struct({
36
36
  requestId: RequestIdSchema,
37
37
  type: Schema.Literal("response-end"),
38
38
  });
39
- export const ResponseErrorMessageSchema = Schema.Struct({
39
+ const ResponseErrorMessageSchema = Schema.Struct({
40
40
  message: Schema.String,
41
41
  requestId: RequestIdSchema,
42
42
  type: Schema.Literal("response-error"),
43
43
  });
44
- export const ServerMessageSchema = Schema.Union([
44
+ const ServerMessageSchema = Schema.Union([
45
45
  RequestStartMessageSchema,
46
46
  RequestChunkMessageSchema,
47
47
  RequestEndMessageSchema,
48
48
  CancelMessageSchema,
49
49
  ]);
50
- export const ClientMessageSchema = Schema.Union([
50
+ const ClientMessageSchema = Schema.Union([
51
51
  ResponseStartMessageSchema,
52
52
  ResponseChunkMessageSchema,
53
53
  ResponseEndMessageSchema,
54
54
  ResponseErrorMessageSchema,
55
55
  ]);
56
- export const RelayMessageSchema = Schema.Union([ServerMessageSchema, ClientMessageSchema]);
56
+ /** Encodes a relay message as the JSON string sent over the WebSocket. */
57
57
  export const encodeRelayMessage = (message) => JSON.stringify(message);
58
- export const decodeRelayMessage = (input) => JSON.parse(input);
58
+ /**
59
+ * Decodes a relay-to-CLI WebSocket message.
60
+ *
61
+ * Returns a result object instead of throwing for invalid JSON or invalid
62
+ * message shapes, which lets callers close or ignore malformed connections
63
+ * using their own error policy.
64
+ */
59
65
  export const decodeServerRelayMessage = (input) => decodeWithSchema(input, ServerMessageSchema);
66
+ /**
67
+ * Decodes a CLI-to-relay WebSocket message.
68
+ *
69
+ * Response headers must be ordered pairs, not collapsed maps, so duplicate
70
+ * response headers remain representable.
71
+ */
60
72
  export const decodeClientRelayMessage = (input) => decodeWithSchema(input, ClientMessageSchema);
61
- export const decodeTypedRelayMessage = (input) => decodeWithSchema(input, RelayMessageSchema);
62
73
  const decodeWithSchema = (input, schema) => {
63
74
  try {
64
75
  const result = Schema.decodeUnknownResult(schema)(JSON.parse(input));
@@ -1,3 +1,5 @@
1
+ /** Canonical public domain used for production LocalPreview URLs. */
1
2
  export declare const LOCALPREVIEW_PUBLIC_DOMAIN = "localpreview.dev";
3
+ /** HTTPS origin for the production LocalPreview control-plane. */
2
4
  export declare const LOCALPREVIEW_PUBLIC_ORIGIN = "https://localpreview.dev";
3
5
  //# sourceMappingURL=public-domain.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public-domain.d.ts","sourceRoot":"","sources":["../src/public-domain.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,0BAA0B,qBAAqB,CAAC;AAC7D,eAAO,MAAM,0BAA0B,6BAA0C,CAAC"}
1
+ {"version":3,"file":"public-domain.d.ts","sourceRoot":"","sources":["../src/public-domain.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,eAAO,MAAM,0BAA0B,qBAAqB,CAAC;AAE7D,kEAAkE;AAClE,eAAO,MAAM,0BAA0B,6BAA0C,CAAC"}
@@ -1,2 +1,4 @@
1
+ /** Canonical public domain used for production LocalPreview URLs. */
1
2
  export const LOCALPREVIEW_PUBLIC_DOMAIN = "localpreview.dev";
3
+ /** HTTPS origin for the production LocalPreview control-plane. */
2
4
  export const LOCALPREVIEW_PUBLIC_ORIGIN = `https://${LOCALPREVIEW_PUBLIC_DOMAIN}`;
@@ -1,9 +1,17 @@
1
- export type SubdomainValidationResult = {
1
+ type SubdomainValidationResult = {
2
2
  readonly valid: true;
3
3
  readonly subdomain: string;
4
4
  } | {
5
5
  readonly valid: false;
6
6
  readonly reason: "invalid-format" | "reserved";
7
7
  };
8
+ /**
9
+ * Normalizes and validates a user-requested public subdomain.
10
+ *
11
+ * The returned `subdomain` is trimmed and lowercased. Reserved product routes
12
+ * and names outside the public subdomain format are rejected with separate
13
+ * reasons so callers can produce specific user-facing errors.
14
+ */
8
15
  export declare const validateRequestedSubdomain: (input: string) => SubdomainValidationResult;
16
+ export {};
9
17
  //# sourceMappingURL=subdomain.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"subdomain.d.ts","sourceRoot":"","sources":["../src/subdomain.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,yBAAyB,GACjC;IACE,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,GACD;IACE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,CAAC;CAChD,CAAC;AAEN,eAAO,MAAM,0BAA0B,GAAI,OAAO,MAAM,KAAG,yBAY1D,CAAC"}
1
+ {"version":3,"file":"subdomain.d.ts","sourceRoot":"","sources":["../src/subdomain.ts"],"names":[],"mappings":"AAaA,KAAK,yBAAyB,GAC1B;IACE,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,GACD;IACE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,CAAC;CAChD,CAAC;AAEN;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,GAAI,OAAO,MAAM,KAAG,yBAY1D,CAAC"}
package/dist/subdomain.js CHANGED
@@ -10,6 +10,13 @@ const reservedSubdomains = new Set([
10
10
  "status",
11
11
  "www",
12
12
  ]);
13
+ /**
14
+ * Normalizes and validates a user-requested public subdomain.
15
+ *
16
+ * The returned `subdomain` is trimmed and lowercased. Reserved product routes
17
+ * and names outside the public subdomain format are rejected with separate
18
+ * reasons so callers can produce specific user-facing errors.
19
+ */
13
20
  export const validateRequestedSubdomain = (input) => {
14
21
  const subdomain = input.trim().toLowerCase();
15
22
  if (reservedSubdomains.has(subdomain)) {
package/dist/target.d.ts CHANGED
@@ -1,14 +1,23 @@
1
+ /** Local HTTP(S) endpoint that the CLI forwards tunnel traffic to. */
1
2
  export type TunnelTarget = {
2
3
  readonly protocol: "http" | "https";
3
4
  readonly hostname: string;
4
5
  readonly port: number;
5
6
  };
6
- export type ParseTargetResult = {
7
+ type ParseTargetResult = {
7
8
  readonly ok: true;
8
9
  readonly target: TunnelTarget;
9
10
  } | {
10
11
  readonly ok: false;
11
12
  readonly message: string;
12
13
  };
14
+ /**
15
+ * Parses CLI target input into a normalized tunnel target.
16
+ *
17
+ * A bare port is treated as `http://127.0.0.1:<port>`. URL-like inputs default
18
+ * to `http` when no protocol is provided, but must include an explicit port and
19
+ * use either `http` or `https`.
20
+ */
13
21
  export declare const parseTarget: (input: string) => ParseTargetResult;
22
+ export {};
14
23
  //# sourceMappingURL=target.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"target.d.ts","sourceRoot":"","sources":["../src/target.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GACzB;IACE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;CAC/B,GACD;IACE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEN,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,KAAG,iBA4B3C,CAAC"}
1
+ {"version":3,"file":"target.d.ts","sourceRoot":"","sources":["../src/target.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,KAAK,iBAAiB,GAClB;IACE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;CAC/B,GACD;IACE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEN;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,KAAG,iBA4B3C,CAAC"}
package/dist/target.js CHANGED
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Parses CLI target input into a normalized tunnel target.
3
+ *
4
+ * A bare port is treated as `http://127.0.0.1:<port>`. URL-like inputs default
5
+ * to `http` when no protocol is provided, but must include an explicit port and
6
+ * use either `http` or `https`.
7
+ */
1
8
  export const parseTarget = (input) => {
2
9
  const trimmed = input.trim();
3
10
  if (/^\d+$/.test(trimmed)) {
package/dist/tunnel.d.ts CHANGED
@@ -1,21 +1,4 @@
1
- import { Schema } from "effect";
2
- export declare const TunnelStatusSchema: Schema.Literals<readonly ["starting", "connected", "disconnected", "expired"]>;
3
- export type TunnelStatus = Schema.Schema.Type<typeof TunnelStatusSchema>;
4
- export type TunnelRecord = {
5
- readonly tunnelId: string;
6
- readonly subdomain: string;
7
- readonly relayHttpUrl: string;
8
- readonly relayWsUrl: string;
9
- readonly proxyToken: string;
10
- readonly relayProvider?: "local" | "vercel";
11
- readonly status: TunnelStatus;
12
- readonly sandboxId?: string;
13
- readonly createdAt: string;
14
- readonly expiresAt: string;
15
- };
16
- export type CreateTunnelRequest = {
17
- readonly requestedSubdomain?: string;
18
- };
1
+ /** Response returned by the control-plane after creating a tunnel session. */
19
2
  export type CreateTunnelResponse = {
20
3
  readonly tunnelId: string;
21
4
  readonly subdomain: string;
@@ -26,6 +9,7 @@ export type CreateTunnelResponse = {
26
9
  readonly protocolVersion: string;
27
10
  readonly relaySnapshotVersion: string;
28
11
  };
12
+ /** Admin request used by the control-plane to register a tunnel with a relay. */
29
13
  export type RegisterRelayTunnelRequest = {
30
14
  readonly tunnelId: string;
31
15
  readonly clientToken: string;
@@ -1 +1 @@
1
- {"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../src/tunnel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,eAAO,MAAM,kBAAkB,gFAKpB,CAAC;AAEZ,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEzE,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5C,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B,CAAC"}
1
+ {"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../src/tunnel.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;CACvC,CAAC;AAEF,iFAAiF;AACjF,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B,CAAC"}
package/dist/tunnel.js CHANGED
@@ -1,7 +1 @@
1
- import { Schema } from "effect";
2
- export const TunnelStatusSchema = Schema.Literals([
3
- "starting",
4
- "connected",
5
- "disconnected",
6
- "expired",
7
- ]);
1
+ export {};
package/dist/version.d.ts CHANGED
@@ -1,20 +1,35 @@
1
1
  import { Schema } from "effect";
2
+ /**
3
+ * Wire and HTTP contract version expected by CLI, control-plane, and relay.
4
+ *
5
+ * Change this when a deployed component would no longer understand the same
6
+ * public request, response, or relay message shapes.
7
+ */
2
8
  export declare const LOCALPREVIEW_PROTOCOL_VERSION = "1";
9
+ /**
10
+ * Production relay snapshot contract version.
11
+ *
12
+ * Change this when the relay snapshot must be rebuilt or promoted even if the
13
+ * shared protocol remains compatible.
14
+ */
3
15
  export declare const LOCALPREVIEW_RELAY_SNAPSHOT_VERSION = "2026-05-25.1";
4
- export declare const RelayHealthResponseSchema: Schema.Struct<{
16
+ declare const RelayHealthResponseSchema: Schema.Struct<{
5
17
  readonly ok: Schema.Literal<true>;
6
18
  readonly protocolVersion: Schema.String;
7
19
  readonly relaySnapshotVersion: Schema.String;
8
20
  readonly service: Schema.String;
9
21
  readonly tunnels: Schema.Number;
10
22
  }>;
23
+ /** Health response emitted by relay instances and checked by snapshot tooling. */
11
24
  export type RelayHealthResponse = Schema.Schema.Type<typeof RelayHealthResponseSchema>;
12
- export type RelayHealthDecodeResult = {
25
+ type RelayHealthDecodeResult = {
13
26
  readonly health: RelayHealthResponse;
14
27
  readonly ok: true;
15
28
  } | {
16
29
  readonly error: unknown;
17
30
  readonly ok: false;
18
31
  };
32
+ /** Validates an unknown relay `/health` response without throwing. */
19
33
  export declare const decodeRelayHealthResponse: (input: unknown) => RelayHealthDecodeResult;
34
+ export {};
20
35
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,eAAO,MAAM,6BAA6B,MAAM,CAAC;AACjD,eAAO,MAAM,mCAAmC,iBAAiB,CAAC;AAElE,eAAO,MAAM,yBAAyB;;;;;;EAMpC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAEvF,MAAM,MAAM,uBAAuB,GAC/B;IACE,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;CACnB,GACD;IACE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;CACpB,CAAC;AAEN,eAAO,MAAM,yBAAyB,GAAI,OAAO,OAAO,KAAG,uBAQ1D,CAAC"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,MAAM,CAAC;AAEjD;;;;;GAKG;AACH,eAAO,MAAM,mCAAmC,iBAAiB,CAAC;AAElE,QAAA,MAAM,yBAAyB;;;;;;EAM7B,CAAC;AAEH,kFAAkF;AAClF,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAEvF,KAAK,uBAAuB,GACxB;IACE,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;CACnB,GACD;IACE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;CACpB,CAAC;AAEN,sEAAsE;AACtE,eAAO,MAAM,yBAAyB,GAAI,OAAO,OAAO,KAAG,uBAQ1D,CAAC"}
package/dist/version.js CHANGED
@@ -1,13 +1,26 @@
1
1
  import { Result, Schema } from "effect";
2
+ /**
3
+ * Wire and HTTP contract version expected by CLI, control-plane, and relay.
4
+ *
5
+ * Change this when a deployed component would no longer understand the same
6
+ * public request, response, or relay message shapes.
7
+ */
2
8
  export const LOCALPREVIEW_PROTOCOL_VERSION = "1";
9
+ /**
10
+ * Production relay snapshot contract version.
11
+ *
12
+ * Change this when the relay snapshot must be rebuilt or promoted even if the
13
+ * shared protocol remains compatible.
14
+ */
3
15
  export const LOCALPREVIEW_RELAY_SNAPSHOT_VERSION = "2026-05-25.1";
4
- export const RelayHealthResponseSchema = Schema.Struct({
16
+ const RelayHealthResponseSchema = Schema.Struct({
5
17
  ok: Schema.Literal(true),
6
18
  protocolVersion: Schema.String,
7
19
  relaySnapshotVersion: Schema.String,
8
20
  service: Schema.String,
9
21
  tunnels: Schema.Number,
10
22
  });
23
+ /** Validates an unknown relay `/health` response without throwing. */
11
24
  export const decodeRelayHealthResponse = (input) => {
12
25
  const result = Schema.decodeUnknownResult(RelayHealthResponseSchema)(input);
13
26
  if (Result.isSuccess(result)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@localpreview/protocol",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -16,18 +16,20 @@
16
16
  "publishConfig": {
17
17
  "access": "public"
18
18
  },
19
- "dependencies": {
20
- "effect": "beta"
21
- },
22
- "devDependencies": {
23
- "typescript": "latest",
24
- "vitest": "latest"
25
- },
26
19
  "scripts": {
27
20
  "build": "tsc -p tsconfig.build.json",
28
21
  "dev": "tsc -p tsconfig.build.json --watch",
29
22
  "lint": "oxlint .",
23
+ "prepack": "pnpm build",
24
+ "prepublishOnly": "pnpm test && pnpm typecheck && pnpm build",
30
25
  "test": "vitest run",
31
26
  "typecheck": "tsc -p tsconfig.json --noEmit"
27
+ },
28
+ "dependencies": {
29
+ "effect": "catalog:"
30
+ },
31
+ "devDependencies": {
32
+ "typescript": "catalog:",
33
+ "vitest": "catalog:"
32
34
  }
33
- }
35
+ }