@stackframe/stack-shared 2.7.8 → 2.7.10

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,20 @@
1
1
  # @stackframe/stack-shared
2
2
 
3
+ ## 2.7.10
4
+
5
+ ### Patch Changes
6
+
7
+ - Various changes
8
+ - Updated dependencies
9
+ - @stackframe/stack-sc@2.7.10
10
+
11
+ ## 2.7.9
12
+
13
+ ### Patch Changes
14
+
15
+ - Various changes
16
+ - @stackframe/stack-sc@2.7.9
17
+
3
18
  ## 2.7.8
4
19
 
5
20
  ### Patch Changes
@@ -121,7 +121,7 @@ export declare const KnownErrors: {
121
121
  };
122
122
  AdminAccessTokenExpired: KnownErrorConstructor<KnownError & KnownErrorBrand<"PROJECT_AUTHENTICATION_ERROR"> & {
123
123
  constructorArgs: [statusCode: number, humanReadableMessage: string, details?: Json | undefined];
124
- } & KnownErrorBrand<"INVALID_PROJECT_AUTHENTICATION"> & KnownErrorBrand<"INVALID_ADMIN_ACCESS_TOKEN"> & KnownErrorBrand<"ADMIN_ACCESS_TOKEN_EXPIRED">, []> & {
124
+ } & KnownErrorBrand<"INVALID_PROJECT_AUTHENTICATION"> & KnownErrorBrand<"INVALID_ADMIN_ACCESS_TOKEN"> & KnownErrorBrand<"ADMIN_ACCESS_TOKEN_EXPIRED">, [expiredAt: Date | undefined]> & {
125
125
  errorCode: "ADMIN_ACCESS_TOKEN_EXPIRED";
126
126
  };
127
127
  InvalidProjectForAdminAccessToken: KnownErrorConstructor<KnownError & KnownErrorBrand<"PROJECT_AUTHENTICATION_ERROR"> & {
@@ -194,7 +194,7 @@ export declare const KnownErrors: {
194
194
  };
195
195
  AccessTokenExpired: KnownErrorConstructor<KnownError & KnownErrorBrand<"SESSION_AUTHENTICATION_ERROR"> & {
196
196
  constructorArgs: [statusCode: number, humanReadableMessage: string, details?: Json | undefined];
197
- } & KnownErrorBrand<"INVALID_SESSION_AUTHENTICATION"> & KnownErrorBrand<"INVALID_ACCESS_TOKEN"> & KnownErrorBrand<"ACCESS_TOKEN_EXPIRED">, []> & {
197
+ } & KnownErrorBrand<"INVALID_SESSION_AUTHENTICATION"> & KnownErrorBrand<"INVALID_ACCESS_TOKEN"> & KnownErrorBrand<"ACCESS_TOKEN_EXPIRED">, [expiredAt: Date | undefined]> & {
198
198
  errorCode: "ACCESS_TOKEN_EXPIRED";
199
199
  };
200
200
  InvalidProjectForAccessToken: KnownErrorConstructor<KnownError & KnownErrorBrand<"SESSION_AUTHENTICATION_ERROR"> & {
@@ -171,10 +171,11 @@ const UnparsableAdminAccessToken = createKnownErrorConstructor(InvalidAdminAcces
171
171
  401,
172
172
  "Admin access token is not parsable.",
173
173
  ], () => []);
174
- const AdminAccessTokenExpired = createKnownErrorConstructor(InvalidAdminAccessToken, "ADMIN_ACCESS_TOKEN_EXPIRED", () => [
174
+ const AdminAccessTokenExpired = createKnownErrorConstructor(InvalidAdminAccessToken, "ADMIN_ACCESS_TOKEN_EXPIRED", (expiredAt) => [
175
175
  401,
176
- "Admin access token has expired. Please refresh it and try again.",
177
- ], () => []);
176
+ `Admin access token has expired. Please refresh it and try again.${expiredAt ? ` (The access token expired at ${expiredAt.toISOString()}.)` : ""}`,
177
+ { expired_at_millis: expiredAt?.getTime() ?? null },
178
+ ], (json) => [json.expired_at_millis ?? undefined]);
178
179
  const InvalidProjectForAdminAccessToken = createKnownErrorConstructor(InvalidAdminAccessToken, "INVALID_PROJECT_FOR_ADMIN_ACCESS_TOKEN", () => [
179
180
  401,
180
181
  "Admin access tokens must be created on the internal project.",
@@ -240,10 +241,11 @@ const UnparsableAccessToken = createKnownErrorConstructor(InvalidAccessToken, "U
240
241
  401,
241
242
  "Access token is not parsable.",
242
243
  ], () => []);
243
- const AccessTokenExpired = createKnownErrorConstructor(InvalidAccessToken, "ACCESS_TOKEN_EXPIRED", () => [
244
+ const AccessTokenExpired = createKnownErrorConstructor(InvalidAccessToken, "ACCESS_TOKEN_EXPIRED", (expiredAt) => [
244
245
  401,
245
- "Access token has expired. Please refresh it and try again.",
246
- ], () => []);
246
+ `Access token has expired. Please refresh it and try again.${expiredAt ? ` (The access token expired at ${expiredAt.toISOString()}.)` : ""}`,
247
+ { expired_at_millis: expiredAt?.getTime() ?? null },
248
+ ], (json) => [json.expired_at_millis ?? undefined]);
247
249
  const InvalidProjectForAccessToken = createKnownErrorConstructor(InvalidAccessToken, "INVALID_PROJECT_FOR_ACCESS_TOKEN", () => [
248
250
  401,
249
251
  "Access token not valid for this project.",
@@ -4,6 +4,10 @@ declare module "yup" {
4
4
  nonEmpty(message?: string): StringSchema<TType, TContext, TDefault, TFlags>;
5
5
  empty(): StringSchema<TType, TContext, TDefault, TFlags>;
6
6
  }
7
+ interface Schema<TType, TContext, TDefault, TFlags> {
8
+ getNested<K extends keyof TType>(path: K): yup.Schema<TType[K], TContext, TDefault, TFlags>;
9
+ concat<U extends yup.AnySchema>(schema: U): yup.Schema<Omit<TType, keyof yup.InferType<U>> & yup.InferType<U>, TContext, TDefault, TFlags>;
10
+ }
7
11
  }
8
12
  export declare function yupValidate<S extends yup.ISchema<any>>(schema: S, obj: unknown, options?: yup.ValidateOptions & {
9
13
  currentUserId?: string | null;
@@ -23,6 +27,7 @@ export declare function yupTuple<T extends [unknown, ...unknown[]]>(...args: Par
23
27
  export declare function yupObject<A extends yup.Maybe<yup.AnyObject>, B extends yup.ObjectShape>(...args: Parameters<typeof yup.object<A, B>>): yup.ObjectSchema<yup.TypeFromShape<B, yup.AnyObject> extends infer T ? T extends yup.TypeFromShape<B, yup.AnyObject> ? T extends {} ? { [k in keyof T]: T[k]; } : T : never : never, yup.AnyObject, yup.DefaultFromShape<B> extends infer T_1 ? T_1 extends yup.DefaultFromShape<B> ? T_1 extends {} ? { [k_1 in keyof T_1]: T_1[k_1]; } : T_1 : never : never, "">;
24
28
  export declare function yupNever(): yup.MixedSchema<never>;
25
29
  export declare function yupUnion<T extends yup.ISchema<any>[]>(...args: T): yup.MixedSchema<yup.InferType<T[number]>>;
30
+ export declare function ensureObjectSchema<T extends yup.AnyObject>(schema: yup.Schema<T>): yup.ObjectSchema<T> & typeof schema;
26
31
  export declare const adaptSchema: yup.MixedSchema<typeof StackAdaptSentinel | undefined, yup.AnyObject, undefined, "">;
27
32
  /**
28
33
  * Yup's URL schema does not recognize some URLs (including `http://localhost`) as a valid URL. This schema is a workaround for that.
@@ -13,6 +13,11 @@ yup.addMethod(yup.string, "nonEmpty", function (message) {
13
13
  return value !== "";
14
14
  });
15
15
  });
16
+ yup.addMethod(yup.Schema, "getNested", function (path) {
17
+ if (!path.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/))
18
+ throw new StackAssertionError(`yupSchema.getNested can currently only be used with alphanumeric keys. Fix this in the future. Provided key: ${path}`);
19
+ return yup.reach(this, path);
20
+ });
16
21
  export async function yupValidate(schema, obj, options) {
17
22
  try {
18
23
  return await schema.validate(obj, {
@@ -155,6 +160,11 @@ export function yupUnion(...args) {
155
160
  });
156
161
  });
157
162
  }
163
+ export function ensureObjectSchema(schema) {
164
+ if (!(schema instanceof yup.ObjectSchema))
165
+ throw new StackAssertionError(`assertObjectSchema: schema is not an ObjectSchema: ${schema.describe().type}`);
166
+ return schema;
167
+ }
158
168
  // Common
159
169
  export const adaptSchema = yupMixed();
160
170
  /**
@@ -0,0 +1,4 @@
1
+ export type Truthy<T> = T extends null | undefined | 0 | "" | false ? false : true;
2
+ export type Falsy<T> = T extends null | undefined | 0 | "" | false ? true : false;
3
+ export declare function isTruthy<T>(value: T): value is T & Truthy<T>;
4
+ export declare function isFalsy<T>(value: T): value is T & Falsy<T>;
@@ -0,0 +1,6 @@
1
+ export function isTruthy(value) {
2
+ return !!value;
3
+ }
4
+ export function isFalsy(value) {
5
+ return !value;
6
+ }
@@ -0,0 +1,4 @@
1
+ export declare function list(path: string): Promise<string[]>;
2
+ export declare function listRecursively(p: string, options?: {
3
+ excludeDirectories?: boolean;
4
+ }): Promise<string[]>;
@@ -0,0 +1,22 @@
1
+ import * as stackFs from "fs";
2
+ import * as path from "path";
3
+ export async function list(path) {
4
+ return await stackFs.promises.readdir(path);
5
+ }
6
+ export async function listRecursively(p, options = {}) {
7
+ const files = await list(p);
8
+ return [
9
+ ...(await Promise.all(files.map(async (fileName) => {
10
+ const filePath = path.join(p, fileName);
11
+ if ((await stackFs.promises.stat(filePath)).isDirectory()) {
12
+ return [
13
+ ...(await listRecursively(filePath, options)),
14
+ ...(options.excludeDirectories ? [] : [filePath]),
15
+ ];
16
+ }
17
+ else {
18
+ return [filePath];
19
+ }
20
+ }))).flat(),
21
+ ];
22
+ }
@@ -1,2 +1,3 @@
1
1
  export declare function prettyPrintWithMagnitudes(num: number): string;
2
2
  export declare function toFixedMax(num: number, maxDecimals: number): string;
3
+ export declare function numberCompare(a: number, b: number): number;
@@ -24,3 +24,6 @@ export function prettyPrintWithMagnitudes(num) {
24
24
  export function toFixedMax(num, maxDecimals) {
25
25
  return num.toFixed(maxDecimals).replace(/\.?0+$/, "");
26
26
  }
27
+ export function numberCompare(a, b) {
28
+ return Math.sign(a - b);
29
+ }
@@ -45,6 +45,7 @@ export declare function deindent(code: string): string;
45
45
  export declare function deindent(strings: TemplateStringsArray | readonly string[], ...values: any[]): string;
46
46
  export declare function extractScopes(scope: string, removeDuplicates?: boolean): string[];
47
47
  export declare function mergeScopeStrings(...scopes: string[]): string;
48
+ export declare function escapeTemplateLiteral(s: string): string;
48
49
  export type Nicifiable = {
49
50
  getNicifiableKeys?(): PropertyKey[];
50
51
  getNicifiedObjectExtraLines?(): string[];
@@ -103,15 +103,20 @@ export function deindent(strings, ...values) {
103
103
  return templateIdentity(deindentedStrings, ...indentedValues);
104
104
  }
105
105
  export function extractScopes(scope, removeDuplicates = true) {
106
+ // TODO what is this for? can we move this into the OAuth code in the backend?
106
107
  const trimmedString = scope.trim();
107
108
  const scopesArray = trimmedString.split(/\s+/);
108
109
  const filtered = scopesArray.filter(scope => scope.length > 0);
109
110
  return removeDuplicates ? [...new Set(filtered)] : filtered;
110
111
  }
111
112
  export function mergeScopeStrings(...scopes) {
113
+ // TODO what is this for? can we move this into the OAuth code in the backend?
112
114
  const allScope = scopes.map((s) => extractScopes(s)).flat().join(" ");
113
115
  return extractScopes(allScope).join(" ");
114
116
  }
117
+ export function escapeTemplateLiteral(s) {
118
+ return s.replaceAll("`", "\\`").replaceAll("\\", "\\\\").replaceAll("$", "\\$");
119
+ }
115
120
  /**
116
121
  * Some classes have different constructor names in different environments (eg. `Headers` is sometimes called `_Headers`,
117
122
  * so we create an object of overrides to handle these cases.
@@ -165,11 +170,27 @@ export function nicify(value, options = {}) {
165
170
  });
166
171
  };
167
172
  switch (typeof value) {
168
- case "string":
169
173
  case "boolean":
170
174
  case "number": {
171
175
  return JSON.stringify(value);
172
176
  }
177
+ case "string": {
178
+ const isDeindentable = (v) => deindent(v) === v && v.includes("\n");
179
+ const wrapInDeindent = (v) => deindent `
180
+ deindent\`
181
+ ${currentIndent + lineIndent}${escapeTemplateLiteral(value).replaceAll("\n", nl + lineIndent)}
182
+ ${currentIndent}\`
183
+ `;
184
+ if (isDeindentable(value)) {
185
+ return wrapInDeindent(value);
186
+ }
187
+ else if (value.endsWith("\n") && isDeindentable(value.slice(0, -1))) {
188
+ return wrapInDeindent(value.slice(0, -1)) + ' + "\\n"';
189
+ }
190
+ else {
191
+ return JSON.stringify(value);
192
+ }
193
+ }
173
194
  case "undefined": {
174
195
  return "undefined";
175
196
  }
@@ -205,7 +226,7 @@ export function nicify(value, options = {}) {
205
226
  }
206
227
  }
207
228
  if (value instanceof URL) {
208
- return `URL(${JSON.stringify(value.toString())})`;
229
+ return `URL(${nicify(value.toString())})`;
209
230
  }
210
231
  if (ArrayBuffer.isView(value)) {
211
232
  return `${value.constructor.name}([${value.toString()}])`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackframe/stack-shared",
3
- "version": "2.7.8",
3
+ "version": "2.7.10",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "files": [
@@ -51,7 +51,7 @@
51
51
  "oauth4webapi": "^2.10.3",
52
52
  "semver": "^7.6.3",
53
53
  "uuid": "^9.0.1",
54
- "@stackframe/stack-sc": "2.7.8"
54
+ "@stackframe/stack-sc": "2.7.10"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@sentry/nextjs": "^8.40.0",