rivetkit 2.0.34 → 2.0.36

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.
Files changed (94) hide show
  1. package/dist/tsup/actor/errors.cjs +6 -2
  2. package/dist/tsup/actor/errors.cjs.map +1 -1
  3. package/dist/tsup/actor/errors.d.cts +7 -1
  4. package/dist/tsup/actor/errors.d.ts +7 -1
  5. package/dist/tsup/actor/errors.js +5 -1
  6. package/dist/tsup/{chunk-6KPMUCTX.js → chunk-2YHR67M4.js} +26 -2
  7. package/dist/tsup/{chunk-6KPMUCTX.js.map → chunk-2YHR67M4.js.map} +1 -1
  8. package/dist/tsup/{chunk-3I3NOVWF.cjs → chunk-65O3MUPO.cjs} +9 -9
  9. package/dist/tsup/{chunk-3I3NOVWF.cjs.map → chunk-65O3MUPO.cjs.map} +1 -1
  10. package/dist/tsup/{chunk-PNDDMBWF.cjs → chunk-6TLJAB64.cjs} +223 -269
  11. package/dist/tsup/chunk-6TLJAB64.cjs.map +1 -0
  12. package/dist/tsup/{chunk-ZNFKB5PN.cjs → chunk-7EGXCVJL.cjs} +55 -48
  13. package/dist/tsup/chunk-7EGXCVJL.cjs.map +1 -0
  14. package/dist/tsup/{chunk-US7J2C4A.js → chunk-D35UUQOR.js} +2 -2
  15. package/dist/tsup/{chunk-GQ5VRA2G.js → chunk-DSPZ7BZN.js} +96 -6
  16. package/dist/tsup/chunk-DSPZ7BZN.js.map +1 -0
  17. package/dist/tsup/{chunk-JXQJHBUL.js → chunk-EO5JABFU.js} +47 -36
  18. package/dist/tsup/chunk-EO5JABFU.js.map +1 -0
  19. package/dist/tsup/{chunk-2PDSCMHY.js → chunk-KJLJLNHR.js} +273 -319
  20. package/dist/tsup/chunk-KJLJLNHR.js.map +1 -0
  21. package/dist/tsup/{chunk-IKLDUHPA.cjs → chunk-OYJU4B2I.cjs} +130 -119
  22. package/dist/tsup/chunk-OYJU4B2I.cjs.map +1 -0
  23. package/dist/tsup/{chunk-ULWSFIPU.cjs → chunk-RTSLQIZ5.cjs} +355 -265
  24. package/dist/tsup/chunk-RTSLQIZ5.cjs.map +1 -0
  25. package/dist/tsup/{chunk-FWT2BTVT.cjs → chunk-SNAUKDDK.cjs} +26 -2
  26. package/dist/tsup/chunk-SNAUKDDK.cjs.map +1 -0
  27. package/dist/tsup/{chunk-VXUKLCT5.js → chunk-UDKWYSU3.js} +7 -3
  28. package/dist/tsup/chunk-UDKWYSU3.js.map +1 -0
  29. package/dist/tsup/{chunk-VGG36TGE.cjs → chunk-VIUUUBXQ.cjs} +3 -3
  30. package/dist/tsup/{chunk-VGG36TGE.cjs.map → chunk-VIUUUBXQ.cjs.map} +1 -1
  31. package/dist/tsup/{chunk-LD52TCZK.cjs → chunk-WRYYREBN.cjs} +13 -9
  32. package/dist/tsup/chunk-WRYYREBN.cjs.map +1 -0
  33. package/dist/tsup/{chunk-YLUTGXVY.js → chunk-X5GKJWTG.js} +12 -5
  34. package/dist/tsup/{chunk-YLUTGXVY.js.map → chunk-X5GKJWTG.js.map} +1 -1
  35. package/dist/tsup/{chunk-BDE7AKE7.js → chunk-Z5CSXFVY.js} +5 -5
  36. package/dist/tsup/client/mod.cjs +6 -6
  37. package/dist/tsup/client/mod.d.cts +8 -8
  38. package/dist/tsup/client/mod.d.ts +8 -8
  39. package/dist/tsup/client/mod.js +5 -5
  40. package/dist/tsup/common/log.cjs +3 -3
  41. package/dist/tsup/common/log.js +2 -2
  42. package/dist/tsup/common/websocket.cjs +4 -4
  43. package/dist/tsup/common/websocket.js +3 -3
  44. package/dist/tsup/{config-BybOFjaz.d.cts → config--NjwiYlS.d.cts} +24 -25
  45. package/dist/tsup/{config-B-whEERh.d.ts → config-CRuzI6n4.d.ts} +24 -25
  46. package/dist/tsup/{driver-DqaHKTyo.d.ts → driver-BcmckRaF.d.ts} +1 -1
  47. package/dist/tsup/driver-helpers/mod.cjs +4 -4
  48. package/dist/tsup/driver-helpers/mod.d.cts +2 -2
  49. package/dist/tsup/driver-helpers/mod.d.ts +2 -2
  50. package/dist/tsup/driver-helpers/mod.js +3 -3
  51. package/dist/tsup/driver-test-suite/mod.cjs +37 -37
  52. package/dist/tsup/driver-test-suite/mod.cjs.map +1 -1
  53. package/dist/tsup/driver-test-suite/mod.d.cts +2 -2
  54. package/dist/tsup/driver-test-suite/mod.d.ts +2 -2
  55. package/dist/tsup/driver-test-suite/mod.js +9 -9
  56. package/dist/tsup/{driver-CcjuH3oe.d.cts → driver-yKjYx9Yy.d.cts} +1 -1
  57. package/dist/tsup/mod.cjs +8 -8
  58. package/dist/tsup/mod.d.cts +4 -4
  59. package/dist/tsup/mod.d.ts +4 -4
  60. package/dist/tsup/mod.js +7 -7
  61. package/dist/tsup/test/mod.cjs +8 -8
  62. package/dist/tsup/test/mod.d.cts +1 -1
  63. package/dist/tsup/test/mod.d.ts +1 -1
  64. package/dist/tsup/test/mod.js +7 -7
  65. package/dist/tsup/utils.cjs +3 -3
  66. package/dist/tsup/utils.js +2 -2
  67. package/package.json +3 -3
  68. package/src/actor/errors.ts +24 -0
  69. package/src/actor/router.ts +13 -1
  70. package/src/client/config.ts +54 -48
  71. package/src/common/router.ts +12 -0
  72. package/src/drivers/engine/actor-driver.ts +2 -1
  73. package/src/drivers/engine/config.ts +6 -3
  74. package/src/globals.d.ts +1 -0
  75. package/src/manager/router.ts +8 -1
  76. package/src/registry/config/index.ts +98 -90
  77. package/src/registry/config/serverless.ts +34 -3
  78. package/src/remote-manager-driver/api-utils.ts +3 -0
  79. package/src/serverless/router.ts +24 -2
  80. package/src/utils/endpoint-parser.test.ts +184 -171
  81. package/src/utils/endpoint-parser.ts +70 -65
  82. package/src/utils/env-vars.ts +6 -0
  83. package/dist/tsup/chunk-2PDSCMHY.js.map +0 -1
  84. package/dist/tsup/chunk-FWT2BTVT.cjs.map +0 -1
  85. package/dist/tsup/chunk-GQ5VRA2G.js.map +0 -1
  86. package/dist/tsup/chunk-IKLDUHPA.cjs.map +0 -1
  87. package/dist/tsup/chunk-JXQJHBUL.js.map +0 -1
  88. package/dist/tsup/chunk-LD52TCZK.cjs.map +0 -1
  89. package/dist/tsup/chunk-PNDDMBWF.cjs.map +0 -1
  90. package/dist/tsup/chunk-ULWSFIPU.cjs.map +0 -1
  91. package/dist/tsup/chunk-VXUKLCT5.js.map +0 -1
  92. package/dist/tsup/chunk-ZNFKB5PN.cjs.map +0 -1
  93. /package/dist/tsup/{chunk-US7J2C4A.js.map → chunk-D35UUQOR.js.map} +0 -0
  94. /package/dist/tsup/{chunk-BDE7AKE7.js.map → chunk-Z5CSXFVY.js.map} +0 -0
@@ -1,189 +1,202 @@
1
1
  import { describe, expect, test } from "vitest";
2
- import { zodParseEndpoint, EndpointSchema } from "./endpoint-parser";
3
-
4
- describe("zodParseEndpoint", () => {
5
- describe("full auth syntax", () => {
6
- test("parses namespace and token from endpoint", () => {
7
- const result = zodParseEndpoint("https://foo:bar@api.rivet.dev");
8
- expect(result.endpoint).toBe("https://api.rivet.dev/");
9
- expect(result.namespace).toBe("foo");
10
- expect(result.token).toBe("bar");
11
- });
12
-
13
- test("parses with port", () => {
14
- const result = zodParseEndpoint("https://foo:bar@api.rivet.dev:8080");
15
- expect(result.endpoint).toBe("https://api.rivet.dev:8080/");
16
- expect(result.namespace).toBe("foo");
17
- expect(result.token).toBe("bar");
18
- });
19
-
20
- test("parses with path", () => {
21
- const result = zodParseEndpoint("https://foo:bar@api.rivet.dev/v1/actors");
22
- expect(result.endpoint).toBe("https://api.rivet.dev/v1/actors");
23
- expect(result.namespace).toBe("foo");
24
- expect(result.token).toBe("bar");
25
- });
26
-
27
- test("throws on query string", () => {
28
- expect(() =>
29
- zodParseEndpoint("https://foo:bar@api.rivet.dev?region=us-east"),
30
- ).toThrow("endpoint cannot contain a query string");
2
+ import type { z } from "zod";
3
+ import { tryParseEndpoint } from "./endpoint-parser";
4
+
5
+ // Helper to create a mock Zod refinement context for testing
6
+ function createMockCtx(): { ctx: z.RefinementCtx; issues: z.ZodIssue[] } {
7
+ const issues: z.ZodIssue[] = [];
8
+ const ctx = {
9
+ addIssue: (issue: z.IssueData) => {
10
+ issues.push(issue as z.ZodIssue);
11
+ },
12
+ path: [],
13
+ value: undefined,
14
+ issues: [],
15
+ } as unknown as z.RefinementCtx;
16
+ return { ctx, issues };
17
+ }
18
+
19
+ describe("tryParseEndpoint", () => {
20
+ describe("basic parsing", () => {
21
+ test("parses endpoint with full auth", () => {
22
+ const { ctx, issues } = createMockCtx();
23
+ const result = tryParseEndpoint(ctx, {
24
+ endpoint: "https://foo:bar@api.rivet.dev",
25
+ });
26
+ expect(issues).toHaveLength(0);
27
+ expect(result).toEqual({
28
+ endpoint: "https://api.rivet.dev/",
29
+ namespace: "foo",
30
+ token: "bar",
31
+ });
32
+ });
33
+
34
+ test("parses endpoint with namespace only", () => {
35
+ const { ctx, issues } = createMockCtx();
36
+ const result = tryParseEndpoint(ctx, {
37
+ endpoint: "https://foo@api.rivet.dev",
38
+ });
39
+ expect(issues).toHaveLength(0);
40
+ expect(result).toEqual({
41
+ endpoint: "https://api.rivet.dev/",
42
+ namespace: "foo",
43
+ token: undefined,
44
+ });
31
45
  });
32
46
 
33
- test("throws on fragment", () => {
34
- expect(() =>
35
- zodParseEndpoint("https://foo:bar@api.rivet.dev#section"),
36
- ).toThrow("endpoint cannot contain a fragment");
37
- });
38
-
39
- test("handles percent-encoded characters in namespace", () => {
40
- const result = zodParseEndpoint("https://foo%40bar:token@api.rivet.dev");
41
- expect(result.endpoint).toBe("https://api.rivet.dev/");
42
- expect(result.namespace).toBe("foo@bar");
43
- expect(result.token).toBe("token");
44
- });
45
-
46
- test("handles percent-encoded characters in token", () => {
47
- const result = zodParseEndpoint("https://foo:bar%3Abaz@api.rivet.dev");
48
- expect(result.endpoint).toBe("https://api.rivet.dev/");
49
- expect(result.namespace).toBe("foo");
50
- expect(result.token).toBe("bar:baz");
51
- });
52
- });
53
-
54
- describe("namespace only (no token)", () => {
55
- test("parses namespace without token", () => {
56
- const result = zodParseEndpoint("https://foo@api.rivet.dev");
57
- expect(result.endpoint).toBe("https://api.rivet.dev/");
58
- expect(result.namespace).toBe("foo");
59
- expect(result.token).toBeUndefined();
60
- });
61
-
62
- test("parses namespace without token with path", () => {
63
- const result = zodParseEndpoint("https://foo@api.rivet.dev/v1/actors");
64
- expect(result.endpoint).toBe("https://api.rivet.dev/v1/actors");
65
- expect(result.namespace).toBe("foo");
66
- expect(result.token).toBeUndefined();
67
- });
68
- });
69
-
70
- describe("no auth", () => {
71
47
  test("parses endpoint without auth", () => {
72
- const result = zodParseEndpoint("https://api.rivet.dev");
73
- expect(result.endpoint).toBe("https://api.rivet.dev/");
74
- expect(result.namespace).toBeUndefined();
75
- expect(result.token).toBeUndefined();
76
- });
77
-
78
- test("parses endpoint without auth with path", () => {
79
- const result = zodParseEndpoint("https://api.rivet.dev/v1/actors");
80
- expect(result.endpoint).toBe("https://api.rivet.dev/v1/actors");
81
- expect(result.namespace).toBeUndefined();
82
- expect(result.token).toBeUndefined();
83
- });
84
-
85
- test("throws on query string without auth", () => {
86
- expect(() =>
87
- zodParseEndpoint("https://api.rivet.dev?region=us-east"),
88
- ).toThrow("endpoint cannot contain a query string");
89
- });
90
- });
91
-
92
- describe("http protocol", () => {
93
- test("parses http endpoint with auth", () => {
94
- const result = zodParseEndpoint("http://foo:bar@localhost:6420");
95
- expect(result.endpoint).toBe("http://localhost:6420/");
96
- expect(result.namespace).toBe("foo");
97
- expect(result.token).toBe("bar");
98
- });
99
-
100
- test("parses http endpoint without auth", () => {
101
- const result = zodParseEndpoint("http://localhost:6420");
102
- expect(result.endpoint).toBe("http://localhost:6420/");
103
- expect(result.namespace).toBeUndefined();
104
- expect(result.token).toBeUndefined();
48
+ const { ctx, issues } = createMockCtx();
49
+ const result = tryParseEndpoint(ctx, {
50
+ endpoint: "https://api.rivet.dev",
51
+ });
52
+ expect(issues).toHaveLength(0);
53
+ expect(result).toEqual({
54
+ endpoint: "https://api.rivet.dev/",
55
+ namespace: undefined,
56
+ token: undefined,
57
+ });
58
+ });
59
+
60
+ test("preserves path", () => {
61
+ const { ctx, issues } = createMockCtx();
62
+ const result = tryParseEndpoint(ctx, {
63
+ endpoint: "https://foo:bar@api.rivet.dev/v1/actors",
64
+ });
65
+ expect(issues).toHaveLength(0);
66
+ expect(result).toEqual({
67
+ endpoint: "https://api.rivet.dev/v1/actors",
68
+ namespace: "foo",
69
+ token: "bar",
70
+ });
105
71
  });
106
72
  });
107
73
 
108
- describe("error handling", () => {
109
- test("throws on invalid URL", () => {
110
- expect(() => zodParseEndpoint("not-a-url")).toThrow();
111
- });
112
-
113
- test("throws on empty string", () => {
114
- expect(() => zodParseEndpoint("")).toThrow();
115
- });
116
-
117
- test("throws on token without namespace", () => {
118
- expect(() => zodParseEndpoint("https://:token@api.rivet.dev")).toThrow(
74
+ describe("validation errors", () => {
75
+ test("adds issue for invalid URL", () => {
76
+ const { ctx, issues } = createMockCtx();
77
+ const result = tryParseEndpoint(ctx, { endpoint: "not-a-url" });
78
+ expect(result).toBeUndefined();
79
+ expect(issues).toHaveLength(1);
80
+ expect(issues[0]?.message).toContain("invalid URL");
81
+ });
82
+
83
+ test("adds issue for query string", () => {
84
+ const { ctx, issues } = createMockCtx();
85
+ const result = tryParseEndpoint(ctx, {
86
+ endpoint: "https://foo:bar@api.rivet.dev?region=us",
87
+ });
88
+ expect(result).toBeUndefined();
89
+ expect(issues).toHaveLength(1);
90
+ expect(issues[0]?.message).toBe("endpoint cannot contain a query string");
91
+ });
92
+
93
+ test("adds issue for fragment", () => {
94
+ const { ctx, issues } = createMockCtx();
95
+ const result = tryParseEndpoint(ctx, {
96
+ endpoint: "https://foo:bar@api.rivet.dev#section",
97
+ });
98
+ expect(result).toBeUndefined();
99
+ expect(issues).toHaveLength(1);
100
+ expect(issues[0]?.message).toBe("endpoint cannot contain a fragment");
101
+ });
102
+
103
+ test("adds issue for token without namespace", () => {
104
+ const { ctx, issues } = createMockCtx();
105
+ const result = tryParseEndpoint(ctx, {
106
+ endpoint: "https://:token@api.rivet.dev",
107
+ });
108
+ expect(result).toBeUndefined();
109
+ expect(issues).toHaveLength(1);
110
+ expect(issues[0]?.message).toBe(
119
111
  "endpoint cannot have a token without a namespace",
120
112
  );
121
113
  });
122
114
  });
123
- });
124
-
125
- describe("EndpointSchema", () => {
126
- test("parses endpoint with full auth", () => {
127
- const result = EndpointSchema.parse("https://foo:bar@api.rivet.dev");
128
- expect(result).toEqual({
129
- endpoint: "https://api.rivet.dev/",
130
- namespace: "foo",
131
- token: "bar",
132
- });
133
- });
134
115
 
135
- test("parses endpoint with namespace only", () => {
136
- const result = EndpointSchema.parse("https://foo@api.rivet.dev");
137
- expect(result).toEqual({
138
- endpoint: "https://api.rivet.dev/",
139
- namespace: "foo",
140
- token: undefined,
141
- });
142
- });
143
-
144
- test("parses endpoint without auth", () => {
145
- const result = EndpointSchema.parse("https://api.rivet.dev");
146
- expect(result).toEqual({
147
- endpoint: "https://api.rivet.dev/",
148
- namespace: undefined,
149
- token: undefined,
150
- });
151
- });
152
-
153
- test("preserves path", () => {
154
- const result = EndpointSchema.parse(
155
- "https://foo:bar@api.rivet.dev/v1/actors",
156
- );
157
- expect(result).toEqual({
158
- endpoint: "https://api.rivet.dev/v1/actors",
159
- namespace: "foo",
160
- token: "bar",
116
+ describe("duplicate credential checking", () => {
117
+ test("adds issue when namespace in URL and config", () => {
118
+ const { ctx, issues } = createMockCtx();
119
+ const result = tryParseEndpoint(ctx, {
120
+ endpoint: "https://url-ns@api.rivet.dev",
121
+ path: ["endpoint"],
122
+ namespace: "config-ns",
123
+ });
124
+ // Still returns result, but adds issue
125
+ expect(result).toEqual({
126
+ endpoint: "https://api.rivet.dev/",
127
+ namespace: "url-ns",
128
+ token: undefined,
129
+ });
130
+ expect(issues).toHaveLength(1);
131
+ expect(issues[0]?.message).toContain(
132
+ "cannot specify namespace both in endpoint URL and as a separate config option",
133
+ );
134
+ expect(issues[0]?.path).toEqual(["namespace"]);
135
+ });
136
+
137
+ test("adds issue when token in URL and config", () => {
138
+ const { ctx, issues } = createMockCtx();
139
+ const result = tryParseEndpoint(ctx, {
140
+ endpoint: "https://ns:url-token@api.rivet.dev",
141
+ path: ["endpoint"],
142
+ token: "config-token",
143
+ });
144
+ // Still returns result, but adds issue
145
+ expect(result).toEqual({
146
+ endpoint: "https://api.rivet.dev/",
147
+ namespace: "ns",
148
+ token: "url-token",
149
+ });
150
+ expect(issues).toHaveLength(1);
151
+ expect(issues[0]?.message).toContain(
152
+ "cannot specify token both in endpoint URL and as a separate config option",
153
+ );
154
+ expect(issues[0]?.path).toEqual(["token"]);
155
+ });
156
+
157
+ test("adds issues for both namespace and token duplicates", () => {
158
+ const { ctx, issues } = createMockCtx();
159
+ const result = tryParseEndpoint(ctx, {
160
+ endpoint: "https://url-ns:url-token@api.rivet.dev",
161
+ path: ["endpoint"],
162
+ namespace: "config-ns",
163
+ token: "config-token",
164
+ });
165
+ expect(result).toBeDefined();
166
+ expect(issues).toHaveLength(2);
167
+ });
168
+
169
+ test("no issue when namespace only in URL", () => {
170
+ const { ctx, issues } = createMockCtx();
171
+ const result = tryParseEndpoint(ctx, {
172
+ endpoint: "https://url-ns@api.rivet.dev",
173
+ path: ["endpoint"],
174
+ token: "config-token",
175
+ });
176
+ expect(result).toBeDefined();
177
+ expect(issues).toHaveLength(0);
178
+ });
179
+
180
+ test("no issue when namespace only in config", () => {
181
+ const { ctx, issues } = createMockCtx();
182
+ const result = tryParseEndpoint(ctx, {
183
+ endpoint: "https://api.rivet.dev",
184
+ path: ["endpoint"],
185
+ namespace: "config-ns",
186
+ });
187
+ expect(result).toBeDefined();
188
+ expect(issues).toHaveLength(0);
161
189
  });
162
190
  });
163
191
 
164
- test("throws on query string", () => {
165
- expect(() =>
166
- EndpointSchema.parse("https://foo:bar@api.rivet.dev?region=us"),
167
- ).toThrow();
168
- });
169
-
170
- test("throws on fragment", () => {
171
- expect(() =>
172
- EndpointSchema.parse("https://foo:bar@api.rivet.dev#section"),
173
- ).toThrow();
174
- });
175
-
176
- test("throws on invalid URL", () => {
177
- expect(() => EndpointSchema.parse("not-a-url")).toThrow();
178
- });
179
-
180
- test("works with optional()", () => {
181
- const schema = EndpointSchema.optional();
182
- expect(schema.parse(undefined)).toBeUndefined();
183
- expect(schema.parse("https://foo:bar@api.rivet.dev")).toEqual({
184
- endpoint: "https://api.rivet.dev/",
185
- namespace: "foo",
186
- token: "bar",
192
+ describe("custom path", () => {
193
+ test("uses custom path in error issues", () => {
194
+ const { ctx, issues } = createMockCtx();
195
+ tryParseEndpoint(ctx, {
196
+ endpoint: "not-a-url",
197
+ path: ["serverless", "publicEndpoint"],
198
+ });
199
+ expect(issues[0]?.path).toEqual(["serverless", "publicEndpoint"]);
187
200
  });
188
201
  });
189
202
  });
@@ -6,9 +6,22 @@ export interface ParsedEndpoint {
6
6
  token: string | undefined;
7
7
  }
8
8
 
9
+ export interface TryParseEndpointOptions {
10
+ /** The endpoint URL string to parse */
11
+ endpoint: string;
12
+ /** Path prefix for error messages (default: ["endpoint"]) */
13
+ path?: (string | number)[];
14
+ /** Namespace from config, to check for duplicate specification */
15
+ namespace?: string;
16
+ /** Token from config, to check for duplicate specification */
17
+ token?: string;
18
+ }
19
+
9
20
  /**
10
21
  * Parses an endpoint URL that may contain auth syntax for namespace and token.
11
22
  *
23
+ * Uses ctx.addIssue for clean error reporting in Zod transforms.
24
+ *
12
25
  * Supports formats like:
13
26
  * - `https://namespace:token@api.rivet.dev`
14
27
  * - `https://namespace@api.rivet.dev` (namespace only, no token)
@@ -17,87 +30,68 @@ export interface ParsedEndpoint {
17
30
  *
18
31
  * Query strings and fragments are not allowed as they may conflict with
19
32
  * runtime parameters.
33
+ *
34
+ * @param ctx - Zod refinement context for error reporting
35
+ * @param options - Parsing options including endpoint, path, and config values
36
+ * @returns ParsedEndpoint on success, undefined on error (after adding issues to ctx)
20
37
  */
21
- export function zodParseEndpoint(endpoint: string): ParsedEndpoint {
38
+ export function tryParseEndpoint(
39
+ ctx: z.RefinementCtx,
40
+ options: TryParseEndpointOptions,
41
+ ): ParsedEndpoint | undefined {
42
+ const { endpoint, path = ["endpoint"], namespace: configNamespace, token: configToken } = options;
22
43
  // Parse the URL
23
- const url = new URL(endpoint);
44
+ let url: URL;
45
+ try {
46
+ url = new URL(endpoint);
47
+ } catch {
48
+ ctx.addIssue({
49
+ code: "custom",
50
+ message: `invalid URL: ${endpoint}`,
51
+ path,
52
+ });
53
+ return undefined;
54
+ }
24
55
 
25
56
  // Reject query strings
26
57
  if (url.search) {
27
- throw new z.ZodError([
28
- {
29
- code: "custom",
30
- message: "endpoint cannot contain a query string",
31
- path: ["endpoint"],
32
- },
33
- ]);
58
+ ctx.addIssue({
59
+ code: "custom",
60
+ message: "endpoint cannot contain a query string",
61
+ path,
62
+ });
63
+ return undefined;
34
64
  }
35
65
 
36
66
  // Reject fragments
37
67
  if (url.hash) {
38
- throw new z.ZodError([
39
- {
40
- code: "custom",
41
- message: "endpoint cannot contain a fragment",
42
- path: ["endpoint"],
43
- },
44
- ]);
68
+ ctx.addIssue({
69
+ code: "custom",
70
+ message: "endpoint cannot contain a fragment",
71
+ path,
72
+ });
73
+ return undefined;
45
74
  }
46
75
 
47
76
  // Extract namespace and token from username and password
48
77
  // URL stores these as percent-encoded, so we need to decode them
49
- const namespace = url.username ? decodeURIComponent(url.username) : undefined;
78
+ const namespace = url.username
79
+ ? decodeURIComponent(url.username)
80
+ : undefined;
50
81
  const token = url.password ? decodeURIComponent(url.password) : undefined;
51
82
 
52
83
  // Reject token without namespace (e.g., https://:token@api.rivet.dev)
53
84
  if (token && !namespace) {
54
- throw new z.ZodError([
55
- {
56
- code: "custom",
57
- message: "endpoint cannot have a token without a namespace",
58
- path: ["endpoint"],
59
- },
60
- ]);
85
+ ctx.addIssue({
86
+ code: "custom",
87
+ message: "endpoint cannot have a token without a namespace",
88
+ path,
89
+ });
90
+ return undefined;
61
91
  }
62
92
 
63
- // Strip auth from the URL by clearing username and password
64
- url.username = "";
65
- url.password = "";
66
-
67
- // Get the cleaned endpoint without auth
68
- const cleanedEndpoint = url.toString();
69
-
70
- return {
71
- endpoint: cleanedEndpoint,
72
- namespace,
73
- token,
74
- };
75
- }
76
-
77
- /**
78
- * Zod schema that parses an endpoint URL string and extracts namespace/token from HTTP auth syntax.
79
- *
80
- * Input: `"https://namespace:token@api.rivet.dev/path"`
81
- * Output: `{ endpoint: "https://api.rivet.dev/path", namespace: "namespace", token: "token" }`
82
- */
83
- export const EndpointSchema = z.string().transform((endpoint): ParsedEndpoint => {
84
- return zodParseEndpoint(endpoint);
85
- });
86
-
87
- export type EndpointSchemaInput = z.input<typeof EndpointSchema>;
88
- export type EndpointSchemaOutput = z.output<typeof EndpointSchema>;
89
-
90
- /**
91
- * Zod refinement that validates namespace/token aren't specified both in the endpoint URL
92
- * and as separate config options.
93
- */
94
- export function zodCheckDuplicateCredentials(
95
- resolvedEndpoint: ParsedEndpoint,
96
- config: { namespace?: string; token?: string },
97
- ctx: z.RefinementCtx,
98
- ): void {
99
- // Check if endpoint contains namespace but namespace is also specified in config
100
- if (resolvedEndpoint.namespace && config.namespace) {
93
+ // Check for duplicate credentials (specified both in URL and config)
94
+ if (namespace && configNamespace) {
101
95
  ctx.addIssue({
102
96
  code: "custom",
103
97
  message:
@@ -105,9 +99,7 @@ export function zodCheckDuplicateCredentials(
105
99
  path: ["namespace"],
106
100
  });
107
101
  }
108
-
109
- // Check if endpoint contains token but token is also specified in config
110
- if (resolvedEndpoint.token && config.token) {
102
+ if (token && configToken) {
111
103
  ctx.addIssue({
112
104
  code: "custom",
113
105
  message:
@@ -115,5 +107,18 @@ export function zodCheckDuplicateCredentials(
115
107
  path: ["token"],
116
108
  });
117
109
  }
110
+
111
+ // Strip auth from the URL by clearing username and password
112
+ url.username = "";
113
+ url.password = "";
114
+
115
+ // Get the cleaned endpoint without auth
116
+ const cleanedEndpoint = url.toString();
117
+
118
+ return {
119
+ endpoint: cleanedEndpoint,
120
+ namespace,
121
+ token,
122
+ };
118
123
  }
119
124
 
@@ -29,6 +29,12 @@ export const getRivetRunnerVersion = (): number | undefined => {
29
29
  const value = getEnvUniversal("RIVET_RUNNER_VERSION");
30
30
  return value !== undefined ? parseInt(value, 10) : undefined;
31
31
  };
32
+ export const getRivetPublicEndpoint = (): string | undefined =>
33
+ getEnvUniversal("RIVET_PUBLIC_ENDPOINT");
34
+ export const getRivetPublicToken = (): string | undefined =>
35
+ getEnvUniversal("RIVET_PUBLIC_TOKEN");
36
+ // There is no RIVET_PUBLIC_NAMESPACE because the frontend and backend cannot
37
+ // use different namespaces
32
38
 
33
39
  // RivetKit configuration
34
40
  export const getRivetkitInspectorToken = (): string | undefined =>