t3code-cli 0.1.3 → 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/dist/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { $ as fromIterable, $n as isNotNull, $t as scoped, A as Crypto, An as map$3, At as catchTags, B as make$8, Bn as getOrElse, Bt as ignore, C as decodeUnknownEffect, Cn as doneUnsafe, Ct as acquireRelease, D as optionalKey, Dn as succeed$1, Dt as asVoid, E as optional$3, En as isSuccess, Et as as, F as make$7, Fn as empty$1, Ft as fn, G as FileSystem, Gn as match, Gt as mapError, H as Path$1, Hn as isNone, Ht as isEffect, I as parseFdName, In as make$13, It as fnUntraced, J as WatchBackend, Jn as Prototype, Jt as provide$1, K as FileTypeId, Kn as none, Kt as matchEffect, L as ChildProcessSpawner, Ln as flatMap$1, Lt as forEach, M as fromReadable, Mn as succeed$3, Mt as effectify, N as fromWritable, Nn as Reference, Nt as fail, O as tag, On as ConsoleRef, Ot as callback, P as fdName, Pn as Service, Pt as flatMap, Q as empty, Qn as hasProperty, Qt as runFork, R as ExitCode, Rn as fromNullishOr, Rt as forkScoped, S as decodeTo, Sn as _await, St as transform$1, T as is, Tn as makeUnsafe, Tt as andThen, U as TypeId$6, Un as isSome, Ut as logError, V as makeHandle, Vn as getOrUndefined, Vt as interrupt, W as FileDescriptor, Wn as map$4, Wt as map$2, X as callback$1, Xn as withFiber, Xt as provideService, Y as make$10, Yn as PipeInspectableProto, Yt as provideContext, Z as decodeText, Zn as format, Zt as result, _ as Literals, _n as provide, _t as offer, a as latestAssistantMessage, an as tapError, ar as identity, at as runLast, b as TaggedErrorClass, bn as sync$2, bt as badArgument, c as Environment, cn as try_, cr as __commonJSMin, ct as transduce, d as ArraySchema, dn as TaggedError, dt as make$11, en as serviceOption, er as isNotUndefined, et as isStream, f as Boolean$2, fn as fail$1, ft as drain, g as Literal, gn as mergeAll, gt as make$12, h as Int, hn as effect, ht as failCauseUnsafe, i as ThreadSessionError, in as tapCause, ir as dual, it as runFold, j as make$9, jn as fail$2, jt as catch_, k as toCodecStringTree, kn as sync$1, kt as catchFilter, l as T3Config, ln as uninterruptible, lr as __require, lt as unwrap, m as ErrorClass, mn as squash, mt as endUnsafe, nn as suspend, nr as isUndefined, nt as run$1, o as threadStatus, on as timeoutOrElse, or as pipe, ot as succeed$4, p as Defect, pn as hasInterruptsOnly, pt as isSink, q as Size, qn as some, qt as orDie, r as NodeEnvironmentLive, rn as sync, rr as constVoid, rt as runDrain, s as T3Application, sn as tryPromise, sr as pipeArguments, st as tap, t as AppLayer, tn as succeed, tr as isNullish, tt as merge, u as T3Auth, un as void_, ut as get, v as String$1, vn as provideMerge, vt as offerUnsafe, w as fromJsonString, wn as isDone, wt as addFinalizer, x as Union, xn as MinimumLogLevel, xt as systemError, y as Struct, yn as succeed$2, yt as BadArgument, z as ProcessId, zn as fromUndefinedOr, zt as gen } from "./runtime-0wuYCEoH.js";
2
+ import { $ as fromIterable, $n as isNotNull, $t as scoped, A as Crypto, An as map$3, At as catchTags, B as make$8, Bn as getOrElse, Bt as ignore, C as decodeUnknownEffect, Cn as doneUnsafe, Ct as acquireRelease, D as optionalKey, Dn as succeed$1, Dt as asVoid, E as optional$3, En as isSuccess, Et as as, F as make$7, Fn as empty$1, Ft as fn, G as FileSystem, Gn as match, Gt as mapError, H as Path$1, Hn as isNone, Ht as isEffect, I as parseFdName, In as make$13, It as fnUntraced, J as WatchBackend, Jn as Prototype, Jt as provide$1, K as FileTypeId, Kn as none, Kt as matchEffect, L as ChildProcessSpawner, Ln as flatMap$1, Lt as forEach, M as fromReadable, Mn as succeed$3, Mt as effectify, N as fromWritable, Nn as Reference, Nt as fail, O as tag, On as ConsoleRef, Ot as callback, P as fdName, Pn as Service, Pt as flatMap, Q as empty, Qn as hasProperty, Qt as runFork, R as ExitCode, Rn as fromNullishOr, Rt as forkScoped, S as decodeTo, Sn as _await, St as transform$1, T as is, Tn as makeUnsafe, Tt as andThen, U as TypeId$6, Un as isSome, Ut as logError, V as makeHandle, Vn as getOrUndefined, Vt as interrupt, W as FileDescriptor, Wn as map$4, Wt as map$2, X as callback$1, Xn as withFiber, Xt as provideService, Y as make$10, Yn as PipeInspectableProto, Yt as provideContext, Z as decodeText, Zn as format, Zt as result, _ as Literals, _n as provide, _t as offer, a as latestAssistantMessage, an as tapError, ar as identity, at as runLast, b as TaggedErrorClass, bn as sync$2, bt as badArgument, c as Environment, cn as try_, cr as __commonJSMin, ct as transduce, d as ArraySchema, dn as TaggedError, dt as make$11, en as serviceOption, er as isNotUndefined, et as isStream, f as Boolean$2, fn as fail$1, ft as drain, g as Literal, gn as mergeAll, gt as make$12, h as Int, hn as effect, ht as failCauseUnsafe, i as ThreadSessionError, in as tapCause, ir as dual, it as runFold, j as make$9, jn as fail$2, jt as catch_, k as toCodecStringTree, kn as sync$1, kt as catchFilter, l as T3Config, ln as uninterruptible, lr as __require, lt as unwrap, m as ErrorClass, mn as squash, mt as endUnsafe, nn as suspend, nr as isUndefined, nt as run$1, o as threadStatus, on as timeoutOrElse, or as pipe, ot as succeed$4, p as Defect, pn as hasInterruptsOnly, pt as isSink, q as Size, qn as some, qt as orDie, r as NodeEnvironmentLive, rn as sync, rr as constVoid, rt as runDrain, s as T3Application, sn as tryPromise, sr as pipeArguments, st as tap, t as AppLayer, tn as succeed, tr as isNullish, tt as merge, u as T3Auth, un as void_, ut as get, v as String$1, vn as provideMerge, vt as offerUnsafe, w as fromJsonString, wn as isDone, wt as addFinalizer, x as Union, xn as MinimumLogLevel, xt as systemError, y as Struct, yn as succeed$2, yt as BadArgument, z as ProcessId, zn as fromUndefinedOr, zt as gen } from "./runtime-CMPZpQaG.js";
3
3
  import * as NodeChildProcess from "node:child_process";
4
4
  import * as NodeCrypto from "node:crypto";
5
5
  import * as NFS from "node:fs";
@@ -17822,7 +17822,7 @@ var T3Version = class extends Service()("t3cli/T3Version") {};
17822
17822
  //#endregion
17823
17823
  //#region src/version/layer.ts
17824
17824
  const PackageJsonSchema = fromJsonString(Struct({ version: String$1 }));
17825
- const T3VersionBundledLive = sync$2(T3Version, () => ({ version: "0.1.3" }));
17825
+ const T3VersionBundledLive = sync$2(T3Version, () => ({ version: "0.2.0" }));
17826
17826
  effect(T3Version, gen(function* () {
17827
17827
  const packageJson = yield* (yield* FileSystem).readFileString(fileURLToPath(new URL("../../package.json", import.meta.url)));
17828
17828
  return { version: (yield* decodeUnknownEffect(PackageJsonSchema)(packageJson)).version };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { n as AuthAppLayer, r as NodeEnvironmentLive, s as T3Application, t as AppLayer } from "./runtime-0wuYCEoH.js";
1
+ import { n as AuthAppLayer, r as NodeEnvironmentLive, s as T3Application, t as AppLayer } from "./runtime-CMPZpQaG.js";
2
2
  export { AppLayer, AuthAppLayer, NodeEnvironmentLive, T3Application };
@@ -31476,21 +31476,6 @@ const setUrl = /*#__PURE__*/ dual(2, (self, url) => {
31476
31476
  return makeWith$1(self.method, clone.toString(), urlParams, hash, self.headers, self.body);
31477
31477
  });
31478
31478
  /**
31479
- * Appends a URL segment to the request URL, inserting or trimming one slash as needed.
31480
- *
31481
- * @category combinators
31482
- * @since 4.0.0
31483
- */
31484
- const appendUrl = /*#__PURE__*/ dual(2, (self, path) => {
31485
- if (path === "") return self;
31486
- return makeWith$1(self.method, joinSegments(self.url, path), self.urlParams, self.hash, self.headers, self.body);
31487
- });
31488
- const joinSegments = (first, second) => {
31489
- const endsWithSlash = first.endsWith("/");
31490
- const startsWithSlash = second.startsWith("/");
31491
- return endsWithSlash && startsWithSlash ? first + second.slice(1) : !endsWithSlash && !startsWithSlash ? first + "/" + second : first + second;
31492
- };
31493
- /**
31494
31479
  * Sets one query parameter, replacing existing values for that parameter name.
31495
31480
  *
31496
31481
  * @category combinators
@@ -64342,7 +64327,11 @@ var AuthConfigError = class extends TaggedErrorClass()("AuthConfigError", {
64342
64327
  }) {};
64343
64328
  var AuthTransportError = class extends TaggedErrorClass()("AuthTransportError", {
64344
64329
  message: String$1,
64345
- cause: optionalKey(Union([HttpClientErrorSchema, instanceOf(SchemaError)]))
64330
+ cause: optionalKey(Union([
64331
+ HttpClientErrorSchema,
64332
+ instanceOf(SchemaError),
64333
+ UrlError
64334
+ ]))
64346
64335
  }) {};
64347
64336
  const AuthLocalErrorCauseSchema = Union([
64348
64337
  instanceOf(PlatformError),
@@ -64358,10 +64347,13 @@ var AuthLocalError = class extends TaggedErrorClass()("AuthLocalError", {
64358
64347
  function normalizeHttpBaseUrl(value) {
64359
64348
  return parseUrl$1(value).pipe(flatMap$2((url) => succeed$2(normalizeBaseUrl$1(url))));
64360
64349
  }
64361
- function toWebSocketBaseUrl(httpBaseUrl) {
64350
+ function toHttpEndpointUrl(httpBaseUrl, pathname) {
64351
+ return parseUrl$1(httpBaseUrl).pipe(flatMap$2((url) => succeed$2(withPathname(url, pathname).toString())));
64352
+ }
64353
+ function toWebSocketEndpointUrl(httpBaseUrl, pathname) {
64362
64354
  return parseUrl$1(httpBaseUrl).pipe(flatMap$2((url) => {
64363
- if (url.protocol === "http:") return succeed$2(makeWebSocketUrl(url, "ws:"));
64364
- if (url.protocol === "https:") return succeed$2(makeWebSocketUrl(url, "wss:"));
64355
+ if (url.protocol === "http:") return succeed$2(withPathname(url, pathname, "ws:").toString());
64356
+ if (url.protocol === "https:") return succeed$2(withPathname(url, pathname, "wss:").toString());
64365
64357
  return fail$2(new UrlError({
64366
64358
  message: `unsupported server url protocol: ${url.protocol}`,
64367
64359
  protocol: url.protocol
@@ -64378,16 +64370,22 @@ function normalizeBaseUrl$1(url) {
64378
64370
  return mutate(url, (current) => {
64379
64371
  current.hash = "";
64380
64372
  current.search = "";
64381
- current.pathname = "";
64373
+ current.pathname = normalizePathname(current.pathname);
64382
64374
  }).toString().replace(/\/$/, "");
64383
64375
  }
64384
- function makeWebSocketUrl(url, protocol) {
64376
+ function withPathname(url, pathname, protocol) {
64385
64377
  return mutate(url, (current) => {
64386
- current.protocol = protocol;
64387
- current.pathname = "/ws";
64378
+ if (protocol !== void 0) current.protocol = protocol;
64379
+ current.pathname = appendPathname(current.pathname, pathname);
64388
64380
  current.search = "";
64389
64381
  current.hash = "";
64390
- }).toString();
64382
+ });
64383
+ }
64384
+ function normalizePathname(pathname) {
64385
+ return pathname === "/" ? "" : pathname.replace(/\/+$/u, "");
64386
+ }
64387
+ function appendPathname(basePathname, pathname) {
64388
+ return `${normalizePathname(basePathname)}${pathname.startsWith("/") ? pathname : `/${pathname}`}`;
64391
64389
  }
64392
64390
  //#endregion
64393
64391
  //#region src/auth/schema.ts
@@ -64558,7 +64556,7 @@ function parsePairingUrl(value) {
64558
64556
  if (token.length === 0) return yield* fail$2(new AuthPairingUrlError({ message: "pairing url missing token" }));
64559
64557
  const hostedHost = url.searchParams.get("host")?.trim();
64560
64558
  return {
64561
- baseUrl: normalizeBaseUrl(yield* parseUrl(hostedHost !== void 0 && hostedHost.length > 0 ? hostedHost : url.origin)),
64559
+ baseUrl: normalizeBaseUrl(yield* parseUrl(hostedHost !== void 0 && hostedHost.length > 0 ? hostedHost : new URL(".", url).toString())),
64562
64560
  credential: token
64563
64561
  };
64564
64562
  });
@@ -64573,7 +64571,7 @@ function normalizeBaseUrl(url) {
64573
64571
  return mutate(url, (current) => {
64574
64572
  current.hash = "";
64575
64573
  current.search = "";
64576
- current.pathname = "";
64574
+ current.pathname = current.pathname === "/" ? "" : current.pathname.replace(/\/+$/u, "");
64577
64575
  }).toString().replace(/\/$/, "");
64578
64576
  }
64579
64577
  function readPairingToken(url) {
@@ -64589,7 +64587,7 @@ const makeAuthTransport = fn("makeAuthTransport")(function* () {
64589
64587
  const client = filterStatusOk(yield* HttpClient);
64590
64588
  return {
64591
64589
  bootstrapBearer: fn("AuthTransport.bootstrapBearer")(function* (input) {
64592
- const request = post$1(input.baseUrl).pipe(appendUrl("/api/auth/bootstrap/bearer"), acceptJson, bodyJsonUnsafe({ credential: input.credential }));
64590
+ const request = post$1(yield* makeHttpEndpointUrl(input.baseUrl, "/api/auth/bootstrap/bearer")).pipe(acceptJson, bodyJsonUnsafe({ credential: input.credential }));
64593
64591
  return yield* (yield* client.execute(request).pipe(catchTags$1({ HttpClientError: (error) => fail$2(new AuthTransportError({
64594
64592
  message: "auth request failed",
64595
64593
  cause: HttpClientErrorSchema.fromHttpClientError(error)
@@ -64605,7 +64603,7 @@ const makeAuthTransport = fn("makeAuthTransport")(function* () {
64605
64603
  }));
64606
64604
  }),
64607
64605
  getSession: fn("AuthTransport.getSession")(function* (config) {
64608
- const request = authenticatedRequest(config, "/api/auth/session", "get");
64606
+ const request = yield* authenticatedRequest(config, "/api/auth/session", "get");
64609
64607
  return yield* (yield* client.execute(request).pipe(catchTags$1({ HttpClientError: (error) => fail$2(new AuthTransportError({
64610
64608
  message: "auth request failed",
64611
64609
  cause: HttpClientErrorSchema.fromHttpClientError(error)
@@ -64621,7 +64619,7 @@ const makeAuthTransport = fn("makeAuthTransport")(function* () {
64621
64619
  }));
64622
64620
  }),
64623
64621
  issueWebSocketToken: fn("AuthTransport.issueWebSocketToken")(function* (config) {
64624
- const request = authenticatedRequest(config, "/api/auth/ws-token", "post");
64622
+ const request = yield* authenticatedRequest(config, "/api/auth/ws-token", "post");
64625
64623
  return yield* (yield* client.execute(request).pipe(catchTags$1({ HttpClientError: (error) => fail$2(new AuthTransportError({
64626
64624
  message: "auth request failed",
64627
64625
  cause: HttpClientErrorSchema.fromHttpClientError(error)
@@ -64639,7 +64637,13 @@ const makeAuthTransport = fn("makeAuthTransport")(function* () {
64639
64637
  };
64640
64638
  });
64641
64639
  function authenticatedRequest(config, path, method) {
64642
- return (method === "get" ? get$1(config.url) : post$1(config.url)).pipe(appendUrl(path), acceptJson, bearerToken(config.token));
64640
+ return makeHttpEndpointUrl(config.url, path).pipe(map$3((url) => method === "get" ? get$1(url) : post$1(url)), map$3((request) => request.pipe(acceptJson, bearerToken(config.token))));
64641
+ }
64642
+ function makeHttpEndpointUrl(baseUrl, path) {
64643
+ return toHttpEndpointUrl(baseUrl, path).pipe(catchTags$1({ UrlError: (error) => fail$2(new AuthTransportError({
64644
+ message: "auth request failed",
64645
+ cause: error
64646
+ })) }));
64643
64647
  }
64644
64648
  const T3AuthLive = effect(T3Auth, fn("makeT3Auth")(function* () {
64645
64649
  const config = yield* T3Config;
@@ -68693,7 +68697,7 @@ const makeT3RpcLayer = fn("makeT3RpcLayer")(function* () {
68693
68697
  const makeWsUrl = fn("makeWsUrl")(function* (input) {
68694
68698
  const resolved = yield* input.config.resolve();
68695
68699
  const wsToken = yield* input.auth.issueWebSocketToken();
68696
- return getOrThrow(toUrl(get$1(yield* toWebSocketBaseUrl(resolved.url)).pipe(setUrlParam("wsToken", wsToken.token)))).toString();
68700
+ return getOrThrow(toUrl(get$1(yield* toWebSocketEndpointUrl(resolved.url, "/ws")).pipe(setUrlParam("wsToken", wsToken.token)))).toString();
68697
68701
  });
68698
68702
  function makeProtocolLayer(url) {
68699
68703
  const socketLayer = layerWebSocket(url).pipe(provide$2(layerWebSocketConstructor));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "t3code-cli",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "CLI for t3code",
5
5
  "keywords": [
6
6
  "claude",
package/src/auth/error.ts CHANGED
@@ -23,7 +23,11 @@ export class AuthTransportError extends Schema.TaggedErrorClass<AuthTransportErr
23
23
  {
24
24
  message: Schema.String,
25
25
  cause: Schema.optionalKey(
26
- Schema.Union([HttpClientError.HttpClientErrorSchema, Schema.instanceOf(Schema.SchemaError)]),
26
+ Schema.Union([
27
+ HttpClientError.HttpClientErrorSchema,
28
+ Schema.instanceOf(Schema.SchemaError),
29
+ UrlError,
30
+ ]),
27
31
  ),
28
32
  },
29
33
  ) {}
@@ -14,7 +14,11 @@ export function parsePairingUrl(value: string): Effect.Effect<PairingUrl, AuthPa
14
14
 
15
15
  const hostedHost = url.searchParams.get("host")?.trim();
16
16
  const baseUrl = normalizeBaseUrl(
17
- yield* parseUrl(hostedHost !== undefined && hostedHost.length > 0 ? hostedHost : url.origin),
17
+ yield* parseUrl(
18
+ hostedHost !== undefined && hostedHost.length > 0
19
+ ? hostedHost
20
+ : new URL(".", url).toString(),
21
+ ),
18
22
  );
19
23
  return { baseUrl, credential: token };
20
24
  });
@@ -33,7 +37,7 @@ function normalizeBaseUrl(url: URL) {
33
37
  return Url.mutate(url, (current) => {
34
38
  current.hash = "";
35
39
  current.search = "";
36
- current.pathname = "";
40
+ current.pathname = current.pathname === "/" ? "" : current.pathname.replace(/\/+$/u, "");
37
41
  })
38
42
  .toString()
39
43
  .replace(/\/$/, "");
@@ -2,6 +2,7 @@ import * as Effect from "effect/Effect";
2
2
  import { HttpClient, HttpClientError, HttpClientRequest } from "effect/unstable/http";
3
3
 
4
4
  import type { ResolvedConfig } from "../config/service.ts";
5
+ import { toHttpEndpointUrl } from "../config/url.ts";
5
6
  import { AuthTransportError } from "./error.ts";
6
7
  import {
7
8
  decodeAuthBearerBootstrapResult,
@@ -16,8 +17,8 @@ export const makeAuthTransport = Effect.fn("makeAuthTransport")(function* () {
16
17
  readonly baseUrl: string;
17
18
  readonly credential: string;
18
19
  }) {
19
- const request = HttpClientRequest.post(input.baseUrl).pipe(
20
- HttpClientRequest.appendUrl("/api/auth/bootstrap/bearer"),
20
+ const url = yield* makeHttpEndpointUrl(input.baseUrl, "/api/auth/bootstrap/bearer");
21
+ const request = HttpClientRequest.post(url).pipe(
21
22
  HttpClientRequest.acceptJson,
22
23
  HttpClientRequest.bodyJsonUnsafe({ credential: input.credential }),
23
24
  );
@@ -51,7 +52,7 @@ export const makeAuthTransport = Effect.fn("makeAuthTransport")(function* () {
51
52
  });
52
53
 
53
54
  const getSession = Effect.fn("AuthTransport.getSession")(function* (config: ResolvedConfig) {
54
- const request = authenticatedRequest(config, "/api/auth/session", "get");
55
+ const request = yield* authenticatedRequest(config, "/api/auth/session", "get");
55
56
  const response = yield* client.execute(request).pipe(
56
57
  Effect.catchTags({
57
58
  HttpClientError: (error) =>
@@ -84,7 +85,7 @@ export const makeAuthTransport = Effect.fn("makeAuthTransport")(function* () {
84
85
  const issueWebSocketToken = Effect.fn("AuthTransport.issueWebSocketToken")(function* (
85
86
  config: ResolvedConfig,
86
87
  ) {
87
- const request = authenticatedRequest(config, "/api/auth/ws-token", "post");
88
+ const request = yield* authenticatedRequest(config, "/api/auth/ws-token", "post");
88
89
  const response = yield* client.execute(request).pipe(
89
90
  Effect.catchTags({
90
91
  HttpClientError: (error) =>
@@ -122,11 +123,21 @@ export const makeAuthTransport = Effect.fn("makeAuthTransport")(function* () {
122
123
  });
123
124
 
124
125
  function authenticatedRequest(config: ResolvedConfig, path: string, method: "get" | "post") {
125
- const request =
126
- method === "get" ? HttpClientRequest.get(config.url) : HttpClientRequest.post(config.url);
127
- return request.pipe(
128
- HttpClientRequest.appendUrl(path),
129
- HttpClientRequest.acceptJson,
130
- HttpClientRequest.bearerToken(config.token),
126
+ return makeHttpEndpointUrl(config.url, path).pipe(
127
+ Effect.map((url) =>
128
+ method === "get" ? HttpClientRequest.get(url) : HttpClientRequest.post(url),
129
+ ),
130
+ Effect.map((request) =>
131
+ request.pipe(HttpClientRequest.acceptJson, HttpClientRequest.bearerToken(config.token)),
132
+ ),
133
+ );
134
+ }
135
+
136
+ function makeHttpEndpointUrl(baseUrl: string, path: string) {
137
+ return toHttpEndpointUrl(baseUrl, path).pipe(
138
+ Effect.catchTags({
139
+ UrlError: (error) =>
140
+ Effect.fail(new AuthTransportError({ message: "auth request failed", cause: error })),
141
+ }),
131
142
  );
132
143
  }
package/src/config/url.ts CHANGED
@@ -26,6 +26,31 @@ export function toWebSocketBaseUrl(httpBaseUrl: string) {
26
26
  );
27
27
  }
28
28
 
29
+ export function toHttpEndpointUrl(httpBaseUrl: string, pathname: string) {
30
+ return parseUrl(httpBaseUrl).pipe(
31
+ Effect.flatMap((url) => Effect.succeed(withPathname(url, pathname).toString())),
32
+ );
33
+ }
34
+
35
+ export function toWebSocketEndpointUrl(httpBaseUrl: string, pathname: string) {
36
+ return parseUrl(httpBaseUrl).pipe(
37
+ Effect.flatMap((url) => {
38
+ if (url.protocol === "http:") {
39
+ return Effect.succeed(withPathname(url, pathname, "ws:").toString());
40
+ }
41
+ if (url.protocol === "https:") {
42
+ return Effect.succeed(withPathname(url, pathname, "wss:").toString());
43
+ }
44
+ return Effect.fail(
45
+ new UrlError({
46
+ message: `unsupported server url protocol: ${url.protocol}`,
47
+ protocol: url.protocol,
48
+ }),
49
+ );
50
+ }),
51
+ );
52
+ }
53
+
29
54
  function parseUrl(value: string): Effect.Effect<URL, UrlError> {
30
55
  return Effect.fromResult(Url.fromString(value)).pipe(
31
56
  Effect.catchTags({
@@ -39,7 +64,7 @@ function normalizeBaseUrl(url: URL) {
39
64
  return Url.mutate(url, (current) => {
40
65
  current.hash = "";
41
66
  current.search = "";
42
- current.pathname = "";
67
+ current.pathname = normalizePathname(current.pathname);
43
68
  })
44
69
  .toString()
45
70
  .replace(/\/$/, "");
@@ -48,8 +73,27 @@ function normalizeBaseUrl(url: URL) {
48
73
  function makeWebSocketUrl(url: URL, protocol: "ws:" | "wss:") {
49
74
  return Url.mutate(url, (current) => {
50
75
  current.protocol = protocol;
51
- current.pathname = "/ws";
76
+ current.pathname = appendPathname(current.pathname, "/ws");
52
77
  current.search = "";
53
78
  current.hash = "";
54
79
  }).toString();
55
80
  }
81
+
82
+ function withPathname(url: URL, pathname: string, protocol?: "ws:" | "wss:") {
83
+ return Url.mutate(url, (current) => {
84
+ if (protocol !== undefined) {
85
+ current.protocol = protocol;
86
+ }
87
+ current.pathname = appendPathname(current.pathname, pathname);
88
+ current.search = "";
89
+ current.hash = "";
90
+ });
91
+ }
92
+
93
+ function normalizePathname(pathname: string) {
94
+ return pathname === "/" ? "" : pathname.replace(/\/+$/u, "");
95
+ }
96
+
97
+ function appendPathname(basePathname: string, pathname: string) {
98
+ return `${normalizePathname(basePathname)}${pathname.startsWith("/") ? pathname : `/${pathname}`}`;
99
+ }
package/src/rpc/layer.ts CHANGED
@@ -13,7 +13,7 @@ import * as Socket from "effect/unstable/socket/Socket";
13
13
 
14
14
  import { T3Auth } from "../auth/service.ts";
15
15
  import { T3Config } from "../config/service.ts";
16
- import { toWebSocketBaseUrl } from "../config/url.ts";
16
+ import { toWebSocketEndpointUrl } from "../config/url.ts";
17
17
  import { CliWsRpcGroup } from "./ws-group.ts";
18
18
  import { RpcError } from "./error.ts";
19
19
  import { T3Rpc, type WsClient } from "./service.ts";
@@ -77,7 +77,7 @@ const makeWsUrl = Effect.fn("makeWsUrl")(function* (input: {
77
77
  }) {
78
78
  const resolved = yield* input.config.resolve();
79
79
  const wsToken = yield* input.auth.issueWebSocketToken();
80
- const wsUrl = yield* toWebSocketBaseUrl(resolved.url);
80
+ const wsUrl = yield* toWebSocketEndpointUrl(resolved.url, "/ws");
81
81
  const request = HttpClientRequest.get(wsUrl).pipe(
82
82
  HttpClientRequest.setUrlParam("wsToken", wsToken.token),
83
83
  );