@stackframe/stack-shared 2.8.2 → 2.8.5

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 (40) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/config/format.d.ts +38 -0
  3. package/dist/config/format.js +224 -0
  4. package/dist/config/schema.d.ts +721 -0
  5. package/dist/config/schema.js +185 -0
  6. package/dist/crud.js +1 -1
  7. package/dist/interface/adminInterface.d.ts +7 -7
  8. package/dist/interface/adminInterface.js +7 -7
  9. package/dist/interface/clientInterface.d.ts +46 -4
  10. package/dist/interface/clientInterface.js +66 -2
  11. package/dist/interface/crud/current-user.d.ts +2 -2
  12. package/dist/interface/crud/{api-keys.d.ts → internal-api-keys.d.ts} +7 -7
  13. package/dist/interface/crud/{api-keys.js → internal-api-keys.js} +10 -10
  14. package/dist/interface/crud/project-api-keys.d.ts +188 -0
  15. package/dist/interface/crud/project-api-keys.js +76 -0
  16. package/dist/interface/crud/projects.d.ts +53 -20
  17. package/dist/interface/crud/projects.js +15 -5
  18. package/dist/interface/crud/team-member-profiles.d.ts +4 -4
  19. package/dist/interface/crud/users.d.ts +8 -8
  20. package/dist/interface/serverInterface.d.ts +2 -1
  21. package/dist/interface/serverInterface.js +4 -0
  22. package/dist/interface/webhooks.d.ts +2 -2
  23. package/dist/known-errors.d.ts +35 -1
  24. package/dist/known-errors.js +42 -4
  25. package/dist/schema-fields.d.ts +6 -5
  26. package/dist/schema-fields.js +36 -3
  27. package/dist/utils/api-keys.d.ts +23 -0
  28. package/dist/utils/api-keys.js +76 -0
  29. package/dist/utils/bytes.d.ts +3 -0
  30. package/dist/utils/bytes.js +55 -6
  31. package/dist/utils/caches.d.ts +10 -10
  32. package/dist/utils/hashes.d.ts +1 -1
  33. package/dist/utils/hashes.js +1 -3
  34. package/dist/utils/objects.d.ts +35 -2
  35. package/dist/utils/objects.js +128 -0
  36. package/dist/utils/promises.d.ts +1 -1
  37. package/dist/utils/promises.js +9 -10
  38. package/dist/utils/stores.d.ts +6 -6
  39. package/dist/utils/types.d.ts +17 -0
  40. package/package.json +2 -1
@@ -2,9 +2,7 @@ import bcrypt from 'bcrypt';
2
2
  import { StackAssertionError } from './errors';
3
3
  export async function sha512(input) {
4
4
  const bytes = typeof input === "string" ? new TextEncoder().encode(input) : input;
5
- return await crypto.subtle.digest("SHA-512", bytes).then(buf => {
6
- return Array.prototype.map.call(new Uint8Array(buf), x => (('00' + x.toString(16)).slice(-2))).join('');
7
- });
5
+ return new Uint8Array(await crypto.subtle.digest("SHA-512", bytes));
8
6
  }
9
7
  export async function hashPassword(password) {
10
8
  const passwordBytes = new TextEncoder().encode(password);
@@ -2,6 +2,9 @@ export declare function isNotNull<T>(value: T): value is NonNullable<T>;
2
2
  export type DeepPartial<T> = T extends object ? {
3
3
  [P in keyof T]?: DeepPartial<T[P]>;
4
4
  } : T;
5
+ export type DeepRequired<T> = T extends object ? {
6
+ [P in keyof T]-?: DeepRequired<T[P]>;
7
+ } : T;
5
8
  /**
6
9
  * Assumes both objects are primitives, arrays, or non-function plain objects, and compares them deeply.
7
10
  *
@@ -10,9 +13,16 @@ export type DeepPartial<T> = T extends object ? {
10
13
  export declare function deepPlainEquals<T>(obj1: T, obj2: unknown, options?: {
11
14
  ignoreUndefinedValues?: boolean;
12
15
  }): obj2 is T;
16
+ export declare function isCloneable<T>(obj: T): obj is Exclude<T, symbol | Function>;
17
+ export declare function shallowClone<T extends object>(obj: T): T;
13
18
  export declare function deepPlainClone<T>(obj: T): T;
19
+ export type DeepMerge<T, U> = Omit<T, keyof U> & Omit<U, keyof T> & DeepMergeInner<Pick<T, keyof U & keyof T>, Pick<U, keyof U & keyof T>>;
20
+ type DeepMergeInner<T, U> = {
21
+ [K in keyof U]-?: undefined extends U[K] ? K extends keyof T ? T[K] extends object ? Exclude<U[K], undefined> extends object ? DeepMerge<T[K], Exclude<U[K], undefined>> : T[K] | Exclude<U[K], undefined> : T[K] | Exclude<U[K], undefined> : Exclude<U[K], undefined> : K extends keyof T ? T[K] extends object ? U[K] extends object ? DeepMerge<T[K], U[K]> : U[K] : U[K] : U[K];
22
+ };
23
+ export declare function deepMerge<T extends {}, U extends {}>(baseObj: T, mergeObj: U): DeepMerge<T, U>;
14
24
  export declare function typedEntries<T extends {}>(obj: T): [keyof T, T[keyof T]][];
15
- export declare function typedFromEntries<K extends PropertyKey, V>(entries: [K, V][]): Record<K, V>;
25
+ export declare function typedFromEntries<K extends PropertyKey, V>(entries: (readonly [K, V])[]): Record<K, V>;
16
26
  export declare function typedKeys<T extends {}>(obj: T): (keyof T)[];
17
27
  export declare function typedValues<T extends {}>(obj: T): T[keyof T][];
18
28
  export declare function typedAssign<T extends {}, U extends {}>(target: T, source: U): T & U;
@@ -25,7 +35,30 @@ export type FilterUndefined<T> = {
25
35
  * Returns a new object with all undefined values removed. Useful when spreading optional parameters on an object, as
26
36
  * TypeScript's `Partial<XYZ>` type allows `undefined` values.
27
37
  */
28
- export declare function filterUndefined<T extends {}>(obj: T): FilterUndefined<T>;
38
+ export declare function filterUndefined<T extends object>(obj: T): FilterUndefined<T>;
39
+ export type FilterUndefinedOrNull<T> = FilterUndefined<{
40
+ [k in keyof T]: null extends T[k] ? NonNullable<T[k]> | undefined : T[k];
41
+ }>;
42
+ /**
43
+ * Returns a new object with all undefined and null values removed. Useful when spreading optional parameters on an object, as
44
+ * TypeScript's `Partial<XYZ>` type allows `undefined` values.
45
+ */
46
+ export declare function filterUndefinedOrNull<T extends object>(obj: T): FilterUndefinedOrNull<T>;
47
+ export type DeepFilterUndefined<T> = T extends object ? FilterUndefined<{
48
+ [K in keyof T]: DeepFilterUndefined<T[K]>;
49
+ }> : T;
50
+ export declare function deepFilterUndefined<T extends object>(obj: T): DeepFilterUndefined<T>;
29
51
  export declare function pick<T extends {}, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>;
30
52
  export declare function omit<T extends {}, K extends keyof T>(obj: T, keys: K[]): Omit<T, K>;
31
53
  export declare function split<T extends {}, K extends keyof T>(obj: T, keys: K[]): [Pick<T, K>, Omit<T, K>];
54
+ export declare function set<T extends object, K extends keyof T>(obj: T, key: K, value: T[K]): void;
55
+ export declare function get<T extends object, K extends keyof T>(obj: T, key: K): T[K];
56
+ export declare function has<T extends object, K extends keyof T>(obj: T, key: K): obj is T & {
57
+ [k in K]: unknown;
58
+ };
59
+ export declare function hasAndNotUndefined<T extends object, K extends keyof T>(obj: T, key: K): obj is T & {
60
+ [k in K]: Exclude<T[K], undefined>;
61
+ };
62
+ export declare function deleteKey<T extends object, K extends keyof T>(obj: T, key: K): void;
63
+ export declare function isObjectLike(value: unknown): value is object;
64
+ export {};
@@ -1,4 +1,5 @@
1
1
  import { StackAssertionError } from "./errors";
2
+ import { identity } from "./functions";
2
3
  export function isNotNull(value) {
3
4
  return value !== null && value !== undefined;
4
5
  }
@@ -78,6 +79,21 @@ import.meta.vitest?.test("deepPlainEquals", ({ expect }) => {
78
79
  expect(deepPlainEquals({ a: 1, b: undefined }, { a: 1 }, { ignoreUndefinedValues: true })).toBe(true);
79
80
  expect(deepPlainEquals({ a: 1, b: undefined }, { a: 1 })).toBe(false);
80
81
  });
82
+ export function isCloneable(obj) {
83
+ return typeof obj !== 'symbol' && typeof obj !== 'function';
84
+ }
85
+ export function shallowClone(obj) {
86
+ if (!isCloneable(obj))
87
+ throw new StackAssertionError("shallowClone does not support symbols or functions", { obj });
88
+ if (Array.isArray(obj))
89
+ return obj.map(identity);
90
+ return { ...obj };
91
+ }
92
+ import.meta.vitest?.test("shallowClone", ({ expect }) => {
93
+ expect(shallowClone({ a: 1, b: 2 })).toEqual({ a: 1, b: 2 });
94
+ expect(shallowClone([1, 2, 3])).toEqual([1, 2, 3]);
95
+ expect(() => shallowClone(() => { })).toThrow();
96
+ });
81
97
  export function deepPlainClone(obj) {
82
98
  if (typeof obj === 'function')
83
99
  throw new StackAssertionError("deepPlainClone does not support functions");
@@ -116,6 +132,75 @@ import.meta.vitest?.test("deepPlainClone", ({ expect }) => {
116
132
  expect(() => deepPlainClone(() => { })).toThrow();
117
133
  expect(() => deepPlainClone(Symbol())).toThrow();
118
134
  });
135
+ export function deepMerge(baseObj, mergeObj) {
136
+ if ([baseObj, mergeObj, ...Object.values(baseObj), ...Object.values(mergeObj)].some(o => !isCloneable(o)))
137
+ throw new StackAssertionError("deepMerge does not support functions or symbols", { baseObj, mergeObj });
138
+ const res = shallowClone(baseObj);
139
+ for (const [key, mergeValue] of Object.entries(mergeObj)) {
140
+ if (has(res, key)) {
141
+ const baseValue = get(res, key);
142
+ if (isObjectLike(baseValue) && isObjectLike(mergeValue)) {
143
+ set(res, key, deepMerge(baseValue, mergeValue));
144
+ continue;
145
+ }
146
+ }
147
+ set(res, key, mergeValue);
148
+ }
149
+ return res;
150
+ }
151
+ import.meta.vitest?.test("deepMerge", ({ expect }) => {
152
+ // Test merging flat objects
153
+ expect(deepMerge({ a: 1 }, { b: 2 })).toEqual({ a: 1, b: 2 });
154
+ expect(deepMerge({ a: 1 }, { a: 2 })).toEqual({ a: 2 });
155
+ expect(deepMerge({ a: 1, b: 2 }, { b: 3, c: 4 })).toEqual({ a: 1, b: 3, c: 4 });
156
+ // Test with nested objects
157
+ expect(deepMerge({ a: { x: 1, y: 2 }, b: 3 }, { a: { y: 3, z: 4 }, c: 5 })).toEqual({ a: { x: 1, y: 3, z: 4 }, b: 3, c: 5 });
158
+ // Test with arrays
159
+ expect(deepMerge({ a: [1, 2], b: 3 }, { a: [3, 4], c: 5 })).toEqual({ a: [3, 4], b: 3, c: 5 });
160
+ // Test with null values
161
+ expect(deepMerge({ a: { x: 1 }, b: null }, { a: { y: 2 }, b: { z: 3 } })).toEqual({ a: { x: 1, y: 2 }, b: { z: 3 } });
162
+ // Test with undefined values
163
+ expect(deepMerge({ a: 1, b: undefined }, { b: 2, c: 3 })).toEqual({ a: 1, b: 2, c: 3 });
164
+ // Test deeply nested structures
165
+ expect(deepMerge({
166
+ a: {
167
+ x: { deep: 1 },
168
+ y: [1, 2]
169
+ },
170
+ b: 2
171
+ }, {
172
+ a: {
173
+ x: { deeper: 3 },
174
+ y: [3, 4]
175
+ },
176
+ c: 3
177
+ })).toEqual({
178
+ a: {
179
+ x: { deep: 1, deeper: 3 },
180
+ y: [3, 4]
181
+ },
182
+ b: 2,
183
+ c: 3
184
+ });
185
+ // Test with empty objects
186
+ expect(deepMerge({}, { a: 1 })).toEqual({ a: 1 });
187
+ expect(deepMerge({ a: 1 }, {})).toEqual({ a: 1 });
188
+ expect(deepMerge({}, {})).toEqual({});
189
+ // Test that original objects are not modified
190
+ const base = { a: { x: 1 }, b: 2 };
191
+ const merge = { a: { y: 2 }, c: 3 };
192
+ const baseClone = deepPlainClone(base);
193
+ const mergeClone = deepPlainClone(merge);
194
+ const result = deepMerge(base, merge);
195
+ expect(base).toEqual(baseClone);
196
+ expect(merge).toEqual(mergeClone);
197
+ expect(result).toEqual({ a: { x: 1, y: 2 }, b: 2, c: 3 });
198
+ // Test error cases
199
+ expect(() => deepMerge({ a: () => { } }, { b: 2 })).toThrow();
200
+ expect(() => deepMerge({ a: 1 }, { b: () => { } })).toThrow();
201
+ expect(() => deepMerge({ a: Symbol() }, { b: 2 })).toThrow();
202
+ expect(() => deepMerge({ a: 1 }, { b: Symbol() })).toThrow();
203
+ });
119
204
  export function typedEntries(obj) {
120
205
  return Object.entries(obj);
121
206
  }
@@ -215,6 +300,23 @@ import.meta.vitest?.test("filterUndefined", ({ expect }) => {
215
300
  expect(filterUndefined({ a: null, b: undefined })).toEqual({ a: null });
216
301
  expect(filterUndefined({ a: 0, b: "", c: false, d: undefined })).toEqual({ a: 0, b: "", c: false });
217
302
  });
303
+ /**
304
+ * Returns a new object with all undefined and null values removed. Useful when spreading optional parameters on an object, as
305
+ * TypeScript's `Partial<XYZ>` type allows `undefined` values.
306
+ */
307
+ export function filterUndefinedOrNull(obj) {
308
+ return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined && v !== null));
309
+ }
310
+ import.meta.vitest?.test("filterUndefinedOrNull", ({ expect }) => {
311
+ expect(filterUndefinedOrNull({})).toEqual({});
312
+ expect(filterUndefinedOrNull({ a: 1, b: 2 })).toEqual({ a: 1, b: 2 });
313
+ });
314
+ export function deepFilterUndefined(obj) {
315
+ return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined).map(([k, v]) => [k, isObjectLike(v) ? deepFilterUndefined(v) : v]));
316
+ }
317
+ import.meta.vitest?.test("deepFilterUndefined", ({ expect }) => {
318
+ expect(deepFilterUndefined({ a: 1, b: undefined })).toEqual({ a: 1 });
319
+ });
218
320
  export function pick(obj, keys) {
219
321
  return Object.fromEntries(Object.entries(obj).filter(([k]) => keys.includes(k)));
220
322
  }
@@ -248,3 +350,29 @@ import.meta.vitest?.test("split", ({ expect }) => {
248
350
  // Use type assertion for empty object to avoid TypeScript error
249
351
  expect(split({}, ["a"])).toEqual([{}, {}]);
250
352
  });
353
+ export function set(obj, key, value) {
354
+ Object.defineProperty(obj, key, { value, writable: true, configurable: true, enumerable: true });
355
+ }
356
+ export function get(obj, key) {
357
+ const descriptor = Object.getOwnPropertyDescriptor(obj, key);
358
+ if (!descriptor)
359
+ throw new StackAssertionError(`get: key ${String(key)} does not exist`, { obj, key });
360
+ return descriptor.value;
361
+ }
362
+ export function has(obj, key) {
363
+ return Object.prototype.hasOwnProperty.call(obj, key);
364
+ }
365
+ export function hasAndNotUndefined(obj, key) {
366
+ return has(obj, key) && get(obj, key) !== undefined;
367
+ }
368
+ export function deleteKey(obj, key) {
369
+ if (has(obj, key)) {
370
+ Reflect.deleteProperty(obj, key);
371
+ }
372
+ else {
373
+ throw new StackAssertionError(`deleteKey: key ${String(key)} does not exist`, { obj, key });
374
+ }
375
+ }
376
+ export function isObjectLike(value) {
377
+ return (typeof value === 'object' || typeof value === 'function') && value !== null;
378
+ }
@@ -31,7 +31,7 @@ export declare function pending<T>(promise: Promise<T>, options?: {
31
31
  *
32
32
  * Vercel kills serverless functions on unhandled promise rejection errors, so this is important.
33
33
  */
34
- export declare function ignoreUnhandledRejection<T extends Promise<any>>(promise: T): T;
34
+ export declare function ignoreUnhandledRejection<T extends Promise<any>>(promise: T): void;
35
35
  export declare function wait(ms: number): Promise<void>;
36
36
  export declare function waitUntil(date: Date): Promise<void>;
37
37
  export declare function runAsynchronouslyWithAlert(...args: Parameters<typeof runAsynchronously>): void;
@@ -114,7 +114,9 @@ export function rejected(reason) {
114
114
  if (rejectedCache.has([reason])) {
115
115
  return rejectedCache.get([reason]);
116
116
  }
117
- const res = Object.assign(ignoreUnhandledRejection(Promise.reject(reason)), {
117
+ const promise = Promise.reject(reason);
118
+ ignoreUnhandledRejection(promise);
119
+ const res = Object.assign(promise, {
118
120
  status: "rejected",
119
121
  reason: reason,
120
122
  });
@@ -191,22 +193,19 @@ import.meta.vitest?.test("pending", async ({ expect }) => {
191
193
  */
192
194
  export function ignoreUnhandledRejection(promise) {
193
195
  promise.catch(() => { });
194
- return promise;
195
196
  }
196
197
  import.meta.vitest?.test("ignoreUnhandledRejection", async ({ expect }) => {
197
198
  // Test with a promise that resolves
198
199
  const resolvePromise = Promise.resolve(42);
199
- const ignoredResolvePromise = ignoreUnhandledRejection(resolvePromise);
200
- expect(ignoredResolvePromise).toBe(resolvePromise); // Should return the same promise
201
- expect(await ignoredResolvePromise).toBe(42); // Should still resolve to the same value
200
+ ignoreUnhandledRejection(resolvePromise);
201
+ expect(await resolvePromise).toBe(42); // Should still resolve to the same value
202
202
  // Test with a promise that rejects
203
- const error = new Error("Test error");
204
- const rejectPromise = Promise.reject(error);
205
- const ignoredRejectPromise = ignoreUnhandledRejection(rejectPromise);
206
- expect(ignoredRejectPromise).toBe(rejectPromise); // Should return the same promise
207
203
  // The promise should still reject, but the rejection is caught internally
208
204
  // so it doesn't cause an unhandled rejection error
209
- await expect(ignoredRejectPromise).rejects.toBe(error);
205
+ const error = new Error("Test error");
206
+ const rejectPromise = Promise.reject(error);
207
+ ignoreUnhandledRejection(rejectPromise);
208
+ await expect(rejectPromise).rejects.toBe(error);
210
209
  });
211
210
  export async function wait(ms) {
212
211
  if (!Number.isFinite(ms) || ms < 0) {
@@ -60,6 +60,12 @@ 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
+ }) | ({
63
69
  status: "error";
64
70
  error: unknown;
65
71
  } & {
@@ -69,12 +75,6 @@ export declare class AsyncStore<T> implements ReadonlyAsyncStore<T> {
69
75
  data: T;
70
76
  } & {
71
77
  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;
@@ -2,3 +2,20 @@ export type IsAny<T> = 0 extends (1 & T) ? true : false;
2
2
  export type isNullish<T> = T extends null | undefined ? true : false;
3
3
  export type NullishCoalesce<T, U> = T extends null | undefined ? U : T;
4
4
  export type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never;
5
+ /**
6
+ * A variation of TypeScript's conditionals with slightly different semantics. It is the perfect type for cases where:
7
+ *
8
+ * - If all possible values are contained in `Extends`, then it will be mapped to `Then`.
9
+ * - If all possible values are not contained in `Extends`, then it will be mapped to `Otherwise`.
10
+ * - If some possible values are contained in `Extends` and some are not, then it will be mapped to `Then | Otherwise`.
11
+ *
12
+ * This is different from TypeScript's built-in conditional types (`Value extends Extends ? Then : Otherwise`), which
13
+ * returns `Otherwise` for the third case (causing unsoundness in many real-world cases).
14
+ */
15
+ export type IfAndOnlyIf<Value, Extends, Then, Otherwise> = (Value extends Extends ? never : Otherwise) | (Value & Extends extends never ? never : Then);
16
+ /**
17
+ * Can be used to prettify a type in the IDE; for example, some complicated intersected types can be flattened into a single type.
18
+ */
19
+ export type PrettifyType<T> = T extends object ? {
20
+ [K in keyof T]: T[K];
21
+ } & {} : T;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackframe/stack-shared",
3
- "version": "2.8.2",
3
+ "version": "2.8.5",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "type": "module",
@@ -45,6 +45,7 @@
45
45
  "@simplewebauthn/browser": "^11.0.0",
46
46
  "async-mutex": "^0.5.0",
47
47
  "bcrypt": "^5.1.1",
48
+ "crc": "^4.3.2",
48
49
  "elliptic": "^6.5.7",
49
50
  "ip-regex": "^5.0.0",
50
51
  "jose": "^5.2.2",