@stackframe/stack-shared 2.4.25 → 2.4.26

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,13 @@
1
1
  # @stackframe/stack-shared
2
2
 
3
+ ## 2.4.26
4
+
5
+ ### Patch Changes
6
+
7
+ - Improve docs
8
+ - Updated dependencies
9
+ - @stackframe/stack-sc@2.4.26
10
+
3
11
  ## 2.4.25
4
12
 
5
13
  ### Patch Changes
package/dist/crud.d.ts CHANGED
@@ -1,6 +1,15 @@
1
1
  import * as yup from 'yup';
2
2
  import { NullishCoalesce } from './utils/types';
3
3
  export type CrudOperation = "create" | "read" | "update" | "delete";
4
+ declare module 'yup' {
5
+ interface CustomSchemaMetadata {
6
+ openapi?: {
7
+ description?: string;
8
+ exampleValue?: any;
9
+ hide?: boolean;
10
+ };
11
+ }
12
+ }
4
13
  type InnerCrudSchema<CreateSchema extends yup.Schema<any> | undefined = yup.Schema<any> | undefined, ReadSchema extends yup.Schema<any> | undefined = yup.Schema<any> | undefined, UpdateSchema extends yup.Schema<any> | undefined = yup.Schema<any> | undefined, DeleteSchema extends yup.Schema<any> | undefined = yup.Schema<any> | undefined> = {
5
14
  createSchema: CreateSchema;
6
15
  readSchema: ReadSchema;
@@ -1,12 +1,12 @@
1
1
  import * as yup from "yup";
2
2
  import { yupJson } from "../../utils/yup";
3
- export const projectIdSchema = yup.string().meta({ description: 'Stack dashboard project ID', example: 'project-id' });
4
- export const userIdSchema = yup.string().meta({ description: 'Unique user identifier', example: 'user-id' });
5
- export const primaryEmailSchema = yup.string().meta({ description: 'Primary email', example: 'johndoe@email.com' });
6
- export const primaryEmailVerifiedSchema = yup.boolean().meta({ description: 'Primary email verified', example: true });
7
- export const userDisplayNameSchema = yup.string().meta({ description: 'User display name', example: 'John Doe' });
8
- export const selectedTeamIdSchema = yup.string().meta({ description: 'Selected team ID', example: 'team-id' });
9
- export const profileImageUrlSchema = yup.string().meta({ description: 'Profile image URL', example: 'https://example.com/image.jpg' });
10
- export const signedUpAtMillisSchema = yup.number().meta({ description: 'Signed up at milliseconds', example: 1630000000000 });
11
- export const userClientMetadataSchema = yupJson.meta({ description: 'Client metadata. Used as a data store, accessible from the client side', example: { key: 'value' } });
12
- export const userServerMetadataSchema = yupJson.meta({ description: 'Server metadata. Used as a data store, only accessible from the server side', example: { key: 'value' } });
3
+ export const projectIdSchema = yup.string().meta({ openapi: { description: 'Stack dashboard project ID', exampleValue: 'project-id' } });
4
+ export const userIdSchema = yup.string().meta({ openapi: { description: 'Unique user identifier', exampleValue: 'user-id' } });
5
+ export const primaryEmailSchema = yup.string().meta({ openapi: { description: 'Primary email', exampleValue: 'johndoe@example.com' } });
6
+ export const primaryEmailVerifiedSchema = yup.boolean().meta({ openapi: { description: 'Whether the primary email has been verified to belong to this user', exampleValue: true } });
7
+ export const userDisplayNameSchema = yup.string().meta({ openapi: { description: 'Human-readable display name', exampleValue: 'John Doe' } });
8
+ export const selectedTeamIdSchema = yup.string().meta({ openapi: { description: 'ID of the team currently selected by the user', exampleValue: 'team-id' } });
9
+ export const profileImageUrlSchema = yup.string().meta({ openapi: { description: 'Profile image URL', exampleValue: 'https://example.com/image.jpg' } });
10
+ export const signedUpAtMillisSchema = yup.number().meta({ openapi: { description: 'Signed up at milliseconds', exampleValue: 1630000000000 } });
11
+ export const userClientMetadataSchema = yupJson.meta({ openapi: { description: 'Client metadata. Used as a data store, accessible from the client side', exampleValue: { key: 'value' } } });
12
+ export const userServerMetadataSchema = yupJson.meta({ openapi: { description: 'Server metadata. Used as a data store, only accessible from the server side', exampleValue: { key: 'value' } } });
@@ -20,10 +20,10 @@ export const usersCrudServerReadSchema = yup.object({
20
20
  selectedTeamId: fieldSchema.selectedTeamIdSchema.nullable().defined(),
21
21
  profileImageUrl: fieldSchema.profileImageUrlSchema.nullable().defined(),
22
22
  signedUpAtMillis: fieldSchema.signedUpAtMillisSchema.required(),
23
- authMethod: yup.string().oneOf(["credential", "oauth"]).required().meta({ hide: true }), // not used anymore, for backwards compatibility
24
- hasPassword: yup.boolean().required().meta({ description: 'Whether the user has a password', example: true }),
25
- authWithEmail: yup.boolean().required().meta({ description: 'Whether the user can authenticate with email (email/password and magic link, depending on the project setting on the dashboard)', example: true }),
26
- oauthProviders: yup.array(yup.string().required()).required().meta({ description: 'All the OAuth providers connected to this account', example: ['google', 'github'] }),
23
+ authMethod: yup.string().oneOf(["credential", "oauth"]).required().meta({ openapi: { hide: true } }), // not used anymore, for backwards compatibility
24
+ hasPassword: yup.boolean().required().meta({ openapi: { description: 'Whether the user has a password', exampleValue: true } }),
25
+ authWithEmail: yup.boolean().required().meta({ openapi: { description: 'Whether the user can authenticate with their primary e-mail. If set to true, the user can log-in with credentials and/or magic link, if enabled in the project settings.', exampleValue: true } }),
26
+ oauthProviders: yup.array(yup.string().required()).required().meta({ openapi: { description: 'All the OAuth providers connected to this account', exampleValue: ['google', 'github'] } }),
27
27
  clientMetadata: fieldSchema.userClientMetadataSchema,
28
28
  serverMetadata: fieldSchema.userServerMetadataSchema,
29
29
  }).required();
@@ -37,7 +37,7 @@ export declare class AsyncCache<D extends any[], T> {
37
37
  readonly forceSetCachedValueAsync: (key: D, value: Promise<T>) => ReactPromise<boolean>;
38
38
  readonly refresh: (key: D) => Promise<T>;
39
39
  readonly invalidate: (key: D) => Promise<T>;
40
- readonly onChange: (key: D, callback: (value: T, oldValue: T | undefined) => void) => {
40
+ readonly onStateChange: (key: D, callback: (value: T, oldValue: T | undefined) => void) => {
41
41
  unsubscribe: () => void;
42
42
  };
43
43
  }
@@ -79,7 +79,7 @@ declare class AsyncValueCache<T> {
79
79
  forceSetCachedValueAsync(value: Promise<T>): ReactPromise<boolean>;
80
80
  refresh(): Promise<T>;
81
81
  invalidate(): Promise<T>;
82
- onChange(callback: (value: T, oldValue: T | undefined) => void): {
82
+ onStateChange(callback: (value: T, oldValue: T | undefined) => void): {
83
83
  unsubscribe: () => void;
84
84
  };
85
85
  }
@@ -49,7 +49,7 @@ export class AsyncCache {
49
49
  forceSetCachedValueAsync = this._createKeyed("forceSetCachedValueAsync");
50
50
  refresh = this._createKeyed("refresh");
51
51
  invalidate = this._createKeyed("invalidate");
52
- onChange = this._createKeyed("onChange");
52
+ onStateChange = this._createKeyed("onStateChange");
53
53
  }
54
54
  class AsyncValueCache {
55
55
  _options;
@@ -117,7 +117,7 @@ class AsyncValueCache {
117
117
  this._pendingPromise = undefined;
118
118
  return await this.refresh();
119
119
  }
120
- onChange(callback) {
120
+ onStateChange(callback) {
121
121
  const storeObj = this._store.onChange(callback);
122
122
  if (this._subscriptionsCount++ === 0 && this._options.onSubscribe) {
123
123
  const unsubscribe = this._options.onSubscribe(() => {
@@ -0,0 +1,2 @@
1
+ export declare const HTTP_METHODS: readonly ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "TRACE", "CONNECT"];
2
+ export type HttpMethod = typeof HTTP_METHODS[number];
@@ -0,0 +1 @@
1
+ export const HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "TRACE", "CONNECT"];
@@ -1,15 +1,24 @@
1
1
  import { Result } from "./results";
2
- import type { RejectedThenable, FulfilledThenable, PendingThenable } from "react";
3
- export type ReactPromise<T> = Promise<T> & (RejectedThenable<T> | FulfilledThenable<T> | PendingThenable<T>);
2
+ export type ReactPromise<T> = Promise<T> & ({
3
+ status: "rejected";
4
+ reason: unknown;
5
+ } | {
6
+ status: "fulfilled";
7
+ value: T;
8
+ } | {
9
+ status: "pending";
10
+ });
4
11
  type Resolve<T> = (value: T) => void;
5
12
  type Reject = (reason: unknown) => void;
6
13
  export declare function createPromise<T>(callback: (resolve: Resolve<T>, reject: Reject) => void): ReactPromise<T>;
7
14
  /**
8
- * Like Promise.resolve(...), but also adds the status and value properties for use with React's `use` hook.
15
+ * Like Promise.resolve(...), but also adds the status and value properties for use with React's `use` hook, and caches
16
+ * the value so that invoking `resolved` twice returns the same promise.
9
17
  */
10
18
  export declare function resolved<T>(value: T): ReactPromise<T>;
11
19
  /**
12
- * Like Promise.resolve(...), but also adds the status and value properties for use with React's `use` hook.
20
+ * Like Promise.reject(...), but also adds the status and value properties for use with React's `use` hook, and caches
21
+ * the value so that invoking `rejected` twice returns the same promise.
13
22
  */
14
23
  export declare function rejected<T>(reason: unknown): ReactPromise<T>;
15
24
  export declare function neverResolve(): ReactPromise<never>;
@@ -1,4 +1,5 @@
1
1
  import { StackAssertionError, captureError } from "./errors";
2
+ import { DependenciesMap } from "./maps";
2
3
  import { Result } from "./results";
3
4
  import { generateUuid } from "./uuids";
4
5
  export function createPromise(callback) {
@@ -29,26 +30,41 @@ export function createPromise(callback) {
29
30
  ...status === "rejected" ? { reason: valueOrReason } : {},
30
31
  });
31
32
  }
33
+ const resolvedCache = new DependenciesMap();
32
34
  /**
33
- * Like Promise.resolve(...), but also adds the status and value properties for use with React's `use` hook.
35
+ * Like Promise.resolve(...), but also adds the status and value properties for use with React's `use` hook, and caches
36
+ * the value so that invoking `resolved` twice returns the same promise.
34
37
  */
35
38
  export function resolved(value) {
36
- return Object.assign(Promise.resolve(value), {
39
+ if (resolvedCache.has([value])) {
40
+ return resolvedCache.get([value]);
41
+ }
42
+ const res = Object.assign(Promise.resolve(value), {
37
43
  status: "fulfilled",
38
44
  value,
39
45
  });
46
+ resolvedCache.set([value], res);
47
+ return res;
40
48
  }
49
+ const rejectedCache = new DependenciesMap();
41
50
  /**
42
- * Like Promise.resolve(...), but also adds the status and value properties for use with React's `use` hook.
51
+ * Like Promise.reject(...), but also adds the status and value properties for use with React's `use` hook, and caches
52
+ * the value so that invoking `rejected` twice returns the same promise.
43
53
  */
44
54
  export function rejected(reason) {
45
- return Object.assign(Promise.reject(reason), {
55
+ if (rejectedCache.has([reason])) {
56
+ return rejectedCache.get([reason]);
57
+ }
58
+ const res = Object.assign(Promise.reject(reason), {
46
59
  status: "rejected",
47
60
  reason: reason,
48
61
  });
62
+ rejectedCache.set([reason], res);
63
+ return res;
49
64
  }
65
+ const neverResolvePromise = pending(new Promise(() => { }));
50
66
  export function neverResolve() {
51
- return pending(new Promise(() => { }));
67
+ return neverResolvePromise;
52
68
  }
53
69
  export function pending(promise, options = {}) {
54
70
  const res = promise.then(value => {
@@ -9,6 +9,28 @@ export type ReadonlyStore<T> = {
9
9
  unsubscribe: () => void;
10
10
  };
11
11
  };
12
+ export type AsyncStoreStateChangeCallback<T> = (args: {
13
+ state: AsyncResult<T>;
14
+ oldState: AsyncResult<T>;
15
+ lastOkValue: T | undefined;
16
+ }) => void;
17
+ export type ReadonlyAsyncStore<T> = {
18
+ isAvailable(): boolean;
19
+ get(): AsyncResult<T, unknown, void>;
20
+ getOrWait(): ReactPromise<T>;
21
+ onChange(callback: (value: T, oldValue: T | undefined) => void): {
22
+ unsubscribe: () => void;
23
+ };
24
+ onceChange(callback: (value: T, oldValue: T | undefined) => void): {
25
+ unsubscribe: () => void;
26
+ };
27
+ onStateChange(callback: AsyncStoreStateChangeCallback<T>): {
28
+ unsubscribe: () => void;
29
+ };
30
+ onceStateChange(callback: AsyncStoreStateChangeCallback<T>): {
31
+ unsubscribe: () => void;
32
+ };
33
+ };
12
34
  export declare class Store<T> implements ReadonlyStore<T> {
13
35
  private _value;
14
36
  private readonly _callbacks;
@@ -23,20 +45,9 @@ export declare class Store<T> implements ReadonlyStore<T> {
23
45
  unsubscribe: () => void;
24
46
  };
25
47
  }
26
- export type ReadonlyAsyncStore<T> = {
27
- isAvailable(): boolean;
28
- get(): AsyncResult<T, unknown, void>;
29
- getOrWait(): ReactPromise<T>;
30
- onChange(callback: (value: T, oldValue: T | undefined) => void): {
31
- unsubscribe: () => void;
32
- };
33
- onceChange(callback: (value: T, oldValue: T | undefined) => void): {
34
- unsubscribe: () => void;
35
- };
36
- };
37
48
  export declare class AsyncStore<T> implements ReadonlyAsyncStore<T> {
38
49
  private _isAvailable;
39
- private _value;
50
+ private _mostRecentOkValue;
40
51
  private _isRejected;
41
52
  private _rejectionError;
42
53
  private readonly _waitingRejectFunctions;
@@ -74,7 +85,13 @@ export declare class AsyncStore<T> implements ReadonlyAsyncStore<T> {
74
85
  onChange(callback: (value: T, oldValue: T | undefined) => void): {
75
86
  unsubscribe: () => void;
76
87
  };
88
+ onStateChange(callback: AsyncStoreStateChangeCallback<T>): {
89
+ unsubscribe: () => void;
90
+ };
77
91
  onceChange(callback: (value: T, oldValue: T | undefined) => void): {
78
92
  unsubscribe: () => void;
79
93
  };
94
+ onceStateChange(callback: AsyncStoreStateChangeCallback<T>): {
95
+ unsubscribe: () => void;
96
+ };
80
97
  }
@@ -39,7 +39,7 @@ export class Store {
39
39
  }
40
40
  export class AsyncStore {
41
41
  _isAvailable;
42
- _value = undefined;
42
+ _mostRecentOkValue = undefined;
43
43
  _isRejected = false;
44
44
  _rejectionError;
45
45
  _waitingRejectFunctions = new Map();
@@ -52,7 +52,7 @@ export class AsyncStore {
52
52
  }
53
53
  else {
54
54
  this._isAvailable = true;
55
- this._value = args[0];
55
+ this._mostRecentOkValue = args[0];
56
56
  }
57
57
  }
58
58
  isAvailable() {
@@ -66,7 +66,7 @@ export class AsyncStore {
66
66
  return AsyncResult.error(this._rejectionError);
67
67
  }
68
68
  else if (this.isAvailable()) {
69
- return AsyncResult.ok(this._value);
69
+ return AsyncResult.ok(this._mostRecentOkValue);
70
70
  }
71
71
  else {
72
72
  return AsyncResult.pending();
@@ -78,7 +78,7 @@ export class AsyncStore {
78
78
  return rejected(this._rejectionError);
79
79
  }
80
80
  else if (this.isAvailable()) {
81
- return resolved(this._value);
81
+ return resolved(this._mostRecentOkValue);
82
82
  }
83
83
  const promise = new Promise((resolve, reject) => {
84
84
  this.onceChange((value) => {
@@ -92,17 +92,22 @@ export class AsyncStore {
92
92
  return pending(withFinally);
93
93
  }
94
94
  _setIfLatest(result, curCounter) {
95
+ const oldState = this.get();
96
+ const oldValue = this._mostRecentOkValue;
95
97
  if (curCounter > this._lastSuccessfulUpdate) {
96
98
  switch (result.status) {
97
99
  case "ok": {
98
- if (!this._isAvailable || this._isRejected || this._value !== result.data) {
99
- const oldValue = this._value;
100
+ if (!this._isAvailable || this._isRejected || this._mostRecentOkValue !== result.data) {
100
101
  this._lastSuccessfulUpdate = curCounter;
101
102
  this._isAvailable = true;
102
103
  this._isRejected = false;
103
- this._value = result.data;
104
+ this._mostRecentOkValue = result.data;
104
105
  this._rejectionError = undefined;
105
- this._callbacks.forEach((callback) => callback(result.data, oldValue));
106
+ this._callbacks.forEach((callback) => callback({
107
+ state: this.get(),
108
+ oldState,
109
+ lastOkValue: oldValue,
110
+ }));
106
111
  return true;
107
112
  }
108
113
  return false;
@@ -111,9 +116,13 @@ export class AsyncStore {
111
116
  this._lastSuccessfulUpdate = curCounter;
112
117
  this._isAvailable = false;
113
118
  this._isRejected = true;
114
- this._value = undefined;
115
119
  this._rejectionError = result.error;
116
120
  this._waitingRejectFunctions.forEach((reject) => reject(result.error));
121
+ this._callbacks.forEach((callback) => callback({
122
+ state: this.get(),
123
+ oldState,
124
+ lastOkValue: oldValue,
125
+ }));
117
126
  return true;
118
127
  }
119
128
  }
@@ -124,7 +133,7 @@ export class AsyncStore {
124
133
  this._setIfLatest(Result.ok(value), ++this._updateCounter);
125
134
  }
126
135
  update(updater) {
127
- const value = updater(this._value);
136
+ const value = updater(this._mostRecentOkValue);
128
137
  this.set(value);
129
138
  return value;
130
139
  }
@@ -137,7 +146,6 @@ export class AsyncStore {
137
146
  this._lastSuccessfulUpdate = ++this._updateCounter;
138
147
  this._isAvailable = false;
139
148
  this._isRejected = false;
140
- this._value = undefined;
141
149
  this._rejectionError = undefined;
142
150
  }
143
151
  setRejected(error) {
@@ -151,6 +159,13 @@ export class AsyncStore {
151
159
  return store;
152
160
  }
153
161
  onChange(callback) {
162
+ return this.onStateChange(({ state, lastOkValue }) => {
163
+ if (state.status === "ok") {
164
+ callback(state.data, lastOkValue);
165
+ }
166
+ });
167
+ }
168
+ onStateChange(callback) {
154
169
  const uuid = generateUuid();
155
170
  this._callbacks.set(uuid, callback);
156
171
  return {
@@ -166,4 +181,11 @@ export class AsyncStore {
166
181
  });
167
182
  return { unsubscribe };
168
183
  }
184
+ onceStateChange(callback) {
185
+ const { unsubscribe } = this.onStateChange((...args) => {
186
+ unsubscribe();
187
+ callback(...args);
188
+ });
189
+ return { unsubscribe };
190
+ }
169
191
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackframe/stack-shared",
3
- "version": "2.4.25",
3
+ "version": "2.4.26",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "files": [
@@ -20,7 +20,7 @@
20
20
  }
21
21
  },
22
22
  "peerDependencies": {
23
- "react": "^18.2",
23
+ "react": ">=18.2",
24
24
  "yup": "^1.4.0"
25
25
  },
26
26
  "peerDependenciesMeta": {
@@ -36,13 +36,15 @@
36
36
  "jose": "^5.2.2",
37
37
  "oauth4webapi": "^2.10.3",
38
38
  "uuid": "^9.0.1",
39
- "@stackframe/stack-sc": "2.4.25"
39
+ "@stackframe/stack-sc": "2.4.26"
40
40
  },
41
41
  "devDependencies": {
42
42
  "rimraf": "^5.0.5",
43
43
  "@types/bcrypt": "^5.0.2",
44
44
  "@types/react": "^18.2.66",
45
- "@types/uuid": "^9.0.8"
45
+ "@types/uuid": "^9.0.8",
46
+ "next": "^14.1.0",
47
+ "react": "^18.2.0"
46
48
  },
47
49
  "scripts": {
48
50
  "build": "tsc",