@stackframe/stack-shared 2.6.36 → 2.6.38
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 +14 -0
- package/dist/utils/caches.d.ts +9 -2
- package/dist/utils/caches.js +25 -5
- package/dist/utils/errors.d.ts +1 -0
- package/dist/utils/errors.js +14 -2
- package/dist/utils/results.d.ts +1 -1
- package/dist/utils/results.js +4 -4
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @stackframe/stack-shared
|
|
2
2
|
|
|
3
|
+
## 2.6.38
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Various changes
|
|
8
|
+
- @stackframe/stack-sc@2.6.38
|
|
9
|
+
|
|
10
|
+
## 2.6.37
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Various changes
|
|
15
|
+
- @stackframe/stack-sc@2.6.37
|
|
16
|
+
|
|
3
17
|
## 2.6.36
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
package/dist/utils/caches.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ export declare class AsyncCache<D extends any[], T> {
|
|
|
37
37
|
readonly forceSetCachedValue: (key: D, value: T) => void;
|
|
38
38
|
readonly forceSetCachedValueAsync: (key: D, value: Promise<T>) => ReactPromise<boolean>;
|
|
39
39
|
readonly refresh: (key: D) => Promise<T>;
|
|
40
|
-
readonly invalidate: (key: D) =>
|
|
40
|
+
readonly invalidate: (key: D) => void;
|
|
41
41
|
readonly onStateChange: (key: D, callback: (value: T, oldValue: T | undefined) => void) => {
|
|
42
42
|
unsubscribe: () => void;
|
|
43
43
|
};
|
|
@@ -78,8 +78,15 @@ declare class AsyncValueCache<T> {
|
|
|
78
78
|
private _refetch;
|
|
79
79
|
forceSetCachedValue(value: T): void;
|
|
80
80
|
forceSetCachedValueAsync(value: Promise<T>): ReactPromise<boolean>;
|
|
81
|
+
/**
|
|
82
|
+
* Refetches the value from the fetcher, and updates the cache with it.
|
|
83
|
+
*/
|
|
81
84
|
refresh(): Promise<T>;
|
|
82
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Invalidates the cache, marking it to refresh on the next read. If anyone was listening to it, it will refresh
|
|
87
|
+
* immediately.
|
|
88
|
+
*/
|
|
89
|
+
invalidate(): void;
|
|
83
90
|
onStateChange(callback: (value: T, oldValue: T | undefined) => void): {
|
|
84
91
|
unsubscribe: () => void;
|
|
85
92
|
};
|
package/dist/utils/caches.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DependenciesMap } from "./maps";
|
|
2
2
|
import { filterUndefined } from "./objects";
|
|
3
|
-
import { pending, rateLimited, resolved, runAsynchronously } from "./promises";
|
|
3
|
+
import { pending, rateLimited, resolved, runAsynchronously, wait } from "./promises";
|
|
4
4
|
import { AsyncStore } from "./stores";
|
|
5
5
|
/**
|
|
6
6
|
* Can be used to cache the result of a function call, for example for the `use` hook in React.
|
|
@@ -111,23 +111,42 @@ class AsyncValueCache {
|
|
|
111
111
|
forceSetCachedValueAsync(value) {
|
|
112
112
|
return this._setAsync(value);
|
|
113
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Refetches the value from the fetcher, and updates the cache with it.
|
|
116
|
+
*/
|
|
114
117
|
async refresh() {
|
|
115
118
|
return await this.getOrWait("write-only");
|
|
116
119
|
}
|
|
117
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Invalidates the cache, marking it to refresh on the next read. If anyone was listening to it, it will refresh
|
|
122
|
+
* immediately.
|
|
123
|
+
*/
|
|
124
|
+
invalidate() {
|
|
118
125
|
this._store.setUnavailable();
|
|
119
126
|
this._pendingPromise = undefined;
|
|
120
|
-
|
|
127
|
+
if (this._subscriptionsCount > 0) {
|
|
128
|
+
runAsynchronously(this.refresh());
|
|
129
|
+
}
|
|
121
130
|
}
|
|
122
131
|
onStateChange(callback) {
|
|
123
132
|
const storeObj = this._store.onChange(callback);
|
|
133
|
+
runAsynchronously(this.getOrWait("read-write"));
|
|
124
134
|
if (this._subscriptionsCount++ === 0 && this._options.onSubscribe) {
|
|
135
|
+
let mostRecentRefreshPromiseIndex = 0;
|
|
125
136
|
const unsubscribe = this._options.onSubscribe(() => {
|
|
126
|
-
|
|
137
|
+
const currentRefreshPromiseIndex = mostRecentRefreshPromiseIndex++;
|
|
138
|
+
runAsynchronously(async () => {
|
|
139
|
+
// wait a few seconds; if anything changes during that time, we don't want to refresh
|
|
140
|
+
// else we do unnecessary requests if we unsubscribe and then subscribe again immediately
|
|
141
|
+
await wait(5000);
|
|
142
|
+
if (this._subscriptionsCount === 0 && currentRefreshPromiseIndex === mostRecentRefreshPromiseIndex) {
|
|
143
|
+
this.invalidate();
|
|
144
|
+
}
|
|
145
|
+
});
|
|
127
146
|
});
|
|
128
147
|
this._unsubscribers.push(unsubscribe);
|
|
129
148
|
}
|
|
130
|
-
|
|
149
|
+
console.log("add", this._subscriptionsCount, new Date().toISOString());
|
|
131
150
|
let hasUnsubscribed = false;
|
|
132
151
|
return {
|
|
133
152
|
unsubscribe: () => {
|
|
@@ -135,6 +154,7 @@ class AsyncValueCache {
|
|
|
135
154
|
return;
|
|
136
155
|
hasUnsubscribed = true;
|
|
137
156
|
storeObj.unsubscribe();
|
|
157
|
+
console.log("remove", this._subscriptionsCount - 1, new Date().toISOString());
|
|
138
158
|
if (--this._subscriptionsCount === 0) {
|
|
139
159
|
for (const unsubscribe of this._unsubscribers) {
|
|
140
160
|
unsubscribe();
|
package/dist/utils/errors.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ export declare class StackAssertionError extends Error {
|
|
|
28
28
|
readonly extraData?: (Record<string, any> & ErrorOptions) | undefined;
|
|
29
29
|
constructor(message: string, extraData?: (Record<string, any> & ErrorOptions) | undefined);
|
|
30
30
|
}
|
|
31
|
+
export declare function errorToNiceString(error: unknown): string;
|
|
31
32
|
export declare function registerErrorSink(sink: (location: string, error: unknown) => void): void;
|
|
32
33
|
export declare function captureError(location: string, error: unknown): void;
|
|
33
34
|
type Status = {
|
package/dist/utils/errors.js
CHANGED
|
@@ -64,6 +64,15 @@ export class StackAssertionError extends Error {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
StackAssertionError.prototype.name = "StackAssertionError";
|
|
67
|
+
export function errorToNiceString(error) {
|
|
68
|
+
if (!(error instanceof Error))
|
|
69
|
+
return `${typeof error}<${error}>`;
|
|
70
|
+
const stack = error.stack ?? "";
|
|
71
|
+
const toString = error.toString();
|
|
72
|
+
if (stack.startsWith(toString))
|
|
73
|
+
return stack;
|
|
74
|
+
return `${toString}\n${stack}`;
|
|
75
|
+
}
|
|
67
76
|
const errorSinks = new Set();
|
|
68
77
|
export function registerErrorSink(sink) {
|
|
69
78
|
if (errorSinks.has(sink)) {
|
|
@@ -71,8 +80,11 @@ export function registerErrorSink(sink) {
|
|
|
71
80
|
}
|
|
72
81
|
errorSinks.add(sink);
|
|
73
82
|
}
|
|
74
|
-
registerErrorSink((location, ...
|
|
75
|
-
console.error(`\x1b[41mCaptured error in ${location}:`,
|
|
83
|
+
registerErrorSink((location, error, ...extraArgs) => {
|
|
84
|
+
console.error(`\x1b[41mCaptured error in ${location}:`,
|
|
85
|
+
// HACK: Log a nicified version of the error to get around buggy Next.js pretty-printing
|
|
86
|
+
// https://www.reddit.com/r/nextjs/comments/1gkxdqe/comment/m19kxgn/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button
|
|
87
|
+
errorToNiceString(error), ...extraArgs, "\x1b[0m");
|
|
76
88
|
});
|
|
77
89
|
registerErrorSink((location, error, ...extraArgs) => {
|
|
78
90
|
globalVar.stackCapturedErrors = globalVar.stackCapturedErrors ?? [];
|
package/dist/utils/results.d.ts
CHANGED
|
@@ -69,7 +69,7 @@ declare class RetryError extends AggregateError {
|
|
|
69
69
|
constructor(errors: unknown[]);
|
|
70
70
|
get retries(): number;
|
|
71
71
|
}
|
|
72
|
-
declare function retry<T>(fn: (attempt: number) => Result<T> | Promise<Result<T>>,
|
|
72
|
+
declare function retry<T>(fn: (attempt: number) => Result<T> | Promise<Result<T>>, totalAttempts: number, { exponentialDelayBase }?: {
|
|
73
73
|
exponentialDelayBase?: number | undefined;
|
|
74
74
|
}): Promise<Result<T, RetryError>>;
|
|
75
75
|
export {};
|
package/dist/utils/results.js
CHANGED
|
@@ -98,7 +98,7 @@ class RetryError extends AggregateError {
|
|
|
98
98
|
const strings = errors.map(e => String(e));
|
|
99
99
|
const isAllSame = strings.length > 1 && strings.every(s => s === strings[0]);
|
|
100
100
|
super(errors, deindent `
|
|
101
|
-
Error after
|
|
101
|
+
Error after ${errors.length} attempts.
|
|
102
102
|
|
|
103
103
|
${isAllSame ? deindent `
|
|
104
104
|
Attempts 1-${errors.length}:
|
|
@@ -116,16 +116,16 @@ class RetryError extends AggregateError {
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
RetryError.prototype.name = "RetryError";
|
|
119
|
-
async function retry(fn,
|
|
119
|
+
async function retry(fn, totalAttempts, { exponentialDelayBase = 1000 } = {}) {
|
|
120
120
|
const errors = [];
|
|
121
|
-
for (let i = 0; i <
|
|
121
|
+
for (let i = 0; i < totalAttempts; i++) {
|
|
122
122
|
const res = await fn(i);
|
|
123
123
|
if (res.status === "ok") {
|
|
124
124
|
return Result.ok(res.data);
|
|
125
125
|
}
|
|
126
126
|
else {
|
|
127
127
|
errors.push(res.error);
|
|
128
|
-
if (i <
|
|
128
|
+
if (i < totalAttempts - 1) {
|
|
129
129
|
await wait((Math.random() + 0.5) * exponentialDelayBase * (2 ** i));
|
|
130
130
|
}
|
|
131
131
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackframe/stack-shared",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.38",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"files": [
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"oauth4webapi": "^2.10.3",
|
|
51
51
|
"semver": "^7.6.3",
|
|
52
52
|
"uuid": "^9.0.1",
|
|
53
|
-
"@stackframe/stack-sc": "2.6.
|
|
53
|
+
"@stackframe/stack-sc": "2.6.38"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@simplewebauthn/types": "^11.0.0",
|