@stackframe/stack-shared 2.7.12 → 2.7.14

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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @stackframe/stack-shared
2
2
 
3
+ ## 2.7.14
4
+
5
+ ### Patch Changes
6
+
7
+ - Various changes
8
+ - @stackframe/stack-sc@2.7.14
9
+
10
+ ## 2.7.13
11
+
12
+ ### Patch Changes
13
+
14
+ - Various changes
15
+ - @stackframe/stack-sc@2.7.13
16
+
3
17
  ## 2.7.12
4
18
 
5
19
  ### Patch Changes
@@ -45,7 +45,9 @@ export const emailConfigSchema = yupObject({
45
45
  }),
46
46
  });
47
47
  const domainSchema = yupObject({
48
- domain: schemaFields.projectTrustedDomainSchema.defined(),
48
+ domain: schemaFields.urlSchema.defined()
49
+ .matches(/^https?:\/\//, 'URL must start with http:// or https://')
50
+ .meta({ openapiField: { description: 'URL. Must start with http:// or https://', exampleValue: 'https://example.com' } }),
49
51
  handler_path: schemaFields.handlerPathSchema.defined(),
50
52
  });
51
53
  export const projectsCrudAdminReadSchema = yupObject({
@@ -7,7 +7,7 @@ export const usersCrudServerUpdateSchema = fieldSchema.yupObject({
7
7
  client_metadata: fieldSchema.userClientMetadataSchema.optional(),
8
8
  client_read_only_metadata: fieldSchema.userClientReadOnlyMetadataSchema.optional(),
9
9
  server_metadata: fieldSchema.userServerMetadataSchema.optional(),
10
- primary_email: fieldSchema.primaryEmailSchema.nullable().optional(),
10
+ primary_email: fieldSchema.primaryEmailSchema.nullable().optional().nonEmpty(),
11
11
  primary_email_verified: fieldSchema.primaryEmailVerifiedSchema.optional(),
12
12
  primary_email_auth_enabled: fieldSchema.primaryEmailAuthEnabledSchema.optional(),
13
13
  passkey_auth_enabled: fieldSchema.userOtpAuthEnabledSchema.optional(),
@@ -2,6 +2,7 @@ import { KnownErrors } from "../known-errors";
2
2
  import { StackAssertionError } from "../utils/errors";
3
3
  import { filterUndefined } from "../utils/objects";
4
4
  import { Result } from "../utils/results";
5
+ import { urlString } from "../utils/urls";
5
6
  import { StackClientInterface } from "./clientInterface";
6
7
  export class StackServerInterface extends StackClientInterface {
7
8
  constructor(options) {
@@ -57,35 +58,35 @@ export class StackServerInterface extends StackClientInterface {
57
58
  return user;
58
59
  }
59
60
  async getServerUserById(userId) {
60
- const response = await this.sendServerRequest(`/users/${userId}`, {}, null);
61
+ const response = await this.sendServerRequest(urlString `/users/${userId}`, {}, null);
61
62
  const user = await response.json();
62
63
  if (!user)
63
64
  return Result.error(new Error("Failed to get user"));
64
65
  return Result.ok(user);
65
66
  }
66
67
  async listServerTeamInvitations(options) {
67
- const response = await this.sendServerRequest("/team-invitations?team_id=" + options.teamId, {}, null);
68
+ const response = await this.sendServerRequest(urlString `/team-invitations?team_id=${options.teamId}`, {}, null);
68
69
  const result = await response.json();
69
70
  return result.items;
70
71
  }
71
72
  async revokeServerTeamInvitation(invitationId, teamId) {
72
- await this.sendServerRequest(`/team-invitations/${invitationId}?team_id=${teamId}`, { method: "DELETE" }, null);
73
+ await this.sendServerRequest(urlString `/team-invitations/${invitationId}?team_id=${teamId}`, { method: "DELETE" }, null);
73
74
  }
74
75
  async listServerTeamMemberProfiles(options) {
75
- const response = await this.sendServerRequest("/team-member-profiles?team_id=" + options.teamId, {}, null);
76
+ const response = await this.sendServerRequest(urlString `/team-member-profiles?team_id=${options.teamId}`, {}, null);
76
77
  const result = await response.json();
77
78
  return result.items;
78
79
  }
79
80
  async getServerTeamMemberProfile(options) {
80
- const response = await this.sendServerRequest(`/team-member-profiles/${options.teamId}/${options.userId}`, {}, null);
81
+ const response = await this.sendServerRequest(urlString `/team-member-profiles/${options.teamId}/${options.userId}`, {}, null);
81
82
  return await response.json();
82
83
  }
83
84
  async listServerTeamPermissions(options, session) {
84
- const response = await this.sendServerRequest("/team-permissions?" + new URLSearchParams(filterUndefined({
85
+ const response = await this.sendServerRequest(`/team-permissions?${new URLSearchParams(filterUndefined({
85
86
  user_id: options.userId,
86
87
  team_id: options.teamId,
87
88
  recursive: options.recursive.toString(),
88
- })), {}, session);
89
+ }))}`, {}, session);
89
90
  const result = await response.json();
90
91
  return result.items;
91
92
  }
@@ -107,9 +108,9 @@ export class StackServerInterface extends StackClientInterface {
107
108
  return await response.json();
108
109
  }
109
110
  async listServerTeams(options) {
110
- const response = await this.sendServerRequest("/teams?" + new URLSearchParams(filterUndefined({
111
+ const response = await this.sendServerRequest(`/teams?${new URLSearchParams(filterUndefined({
111
112
  user_id: options?.userId,
112
- })), {}, null);
113
+ }))}`, {}, null);
113
114
  const result = await response.json();
114
115
  return result.items;
115
116
  }
@@ -130,7 +131,7 @@ export class StackServerInterface extends StackClientInterface {
130
131
  return await response.json();
131
132
  }
132
133
  async updateServerTeam(teamId, data) {
133
- const response = await this.sendServerRequest(`/teams/${teamId}`, {
134
+ const response = await this.sendServerRequest(urlString `/teams/${teamId}`, {
134
135
  method: "PATCH",
135
136
  headers: {
136
137
  "content-type": "application/json",
@@ -140,10 +141,10 @@ export class StackServerInterface extends StackClientInterface {
140
141
  return await response.json();
141
142
  }
142
143
  async deleteServerTeam(teamId) {
143
- await this.sendServerRequest(`/teams/${teamId}`, { method: "DELETE" }, null);
144
+ await this.sendServerRequest(urlString `/teams/${teamId}`, { method: "DELETE" }, null);
144
145
  }
145
146
  async addServerUserToTeam(options) {
146
- const response = await this.sendServerRequest(`/team-memberships/${options.teamId}/${options.userId}`, {
147
+ const response = await this.sendServerRequest(urlString `/team-memberships/${options.teamId}/${options.userId}`, {
147
148
  method: "POST",
148
149
  headers: {
149
150
  "content-type": "application/json",
@@ -153,7 +154,7 @@ export class StackServerInterface extends StackClientInterface {
153
154
  return await response.json();
154
155
  }
155
156
  async removeServerUserFromTeam(options) {
156
- await this.sendServerRequest(`/team-memberships/${options.teamId}/${options.userId}`, {
157
+ await this.sendServerRequest(urlString `/team-memberships/${options.teamId}/${options.userId}`, {
157
158
  method: "DELETE",
158
159
  headers: {
159
160
  "content-type": "application/json",
@@ -162,7 +163,7 @@ export class StackServerInterface extends StackClientInterface {
162
163
  }, null);
163
164
  }
164
165
  async updateServerUser(userId, update) {
165
- const response = await this.sendServerRequest(`/users/${userId}`, {
166
+ const response = await this.sendServerRequest(urlString `/users/${userId}`, {
166
167
  method: "PATCH",
167
168
  headers: {
168
169
  "content-type": "application/json",
@@ -172,7 +173,7 @@ export class StackServerInterface extends StackClientInterface {
172
173
  return await response.json();
173
174
  }
174
175
  async createServerProviderAccessToken(userId, provider, scope) {
175
- const response = await this.sendServerRequest(`/connected-accounts/${userId}/${provider}/access-token`, {
176
+ const response = await this.sendServerRequest(urlString `/connected-accounts/${userId}/${provider}/access-token`, {
176
177
  method: "POST",
177
178
  headers: {
178
179
  "content-type": "application/json",
@@ -199,7 +200,7 @@ export class StackServerInterface extends StackClientInterface {
199
200
  };
200
201
  }
201
202
  async leaveServerTeam(options) {
202
- await this.sendClientRequest(`/team-memberships/${options.teamId}/${options.userId}`, {
203
+ await this.sendClientRequest(urlString `/team-memberships/${options.teamId}/${options.userId}`, {
203
204
  method: "DELETE",
204
205
  headers: {
205
206
  "content-type": "application/json",
@@ -208,7 +209,7 @@ export class StackServerInterface extends StackClientInterface {
208
209
  }, null);
209
210
  }
210
211
  async updateServerTeamMemberProfile(options) {
211
- await this.sendServerRequest(`/team-member-profiles/${options.teamId}/${options.userId}`, {
212
+ await this.sendServerRequest(urlString `/team-member-profiles/${options.teamId}/${options.userId}`, {
212
213
  method: "PATCH",
213
214
  headers: {
214
215
  "content-type": "application/json",
@@ -217,7 +218,7 @@ export class StackServerInterface extends StackClientInterface {
217
218
  }, null);
218
219
  }
219
220
  async grantServerTeamUserPermission(teamId, userId, permissionId) {
220
- await this.sendServerRequest(`/team-permissions/${teamId}/${userId}/${permissionId}`, {
221
+ await this.sendServerRequest(urlString `/team-permissions/${teamId}/${userId}/${permissionId}`, {
221
222
  method: "POST",
222
223
  headers: {
223
224
  "content-type": "application/json",
@@ -226,7 +227,7 @@ export class StackServerInterface extends StackClientInterface {
226
227
  }, null);
227
228
  }
228
229
  async revokeServerTeamUserPermission(teamId, userId, permissionId) {
229
- await this.sendServerRequest(`/team-permissions/${teamId}/${userId}/${permissionId}`, {
230
+ await this.sendServerRequest(urlString `/team-permissions/${teamId}/${userId}/${permissionId}`, {
230
231
  method: "DELETE",
231
232
  headers: {
232
233
  "content-type": "application/json",
@@ -235,7 +236,7 @@ export class StackServerInterface extends StackClientInterface {
235
236
  }, null);
236
237
  }
237
238
  async deleteServerServerUser(userId) {
238
- await this.sendServerRequest(`/users/${userId}`, {
239
+ await this.sendServerRequest(urlString `/users/${userId}`, {
239
240
  method: "DELETE",
240
241
  headers: {
241
242
  "content-type": "application/json",
@@ -254,7 +255,7 @@ export class StackServerInterface extends StackClientInterface {
254
255
  return await response.json();
255
256
  }
256
257
  async updateServerContactChannel(userId, contactChannelId, data) {
257
- const response = await this.sendServerRequest(`/contact-channels/${userId}/${contactChannelId}`, {
258
+ const response = await this.sendServerRequest(urlString `/contact-channels/${userId}/${contactChannelId}`, {
258
259
  method: "PATCH",
259
260
  headers: {
260
261
  "content-type": "application/json",
@@ -264,19 +265,19 @@ export class StackServerInterface extends StackClientInterface {
264
265
  return await response.json();
265
266
  }
266
267
  async deleteServerContactChannel(userId, contactChannelId) {
267
- await this.sendServerRequest(`/contact-channels/${userId}/${contactChannelId}`, {
268
+ await this.sendServerRequest(urlString `/contact-channels/${userId}/${contactChannelId}`, {
268
269
  method: "DELETE",
269
270
  }, null);
270
271
  }
271
272
  async listServerContactChannels(userId) {
272
- const response = await this.sendServerRequest(`/contact-channels?user_id=${userId}`, {
273
+ const response = await this.sendServerRequest(urlString `/contact-channels?user_id=${userId}`, {
273
274
  method: "GET",
274
275
  }, null);
275
276
  const json = await response.json();
276
277
  return json.items;
277
278
  }
278
279
  async sendServerContactChannelVerificationEmail(userId, contactChannelId, callbackUrl) {
279
- await this.sendServerRequest(`/contact-channels/${userId}/${contactChannelId}/send-verification-code`, {
280
+ await this.sendServerRequest(urlString `/contact-channels/${userId}/${contactChannelId}/send-verification-code`, {
280
281
  method: "POST",
281
282
  headers: {
282
283
  "content-type": "application/json",
@@ -79,7 +79,6 @@ export declare const emailPortSchema: yup.NumberSchema<number | undefined, yup.A
79
79
  export declare const emailUsernameSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
80
80
  export declare const emailSenderEmailSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
81
81
  export declare const emailPasswordSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
82
- export declare const projectTrustedDomainSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
83
82
  export declare const handlerPathSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
84
83
  export declare class ReplaceFieldWithOwnUserId extends Error {
85
84
  readonly path: string;
@@ -248,12 +248,11 @@ export const oauthMicrosoftTenantIdSchema = yupString().meta({ openapiField: { d
248
248
  export const emailTypeSchema = yupString().oneOf(['shared', 'standard']).meta({ openapiField: { description: 'Email provider type, one of shared, standard. "shared" uses Stack shared email provider and it is only meant for development. "standard" uses your own email server and will have your email address as the sender.', exampleValue: 'standard' } });
249
249
  export const emailSenderNameSchema = yupString().meta({ openapiField: { description: 'Email sender name. Needs to be specified when using type="standard"', exampleValue: 'Stack' } });
250
250
  export const emailHostSchema = yupString().meta({ openapiField: { description: 'Email host. Needs to be specified when using type="standard"', exampleValue: 'smtp.your-domain.com' } });
251
- export const emailPortSchema = yupNumber().meta({ openapiField: { description: 'Email port. Needs to be specified when using type="standard"', exampleValue: 587 } });
251
+ export const emailPortSchema = yupNumber().min(0).max(65535).meta({ openapiField: { description: 'Email port. Needs to be specified when using type="standard"', exampleValue: 587 } });
252
252
  export const emailUsernameSchema = yupString().meta({ openapiField: { description: 'Email username. Needs to be specified when using type="standard"', exampleValue: 'smtp-email' } });
253
253
  export const emailSenderEmailSchema = emailSchema.meta({ openapiField: { description: 'Email sender email. Needs to be specified when using type="standard"', exampleValue: 'example@your-domain.com' } });
254
254
  export const emailPasswordSchema = passwordSchema.meta({ openapiField: { description: 'Email password. Needs to be specified when using type="standard"', exampleValue: 'your-email-password' } });
255
255
  // Project domain config
256
- export const projectTrustedDomainSchema = urlSchema.test('is-https', 'Trusted domain must start with https://', (value) => value?.startsWith('https://')).meta({ openapiField: { description: 'Your domain URL. Make sure you own and trust this domain. Needs to start with https://', exampleValue: 'https://example.com' } });
257
256
  export const handlerPathSchema = yupString().test('is-handler-path', 'Handler path must start with /', (value) => value?.startsWith('/')).meta({ openapiField: { description: 'Handler path. If you did not setup a custom handler path, it should be "/handler" by default. It needs to start with /', exampleValue: '/handler' } });
258
257
  // Users
259
258
  export class ReplaceFieldWithOwnUserId extends Error {
@@ -17,16 +17,16 @@ export declare class AsyncCache<D extends any[], T> {
17
17
  refreshWhere(predicate: (dependencies: D) => boolean): Promise<void>;
18
18
  readonly isCacheAvailable: (key: D) => boolean;
19
19
  readonly getIfCached: (key: D) => ({
20
+ status: "error";
21
+ error: unknown;
22
+ } & {
23
+ status: "error";
24
+ }) | ({
20
25
  status: "pending";
21
26
  } & {
22
27
  progress: void;
23
28
  } & {
24
29
  status: "pending";
25
- }) | ({
26
- status: "error";
27
- error: unknown;
28
- } & {
29
- status: "error";
30
30
  }) | ({
31
31
  status: "ok";
32
32
  data: T;
@@ -57,16 +57,16 @@ declare class AsyncValueCache<T> {
57
57
  });
58
58
  isCacheAvailable(): boolean;
59
59
  getIfCached(): ({
60
+ status: "error";
61
+ error: unknown;
62
+ } & {
63
+ status: "error";
64
+ }) | ({
60
65
  status: "pending";
61
66
  } & {
62
67
  progress: void;
63
68
  } & {
64
69
  status: "pending";
65
- }) | ({
66
- status: "error";
67
- error: unknown;
68
- } & {
69
- status: "error";
70
70
  }) | ({
71
71
  status: "ok";
72
72
  data: T;
@@ -67,7 +67,7 @@ export class StackAssertionError extends Error {
67
67
  StackAssertionError.prototype.name = "StackAssertionError";
68
68
  export function errorToNiceString(error) {
69
69
  if (!(error instanceof Error))
70
- return `${typeof error}<${error}>`;
70
+ return `${typeof error}<${nicify(error)}>`;
71
71
  let stack = error.stack ?? "";
72
72
  const toString = error.toString();
73
73
  if (!stack.startsWith(toString))
@@ -1,3 +1,4 @@
1
+ import { KnownError } from "..";
1
2
  import { StackAssertionError, captureError, concatStacktraces } from "./errors";
2
3
  import { DependenciesMap } from "./maps";
3
4
  import { Result } from "./results";
@@ -105,7 +106,12 @@ export function runAsynchronouslyWithAlert(...args) {
105
106
  return runAsynchronously(args[0], {
106
107
  ...args[1],
107
108
  onError: error => {
108
- alert(`An unhandled error occurred. Please ${process.env.NODE_ENV === "development" ? `check the browser console for the full error.` : "report this to the developer."}\n\n${error}`);
109
+ if (error instanceof KnownError && process.env.NODE_ENV.includes("production")) {
110
+ alert(error.message);
111
+ }
112
+ else {
113
+ alert(`An unhandled error occurred. Please ${process.env.NODE_ENV === "development" ? `check the browser console for the full error.` : "report this to the developer."}\n\n${error}`);
114
+ }
109
115
  args[1]?.onError?.(error);
110
116
  },
111
117
  }, ...args.slice(2));
@@ -60,12 +60,6 @@ export declare class AsyncStore<T> implements ReadonlyAsyncStore<T> {
60
60
  isAvailable(): boolean;
61
61
  isRejected(): boolean;
62
62
  get(): ({
63
- status: "pending";
64
- } & {
65
- progress: void;
66
- } & {
67
- status: "pending";
68
- }) | ({
69
63
  status: "error";
70
64
  error: unknown;
71
65
  } & {
@@ -75,6 +69,12 @@ export declare class AsyncStore<T> implements ReadonlyAsyncStore<T> {
75
69
  data: T;
76
70
  } & {
77
71
  status: "ok";
72
+ }) | ({
73
+ status: "pending";
74
+ } & {
75
+ progress: void;
76
+ } & {
77
+ status: "pending";
78
78
  });
79
79
  getOrWait(): ReactPromise<T>;
80
80
  _setIfLatest(result: Result<T>, curCounter: number): boolean;
@@ -67,11 +67,7 @@ export function trimLines(s) {
67
67
  * Useful for implementing your own template literal tags.
68
68
  */
69
69
  export function templateIdentity(strings, ...values) {
70
- if (strings.length === 0)
71
- return "";
72
- if (values.length !== strings.length - 1)
73
- throw new Error("Invalid number of values; must be one less than strings");
74
- return strings.slice(1).reduce((result, string, i) => `${result}${values[i] ?? "n/a"}${string}`, strings[0]);
70
+ return strings.reduce((result, str, i) => result + str + (values[i] ?? ''), '');
75
71
  }
76
72
  export function deindent(strings, ...values) {
77
73
  if (typeof strings === "string")
@@ -3,3 +3,15 @@ export declare function isValidUrl(url: string): boolean;
3
3
  export declare function isLocalhost(urlOrString: string | URL): boolean;
4
4
  export declare function isRelative(url: string): boolean;
5
5
  export declare function getRelativePart(url: URL): string;
6
+ /**
7
+ * A template literal tag that returns a URL.
8
+ *
9
+ * Any values passed are encoded.
10
+ */
11
+ export declare function url(strings: TemplateStringsArray | readonly string[], ...values: (string | number | boolean)[]): URL;
12
+ /**
13
+ * A template literal tag that returns a URL string.
14
+ *
15
+ * Any values passed are encoded.
16
+ */
17
+ export declare function urlString(strings: TemplateStringsArray | readonly string[], ...values: (string | number | boolean)[]): string;
@@ -1,4 +1,5 @@
1
1
  import { generateSecureRandomString } from "./crypto";
2
+ import { templateIdentity } from "./strings";
2
3
  export function createUrlIfValid(...args) {
3
4
  try {
4
5
  return new URL(...args);
@@ -34,3 +35,19 @@ export function isRelative(url) {
34
35
  export function getRelativePart(url) {
35
36
  return url.pathname + url.search + url.hash;
36
37
  }
38
+ /**
39
+ * A template literal tag that returns a URL.
40
+ *
41
+ * Any values passed are encoded.
42
+ */
43
+ export function url(strings, ...values) {
44
+ return new URL(urlString(strings, ...values));
45
+ }
46
+ /**
47
+ * A template literal tag that returns a URL string.
48
+ *
49
+ * Any values passed are encoded.
50
+ */
51
+ export function urlString(strings, ...values) {
52
+ return templateIdentity(strings, values.map(encodeURIComponent));
53
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackframe/stack-shared",
3
- "version": "2.7.12",
3
+ "version": "2.7.14",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "type": "module",
@@ -52,7 +52,7 @@
52
52
  "oauth4webapi": "^2.10.3",
53
53
  "semver": "^7.6.3",
54
54
  "uuid": "^9.0.1",
55
- "@stackframe/stack-sc": "2.7.12"
55
+ "@stackframe/stack-sc": "2.7.14"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@sentry/nextjs": "^8.40.0",