@stackframe/stack-shared 2.6.38 → 2.7.0

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,22 @@
1
1
  # @stackframe/stack-shared
2
2
 
3
+ ## 2.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Various changes
8
+
9
+ ### Patch Changes
10
+
11
+ - @stackframe/stack-sc@2.7.0
12
+
13
+ ## 2.6.39
14
+
15
+ ### Patch Changes
16
+
17
+ - Various changes
18
+ - @stackframe/stack-sc@2.6.39
19
+
3
20
  ## 2.6.38
4
21
 
5
22
  ### Patch Changes
@@ -25,7 +25,7 @@ export type ApiKeyCreateCrudResponse = ApiKeysCrud["Admin"]["Read"] & {
25
25
  export declare class StackAdminInterface extends StackServerInterface {
26
26
  readonly options: AdminAuthApplicationOptions;
27
27
  constructor(options: AdminAuthApplicationOptions);
28
- protected sendAdminRequest(path: string, options: RequestInit, session: InternalSession | null, requestType?: "admin"): Promise<Response & {
28
+ sendAdminRequest(path: string, options: RequestInit, session: InternalSession | null, requestType?: "admin"): Promise<Response & {
29
29
  usedTokens: {
30
30
  accessToken: import("../sessions").AccessToken;
31
31
  refreshToken: import("../sessions").RefreshToken | null;
@@ -46,4 +46,19 @@ export declare class StackAdminInterface extends StackServerInterface {
46
46
  deletePermissionDefinition(permissionId: string): Promise<void>;
47
47
  getSvixToken(): Promise<SvixTokenCrud["Admin"]["Read"]>;
48
48
  deleteProject(): Promise<void>;
49
+ getMetrics(): Promise<any>;
50
+ sendTestEmail(data: {
51
+ recipient_email: string;
52
+ email_config: {
53
+ host: string;
54
+ port: number;
55
+ username: string;
56
+ password: string;
57
+ sender_email: string;
58
+ sender_name: string;
59
+ };
60
+ }): Promise<{
61
+ success: boolean;
62
+ error_message?: string;
63
+ }>;
49
64
  }
@@ -120,4 +120,20 @@ export class StackAdminInterface extends StackServerInterface {
120
120
  method: "DELETE",
121
121
  }, null);
122
122
  }
123
+ async getMetrics() {
124
+ const response = await this.sendAdminRequest("/internal/metrics", {
125
+ method: "GET",
126
+ }, null);
127
+ return await response.json();
128
+ }
129
+ async sendTestEmail(data) {
130
+ const response = await this.sendAdminRequest(`/internal/send-test-email`, {
131
+ method: "POST",
132
+ headers: {
133
+ "content-type": "application/json",
134
+ },
135
+ body: JSON.stringify(data),
136
+ }, null);
137
+ return await response.json();
138
+ }
123
139
  }
@@ -1,4 +1,21 @@
1
1
  import { CrudTypeOf } from "../../crud";
2
+ export declare const emailConfigSchema: import("yup").ObjectSchema<{
3
+ type: "shared" | "standard";
4
+ host: string | undefined;
5
+ port: number | undefined;
6
+ username: string | undefined;
7
+ password: string | undefined;
8
+ sender_name: string | undefined;
9
+ sender_email: string | undefined;
10
+ }, import("yup").AnyObject, {
11
+ type: undefined;
12
+ host: undefined;
13
+ port: undefined;
14
+ username: undefined;
15
+ password: undefined;
16
+ sender_name: undefined;
17
+ sender_email: undefined;
18
+ }, "">;
2
19
  export declare const projectsCrudAdminReadSchema: import("yup").ObjectSchema<{
3
20
  id: string;
4
21
  display_name: string;
@@ -17,7 +17,7 @@ const oauthProviderSchema = yupObject({
17
17
  const enabledOAuthProviderSchema = yupObject({
18
18
  id: schemaFields.oauthIdSchema.defined(),
19
19
  });
20
- const emailConfigSchema = yupObject({
20
+ export const emailConfigSchema = yupObject({
21
21
  type: schemaFields.emailTypeSchema.defined(),
22
22
  host: yupDefinedWhen(schemaFields.emailHostSchema, 'type', 'standard'),
23
23
  port: yupDefinedWhen(schemaFields.emailPortSchema, 'type', 'standard'),
@@ -146,7 +146,6 @@ class AsyncValueCache {
146
146
  });
147
147
  this._unsubscribers.push(unsubscribe);
148
148
  }
149
- console.log("add", this._subscriptionsCount, new Date().toISOString());
150
149
  let hasUnsubscribed = false;
151
150
  return {
152
151
  unsubscribe: () => {
@@ -154,7 +153,6 @@ class AsyncValueCache {
154
153
  return;
155
154
  hasUnsubscribed = true;
156
155
  storeObj.unsubscribe();
157
- console.log("remove", this._subscriptionsCount - 1, new Date().toISOString());
158
156
  if (--this._subscriptionsCount === 0) {
159
157
  for (const unsubscribe of this._unsubscribers) {
160
158
  unsubscribe();
@@ -1,3 +1,4 @@
1
+ export declare function isWeekend(date: Date): boolean;
1
2
  export declare function fromNow(date: Date): string;
2
3
  export declare function fromNowDetailed(date: Date): {
3
4
  result: string;
@@ -1,4 +1,7 @@
1
1
  import { remainder } from "./math";
2
+ export function isWeekend(date) {
3
+ return date.getDay() === 0 || date.getDay() === 6;
4
+ }
2
5
  const agoUnits = [
3
6
  [60, 'second'],
4
7
  [60, 'minute'],
@@ -0,0 +1,14 @@
1
+ type LockCallback<T> = () => Promise<T>;
2
+ export declare class ReadWriteLock {
3
+ private semaphore;
4
+ private readers;
5
+ private readersMutex;
6
+ constructor();
7
+ withReadLock<T>(callback: LockCallback<T>): Promise<T>;
8
+ withWriteLock<T>(callback: LockCallback<T>): Promise<T>;
9
+ private _acquireReadLock;
10
+ private _releaseReadLock;
11
+ private _acquireWriteLock;
12
+ private _releaseWriteLock;
13
+ }
14
+ export {};
@@ -0,0 +1,62 @@
1
+ import { Semaphore } from 'async-mutex';
2
+ export class ReadWriteLock {
3
+ constructor() {
4
+ this.semaphore = new Semaphore(1); // Semaphore with 1 permit
5
+ this.readers = 0; // Track the number of readers
6
+ this.readersMutex = new Semaphore(1); // Protect access to `readers` count
7
+ }
8
+ async withReadLock(callback) {
9
+ await this._acquireReadLock();
10
+ try {
11
+ return await callback();
12
+ }
13
+ finally {
14
+ await this._releaseReadLock();
15
+ }
16
+ }
17
+ async withWriteLock(callback) {
18
+ await this._acquireWriteLock();
19
+ try {
20
+ return await callback();
21
+ }
22
+ finally {
23
+ await this._releaseWriteLock();
24
+ }
25
+ }
26
+ async _acquireReadLock() {
27
+ // Increment the readers count
28
+ await this.readersMutex.acquire();
29
+ try {
30
+ this.readers += 1;
31
+ // If this is the first reader, block writers
32
+ if (this.readers === 1) {
33
+ await this.semaphore.acquire();
34
+ }
35
+ }
36
+ finally {
37
+ this.readersMutex.release();
38
+ }
39
+ }
40
+ async _releaseReadLock() {
41
+ // Decrement the readers count
42
+ await this.readersMutex.acquire();
43
+ try {
44
+ this.readers -= 1;
45
+ // If this was the last reader, release the writer block
46
+ if (this.readers === 0) {
47
+ this.semaphore.release();
48
+ }
49
+ }
50
+ finally {
51
+ this.readersMutex.release();
52
+ }
53
+ }
54
+ async _acquireWriteLock() {
55
+ // Writers acquire the main semaphore exclusively
56
+ await this.semaphore.acquire();
57
+ }
58
+ async _releaseWriteLock() {
59
+ // Writers release the main semaphore
60
+ this.semaphore.release();
61
+ }
62
+ }
@@ -1,5 +1,6 @@
1
- import { AsyncResult, Result } from "./results";
1
+ import { ReadWriteLock } from "./locks";
2
2
  import { ReactPromise } from "./promises";
3
+ import { AsyncResult, Result } from "./results";
3
4
  export type ReadonlyStore<T> = {
4
5
  get(): T;
5
6
  onChange(callback: (value: T, oldValue: T | undefined) => void): {
@@ -45,6 +46,7 @@ export declare class Store<T> implements ReadonlyStore<T> {
45
46
  unsubscribe: () => void;
46
47
  };
47
48
  }
49
+ export declare const storeLock: ReadWriteLock;
48
50
  export declare class AsyncStore<T> implements ReadonlyAsyncStore<T> {
49
51
  private _isAvailable;
50
52
  private _mostRecentOkValue;
@@ -1,6 +1,7 @@
1
+ import { ReadWriteLock } from "./locks";
2
+ import { pending, rejected, resolved } from "./promises";
1
3
  import { AsyncResult, Result } from "./results";
2
4
  import { generateUuid } from "./uuids";
3
- import { pending, rejected, resolved } from "./promises";
4
5
  export class Store {
5
6
  constructor(_value) {
6
7
  this._value = _value;
@@ -36,6 +37,7 @@ export class Store {
36
37
  return { unsubscribe };
37
38
  }
38
39
  }
40
+ export const storeLock = new ReadWriteLock();
39
41
  export class AsyncStore {
40
42
  constructor(...args) {
41
43
  this._mostRecentOkValue = undefined;
@@ -135,9 +137,11 @@ export class AsyncStore {
135
137
  return value;
136
138
  }
137
139
  async setAsync(promise) {
138
- const curCounter = ++this._updateCounter;
139
- const result = await Result.fromPromise(promise);
140
- return this._setIfLatest(result, curCounter);
140
+ return await storeLock.withReadLock(async () => {
141
+ const curCounter = ++this._updateCounter;
142
+ const result = await Result.fromPromise(promise);
143
+ return this._setIfLatest(result, curCounter);
144
+ });
141
145
  }
142
146
  setUnavailable() {
143
147
  this._lastSuccessfulUpdate = ++this._updateCounter;
@@ -0,0 +1 @@
1
+ export declare function getFlagEmoji(twoLetterCountryCode: string): string;
@@ -0,0 +1,10 @@
1
+ import { StackAssertionError } from "./errors";
2
+ export function getFlagEmoji(twoLetterCountryCode) {
3
+ if (!/^[a-zA-Z][a-zA-Z]$/.test(twoLetterCountryCode))
4
+ throw new StackAssertionError("Country code must be two alphabetical letters");
5
+ const codePoints = twoLetterCountryCode
6
+ .toUpperCase()
7
+ .split('')
8
+ .map(char => 127397 + char.charCodeAt(0));
9
+ return String.fromCodePoint(...codePoints);
10
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackframe/stack-shared",
3
- "version": "2.6.38",
3
+ "version": "2.7.0",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "files": [
@@ -20,11 +20,11 @@
20
20
  }
21
21
  },
22
22
  "peerDependencies": {
23
- "react": ">=18.2 || >=19.0.0-rc.0",
24
- "react-dom": ">=18.2 || >=19.0.0-rc.0",
25
23
  "@types/react": ">=18.2 || >=19.0.0-rc.0",
26
24
  "@types/react-dom": ">=18.2 || >=19.0.0-rc.0",
27
25
  "next": ">=14.1.0 || >=15.0.0-rc.0",
26
+ "react": ">=18.2 || >=19.0.0-rc.0",
27
+ "react-dom": ">=18.2 || >=19.0.0-rc.0",
28
28
  "yup": "^1.4.0"
29
29
  },
30
30
  "peerDependenciesMeta": {
@@ -43,26 +43,27 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "@simplewebauthn/browser": "^11.0.0",
46
+ "async-mutex": "^0.5.0",
46
47
  "bcrypt": "^5.1.1",
47
48
  "elliptic": "^6.5.7",
48
- "jose": "^5.2.2",
49
49
  "ip-regex": "^5.0.0",
50
+ "jose": "^5.2.2",
50
51
  "oauth4webapi": "^2.10.3",
51
52
  "semver": "^7.6.3",
52
53
  "uuid": "^9.0.1",
53
- "@stackframe/stack-sc": "2.6.38"
54
+ "@stackframe/stack-sc": "2.7.0"
54
55
  },
55
56
  "devDependencies": {
57
+ "@sentry/nextjs": "^8.40.0",
56
58
  "@simplewebauthn/types": "^11.0.0",
57
59
  "@types/bcrypt": "^5.0.2",
58
60
  "@types/elliptic": "^6.4.18",
59
61
  "@types/semver": "^7.5.8",
60
62
  "@types/uuid": "^9.0.8",
61
- "rimraf": "^5.0.5",
63
+ "next": "^14.1.0",
62
64
  "react": "^18.2",
63
65
  "react-dom": "^18.2",
64
- "next": "^14.1.0",
65
- "@sentry/nextjs": "^8.40.0"
66
+ "rimraf": "^5.0.5"
66
67
  },
67
68
  "scripts": {
68
69
  "build": "tsc",