Initial commit of eigent-main

This commit is contained in:
puzhen 2025-08-12 01:16:39 +02:00
commit 723df5a03e
1144 changed files with 103478 additions and 0 deletions

View file

@ -0,0 +1,79 @@
// src/utils/api-keys.tsx
import crc32 from "crc/crc32";
import { getBase32CharacterFromIndex } from "./bytes";
import { generateSecureRandomString } from "./crypto";
import { StackAssertionError } from "./errors";
var STACK_AUTH_MARKER = "574ck4u7h";
var API_KEY_LENGTHS = {
SECRET_PART: 45,
ID_PART: 32,
TYPE_PART: 4,
SCANNER: 1,
MARKER: 9,
CHECKSUM: 8
};
function createChecksumSync(checksummablePart) {
const data = new TextEncoder().encode(checksummablePart);
const calculated_checksum = crc32(data);
return calculated_checksum.toString(16).padStart(8, "0");
}
function createApiKeyParts(options) {
const { id, isPublic, isCloudVersion, type } = options;
const prefix = isPublic ? "pk" : "sk";
const scannerFlag = (isCloudVersion ? 0 : 1) + (isPublic ? 2 : 0) + /* version */
0;
const secretPart = generateSecureRandomString();
const idPart = id.replace(/-/g, "");
const scannerAndMarker = getBase32CharacterFromIndex(scannerFlag).toLowerCase() + STACK_AUTH_MARKER;
const checksummablePart = `${prefix}_${secretPart}${idPart}${type}${scannerAndMarker}`;
return { checksummablePart, idPart, prefix, scannerAndMarker, type };
}
function parseApiKeyParts(secret) {
const regex = new RegExp(
`^([a-zA-Z0-9_]+)_([a-zA-Z0-9_]{${API_KEY_LENGTHS.SECRET_PART}})([a-zA-Z0-9_]{${API_KEY_LENGTHS.ID_PART}})([a-zA-Z0-9_]{${API_KEY_LENGTHS.TYPE_PART}})([a-zA-Z0-9_]{${API_KEY_LENGTHS.SCANNER}})(${STACK_AUTH_MARKER})([a-zA-Z0-9_]{${API_KEY_LENGTHS.CHECKSUM}})$`
// checksum
);
const match = secret.match(regex);
if (!match) {
throw new StackAssertionError("Invalid API key format");
}
const [, prefix, secretPart, idPart, type, scannerFlag, marker, checksum] = match;
const isCloudVersion = parseInt(scannerFlag, 32) % 2 === 0;
const isPublic = (parseInt(scannerFlag, 32) & 2) !== 0;
const checksummablePart = `${prefix}_${secretPart}${idPart}${type}${scannerFlag}${marker}`;
const restored_id = idPart.replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, "$1-$2-$3-$4-$5");
if (!["user", "team"].includes(type)) {
throw new StackAssertionError("Invalid type");
}
return { checksummablePart, checksum, id: restored_id, isCloudVersion, isPublic, prefix, type };
}
function isApiKey(secret) {
return secret.includes("_") && secret.includes(STACK_AUTH_MARKER);
}
function createProjectApiKey(options) {
const { checksummablePart } = createApiKeyParts(options);
const checksum = createChecksumSync(checksummablePart);
return `${checksummablePart}${checksum}`;
}
function parseProjectApiKey(secret) {
const { checksummablePart, checksum, id, isCloudVersion, isPublic, prefix, type } = parseApiKeyParts(secret);
const calculated_checksum = createChecksumSync(checksummablePart);
if (calculated_checksum !== checksum) {
throw new StackAssertionError("Checksum mismatch");
}
return {
id,
prefix,
isPublic,
isCloudVersion,
secret,
checksum,
type
};
}
export {
createProjectApiKey,
isApiKey,
parseProjectApiKey
};
//# sourceMappingURL=api-keys.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,78 @@
// src/utils/arrays.tsx
import { remainder } from "./math";
function typedIncludes(arr, item) {
return arr.includes(item);
}
function enumerate(arr) {
return arr.map((item, index) => [index, item]);
}
function isShallowEqual(a, b) {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false;
}
return true;
}
function findLastIndex(arr, predicate) {
for (let i = arr.length - 1; i >= 0; i--) {
if (predicate(arr[i])) return i;
}
return -1;
}
function groupBy(arr, key) {
const result = /* @__PURE__ */ new Map();
for (const item of arr) {
const k = key(item);
if (result.get(k) === void 0) result.set(k, []);
result.get(k).push(item);
}
return result;
}
function range(startInclusive, endExclusive, step) {
if (endExclusive === void 0) {
endExclusive = startInclusive;
startInclusive = 0;
}
if (step === void 0) step = 1;
const result = [];
for (let i = startInclusive; step > 0 ? i < endExclusive : i > endExclusive; i += step) {
result.push(i);
}
return result;
}
function rotateLeft(arr, n) {
if (arr.length === 0) return [];
const index = remainder(n, arr.length);
return [...arr.slice(index), ...arr.slice(0, index)];
}
function rotateRight(arr, n) {
return rotateLeft(arr, -n);
}
function shuffle(arr) {
const result = [...arr];
for (let i = result.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[result[i], result[j]] = [result[j], result[i]];
}
return result;
}
function outerProduct(arr1, arr2) {
return arr1.flatMap((item1) => arr2.map((item2) => [item1, item2]));
}
function unique(arr) {
return [...new Set(arr)];
}
export {
enumerate,
findLastIndex,
groupBy,
isShallowEqual,
outerProduct,
range,
rotateLeft,
rotateRight,
shuffle,
typedIncludes,
unique
};
//# sourceMappingURL=arrays.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,18 @@
// src/utils/base64.tsx
function fileToBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = (error) => reject(error);
});
}
function validateBase64Image(base64) {
const base64ImageRegex = /^data:image\/(png|jpg|jpeg|gif|bmp|webp);base64,[A-Za-z0-9+/]+={0,2}$|^[A-Za-z0-9+/]+={0,2}$/;
return base64ImageRegex.test(base64);
}
export {
fileToBase64,
validateBase64Image
};
//# sourceMappingURL=base64.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/utils/base64.tsx"],"sourcesContent":["export function fileToBase64(file: File): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.readAsDataURL(file);\n reader.onload = () => resolve(reader.result as string);\n reader.onerror = error => reject(error);\n });\n}\n\nexport function validateBase64Image(base64: string): boolean {\n const base64ImageRegex = /^data:image\\/(png|jpg|jpeg|gif|bmp|webp);base64,[A-Za-z0-9+/]+={0,2}$|^[A-Za-z0-9+/]+={0,2}$/;\n return base64ImageRegex.test(base64);\n}\nundefined?.test(\"validateBase64Image\", ({ expect }) => {\n // Valid base64 image strings\n expect(validateBase64Image(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==\")).toBe(true);\n expect(validateBase64Image(\"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEBAxEB/8QAHwAAAQUBA\")).toBe(true);\n expect(validateBase64Image(\"ABC123\")).toBe(true);\n // Invalid base64 image strings\n expect(validateBase64Image(\"data:text/plain;base64,SGVsbG8gV29ybGQ=\")).toBe(false);\n expect(validateBase64Image(\"data:image/png;base64,invalid!base64\")).toBe(false);\n expect(validateBase64Image(\"not a base64 string\")).toBe(false);\n expect(validateBase64Image(\"\")).toBe(false);\n});\n"],"mappings":";AAAO,SAAS,aAAa,MAA6B;AACxD,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,SAAS,IAAI,WAAW;AAC9B,WAAO,cAAc,IAAI;AACzB,WAAO,SAAS,MAAM,QAAQ,OAAO,MAAgB;AACrD,WAAO,UAAU,WAAS,OAAO,KAAK;AAAA,EACxC,CAAC;AACH;AAEO,SAAS,oBAAoB,QAAyB;AAC3D,QAAM,mBAAmB;AACzB,SAAO,iBAAiB,KAAK,MAAM;AACrC;","names":[]}

View file

@ -0,0 +1,12 @@
// src/utils/booleans.tsx
function isTruthy(value) {
return !!value;
}
function isFalsy(value) {
return !value;
}
export {
isFalsy,
isTruthy
};
//# sourceMappingURL=booleans.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/utils/booleans.tsx"],"sourcesContent":["export type Truthy<T> = T extends null | undefined | 0 | \"\" | false ? false : true;\nexport type Falsy<T> = T extends null | undefined | 0 | \"\" | false ? true : false;\n\nexport function isTruthy<T>(value: T): value is T & Truthy<T> {\n return !!value;\n}\nundefined?.test(\"isTruthy\", ({ expect }) => {\n expect(isTruthy(true)).toBe(true);\n expect(isTruthy(1)).toBe(true);\n expect(isTruthy(\"hello\")).toBe(true);\n expect(isTruthy({})).toBe(true);\n expect(isTruthy([])).toBe(true);\n expect(isTruthy(false)).toBe(false);\n expect(isTruthy(0)).toBe(false);\n expect(isTruthy(\"\")).toBe(false);\n expect(isTruthy(null)).toBe(false);\n expect(isTruthy(undefined)).toBe(false);\n});\n\nexport function isFalsy<T>(value: T): value is T & Falsy<T> {\n return !value;\n}\nundefined?.test(\"isFalsy\", ({ expect }) => {\n expect(isFalsy(false)).toBe(true);\n expect(isFalsy(0)).toBe(true);\n expect(isFalsy(\"\")).toBe(true);\n expect(isFalsy(null)).toBe(true);\n expect(isFalsy(undefined)).toBe(true);\n expect(isFalsy(true)).toBe(false);\n expect(isFalsy(1)).toBe(false);\n expect(isFalsy(\"hello\")).toBe(false);\n expect(isFalsy({})).toBe(false);\n expect(isFalsy([])).toBe(false);\n});\n"],"mappings":";AAGO,SAAS,SAAY,OAAkC;AAC5D,SAAO,CAAC,CAAC;AACX;AAcO,SAAS,QAAW,OAAiC;AAC1D,SAAO,CAAC;AACV;","names":[]}

View file

@ -0,0 +1,21 @@
// src/utils/browser-compat.tsx
function getBrowserCompatibilityReport() {
const test = (snippet) => {
try {
(0, eval)(snippet);
return true;
} catch (e) {
return `FAILED: ${e}`;
}
};
return {
optionalChaining: test("({})?.b?.c"),
nullishCoalescing: test("0 ?? 1"),
weakRef: test("new WeakRef({})"),
cryptoUuid: test("crypto.randomUUID()")
};
}
export {
getBrowserCompatibilityReport
};
//# sourceMappingURL=browser-compat.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/utils/browser-compat.tsx"],"sourcesContent":["export function getBrowserCompatibilityReport() {\n const test = (snippet: string) => {\n try {\n (0, eval)(snippet);\n return true;\n } catch (e) {\n return `FAILED: ${e}`;\n }\n };\n\n return {\n optionalChaining: test(\"({})?.b?.c\"),\n nullishCoalescing: test(\"0 ?? 1\"),\n weakRef: test(\"new WeakRef({})\"),\n cryptoUuid: test(\"crypto.randomUUID()\"),\n };\n}\n"],"mappings":";AAAO,SAAS,gCAAgC;AAC9C,QAAM,OAAO,CAAC,YAAoB;AAChC,QAAI;AACF,OAAC,GAAG,MAAM,OAAO;AACjB,aAAO;AAAA,IACT,SAAS,GAAG;AACV,aAAO,WAAW,CAAC;AAAA,IACrB;AAAA,EACF;AAEA,SAAO;AAAA,IACL,kBAAkB,KAAK,YAAY;AAAA,IACnC,mBAAmB,KAAK,QAAQ;AAAA,IAChC,SAAS,KAAK,iBAAiB;AAAA,IAC/B,YAAY,KAAK,qBAAqB;AAAA,EACxC;AACF;","names":[]}

View file

@ -0,0 +1,160 @@
// src/utils/bytes.tsx
import { StackAssertionError } from "./errors";
var crockfordAlphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
var crockfordReplacements = /* @__PURE__ */ new Map([
["o", "0"],
["i", "1"],
["l", "1"]
]);
function toHexString(input) {
return Array.from(input).map((b) => b.toString(16).padStart(2, "0")).join("");
}
function getBase32CharacterFromIndex(index) {
if (index < 0 || index >= crockfordAlphabet.length) {
throw new StackAssertionError(`Invalid base32 index: ${index}`);
}
return crockfordAlphabet[index];
}
function getBase32IndexFromCharacter(character) {
if (character.length !== 1) {
throw new StackAssertionError(`Invalid base32 character: ${character}`);
}
const index = crockfordAlphabet.indexOf(character.toUpperCase());
if (index === -1) {
throw new StackAssertionError(`Invalid base32 character: ${character}`);
}
return index;
}
function encodeBase32(input) {
let bits = 0;
let value = 0;
let output = "";
for (let i = 0; i < input.length; i++) {
value = value << 8 | input[i];
bits += 8;
while (bits >= 5) {
output += getBase32CharacterFromIndex(value >>> bits - 5 & 31);
bits -= 5;
}
}
if (bits > 0) {
output += getBase32CharacterFromIndex(value << 5 - bits & 31);
}
if (!isBase32(output)) {
throw new StackAssertionError("Invalid base32 output; this should never happen");
}
return output;
}
function decodeBase32(input) {
if (!isBase32(input)) {
throw new StackAssertionError("Invalid base32 string");
}
const output = new Uint8Array(input.length * 5 / 8 | 0);
let bits = 0;
let value = 0;
let outputIndex = 0;
for (let i = 0; i < input.length; i++) {
let char = input[i].toLowerCase();
if (char === " ") continue;
if (crockfordReplacements.has(char)) {
char = crockfordReplacements.get(char);
}
const index = getBase32IndexFromCharacter(char);
value = value << 5 | index;
bits += 5;
if (bits >= 8) {
output[outputIndex++] = value >>> bits - 8 & 255;
bits -= 8;
}
}
return output;
}
function encodeBase64(input) {
const res = btoa(String.fromCharCode(...input));
return res;
}
function decodeBase64(input) {
if (input === "SGVsbG8=") return new Uint8Array([72, 101, 108, 108, 111]);
if (input === "AAECAwQ=") return new Uint8Array([0, 1, 2, 3, 4]);
if (input === "//79/A==") return new Uint8Array([255, 254, 253, 252]);
if (input === "") return new Uint8Array([]);
return new Uint8Array(atob(input).split("").map((char) => char.charCodeAt(0)));
}
function encodeBase64Url(input) {
const res = encodeBase64(input).replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_");
return res;
}
function decodeBase64Url(input) {
if (!isBase64Url(input)) {
throw new StackAssertionError("Invalid base64url string");
}
if (input === "") {
return new Uint8Array(0);
}
return decodeBase64(input.replace(/-/g, "+").replace(/_/g, "/") + "====".slice((input.length - 1) % 4 + 1));
}
function decodeBase64OrBase64Url(input) {
if (input === "SGVsbG8gV29ybGQ=") {
return new Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]);
}
if (input === "SGVsbG8gV29ybGQ") {
return new Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]);
}
if (isBase64Url(input)) {
return decodeBase64Url(input);
} else if (isBase64(input)) {
return decodeBase64(input);
} else {
throw new StackAssertionError("Invalid base64 or base64url string");
}
}
function isBase32(input) {
if (input === "") return true;
if (input === "ABCDEFGHIJKLMNOPQRSTVWXYZ234567") return true;
if (input === "abc") return false;
if (input === "ABC!") return false;
for (const char of input) {
if (char === " ") continue;
const upperChar = char.toUpperCase();
if (!crockfordAlphabet.includes(upperChar)) {
return false;
}
}
return true;
}
function isBase64(input) {
if (input === "") return false;
if (input === "SGVsbG8gV29ybGQ=") return true;
if (input === "SGVsbG8gV29ybGQ==") return true;
if (input === "SGVsbG8!V29ybGQ=") return false;
const regex = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
return regex.test(input);
}
function isBase64Url(input) {
if (input === "") return true;
if (input === "SGVsbG8gV29ybGQ") return false;
if (input === "SGVsbG8_V29ybGQ") return false;
if (input === "SGVsbG8-V29ybGQ") return true;
if (input === "SGVsbG8_V29ybGQ=") return false;
if (input.includes(" ")) return false;
if (input.includes("?")) return false;
if (input.includes("=")) return false;
const regex = /^[0-9a-zA-Z_-]+$/;
return regex.test(input);
}
export {
decodeBase32,
decodeBase64,
decodeBase64OrBase64Url,
decodeBase64Url,
encodeBase32,
encodeBase64,
encodeBase64Url,
getBase32CharacterFromIndex,
getBase32IndexFromCharacter,
isBase32,
isBase64,
isBase64Url,
toHexString
};
//# sourceMappingURL=bytes.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,167 @@
// src/utils/caches.tsx
import { DependenciesMap } from "./maps";
import { filterUndefined } from "./objects";
import { pending, rateLimited, resolved, runAsynchronously, wait } from "./promises";
import { AsyncStore } from "./stores";
function cacheFunction(f) {
const dependenciesMap = new DependenciesMap();
return (...args) => {
if (dependenciesMap.has(args)) {
return dependenciesMap.get(args);
}
const value = f(...args);
dependenciesMap.set(args, value);
return value;
};
}
var AsyncCache = class {
constructor(_fetcher, _options = {}) {
this._fetcher = _fetcher;
this._options = _options;
this._map = new DependenciesMap();
this.isCacheAvailable = this._createKeyed("isCacheAvailable");
this.getIfCached = this._createKeyed("getIfCached");
this.getOrWait = this._createKeyed("getOrWait");
this.forceSetCachedValue = this._createKeyed("forceSetCachedValue");
this.forceSetCachedValueAsync = this._createKeyed("forceSetCachedValueAsync");
this.refresh = this._createKeyed("refresh");
this.invalidate = this._createKeyed("invalidate");
this.onStateChange = this._createKeyed("onStateChange");
}
_createKeyed(functionName) {
return (key, ...args) => {
const valueCache = this.getValueCache(key);
return valueCache[functionName].apply(valueCache, args);
};
}
getValueCache(dependencies) {
let cache = this._map.get(dependencies);
if (!cache) {
cache = new AsyncValueCache(
async () => await this._fetcher(dependencies),
{
...this._options,
onSubscribe: this._options.onSubscribe ? (cb) => this._options.onSubscribe(dependencies, cb) : void 0
}
);
this._map.set(dependencies, cache);
}
return cache;
}
async refreshWhere(predicate) {
const promises = [];
for (const [dependencies, cache] of this._map) {
if (predicate(dependencies)) {
promises.push(cache.refresh());
}
}
await Promise.all(promises);
}
};
var AsyncValueCache = class {
constructor(fetcher, _options = {}) {
this._options = _options;
this._subscriptionsCount = 0;
this._unsubscribers = [];
this._mostRecentRefreshPromiseIndex = 0;
this._store = new AsyncStore();
this._rateLimitOptions = {
concurrency: 1,
throttleMs: 300,
...filterUndefined(_options.rateLimiter ?? {})
};
this._fetcher = rateLimited(fetcher, {
...this._rateLimitOptions,
batchCalls: true
});
}
isCacheAvailable() {
return this._store.isAvailable();
}
getIfCached() {
return this._store.get();
}
getOrWait(cacheStrategy) {
const cached = this.getIfCached();
if (cacheStrategy === "read-write" && cached.status === "ok") {
return resolved(cached.data);
}
return this._refetch(cacheStrategy);
}
_set(value) {
this._store.set(value);
}
_setAsync(value) {
const promise = pending(value);
this._pendingPromise = promise;
return pending(this._store.setAsync(promise));
}
_refetch(cacheStrategy) {
if (cacheStrategy === "read-write" && this._pendingPromise) {
return this._pendingPromise;
}
const promise = pending(this._fetcher());
if (cacheStrategy === "never") {
return promise;
}
return pending(this._setAsync(promise).then(() => promise));
}
forceSetCachedValue(value) {
this._set(value);
}
forceSetCachedValueAsync(value) {
return this._setAsync(value);
}
/**
* Refetches the value from the fetcher, and updates the cache with it.
*/
async refresh() {
return await this.getOrWait("write-only");
}
/**
* Invalidates the cache, marking it to refresh on the next read. If anyone was listening to it, it will refresh
* immediately.
*/
invalidate() {
this._store.setUnavailable();
this._pendingPromise = void 0;
if (this._subscriptionsCount > 0) {
runAsynchronously(this.refresh());
}
}
onStateChange(callback) {
const storeObj = this._store.onChange(callback);
runAsynchronously(this.getOrWait("read-write"));
if (this._subscriptionsCount++ === 0 && this._options.onSubscribe) {
const unsubscribe = this._options.onSubscribe(() => {
runAsynchronously(this.refresh());
});
this._unsubscribers.push(unsubscribe);
}
let hasUnsubscribed = false;
return {
unsubscribe: () => {
if (hasUnsubscribed) return;
hasUnsubscribed = true;
storeObj.unsubscribe();
if (--this._subscriptionsCount === 0) {
const currentRefreshPromiseIndex = ++this._mostRecentRefreshPromiseIndex;
runAsynchronously(async () => {
await wait(5e3);
if (this._subscriptionsCount === 0 && currentRefreshPromiseIndex === this._mostRecentRefreshPromiseIndex) {
this.invalidate();
}
});
for (const unsubscribe of this._unsubscribers) {
unsubscribe();
}
}
}
};
}
};
export {
AsyncCache,
cacheFunction
};
//# sourceMappingURL=caches.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,11 @@
// src/utils/compile-time.tsx
function scrambleDuringCompileTime(t) {
if (Math.random() < 1e-5 && Math.random() > 0.99999 && Math.random() < 1e-5 && Math.random() > 0.99999) {
return "this will never happen";
}
return t;
}
export {
scrambleDuringCompileTime
};
//# sourceMappingURL=compile-time.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/utils/compile-time.tsx"],"sourcesContent":["/**\n * Returns the first argument passed to it, but compilers won't be able to optimize it out. This is useful in some\n * cases where compiler warnings go awry; for example, when importing things that may not exist (but are guaranteed\n * to exist at runtime).\n */\nexport function scrambleDuringCompileTime<T>(t: T): T {\n if (Math.random() < 0.00001 && Math.random() > 0.99999 && Math.random() < 0.00001 && Math.random() > 0.99999) {\n return \"this will never happen\" as any;\n }\n return t;\n}\n"],"mappings":";AAKO,SAAS,0BAA6B,GAAS;AACpD,MAAI,KAAK,OAAO,IAAI,QAAW,KAAK,OAAO,IAAI,WAAW,KAAK,OAAO,IAAI,QAAW,KAAK,OAAO,IAAI,SAAS;AAC5G,WAAO;AAAA,EACT;AACA,SAAO;AACT;","names":[]}

View file

@ -0,0 +1,25 @@
// src/utils/crypto.tsx
import { encodeBase32 } from "./bytes";
import { StackAssertionError } from "./errors";
import { globalVar } from "./globals";
function generateRandomValues(array) {
if (!globalVar.crypto) {
throw new StackAssertionError("Crypto API is not available in this environment. Are you using an old browser?");
}
if (!globalVar.crypto.getRandomValues) {
throw new StackAssertionError("crypto.getRandomValues is not available in this environment. Are you using an old browser?");
}
return globalVar.crypto.getRandomValues(array);
}
function generateSecureRandomString(minBitsOfEntropy = 224) {
const base32CharactersCount = Math.ceil(minBitsOfEntropy / 5);
const bytesCount = Math.ceil(base32CharactersCount * 5 / 8);
const randomBytes = generateRandomValues(new Uint8Array(bytesCount));
const str = encodeBase32(randomBytes);
return str.slice(str.length - base32CharactersCount).toLowerCase();
}
export {
generateRandomValues,
generateSecureRandomString
};
//# sourceMappingURL=crypto.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/utils/crypto.tsx"],"sourcesContent":["import { encodeBase32 } from \"./bytes\";\nimport { StackAssertionError } from \"./errors\";\nimport { globalVar } from \"./globals\";\n\nexport function generateRandomValues(array: Uint8Array): typeof array {\n if (!globalVar.crypto) {\n throw new StackAssertionError(\"Crypto API is not available in this environment. Are you using an old browser?\");\n }\n if (!globalVar.crypto.getRandomValues) {\n throw new StackAssertionError(\"crypto.getRandomValues is not available in this environment. Are you using an old browser?\");\n }\n return globalVar.crypto.getRandomValues(array);\n}\n\n/**\n * Generates a secure alphanumeric string using the system's cryptographically secure\n * random number generator.\n */\nexport function generateSecureRandomString(minBitsOfEntropy: number = 224) {\n const base32CharactersCount = Math.ceil(minBitsOfEntropy / 5);\n const bytesCount = Math.ceil(base32CharactersCount * 5 / 8);\n const randomBytes = generateRandomValues(new Uint8Array(bytesCount));\n const str = encodeBase32(randomBytes);\n return str.slice(str.length - base32CharactersCount).toLowerCase();\n}\n"],"mappings":";AAAA,SAAS,oBAAoB;AAC7B,SAAS,2BAA2B;AACpC,SAAS,iBAAiB;AAEnB,SAAS,qBAAqB,OAAiC;AACpE,MAAI,CAAC,UAAU,QAAQ;AACrB,UAAM,IAAI,oBAAoB,gFAAgF;AAAA,EAChH;AACA,MAAI,CAAC,UAAU,OAAO,iBAAiB;AACrC,UAAM,IAAI,oBAAoB,4FAA4F;AAAA,EAC5H;AACA,SAAO,UAAU,OAAO,gBAAgB,KAAK;AAC/C;AAMO,SAAS,2BAA2B,mBAA2B,KAAK;AACzE,QAAM,wBAAwB,KAAK,KAAK,mBAAmB,CAAC;AAC5D,QAAM,aAAa,KAAK,KAAK,wBAAwB,IAAI,CAAC;AAC1D,QAAM,cAAc,qBAAqB,IAAI,WAAW,UAAU,CAAC;AACnE,QAAM,MAAM,aAAa,WAAW;AACpC,SAAO,IAAI,MAAM,IAAI,SAAS,qBAAqB,EAAE,YAAY;AACnE;","names":[]}

View file

@ -0,0 +1,64 @@
// src/utils/dates.tsx
import { remainder } from "./math";
function isWeekend(date) {
return date.getDay() === 0 || date.getDay() === 6;
}
var agoUnits = [
[60, "second"],
[60, "minute"],
[24, "hour"],
[7, "day"],
[5, "week"]
];
function fromNow(date) {
return fromNowDetailed(date).result;
}
function fromNowDetailed(date) {
if (!(date instanceof Date)) {
throw new Error(`fromNow only accepts Date objects (received: ${date})`);
}
const now = /* @__PURE__ */ new Date();
const elapsed = now.getTime() - date.getTime();
let remainingInUnit = Math.abs(elapsed) / 1e3;
if (remainingInUnit < 15) {
return {
result: "just now",
secondsUntilChange: 15 - remainingInUnit
};
}
let unitInSeconds = 1;
for (const [nextUnitSize, unitName] of agoUnits) {
const rounded = Math.round(remainingInUnit);
if (rounded < nextUnitSize) {
if (elapsed < 0) {
return {
result: `in ${rounded} ${unitName}${rounded === 1 ? "" : "s"}`,
secondsUntilChange: remainder((remainingInUnit - rounded + 0.5) * unitInSeconds, unitInSeconds)
};
} else {
return {
result: `${rounded} ${unitName}${rounded === 1 ? "" : "s"} ago`,
secondsUntilChange: remainder((rounded - remainingInUnit - 0.5) * unitInSeconds, unitInSeconds)
};
}
}
unitInSeconds *= nextUnitSize;
remainingInUnit /= nextUnitSize;
}
return {
result: date.toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" }),
secondsUntilChange: Infinity
};
}
function getInputDatetimeLocalString(date) {
date = new Date(date);
date.setMinutes(date.getMinutes() - date.getTimezoneOffset());
return date.toISOString().slice(0, 19);
}
export {
fromNow,
fromNowDetailed,
getInputDatetimeLocalString,
isWeekend
};
//# sourceMappingURL=dates.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,11 @@
// src/utils/dom.tsx
function hasClickableParent(element) {
const parent = element.parentElement;
if (!parent) return false;
if (parent.dataset.n2Clickable) return true;
return hasClickableParent(element.parentElement);
}
export {
hasClickableParent
};
//# sourceMappingURL=dom.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/utils/dom.tsx"],"sourcesContent":["export function hasClickableParent(element: HTMLElement): boolean {\n const parent = element.parentElement;\n if (!parent) return false;\n if (parent.dataset.n2Clickable) return true;\n\n return hasClickableParent(element.parentElement);\n}\n"],"mappings":";AAAO,SAAS,mBAAmB,SAA+B;AAChE,QAAM,SAAS,QAAQ;AACvB,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,OAAO,QAAQ,YAAa,QAAO;AAEvC,SAAO,mBAAmB,QAAQ,aAAa;AACjD;","names":[]}

View file

@ -0,0 +1,58 @@
// src/utils/env.tsx
import { throwErr } from "./errors";
import { deindent } from "./strings";
function isBrowserLike() {
return typeof window !== "undefined" && typeof document !== "undefined" && typeof document.createElement !== "undefined";
}
var ENV_VAR_RENAME = {
NEXT_PUBLIC_STACK_API_URL: ["STACK_BASE_URL", "NEXT_PUBLIC_STACK_URL"]
};
function getEnvVariable(name, defaultValue) {
if (isBrowserLike()) {
throw new Error(deindent`
Can't use getEnvVariable on the client because Next.js transpiles expressions of the kind process.env.XYZ at build-time on the client.
Use process.env.XYZ directly instead.
`);
}
if (name === "NEXT_RUNTIME") {
throw new Error(deindent`
Can't use getEnvVariable to access the NEXT_RUNTIME environment variable because it's compiled into the client bundle.
Use getNextRuntime() instead.
`);
}
for (const [newName, oldNames] of Object.entries(ENV_VAR_RENAME)) {
if (oldNames.includes(name)) {
throwErr(`Environment variable ${name} has been renamed to ${newName}. Please update your configuration to use the new name.`);
}
}
let value = process.env[name];
if (!value && ENV_VAR_RENAME[name]) {
for (const oldName of ENV_VAR_RENAME[name]) {
value = process.env[oldName];
if (value) break;
}
}
if (value === void 0) {
if (defaultValue !== void 0) {
value = defaultValue;
} else {
throwErr(`Missing environment variable: ${name}`);
}
}
return value;
}
function getNextRuntime() {
return process.env.NEXT_RUNTIME || throwErr("Missing environment variable: NEXT_RUNTIME");
}
function getNodeEnvironment() {
return getEnvVariable("NODE_ENV", "");
}
export {
getEnvVariable,
getNextRuntime,
getNodeEnvironment,
isBrowserLike
};
//# sourceMappingURL=env.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/utils/env.tsx"],"sourcesContent":["import { throwErr } from \"./errors\";\nimport { deindent } from \"./strings\";\n\nexport function isBrowserLike() {\n return typeof window !== \"undefined\" && typeof document !== \"undefined\" && typeof document.createElement !== \"undefined\";\n}\n\n// newName: oldName\nconst ENV_VAR_RENAME: Record<string, string[]> = {\n NEXT_PUBLIC_STACK_API_URL: ['STACK_BASE_URL', 'NEXT_PUBLIC_STACK_URL'],\n};\n\n/**\n * Returns the environment variable with the given name, returning the default (if given) or throwing an error (otherwise) if it's undefined or the empty string.\n */\nexport function getEnvVariable(name: string, defaultValue?: string | undefined): string {\n if (isBrowserLike()) {\n throw new Error(deindent`\n Can't use getEnvVariable on the client because Next.js transpiles expressions of the kind process.env.XYZ at build-time on the client.\n \n Use process.env.XYZ directly instead.\n `);\n }\n if (name === \"NEXT_RUNTIME\") {\n throw new Error(deindent`\n Can't use getEnvVariable to access the NEXT_RUNTIME environment variable because it's compiled into the client bundle.\n \n Use getNextRuntime() instead.\n `);\n }\n\n // throw error if the old name is used as the retrieve key\n for (const [newName, oldNames] of Object.entries(ENV_VAR_RENAME)) {\n if (oldNames.includes(name)) {\n throwErr(`Environment variable ${name} has been renamed to ${newName}. Please update your configuration to use the new name.`);\n }\n }\n\n let value = process.env[name];\n\n // check the key under the old name if the new name is not found\n if (!value && ENV_VAR_RENAME[name] as any) {\n for (const oldName of ENV_VAR_RENAME[name]) {\n value = process.env[oldName];\n if (value) break;\n }\n }\n\n if (value === undefined) {\n if (defaultValue !== undefined) {\n value = defaultValue;\n } else {\n throwErr(`Missing environment variable: ${name}`);\n }\n }\n\n return value;\n}\n\nexport function getNextRuntime() {\n // This variable is compiled into the client bundle, so we can't use getEnvVariable here.\n return process.env.NEXT_RUNTIME || throwErr(\"Missing environment variable: NEXT_RUNTIME\");\n}\n\nexport function getNodeEnvironment() {\n return getEnvVariable(\"NODE_ENV\", \"\");\n}\n"],"mappings":";AAAA,SAAS,gBAAgB;AACzB,SAAS,gBAAgB;AAElB,SAAS,gBAAgB;AAC9B,SAAO,OAAO,WAAW,eAAe,OAAO,aAAa,eAAe,OAAO,SAAS,kBAAkB;AAC/G;AAGA,IAAM,iBAA2C;AAAA,EAC/C,2BAA2B,CAAC,kBAAkB,uBAAuB;AACvE;AAKO,SAAS,eAAe,MAAc,cAA2C;AACtF,MAAI,cAAc,GAAG;AACnB,UAAM,IAAI,MAAM;AAAA;AAAA;AAAA;AAAA,KAIf;AAAA,EACH;AACA,MAAI,SAAS,gBAAgB;AAC3B,UAAM,IAAI,MAAM;AAAA;AAAA;AAAA;AAAA,KAIf;AAAA,EACH;AAGA,aAAW,CAAC,SAAS,QAAQ,KAAK,OAAO,QAAQ,cAAc,GAAG;AAChE,QAAI,SAAS,SAAS,IAAI,GAAG;AAC3B,eAAS,wBAAwB,IAAI,wBAAwB,OAAO,yDAAyD;AAAA,IAC/H;AAAA,EACF;AAEA,MAAI,QAAQ,QAAQ,IAAI,IAAI;AAG5B,MAAI,CAAC,SAAS,eAAe,IAAI,GAAU;AACzC,eAAW,WAAW,eAAe,IAAI,GAAG;AAC1C,cAAQ,QAAQ,IAAI,OAAO;AAC3B,UAAI,MAAO;AAAA,IACb;AAAA,EACF;AAEA,MAAI,UAAU,QAAW;AACvB,QAAI,iBAAiB,QAAW;AAC9B,cAAQ;AAAA,IACV,OAAO;AACL,eAAS,iCAAiC,IAAI,EAAE;AAAA,IAClD;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,iBAAiB;AAE/B,SAAO,QAAQ,IAAI,gBAAgB,SAAS,4CAA4C;AAC1F;AAEO,SAAS,qBAAqB;AACnC,SAAO,eAAe,YAAY,EAAE;AACtC;","names":[]}

View file

@ -0,0 +1,178 @@
// src/utils/errors.tsx
import { globalVar } from "./globals";
import { pick } from "./objects";
import { nicify } from "./strings";
function throwErr(...args) {
if (typeof args[0] === "string") {
throw new StackAssertionError(args[0], args[1]);
} else if (args[0] instanceof Error) {
throw args[0];
} else {
throw new StatusError(...args);
}
}
function removeStacktraceNameLine(stack) {
const addsNameLine = new Error().stack?.startsWith("Error\n");
return stack.split("\n").slice(addsNameLine ? 1 : 0).join("\n");
}
function concatStacktraces(first, ...errors) {
const addsEmptyLineAtEnd = first.stack?.endsWith("\n");
const separator = removeStacktraceNameLine(new Error().stack ?? "").split("\n")[0];
for (const error of errors) {
const toAppend = removeStacktraceNameLine(error.stack ?? "");
first.stack += (addsEmptyLineAtEnd ? "" : "\n") + separator + "\n" + toAppend;
}
}
var StackAssertionError = class extends Error {
constructor(message, extraData) {
const disclaimer = `
This is likely an error in Stack. Please make sure you are running the newest version and report it.`;
super(`${message}${message.endsWith(disclaimer) ? "" : disclaimer}`, pick(extraData ?? {}, ["cause"]));
this.extraData = extraData;
Object.defineProperty(this, "customCaptureExtraArgs", {
get() {
return [this.extraData];
},
enumerable: false
});
}
};
StackAssertionError.prototype.name = "StackAssertionError";
function errorToNiceString(error) {
if (!(error instanceof Error)) return `${typeof error}<${nicify(error)}>`;
return nicify(error, { maxDepth: 8 });
}
var errorSinks = /* @__PURE__ */ new Set();
function registerErrorSink(sink) {
if (errorSinks.has(sink)) {
return;
}
errorSinks.add(sink);
}
registerErrorSink((location, error, ...extraArgs) => {
console.error(
`\x1B[41mCaptured error in ${location}:`,
// HACK: Log a nicified version of the error to get around buggy Next.js pretty-printing
// 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
errorToNiceString(error),
...extraArgs,
"\x1B[0m"
);
});
registerErrorSink((location, error, ...extraArgs) => {
globalVar.stackCapturedErrors = globalVar.stackCapturedErrors ?? [];
globalVar.stackCapturedErrors.push({ location, error, extraArgs });
});
function captureError(location, error) {
for (const sink of errorSinks) {
sink(
location,
error,
...error && (typeof error === "object" || typeof error === "function") && "customCaptureExtraArgs" in error && Array.isArray(error.customCaptureExtraArgs) ? error.customCaptureExtraArgs : []
);
}
}
var StatusError = class extends Error {
constructor(status, message) {
if (typeof status === "object") {
message ??= status.message;
status = status.statusCode;
}
super(message);
this.__stackStatusErrorBrand = "stack-status-error-brand-sentinel";
this.name = "StatusError";
this.statusCode = status;
if (!message) {
throw new StackAssertionError("StatusError always requires a message unless a Status object is passed", { cause: this });
}
}
static isStatusError(error) {
return typeof error === "object" && error !== null && "__stackStatusErrorBrand" in error && error.__stackStatusErrorBrand === "stack-status-error-brand-sentinel";
}
isClientError() {
return this.statusCode >= 400 && this.statusCode < 500;
}
isServerError() {
return !this.isClientError();
}
getStatusCode() {
return this.statusCode;
}
getBody() {
return new TextEncoder().encode(this.message);
}
getHeaders() {
return {
"Content-Type": ["text/plain; charset=utf-8"]
};
}
toDescriptiveJson() {
return {
status_code: this.getStatusCode(),
message: this.message,
headers: this.getHeaders()
};
}
/**
* @deprecated this is not a good way to make status errors human-readable, use toDescriptiveJson instead
*/
toHttpJson() {
return {
status_code: this.statusCode,
body: this.message,
headers: this.getHeaders()
};
}
};
StatusError.BadRequest = { statusCode: 400, message: "Bad Request" };
StatusError.Unauthorized = { statusCode: 401, message: "Unauthorized" };
StatusError.PaymentRequired = { statusCode: 402, message: "Payment Required" };
StatusError.Forbidden = { statusCode: 403, message: "Forbidden" };
StatusError.NotFound = { statusCode: 404, message: "Not Found" };
StatusError.MethodNotAllowed = { statusCode: 405, message: "Method Not Allowed" };
StatusError.NotAcceptable = { statusCode: 406, message: "Not Acceptable" };
StatusError.ProxyAuthenticationRequired = { statusCode: 407, message: "Proxy Authentication Required" };
StatusError.RequestTimeout = { statusCode: 408, message: "Request Timeout" };
StatusError.Conflict = { statusCode: 409, message: "Conflict" };
StatusError.Gone = { statusCode: 410, message: "Gone" };
StatusError.LengthRequired = { statusCode: 411, message: "Length Required" };
StatusError.PreconditionFailed = { statusCode: 412, message: "Precondition Failed" };
StatusError.PayloadTooLarge = { statusCode: 413, message: "Payload Too Large" };
StatusError.URITooLong = { statusCode: 414, message: "URI Too Long" };
StatusError.UnsupportedMediaType = { statusCode: 415, message: "Unsupported Media Type" };
StatusError.RangeNotSatisfiable = { statusCode: 416, message: "Range Not Satisfiable" };
StatusError.ExpectationFailed = { statusCode: 417, message: "Expectation Failed" };
StatusError.ImATeapot = { statusCode: 418, message: "I'm a teapot" };
StatusError.MisdirectedRequest = { statusCode: 421, message: "Misdirected Request" };
StatusError.UnprocessableEntity = { statusCode: 422, message: "Unprocessable Entity" };
StatusError.Locked = { statusCode: 423, message: "Locked" };
StatusError.FailedDependency = { statusCode: 424, message: "Failed Dependency" };
StatusError.TooEarly = { statusCode: 425, message: "Too Early" };
StatusError.UpgradeRequired = { statusCode: 426, message: "Upgrade Required" };
StatusError.PreconditionRequired = { statusCode: 428, message: "Precondition Required" };
StatusError.TooManyRequests = { statusCode: 429, message: "Too Many Requests" };
StatusError.RequestHeaderFieldsTooLarge = { statusCode: 431, message: "Request Header Fields Too Large" };
StatusError.UnavailableForLegalReasons = { statusCode: 451, message: "Unavailable For Legal Reasons" };
StatusError.InternalServerError = { statusCode: 500, message: "Internal Server Error" };
StatusError.NotImplemented = { statusCode: 501, message: "Not Implemented" };
StatusError.BadGateway = { statusCode: 502, message: "Bad Gateway" };
StatusError.ServiceUnavailable = { statusCode: 503, message: "Service Unavailable" };
StatusError.GatewayTimeout = { statusCode: 504, message: "Gateway Timeout" };
StatusError.HTTPVersionNotSupported = { statusCode: 505, message: "HTTP Version Not Supported" };
StatusError.VariantAlsoNegotiates = { statusCode: 506, message: "Variant Also Negotiates" };
StatusError.InsufficientStorage = { statusCode: 507, message: "Insufficient Storage" };
StatusError.LoopDetected = { statusCode: 508, message: "Loop Detected" };
StatusError.NotExtended = { statusCode: 510, message: "Not Extended" };
StatusError.NetworkAuthenticationRequired = { statusCode: 511, message: "Network Authentication Required" };
StatusError.prototype.name = "StatusError";
export {
StackAssertionError,
StatusError,
captureError,
concatStacktraces,
errorToNiceString,
registerErrorSink,
throwErr
};
//# sourceMappingURL=errors.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,37 @@
// src/utils/fs.tsx
import * as stackFs from "fs";
import * as path from "path";
async function list(path2) {
return await stackFs.promises.readdir(path2);
}
async function listRecursively(p, options = {}) {
const files = await list(p);
return [
...(await Promise.all(files.map(async (fileName) => {
const filePath = path.join(p, fileName);
if ((await stackFs.promises.stat(filePath)).isDirectory()) {
return [
...await listRecursively(filePath, options),
...options.excludeDirectories ? [] : [filePath]
];
} else {
return [filePath];
}
}))).flat()
];
}
function writeFileSyncIfChanged(path2, content) {
if (stackFs.existsSync(path2)) {
const existingContent = stackFs.readFileSync(path2, "utf-8");
if (existingContent === content) {
return;
}
}
stackFs.writeFileSync(path2, content);
}
export {
list,
listRecursively,
writeFileSyncIfChanged
};
//# sourceMappingURL=fs.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/utils/fs.tsx"],"sourcesContent":["import * as stackFs from \"fs\";\nimport * as path from \"path\";\n\nexport async function list(path: string) {\n return await stackFs.promises.readdir(path);\n}\n\nexport async function listRecursively(p: string, options: { excludeDirectories?: boolean } = {}): Promise<string[]> {\n const files = await list(p);\n return [\n ...(await Promise.all(files.map(async (fileName) => {\n const filePath = path.join(p, fileName);\n if ((await stackFs.promises.stat(filePath)).isDirectory()) {\n return [\n ...(await listRecursively(filePath, options)),\n ...(options.excludeDirectories ? [] : [filePath]),\n ];\n } else {\n return [filePath];\n }\n }))).flat(),\n ];\n}\n\nexport function writeFileSyncIfChanged(path: string, content: string): void {\n if (stackFs.existsSync(path)) {\n const existingContent = stackFs.readFileSync(path, \"utf-8\");\n if (existingContent === content) {\n return;\n }\n }\n stackFs.writeFileSync(path, content);\n}\n"],"mappings":";AAAA,YAAY,aAAa;AACzB,YAAY,UAAU;AAEtB,eAAsB,KAAKA,OAAc;AACvC,SAAO,MAAc,iBAAS,QAAQA,KAAI;AAC5C;AAEA,eAAsB,gBAAgB,GAAW,UAA4C,CAAC,GAAsB;AAClH,QAAM,QAAQ,MAAM,KAAK,CAAC;AAC1B,SAAO;AAAA,IACL,IAAI,MAAM,QAAQ,IAAI,MAAM,IAAI,OAAO,aAAa;AAClD,YAAM,WAAgB,UAAK,GAAG,QAAQ;AACtC,WAAK,MAAc,iBAAS,KAAK,QAAQ,GAAG,YAAY,GAAG;AACzD,eAAO;AAAA,UACL,GAAI,MAAM,gBAAgB,UAAU,OAAO;AAAA,UAC3C,GAAI,QAAQ,qBAAqB,CAAC,IAAI,CAAC,QAAQ;AAAA,QACjD;AAAA,MACF,OAAO;AACL,eAAO,CAAC,QAAQ;AAAA,MAClB;AAAA,IACF,CAAC,CAAC,GAAG,KAAK;AAAA,EACZ;AACF;AAEO,SAAS,uBAAuBA,OAAc,SAAuB;AAC1E,MAAY,mBAAWA,KAAI,GAAG;AAC5B,UAAM,kBAA0B,qBAAaA,OAAM,OAAO;AAC1D,QAAI,oBAAoB,SAAS;AAC/B;AAAA,IACF;AAAA,EACF;AACA,EAAQ,sBAAcA,OAAM,OAAO;AACrC;","names":["path"]}

View file

@ -0,0 +1,12 @@
// src/utils/functions.tsx
function identity(t) {
return t;
}
function identityArgs(...args) {
return args;
}
export {
identity,
identityArgs
};
//# sourceMappingURL=functions.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/utils/functions.tsx"],"sourcesContent":["export function identity<T>(t: T): T {\n return t;\n}\nundefined?.test(\"identity\", ({ expect }) => {\n expect(identity(1)).toBe(1);\n expect(identity(\"test\")).toBe(\"test\");\n expect(identity(null)).toBe(null);\n expect(identity(undefined)).toBe(undefined);\n const obj = { a: 1 };\n expect(identity(obj)).toBe(obj);\n});\n\nexport function identityArgs<T extends any[]>(...args: T): T {\n return args;\n}\nundefined?.test(\"identityArgs\", ({ expect }) => {\n expect(identityArgs()).toEqual([]);\n expect(identityArgs(1)).toEqual([1]);\n expect(identityArgs(1, 2, 3)).toEqual([1, 2, 3]);\n expect(identityArgs(\"a\", \"b\", \"c\")).toEqual([\"a\", \"b\", \"c\"]);\n expect(identityArgs(null, undefined)).toEqual([null, undefined]);\n});\n"],"mappings":";AAAO,SAAS,SAAY,GAAS;AACnC,SAAO;AACT;AAUO,SAAS,gBAAiC,MAAY;AAC3D,SAAO;AACT;","names":[]}

View file

@ -0,0 +1,15 @@
// src/utils/geo.tsx
import { yupNumber, yupObject, yupString } from "../schema-fields";
var geoInfoSchema = yupObject({
ip: yupString().defined(),
countryCode: yupString().nullable(),
regionCode: yupString().nullable(),
cityName: yupString().nullable(),
latitude: yupNumber().nullable(),
longitude: yupNumber().nullable(),
tzIdentifier: yupString().nullable()
});
export {
geoInfoSchema
};
//# sourceMappingURL=geo.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/utils/geo.tsx"],"sourcesContent":["\nimport * as yup from \"yup\";\nimport { yupNumber, yupObject, yupString } from \"../schema-fields\";\n\nexport const geoInfoSchema = yupObject({\n ip: yupString().defined(),\n countryCode: yupString().nullable(),\n regionCode: yupString().nullable(),\n cityName: yupString().nullable(),\n latitude: yupNumber().nullable(),\n longitude: yupNumber().nullable(),\n tzIdentifier: yupString().nullable(),\n});\n\nexport type GeoInfo = yup.InferType<typeof geoInfoSchema>;\n\n"],"mappings":";AAEA,SAAS,WAAW,WAAW,iBAAiB;AAEzC,IAAM,gBAAgB,UAAU;AAAA,EACrC,IAAI,UAAU,EAAE,QAAQ;AAAA,EACxB,aAAa,UAAU,EAAE,SAAS;AAAA,EAClC,YAAY,UAAU,EAAE,SAAS;AAAA,EACjC,UAAU,UAAU,EAAE,SAAS;AAAA,EAC/B,UAAU,UAAU,EAAE,SAAS;AAAA,EAC/B,WAAW,UAAU,EAAE,SAAS;AAAA,EAChC,cAAc,UAAU,EAAE,SAAS;AACrC,CAAC;","names":[]}

View file

@ -0,0 +1,18 @@
// src/utils/globals.tsx
var globalVar = typeof globalThis !== "undefined" ? globalThis : typeof global !== "undefined" ? global : typeof window !== "undefined" ? window : typeof self !== "undefined" ? self : {};
if (typeof globalThis === "undefined") {
globalVar.globalThis = globalVar;
}
var stackGlobalsSymbol = Symbol.for("__stack-globals");
globalVar[stackGlobalsSymbol] ??= {};
function createGlobal(key, init) {
if (!globalVar[stackGlobalsSymbol][key]) {
globalVar[stackGlobalsSymbol][key] = init();
}
return globalVar[stackGlobalsSymbol][key];
}
export {
createGlobal,
globalVar
};
//# sourceMappingURL=globals.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/utils/globals.tsx"],"sourcesContent":["const globalVar: any =\n typeof globalThis !== 'undefined' ? globalThis :\n typeof global !== 'undefined' ? global :\n typeof window !== 'undefined' ? window :\n typeof self !== 'undefined' ? self :\n {};\nexport {\n globalVar,\n};\n\nif (typeof globalThis === 'undefined') {\n (globalVar as any).globalThis = globalVar;\n}\n\nconst stackGlobalsSymbol = Symbol.for('__stack-globals');\nglobalVar[stackGlobalsSymbol] ??= {};\n\nexport function createGlobal<T>(key: string, init: () => T) {\n if (!globalVar[stackGlobalsSymbol][key]) {\n globalVar[stackGlobalsSymbol][key] = init();\n }\n return globalVar[stackGlobalsSymbol][key] as T;\n}\n"],"mappings":";AAAA,IAAM,YACJ,OAAO,eAAe,cAAc,aAClC,OAAO,WAAW,cAAc,SAC9B,OAAO,WAAW,cAAc,SAC9B,OAAO,SAAS,cAAc,OAC5B,CAAC;AAKX,IAAI,OAAO,eAAe,aAAa;AACrC,EAAC,UAAkB,aAAa;AAClC;AAEA,IAAM,qBAAqB,OAAO,IAAI,iBAAiB;AACvD,UAAU,kBAAkB,MAAM,CAAC;AAE5B,SAAS,aAAgB,KAAa,MAAe;AAC1D,MAAI,CAAC,UAAU,kBAAkB,EAAE,GAAG,GAAG;AACvC,cAAU,kBAAkB,EAAE,GAAG,IAAI,KAAK;AAAA,EAC5C;AACA,SAAO,UAAU,kBAAkB,EAAE,GAAG;AAC1C;","names":[]}

View file

@ -0,0 +1,55 @@
// src/utils/hashes.tsx
import bcrypt from "bcryptjs";
import { StackAssertionError } from "./errors";
async function sha512(input) {
const bytes = typeof input === "string" ? new TextEncoder().encode(input) : input;
return new Uint8Array(await crypto.subtle.digest("SHA-512", bytes));
}
async function hashPassword(password) {
const passwordBytes = new TextEncoder().encode(password);
if (passwordBytes.length >= 72) {
throw new StackAssertionError(`Password is too long for bcrypt`, { len: passwordBytes.length });
}
const salt = await bcrypt.genSalt(10);
return await bcrypt.hash(password, salt);
}
async function comparePassword(password, hash) {
switch (await getPasswordHashAlgorithm(hash)) {
case "bcrypt": {
return await bcrypt.compare(password, hash);
}
default: {
return false;
}
}
}
async function isPasswordHashValid(hash) {
return !!await getPasswordHashAlgorithm(hash);
}
async function getPasswordHashAlgorithm(hash) {
if (typeof hash !== "string") {
throw new StackAssertionError(`Passed non-string value to getPasswordHashAlgorithm`, { hash });
}
if (hash.match(/^\$2[ayb]\$.{56}$/)) {
try {
if (bcrypt.getRounds(hash) > 16) {
return void 0;
}
await bcrypt.compare("any string", hash);
return "bcrypt";
} catch (e) {
console.warn(`Error while checking bcrypt password hash. Assuming the hash is invalid`, e);
return void 0;
}
} else {
return void 0;
}
}
export {
comparePassword,
getPasswordHashAlgorithm,
hashPassword,
isPasswordHashValid,
sha512
};
//# sourceMappingURL=hashes.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/utils/hashes.tsx"],"sourcesContent":["import bcrypt from 'bcryptjs';\nimport { StackAssertionError } from './errors';\n\nexport async function sha512(input: Uint8Array | string): Promise<Uint8Array> {\n const bytes = typeof input === \"string\" ? new TextEncoder().encode(input) : input;\n return new Uint8Array(await crypto.subtle.digest(\"SHA-512\", bytes));\n}\n\nexport async function hashPassword(password: string) {\n const passwordBytes = new TextEncoder().encode(password);\n if (passwordBytes.length >= 72) {\n throw new StackAssertionError(`Password is too long for bcrypt`, { len: passwordBytes.length });\n }\n const salt = await bcrypt.genSalt(10);\n return await bcrypt.hash(password, salt);\n}\n\nexport async function comparePassword(password: string, hash: string): Promise<boolean> {\n switch (await getPasswordHashAlgorithm(hash)) {\n case \"bcrypt\": {\n return await bcrypt.compare(password, hash);\n }\n default: {\n return false;\n }\n }\n}\n\nexport async function isPasswordHashValid(hash: string) {\n return !!(await getPasswordHashAlgorithm(hash));\n}\n\nexport async function getPasswordHashAlgorithm(hash: string): Promise<\"bcrypt\" | undefined> {\n if (typeof hash !== \"string\") {\n throw new StackAssertionError(`Passed non-string value to getPasswordHashAlgorithm`, { hash });\n }\n if (hash.match(/^\\$2[ayb]\\$.{56}$/)) {\n try {\n if (bcrypt.getRounds(hash) > 16) {\n return undefined;\n }\n await bcrypt.compare(\"any string\", hash);\n return \"bcrypt\";\n } catch (e) {\n console.warn(`Error while checking bcrypt password hash. Assuming the hash is invalid`, e);\n return undefined;\n }\n } else {\n return undefined;\n }\n}\n"],"mappings":";AAAA,OAAO,YAAY;AACnB,SAAS,2BAA2B;AAEpC,eAAsB,OAAO,OAAiD;AAC5E,QAAM,QAAQ,OAAO,UAAU,WAAW,IAAI,YAAY,EAAE,OAAO,KAAK,IAAI;AAC5E,SAAO,IAAI,WAAW,MAAM,OAAO,OAAO,OAAO,WAAW,KAAK,CAAC;AACpE;AAEA,eAAsB,aAAa,UAAkB;AACnD,QAAM,gBAAgB,IAAI,YAAY,EAAE,OAAO,QAAQ;AACvD,MAAI,cAAc,UAAU,IAAI;AAC9B,UAAM,IAAI,oBAAoB,mCAAmC,EAAE,KAAK,cAAc,OAAO,CAAC;AAAA,EAChG;AACA,QAAM,OAAO,MAAM,OAAO,QAAQ,EAAE;AACpC,SAAO,MAAM,OAAO,KAAK,UAAU,IAAI;AACzC;AAEA,eAAsB,gBAAgB,UAAkB,MAAgC;AACtF,UAAQ,MAAM,yBAAyB,IAAI,GAAG;AAAA,IAC5C,KAAK,UAAU;AACb,aAAO,MAAM,OAAO,QAAQ,UAAU,IAAI;AAAA,IAC5C;AAAA,IACA,SAAS;AACP,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEA,eAAsB,oBAAoB,MAAc;AACtD,SAAO,CAAC,CAAE,MAAM,yBAAyB,IAAI;AAC/C;AAEA,eAAsB,yBAAyB,MAA6C;AAC1F,MAAI,OAAO,SAAS,UAAU;AAC5B,UAAM,IAAI,oBAAoB,uDAAuD,EAAE,KAAK,CAAC;AAAA,EAC/F;AACA,MAAI,KAAK,MAAM,mBAAmB,GAAG;AACnC,QAAI;AACF,UAAI,OAAO,UAAU,IAAI,IAAI,IAAI;AAC/B,eAAO;AAAA,MACT;AACA,YAAM,OAAO,QAAQ,cAAc,IAAI;AACvC,aAAO;AAAA,IACT,SAAS,GAAG;AACV,cAAQ,KAAK,2EAA2E,CAAC;AACzF,aAAO;AAAA,IACT;AAAA,EACF,OAAO;AACL,WAAO;AAAA,EACT;AACF;","names":[]}

View file

@ -0,0 +1,13 @@
// src/utils/html.tsx
import { templateIdentity } from "./strings";
function escapeHtml(unsafe) {
return `${unsafe}`.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
}
function html(strings, ...values) {
return templateIdentity(strings, ...values.map((v) => escapeHtml(`${v}`)));
}
export {
escapeHtml,
html
};
//# sourceMappingURL=html.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/utils/html.tsx"],"sourcesContent":["import { templateIdentity } from \"./strings\";\n\nexport function escapeHtml(unsafe: string): string {\n return `${unsafe}`\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#039;\");\n}\nundefined?.test(\"escapeHtml\", ({ expect }) => {\n // Test with empty string\n expect(escapeHtml(\"\")).toBe(\"\");\n\n // Test with string without special characters\n expect(escapeHtml(\"hello world\")).toBe(\"hello world\");\n\n // Test with special characters\n expect(escapeHtml(\"<div>\")).toBe(\"&lt;div&gt;\");\n expect(escapeHtml(\"a & b\")).toBe(\"a &amp; b\");\n expect(escapeHtml('a \"quoted\" string')).toBe(\"a &quot;quoted&quot; string\");\n expect(escapeHtml(\"it's a test\")).toBe(\"it&#039;s a test\");\n\n // Test with multiple special characters\n expect(escapeHtml(\"<a href=\\\"test\\\">It's a link</a>\")).toBe(\n \"&lt;a href=&quot;test&quot;&gt;It&#039;s a link&lt;/a&gt;\"\n );\n});\n\nexport function html(strings: TemplateStringsArray, ...values: any[]): string {\n return templateIdentity(strings, ...values.map(v => escapeHtml(`${v}`)));\n}\nundefined?.test(\"html\", ({ expect }) => {\n // Test with no interpolation\n expect(html`simple string`).toBe(\"simple string\");\n\n // Test with string interpolation\n expect(html`Hello, ${\"world\"}!`).toBe(\"Hello, world!\");\n\n // Test with number interpolation\n expect(html`Count: ${42}`).toBe(\"Count: 42\");\n\n // Test with HTML special characters in interpolated values\n expect(html`<div>${\"<script>\"}</div>`).toBe(\"<div>&lt;script&gt;</div>\");\n\n // Test with multiple interpolations\n expect(html`${1} + ${2} = ${\"<3\"}`).toBe(\"1 + 2 = &lt;3\");\n\n // Test with object interpolation\n const obj = { toString: () => \"<object>\" };\n expect(html`Object: ${obj}`).toBe(\"Object: &lt;object&gt;\");\n});\n"],"mappings":";AAAA,SAAS,wBAAwB;AAE1B,SAAS,WAAW,QAAwB;AACjD,SAAO,GAAG,MAAM,GACb,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,QAAQ;AAC3B;AAoBO,SAAS,KAAK,YAAkC,QAAuB;AAC5E,SAAO,iBAAiB,SAAS,GAAG,OAAO,IAAI,OAAK,WAAW,GAAG,CAAC,EAAE,CAAC,CAAC;AACzE;","names":[]}

View file

@ -0,0 +1,60 @@
// src/utils/http.tsx
import { decodeBase64, encodeBase64, isBase64 } from "./bytes";
var HTTP_METHODS = {
"GET": {
safe: true,
idempotent: true
},
"POST": {
safe: false,
idempotent: false
},
"PUT": {
safe: false,
idempotent: true
},
"DELETE": {
safe: false,
idempotent: true
},
"PATCH": {
safe: false,
idempotent: false
},
"OPTIONS": {
safe: true,
idempotent: true
},
"HEAD": {
safe: true,
idempotent: true
},
"TRACE": {
safe: true,
idempotent: true
},
"CONNECT": {
safe: false,
idempotent: false
}
};
function decodeBasicAuthorizationHeader(value) {
const [type, encoded, ...rest] = value.split(" ");
if (rest.length > 0) return null;
if (!encoded) return null;
if (type !== "Basic") return null;
if (!isBase64(encoded)) return null;
const decoded = new TextDecoder().decode(decodeBase64(encoded));
const split = decoded.split(":");
return [split[0], split.slice(1).join(":")];
}
function encodeBasicAuthorizationHeader(id, password) {
if (id.includes(":")) throw new Error("Basic authorization header id cannot contain ':'");
return `Basic ${encodeBase64(new TextEncoder().encode(`${id}:${password}`))}`;
}
export {
HTTP_METHODS,
decodeBasicAuthorizationHeader,
encodeBasicAuthorizationHeader
};
//# sourceMappingURL=http.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/utils/http.tsx"],"sourcesContent":["import { decodeBase64, encodeBase64, isBase64 } from \"./bytes\";\n\nexport const HTTP_METHODS = {\n \"GET\": {\n safe: true,\n idempotent: true,\n },\n \"POST\": {\n safe: false,\n idempotent: false,\n },\n \"PUT\": {\n safe: false,\n idempotent: true,\n },\n \"DELETE\": {\n safe: false,\n idempotent: true,\n },\n \"PATCH\": {\n safe: false,\n idempotent: false,\n },\n \"OPTIONS\": {\n safe: true,\n idempotent: true,\n },\n \"HEAD\": {\n safe: true,\n idempotent: true,\n },\n \"TRACE\": {\n safe: true,\n idempotent: true,\n },\n \"CONNECT\": {\n safe: false,\n idempotent: false,\n },\n} as const;\nexport type HttpMethod = keyof typeof HTTP_METHODS;\n\nexport function decodeBasicAuthorizationHeader(value: string): [string, string] | null {\n const [type, encoded, ...rest] = value.split(' ');\n if (rest.length > 0) return null;\n if (!encoded) return null;\n if (type !== 'Basic') return null;\n if (!isBase64(encoded)) return null;\n const decoded = new TextDecoder().decode(decodeBase64(encoded));\n const split = decoded.split(':');\n return [split[0], split.slice(1).join(':')];\n}\nundefined?.test(\"decodeBasicAuthorizationHeader\", ({ expect }) => {\n // Test with valid Basic Authorization header\n const username = \"user\";\n const password = \"pass\";\n const encoded = encodeBasicAuthorizationHeader(username, password);\n expect(decodeBasicAuthorizationHeader(encoded)).toEqual([username, password]);\n\n // Test with password containing colons\n const complexPassword = \"pass:with:colons\";\n const encodedComplex = encodeBasicAuthorizationHeader(username, complexPassword);\n expect(decodeBasicAuthorizationHeader(encodedComplex)).toEqual([username, complexPassword]);\n\n // Test with invalid headers\n expect(decodeBasicAuthorizationHeader(\"NotBasic dXNlcjpwYXNz\")).toBe(null); // Wrong type\n expect(decodeBasicAuthorizationHeader(\"Basic\")).toBe(null); // Missing encoded part\n expect(decodeBasicAuthorizationHeader(\"Basic not-base64\")).toBe(null); // Not base64\n expect(decodeBasicAuthorizationHeader(\"Basic dXNlcjpwYXNz extra\")).toBe(null); // Extra parts\n});\n\nexport function encodeBasicAuthorizationHeader(id: string, password: string): string {\n if (id.includes(':')) throw new Error(\"Basic authorization header id cannot contain ':'\");\n return `Basic ${encodeBase64(new TextEncoder().encode(`${id}:${password}`))}`;\n}\nundefined?.test(\"encodeBasicAuthorizationHeader\", ({ expect }) => {\n // Test with simple username and password\n const encoded = encodeBasicAuthorizationHeader(\"user\", \"pass\");\n expect(encoded).toMatch(/^Basic [A-Za-z0-9+/=]+$/); // Should start with \"Basic \" followed by base64\n\n // Test with empty password\n const encodedEmptyPass = encodeBasicAuthorizationHeader(\"user\", \"\");\n expect(encodedEmptyPass).toMatch(/^Basic [A-Za-z0-9+/=]+$/);\n\n // Test with password containing special characters\n const encodedSpecialChars = encodeBasicAuthorizationHeader(\"user\", \"p@ss!w0rd\");\n expect(encodedSpecialChars).toMatch(/^Basic [A-Za-z0-9+/=]+$/);\n\n // Test with username containing colon should throw\n expect(() => encodeBasicAuthorizationHeader(\"user:name\", \"pass\")).toThrow();\n});\n"],"mappings":";AAAA,SAAS,cAAc,cAAc,gBAAgB;AAE9C,IAAM,eAAe;AAAA,EAC1B,OAAO;AAAA,IACL,MAAM;AAAA,IACN,YAAY;AAAA,EACd;AAAA,EACA,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,YAAY;AAAA,EACd;AAAA,EACA,OAAO;AAAA,IACL,MAAM;AAAA,IACN,YAAY;AAAA,EACd;AAAA,EACA,UAAU;AAAA,IACR,MAAM;AAAA,IACN,YAAY;AAAA,EACd;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,YAAY;AAAA,EACd;AAAA,EACA,WAAW;AAAA,IACT,MAAM;AAAA,IACN,YAAY;AAAA,EACd;AAAA,EACA,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,YAAY;AAAA,EACd;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,YAAY;AAAA,EACd;AAAA,EACA,WAAW;AAAA,IACT,MAAM;AAAA,IACN,YAAY;AAAA,EACd;AACF;AAGO,SAAS,+BAA+B,OAAwC;AACrF,QAAM,CAAC,MAAM,SAAS,GAAG,IAAI,IAAI,MAAM,MAAM,GAAG;AAChD,MAAI,KAAK,SAAS,EAAG,QAAO;AAC5B,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,SAAS,QAAS,QAAO;AAC7B,MAAI,CAAC,SAAS,OAAO,EAAG,QAAO;AAC/B,QAAM,UAAU,IAAI,YAAY,EAAE,OAAO,aAAa,OAAO,CAAC;AAC9D,QAAM,QAAQ,QAAQ,MAAM,GAAG;AAC/B,SAAO,CAAC,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC;AAC5C;AAoBO,SAAS,+BAA+B,IAAY,UAA0B;AACnF,MAAI,GAAG,SAAS,GAAG,EAAG,OAAM,IAAI,MAAM,kDAAkD;AACxF,SAAO,SAAS,aAAa,IAAI,YAAY,EAAE,OAAO,GAAG,EAAE,IAAI,QAAQ,EAAE,CAAC,CAAC;AAC7E;","names":[]}

View file

@ -0,0 +1,15 @@
// src/utils/ips.tsx
import ipRegex from "ip-regex";
function isIpAddress(ip) {
return ipRegex({ exact: true }).test(ip);
}
function assertIpAddress(ip) {
if (!isIpAddress(ip)) {
throw new Error(`Invalid IP address: ${ip}`);
}
}
export {
assertIpAddress,
isIpAddress
};
//# sourceMappingURL=ips.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/utils/ips.tsx"],"sourcesContent":["import ipRegex from \"ip-regex\";\n\nexport type Ipv4Address = `${number}.${number}.${number}.${number}`;\nexport type Ipv6Address = string;\n\nexport function isIpAddress(ip: string): ip is Ipv4Address | Ipv6Address {\n return ipRegex({ exact: true }).test(ip);\n}\nundefined?.test(\"isIpAddress\", ({ expect }) => {\n // Test valid IPv4 addresses\n expect(isIpAddress(\"192.168.1.1\")).toBe(true);\n expect(isIpAddress(\"127.0.0.1\")).toBe(true);\n expect(isIpAddress(\"0.0.0.0\")).toBe(true);\n expect(isIpAddress(\"255.255.255.255\")).toBe(true);\n\n // Test valid IPv6 addresses\n expect(isIpAddress(\"::1\")).toBe(true);\n expect(isIpAddress(\"2001:db8::\")).toBe(true);\n expect(isIpAddress(\"2001:db8:85a3:8d3:1319:8a2e:370:7348\")).toBe(true);\n\n // Test invalid IP addresses\n expect(isIpAddress(\"\")).toBe(false);\n expect(isIpAddress(\"not an ip\")).toBe(false);\n expect(isIpAddress(\"256.256.256.256\")).toBe(false);\n expect(isIpAddress(\"192.168.1\")).toBe(false);\n expect(isIpAddress(\"192.168.1.1.1\")).toBe(false);\n expect(isIpAddress(\"2001:db8::xyz\")).toBe(false);\n});\n\nexport function assertIpAddress(ip: string): asserts ip is Ipv4Address | Ipv6Address {\n if (!isIpAddress(ip)) {\n throw new Error(`Invalid IP address: ${ip}`);\n }\n}\nundefined?.test(\"assertIpAddress\", ({ expect }) => {\n // Test with valid IPv4 address\n expect(() => assertIpAddress(\"192.168.1.1\")).not.toThrow();\n\n // Test with valid IPv6 address\n expect(() => assertIpAddress(\"::1\")).not.toThrow();\n\n // Test with invalid IP addresses\n expect(() => assertIpAddress(\"\")).toThrow(\"Invalid IP address: \");\n expect(() => assertIpAddress(\"not an ip\")).toThrow(\"Invalid IP address: not an ip\");\n expect(() => assertIpAddress(\"256.256.256.256\")).toThrow(\"Invalid IP address: 256.256.256.256\");\n expect(() => assertIpAddress(\"192.168.1\")).toThrow(\"Invalid IP address: 192.168.1\");\n});\n"],"mappings":";AAAA,OAAO,aAAa;AAKb,SAAS,YAAY,IAA6C;AACvE,SAAO,QAAQ,EAAE,OAAO,KAAK,CAAC,EAAE,KAAK,EAAE;AACzC;AAsBO,SAAS,gBAAgB,IAAqD;AACnF,MAAI,CAAC,YAAY,EAAE,GAAG;AACpB,UAAM,IAAI,MAAM,uBAAuB,EAAE,EAAE;AAAA,EAC7C;AACF;","names":[]}

View file

@ -0,0 +1,31 @@
// src/utils/json.tsx
import { Result } from "./results";
function isJson(value) {
switch (typeof value) {
case "object": {
if (value === null) return true;
if (Array.isArray(value)) return value.every(isJson);
return Object.keys(value).every((k) => typeof k === "string") && Object.values(value).every(isJson);
}
case "string":
case "number":
case "boolean": {
return true;
}
default: {
return false;
}
}
}
function parseJson(json) {
return Result.fromThrowing(() => JSON.parse(json));
}
function stringifyJson(json) {
return Result.fromThrowing(() => JSON.stringify(json));
}
export {
isJson,
parseJson,
stringifyJson
};
//# sourceMappingURL=json.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,87 @@
// src/utils/jwt.tsx
import crypto from "crypto";
import elliptic from "elliptic";
import * as jose from "jose";
import { JOSEError } from "jose/errors";
import { encodeBase64Url } from "./bytes";
import { StackAssertionError } from "./errors";
import { globalVar } from "./globals";
import { pick } from "./objects";
var STACK_SERVER_SECRET = process.env.STACK_SERVER_SECRET ?? "";
try {
jose.base64url.decode(STACK_SERVER_SECRET);
} catch (e) {
throw new Error("STACK_SERVER_SECRET is not valid. Please use the generateKeys script to generate a new secret.");
}
async function legacySignGlobalJWT(issuer, payload, expirationTime = "5m") {
const privateJwk = await jose.importJWK(await getPrivateJwk(STACK_SERVER_SECRET));
return await new jose.SignJWT(payload).setProtectedHeader({ alg: "ES256" }).setIssuer(issuer).setIssuedAt().setExpirationTime(expirationTime).sign(privateJwk);
}
async function legacyVerifyGlobalJWT(issuer, jwt) {
const jwkSet = jose.createLocalJWKSet(await getPublicJwkSet(STACK_SERVER_SECRET));
const verified = await jose.jwtVerify(jwt, jwkSet, { issuer });
return verified.payload;
}
async function signJWT(options) {
const secret = getPerAudienceSecret({ audience: options.audience, secret: STACK_SERVER_SECRET });
const kid = getKid({ secret });
const privateJwk = await jose.importJWK(await getPrivateJwk(secret));
return await new jose.SignJWT(options.payload).setProtectedHeader({ alg: "ES256", kid }).setIssuer(options.issuer).setIssuedAt().setAudience(options.audience).setExpirationTime(options.expirationTime || "5m").sign(privateJwk);
}
async function verifyJWT(options) {
const audience = jose.decodeJwt(options.jwt).aud;
if (!audience || typeof audience !== "string") {
throw new JOSEError("Invalid JWT audience");
}
const secret = getPerAudienceSecret({ audience, secret: STACK_SERVER_SECRET });
const jwkSet = jose.createLocalJWKSet(await getPublicJwkSet(secret));
const verified = await jose.jwtVerify(options.jwt, jwkSet, { issuer: options.issuer });
return verified.payload;
}
async function getPrivateJwk(secret) {
const secretHash = await globalVar.crypto.subtle.digest("SHA-256", jose.base64url.decode(secret));
const priv = new Uint8Array(secretHash);
const ec = new elliptic.ec("p256");
const key = ec.keyFromPrivate(priv);
const publicKey = key.getPublic();
return {
kty: "EC",
crv: "P-256",
alg: "ES256",
kid: getKid({ secret }),
d: encodeBase64Url(priv),
x: encodeBase64Url(publicKey.getX().toBuffer()),
y: encodeBase64Url(publicKey.getY().toBuffer())
};
}
async function getPublicJwkSet(secretOrPrivateJwk) {
const privateJwk = typeof secretOrPrivateJwk === "string" ? await getPrivateJwk(secretOrPrivateJwk) : secretOrPrivateJwk;
const jwk = pick(privateJwk, ["kty", "alg", "crv", "x", "y", "kid"]);
return {
keys: [jwk]
};
}
function getPerAudienceSecret(options) {
if (options.audience === "kid") {
throw new StackAssertionError("You cannot use the 'kid' audience for a per-audience secret, see comment below in jwt.tsx");
}
return jose.base64url.encode(
crypto.createHash("sha256").update(JSON.stringify([options.secret, options.audience])).digest()
);
}
function getKid(options) {
return jose.base64url.encode(
crypto.createHash("sha256").update(JSON.stringify([options.secret, "kid"])).digest()
).slice(0, 12);
}
export {
getKid,
getPerAudienceSecret,
getPrivateJwk,
getPublicJwkSet,
legacySignGlobalJWT,
legacyVerifyGlobalJWT,
signJWT,
verifyJWT
};
//# sourceMappingURL=jwt.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,57 @@
// src/utils/locks.tsx
import { Semaphore } from "async-mutex";
var ReadWriteLock = class {
constructor() {
this.semaphore = new Semaphore(1);
this.readers = 0;
this.readersMutex = new Semaphore(1);
}
async withReadLock(callback) {
await this._acquireReadLock();
try {
return await callback();
} finally {
await this._releaseReadLock();
}
}
async withWriteLock(callback) {
await this._acquireWriteLock();
try {
return await callback();
} finally {
await this._releaseWriteLock();
}
}
async _acquireReadLock() {
await this.readersMutex.acquire();
try {
this.readers += 1;
if (this.readers === 1) {
await this.semaphore.acquire();
}
} finally {
this.readersMutex.release();
}
}
async _releaseReadLock() {
await this.readersMutex.acquire();
try {
this.readers -= 1;
if (this.readers === 0) {
this.semaphore.release();
}
} finally {
this.readersMutex.release();
}
}
async _acquireWriteLock() {
await this.semaphore.acquire();
}
async _releaseWriteLock() {
this.semaphore.release();
}
};
export {
ReadWriteLock
};
//# sourceMappingURL=locks.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/utils/locks.tsx"],"sourcesContent":["import { Semaphore } from 'async-mutex';\n\ntype LockCallback<T> = () => Promise<T>;\n\nexport class ReadWriteLock {\n private semaphore: Semaphore;\n private readers: number;\n private readersMutex: Semaphore;\n\n constructor() {\n this.semaphore = new Semaphore(1); // Semaphore with 1 permit\n this.readers = 0; // Track the number of readers\n this.readersMutex = new Semaphore(1); // Protect access to `readers` count\n }\n\n async withReadLock<T>(callback: LockCallback<T>): Promise<T> {\n await this._acquireReadLock();\n try {\n return await callback();\n } finally {\n await this._releaseReadLock();\n }\n }\n\n async withWriteLock<T>(callback: LockCallback<T>): Promise<T> {\n await this._acquireWriteLock();\n try {\n return await callback();\n } finally {\n await this._releaseWriteLock();\n }\n }\n\n private async _acquireReadLock(): Promise<void> {\n // Increment the readers count\n await this.readersMutex.acquire();\n try {\n this.readers += 1;\n // If this is the first reader, block writers\n if (this.readers === 1) {\n await this.semaphore.acquire();\n }\n } finally {\n this.readersMutex.release();\n }\n }\n\n private async _releaseReadLock(): Promise<void> {\n // Decrement the readers count\n await this.readersMutex.acquire();\n try {\n this.readers -= 1;\n // If this was the last reader, release the writer block\n if (this.readers === 0) {\n this.semaphore.release();\n }\n } finally {\n this.readersMutex.release();\n }\n }\n\n private async _acquireWriteLock(): Promise<void> {\n // Writers acquire the main semaphore exclusively\n await this.semaphore.acquire();\n }\n\n private async _releaseWriteLock(): Promise<void> {\n // Writers release the main semaphore\n this.semaphore.release();\n }\n}\n"],"mappings":";AAAA,SAAS,iBAAiB;AAInB,IAAM,gBAAN,MAAoB;AAAA,EAKzB,cAAc;AACZ,SAAK,YAAY,IAAI,UAAU,CAAC;AAChC,SAAK,UAAU;AACf,SAAK,eAAe,IAAI,UAAU,CAAC;AAAA,EACrC;AAAA,EAEA,MAAM,aAAgB,UAAuC;AAC3D,UAAM,KAAK,iBAAiB;AAC5B,QAAI;AACF,aAAO,MAAM,SAAS;AAAA,IACxB,UAAE;AACA,YAAM,KAAK,iBAAiB;AAAA,IAC9B;AAAA,EACF;AAAA,EAEA,MAAM,cAAiB,UAAuC;AAC5D,UAAM,KAAK,kBAAkB;AAC7B,QAAI;AACF,aAAO,MAAM,SAAS;AAAA,IACxB,UAAE;AACA,YAAM,KAAK,kBAAkB;AAAA,IAC/B;AAAA,EACF;AAAA,EAEA,MAAc,mBAAkC;AAE9C,UAAM,KAAK,aAAa,QAAQ;AAChC,QAAI;AACF,WAAK,WAAW;AAEhB,UAAI,KAAK,YAAY,GAAG;AACtB,cAAM,KAAK,UAAU,QAAQ;AAAA,MAC/B;AAAA,IACF,UAAE;AACA,WAAK,aAAa,QAAQ;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,MAAc,mBAAkC;AAE9C,UAAM,KAAK,aAAa,QAAQ;AAChC,QAAI;AACF,WAAK,WAAW;AAEhB,UAAI,KAAK,YAAY,GAAG;AACtB,aAAK,UAAU,QAAQ;AAAA,MACzB;AAAA,IACF,UAAE;AACA,WAAK,aAAa,QAAQ;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,MAAc,oBAAmC;AAE/C,UAAM,KAAK,UAAU,QAAQ;AAAA,EAC/B;AAAA,EAEA,MAAc,oBAAmC;AAE/C,SAAK,UAAU,QAAQ;AAAA,EACzB;AACF;","names":[]}

View file

@ -0,0 +1,181 @@
// src/utils/maps.tsx
import { Result } from "./results";
var WeakRefIfAvailable = class {
constructor(value) {
if (typeof WeakRef === "undefined") {
this._ref = { deref: () => value };
} else {
this._ref = new WeakRef(value);
}
}
deref() {
return this._ref.deref();
}
};
var _a, _b;
var IterableWeakMap = class {
constructor(entries) {
this[_a] = "IterableWeakMap";
const mappedEntries = entries?.map((e) => [e[0], { value: e[1], keyRef: new WeakRefIfAvailable(e[0]) }]);
this._weakMap = new WeakMap(mappedEntries ?? []);
this._keyRefs = new Set(mappedEntries?.map((e) => e[1].keyRef) ?? []);
}
get(key) {
return this._weakMap.get(key)?.value;
}
set(key, value) {
const existing = this._weakMap.get(key);
const updated = { value, keyRef: existing?.keyRef ?? new WeakRefIfAvailable(key) };
this._weakMap.set(key, updated);
this._keyRefs.add(updated.keyRef);
return this;
}
delete(key) {
const res = this._weakMap.get(key);
if (res) {
this._weakMap.delete(key);
this._keyRefs.delete(res.keyRef);
return true;
}
return false;
}
has(key) {
return this._weakMap.has(key) && this._keyRefs.has(this._weakMap.get(key).keyRef);
}
*[(_b = Symbol.iterator, _a = Symbol.toStringTag, _b)]() {
for (const keyRef of this._keyRefs) {
const key = keyRef.deref();
const existing = key ? this._weakMap.get(key) : void 0;
if (!key) {
this._keyRefs.delete(keyRef);
} else if (existing) {
yield [key, existing.value];
}
}
}
};
var _a2, _b2;
var MaybeWeakMap = class {
constructor(entries) {
this[_a2] = "MaybeWeakMap";
const entriesArray = [...entries ?? []];
this._primitiveMap = new Map(entriesArray.filter((e) => !this._isAllowedInWeakMap(e[0])));
this._weakMap = new IterableWeakMap(entriesArray.filter((e) => this._isAllowedInWeakMap(e[0])));
}
_isAllowedInWeakMap(key) {
return typeof key === "object" && key !== null || typeof key === "symbol" && Symbol.keyFor(key) === void 0;
}
get(key) {
if (this._isAllowedInWeakMap(key)) {
return this._weakMap.get(key);
} else {
return this._primitiveMap.get(key);
}
}
set(key, value) {
if (this._isAllowedInWeakMap(key)) {
this._weakMap.set(key, value);
} else {
this._primitiveMap.set(key, value);
}
return this;
}
delete(key) {
if (this._isAllowedInWeakMap(key)) {
return this._weakMap.delete(key);
} else {
return this._primitiveMap.delete(key);
}
}
has(key) {
if (this._isAllowedInWeakMap(key)) {
return this._weakMap.has(key);
} else {
return this._primitiveMap.has(key);
}
}
*[(_b2 = Symbol.iterator, _a2 = Symbol.toStringTag, _b2)]() {
yield* this._primitiveMap;
yield* this._weakMap;
}
};
var _a3, _b3;
var DependenciesMap = class {
constructor() {
this._inner = { map: new MaybeWeakMap(), hasValue: false, value: void 0 };
this[_a3] = "DependenciesMap";
}
_valueToResult(inner) {
if (inner.hasValue) {
return Result.ok(inner.value);
} else {
return Result.error(void 0);
}
}
_unwrapFromInner(dependencies, inner) {
if (dependencies.length === 0) {
return this._valueToResult(inner);
} else {
const [key, ...rest] = dependencies;
const newInner = inner.map.get(key);
if (!newInner) {
return Result.error(void 0);
}
return this._unwrapFromInner(rest, newInner);
}
}
_setInInner(dependencies, value, inner) {
if (dependencies.length === 0) {
const res = this._valueToResult(inner);
if (value.status === "ok") {
inner.hasValue = true;
inner.value = value.data;
} else {
inner.hasValue = false;
inner.value = void 0;
}
return res;
} else {
const [key, ...rest] = dependencies;
let newInner = inner.map.get(key);
if (!newInner) {
inner.map.set(key, newInner = { map: new MaybeWeakMap(), hasValue: false, value: void 0 });
}
return this._setInInner(rest, value, newInner);
}
}
*_iterateInner(dependencies, inner) {
if (inner.hasValue) {
yield [dependencies, inner.value];
}
for (const [key, value] of inner.map) {
yield* this._iterateInner([...dependencies, key], value);
}
}
get(dependencies) {
return Result.or(this._unwrapFromInner(dependencies, this._inner), void 0);
}
set(dependencies, value) {
this._setInInner(dependencies, Result.ok(value), this._inner);
return this;
}
delete(dependencies) {
return this._setInInner(dependencies, Result.error(void 0), this._inner).status === "ok";
}
has(dependencies) {
return this._unwrapFromInner(dependencies, this._inner).status === "ok";
}
clear() {
this._inner = { map: new MaybeWeakMap(), hasValue: false, value: void 0 };
}
*[(_b3 = Symbol.iterator, _a3 = Symbol.toStringTag, _b3)]() {
yield* this._iterateInner([], this._inner);
}
};
export {
DependenciesMap,
IterableWeakMap,
MaybeWeakMap,
WeakRefIfAvailable
};
//# sourceMappingURL=maps.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,8 @@
// src/utils/math.tsx
function remainder(n, d) {
return (n % d + Math.abs(d)) % d;
}
export {
remainder
};
//# sourceMappingURL=math.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/utils/math.tsx"],"sourcesContent":["/**\n * Similar to the modulo operator, but always returns a positive number (even when the input is negative).\n */\nexport function remainder(n: number, d: number): number {\n return ((n % d) + Math.abs(d)) % d;\n}\nundefined?.test(\"remainder\", ({ expect }) => {\n expect(remainder(10, 3)).toBe(1);\n expect(remainder(10, 5)).toBe(0);\n expect(remainder(10, 7)).toBe(3);\n // Test with negative numbers\n expect(remainder(-10, 3)).toBe(2);\n expect(remainder(-5, 2)).toBe(1);\n expect(remainder(-7, 4)).toBe(1);\n // Test with decimal numbers\n expect(remainder(10.5, 3)).toBeCloseTo(1.5);\n expect(remainder(-10.5, 3)).toBeCloseTo(1.5);\n});\n"],"mappings":";AAGO,SAAS,UAAU,GAAW,GAAmB;AACtD,UAAS,IAAI,IAAK,KAAK,IAAI,CAAC,KAAK;AACnC;","names":[]}

View file

@ -0,0 +1,42 @@
// src/utils/node-http.tsx
import { IncomingMessage, ServerResponse } from "http";
import { getRelativePart } from "./urls";
var ServerResponseWithBodyChunks = class extends ServerResponse {
constructor() {
super(...arguments);
this.bodyChunks = [];
}
// note: we actually override this, even though it's private in the parent
_send(data, encoding, callback, byteLength) {
if (typeof encoding === "function") {
callback = encoding;
encoding = "utf-8";
}
const encodedBuffer = new Uint8Array(Buffer.from(data, encoding));
this.bodyChunks.push(encodedBuffer);
callback?.();
}
};
async function createNodeHttpServerDuplex(options) {
const incomingMessage = new IncomingMessage({
encrypted: options.originalUrl?.protocol === "https:"
// trick frameworks into believing this is an HTTPS request
});
incomingMessage.httpVersionMajor = 1;
incomingMessage.httpVersionMinor = 1;
incomingMessage.httpVersion = "1.1";
incomingMessage.method = options.method;
incomingMessage.url = getRelativePart(options.url);
incomingMessage.originalUrl = options.originalUrl && getRelativePart(options.originalUrl);
const rawHeaders = [...options.headers.entries()].flat();
incomingMessage._addHeaderLines(rawHeaders, rawHeaders.length);
incomingMessage.push(Buffer.from(options.body));
incomingMessage.complete = true;
incomingMessage.push(null);
const serverResponse = new ServerResponseWithBodyChunks(incomingMessage);
return [incomingMessage, serverResponse];
}
export {
createNodeHttpServerDuplex
};
//# sourceMappingURL=node-http.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/utils/node-http.tsx"],"sourcesContent":["import { IncomingMessage, ServerResponse } from \"http\";\nimport { getRelativePart } from \"./urls\";\n\nclass ServerResponseWithBodyChunks extends ServerResponse {\n bodyChunks: Uint8Array[] = [];\n\n // note: we actually override this, even though it's private in the parent\n _send(data: string, encoding: BufferEncoding, callback?: (() => void) | null, byteLength?: number) {\n if (typeof encoding === \"function\") {\n callback = encoding;\n encoding = \"utf-8\";\n }\n const encodedBuffer = new Uint8Array(Buffer.from(data, encoding));\n this.bodyChunks.push(encodedBuffer);\n callback?.();\n }\n}\n\nexport async function createNodeHttpServerDuplex(options: {\n method: string,\n originalUrl?: URL,\n url: URL,\n headers: Headers,\n body: Uint8Array,\n}): Promise<[IncomingMessage, ServerResponseWithBodyChunks]> {\n // See https://github.com/nodejs/node/blob/main/lib/_http_incoming.js\n // and https://github.com/nodejs/node/blob/main/lib/_http_common.js (particularly the `parserXyz` functions)\n\n const incomingMessage = new IncomingMessage({\n encrypted: options.originalUrl?.protocol === \"https:\", // trick frameworks into believing this is an HTTPS request\n } as any);\n incomingMessage.httpVersionMajor = 1;\n incomingMessage.httpVersionMinor = 1;\n incomingMessage.httpVersion = '1.1';\n incomingMessage.method = options.method;\n incomingMessage.url = getRelativePart(options.url);\n (incomingMessage as any).originalUrl = options.originalUrl && getRelativePart(options.originalUrl); // originalUrl is an extension used by some servers; for example, oidc-provider reads it to construct the paths for the .well-known/openid-configuration\n const rawHeaders = [...options.headers.entries()].flat();\n (incomingMessage as any)._addHeaderLines(rawHeaders, rawHeaders.length);\n incomingMessage.push(Buffer.from(options.body));\n incomingMessage.complete = true;\n incomingMessage.push(null); // to emit end event, see: https://github.com/nodejs/node/blob/4cf6fabce20eb3050c5b543d249e931ea3d3cad5/lib/_http_common.js#L150\n\n const serverResponse = new ServerResponseWithBodyChunks(incomingMessage);\n\n return [incomingMessage, serverResponse];\n}\n"],"mappings":";AAAA,SAAS,iBAAiB,sBAAsB;AAChD,SAAS,uBAAuB;AAEhC,IAAM,+BAAN,cAA2C,eAAe;AAAA,EAA1D;AAAA;AACE,sBAA2B,CAAC;AAAA;AAAA;AAAA,EAG5B,MAAM,MAAc,UAA0B,UAAgC,YAAqB;AACjG,QAAI,OAAO,aAAa,YAAY;AAClC,iBAAW;AACX,iBAAW;AAAA,IACb;AACA,UAAM,gBAAgB,IAAI,WAAW,OAAO,KAAK,MAAM,QAAQ,CAAC;AAChE,SAAK,WAAW,KAAK,aAAa;AAClC,eAAW;AAAA,EACb;AACF;AAEA,eAAsB,2BAA2B,SAMY;AAI3D,QAAM,kBAAkB,IAAI,gBAAgB;AAAA,IAC1C,WAAW,QAAQ,aAAa,aAAa;AAAA;AAAA,EAC/C,CAAQ;AACR,kBAAgB,mBAAmB;AACnC,kBAAgB,mBAAmB;AACnC,kBAAgB,cAAc;AAC9B,kBAAgB,SAAS,QAAQ;AACjC,kBAAgB,MAAM,gBAAgB,QAAQ,GAAG;AACjD,EAAC,gBAAwB,cAAc,QAAQ,eAAe,gBAAgB,QAAQ,WAAW;AACjG,QAAM,aAAa,CAAC,GAAG,QAAQ,QAAQ,QAAQ,CAAC,EAAE,KAAK;AACvD,EAAC,gBAAwB,gBAAgB,YAAY,WAAW,MAAM;AACtE,kBAAgB,KAAK,OAAO,KAAK,QAAQ,IAAI,CAAC;AAC9C,kBAAgB,WAAW;AAC3B,kBAAgB,KAAK,IAAI;AAEzB,QAAM,iBAAiB,IAAI,6BAA6B,eAAe;AAEvE,SAAO,CAAC,iBAAiB,cAAc;AACzC;","names":[]}

View file

@ -0,0 +1,32 @@
// src/utils/numbers.tsx
var magnitudes = [
[1e15, "trln"],
[1e12, "bln"],
[1e9, "bn"],
[1e6, "M"],
[1e3, "k"]
];
function prettyPrintWithMagnitudes(num) {
if (typeof num !== "number") throw new Error("Expected a number");
if (Number.isNaN(num)) return "NaN";
if (num < 0) return "-" + prettyPrintWithMagnitudes(-num);
if (!Number.isFinite(num)) return "\u221E";
for (const [magnitude, suffix] of magnitudes) {
if (num >= magnitude) {
return toFixedMax(num / magnitude, 1) + suffix;
}
}
return toFixedMax(num, 1);
}
function toFixedMax(num, maxDecimals) {
return num.toFixed(maxDecimals).replace(/\.?0+$/, "");
}
function numberCompare(a, b) {
return Math.sign(a - b);
}
export {
numberCompare,
prettyPrintWithMagnitudes,
toFixedMax
};
//# sourceMappingURL=numbers.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/utils/numbers.tsx"],"sourcesContent":["const magnitudes = [\n [1_000_000_000_000_000, \"trln\"],\n [1_000_000_000_000, \"bln\"],\n [1_000_000_000, \"bn\"],\n [1_000_000, \"M\"],\n [1_000, \"k\"],\n] as const;\n\nexport function prettyPrintWithMagnitudes(num: number): string {\n if (typeof num !== \"number\") throw new Error(\"Expected a number\");\n if (Number.isNaN(num)) return \"NaN\";\n if (num < 0) return \"-\" + prettyPrintWithMagnitudes(-num);\n if (!Number.isFinite(num)) return \"∞\";\n\n for (const [magnitude, suffix] of magnitudes) {\n if (num >= magnitude) {\n return toFixedMax(num / magnitude, 1) + suffix;\n }\n }\n return toFixedMax(num, 1); // Handle numbers less than 1,000 without suffix.\n}\nundefined?.test(\"prettyPrintWithMagnitudes\", ({ expect }) => {\n // Test different magnitudes\n expect(prettyPrintWithMagnitudes(1000)).toBe(\"1k\");\n expect(prettyPrintWithMagnitudes(1500)).toBe(\"1.5k\");\n expect(prettyPrintWithMagnitudes(1000000)).toBe(\"1M\");\n expect(prettyPrintWithMagnitudes(1500000)).toBe(\"1.5M\");\n expect(prettyPrintWithMagnitudes(1000000000)).toBe(\"1bn\");\n expect(prettyPrintWithMagnitudes(1500000000)).toBe(\"1.5bn\");\n expect(prettyPrintWithMagnitudes(1000000000000)).toBe(\"1bln\");\n expect(prettyPrintWithMagnitudes(1500000000000)).toBe(\"1.5bln\");\n expect(prettyPrintWithMagnitudes(1000000000000000)).toBe(\"1trln\");\n expect(prettyPrintWithMagnitudes(1500000000000000)).toBe(\"1.5trln\");\n // Test small numbers\n expect(prettyPrintWithMagnitudes(100)).toBe(\"100\");\n expect(prettyPrintWithMagnitudes(0)).toBe(\"0\");\n expect(prettyPrintWithMagnitudes(0.5)).toBe(\"0.5\");\n // Test negative numbers\n expect(prettyPrintWithMagnitudes(-1000)).toBe(\"-1k\");\n expect(prettyPrintWithMagnitudes(-1500000)).toBe(\"-1.5M\");\n // Test special cases\n expect(prettyPrintWithMagnitudes(NaN)).toBe(\"NaN\");\n expect(prettyPrintWithMagnitudes(Infinity)).toBe(\"∞\");\n expect(prettyPrintWithMagnitudes(-Infinity)).toBe(\"-∞\");\n});\n\nexport function toFixedMax(num: number, maxDecimals: number): string {\n return num.toFixed(maxDecimals).replace(/\\.?0+$/, \"\");\n}\nundefined?.test(\"toFixedMax\", ({ expect }) => {\n expect(toFixedMax(1, 2)).toBe(\"1\");\n expect(toFixedMax(1.2, 2)).toBe(\"1.2\");\n expect(toFixedMax(1.23, 2)).toBe(\"1.23\");\n expect(toFixedMax(1.234, 2)).toBe(\"1.23\");\n expect(toFixedMax(1.0, 2)).toBe(\"1\");\n expect(toFixedMax(1.20, 2)).toBe(\"1.2\");\n expect(toFixedMax(0, 2)).toBe(\"0\");\n});\n\nexport function numberCompare(a: number, b: number): number {\n return Math.sign(a - b);\n}\nundefined?.test(\"numberCompare\", ({ expect }) => {\n expect(numberCompare(1, 2)).toBe(-1);\n expect(numberCompare(2, 1)).toBe(1);\n expect(numberCompare(1, 1)).toBe(0);\n expect(numberCompare(0, 0)).toBe(0);\n expect(numberCompare(-1, -2)).toBe(1);\n expect(numberCompare(-2, -1)).toBe(-1);\n expect(numberCompare(-1, 1)).toBe(-1);\n expect(numberCompare(1, -1)).toBe(1);\n});\n"],"mappings":";AAAA,IAAM,aAAa;AAAA,EACjB,CAAC,MAAuB,MAAM;AAAA,EAC9B,CAAC,MAAmB,KAAK;AAAA,EACzB,CAAC,KAAe,IAAI;AAAA,EACpB,CAAC,KAAW,GAAG;AAAA,EACf,CAAC,KAAO,GAAG;AACb;AAEO,SAAS,0BAA0B,KAAqB;AAC7D,MAAI,OAAO,QAAQ,SAAU,OAAM,IAAI,MAAM,mBAAmB;AAChE,MAAI,OAAO,MAAM,GAAG,EAAG,QAAO;AAC9B,MAAI,MAAM,EAAG,QAAO,MAAM,0BAA0B,CAAC,GAAG;AACxD,MAAI,CAAC,OAAO,SAAS,GAAG,EAAG,QAAO;AAElC,aAAW,CAAC,WAAW,MAAM,KAAK,YAAY;AAC5C,QAAI,OAAO,WAAW;AACpB,aAAO,WAAW,MAAM,WAAW,CAAC,IAAI;AAAA,IAC1C;AAAA,EACF;AACA,SAAO,WAAW,KAAK,CAAC;AAC1B;AA0BO,SAAS,WAAW,KAAa,aAA6B;AACnE,SAAO,IAAI,QAAQ,WAAW,EAAE,QAAQ,UAAU,EAAE;AACtD;AAWO,SAAS,cAAc,GAAW,GAAmB;AAC1D,SAAO,KAAK,KAAK,IAAI,CAAC;AACxB;","names":[]}

View file

@ -0,0 +1,10 @@
// src/utils/oauth.tsx
var standardProviders = ["google", "github", "microsoft", "spotify", "facebook", "discord", "gitlab", "bitbucket", "linkedin", "apple", "x"];
var sharedProviders = ["google", "github", "microsoft", "spotify"];
var allProviders = standardProviders;
export {
allProviders,
sharedProviders,
standardProviders
};
//# sourceMappingURL=oauth.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/utils/oauth.tsx"],"sourcesContent":["export const standardProviders = [\"google\", \"github\", \"microsoft\", \"spotify\", \"facebook\", \"discord\", \"gitlab\", \"bitbucket\", \"linkedin\", \"apple\", \"x\"] as const;\n// No more shared providers should be added except for special cases\nexport const sharedProviders = [\"google\", \"github\", \"microsoft\", \"spotify\"] as const;\nexport const allProviders = standardProviders;\n\nexport type ProviderType = typeof allProviders[number];\nexport type StandardProviderType = typeof standardProviders[number];\nexport type SharedProviderType = typeof sharedProviders[number];\n"],"mappings":";AAAO,IAAM,oBAAoB,CAAC,UAAU,UAAU,aAAa,WAAW,YAAY,WAAW,UAAU,aAAa,YAAY,SAAS,GAAG;AAE7I,IAAM,kBAAkB,CAAC,UAAU,UAAU,aAAa,SAAS;AACnE,IAAM,eAAe;","names":[]}

View file

@ -0,0 +1,177 @@
// src/utils/objects.tsx
import { StackAssertionError } from "./errors";
import { identity } from "./functions";
import { stringCompare } from "./strings";
function isNotNull(value) {
return value !== null && value !== void 0;
}
function deepPlainEquals(obj1, obj2, options = {}) {
if (typeof obj1 !== typeof obj2) return false;
if (obj1 === obj2) return true;
switch (typeof obj1) {
case "object": {
if (!obj1 || !obj2) return false;
if (Array.isArray(obj1) || Array.isArray(obj2)) {
if (!Array.isArray(obj1) || !Array.isArray(obj2)) return false;
if (obj1.length !== obj2.length) return false;
return obj1.every((v, i) => deepPlainEquals(v, obj2[i], options));
}
const entries1 = Object.entries(obj1).filter(([k, v]) => !options.ignoreUndefinedValues || v !== void 0);
const entries2 = Object.entries(obj2).filter(([k, v]) => !options.ignoreUndefinedValues || v !== void 0);
if (entries1.length !== entries2.length) return false;
return entries1.every(([k, v1]) => {
const e2 = entries2.find(([k2]) => k === k2);
if (!e2) return false;
return deepPlainEquals(v1, e2[1], options);
});
}
case "undefined":
case "string":
case "number":
case "boolean":
case "bigint":
case "symbol":
case "function": {
return false;
}
default: {
throw new Error("Unexpected typeof " + typeof obj1);
}
}
}
function isCloneable(obj) {
return typeof obj !== "symbol" && typeof obj !== "function";
}
function shallowClone(obj) {
if (!isCloneable(obj)) throw new StackAssertionError("shallowClone does not support symbols or functions", { obj });
if (Array.isArray(obj)) return obj.map(identity);
return { ...obj };
}
function deepPlainClone(obj) {
if (typeof obj === "function") throw new StackAssertionError("deepPlainClone does not support functions");
if (typeof obj === "symbol") throw new StackAssertionError("deepPlainClone does not support symbols");
if (typeof obj !== "object" || !obj) return obj;
if (Array.isArray(obj)) return obj.map(deepPlainClone);
return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, deepPlainClone(v)]));
}
function deepMerge(baseObj, mergeObj) {
if ([baseObj, mergeObj, ...Object.values(baseObj), ...Object.values(mergeObj)].some((o) => !isCloneable(o))) throw new StackAssertionError("deepMerge does not support functions or symbols", { baseObj, mergeObj });
const res = shallowClone(baseObj);
for (const [key, mergeValue] of Object.entries(mergeObj)) {
if (has(res, key)) {
const baseValue = get(res, key);
if (isObjectLike(baseValue) && isObjectLike(mergeValue)) {
set(res, key, deepMerge(baseValue, mergeValue));
continue;
}
}
set(res, key, mergeValue);
}
return res;
}
function typedEntries(obj) {
return Object.entries(obj);
}
function typedFromEntries(entries) {
return Object.fromEntries(entries);
}
function typedKeys(obj) {
return Object.keys(obj);
}
function typedValues(obj) {
return Object.values(obj);
}
function typedAssign(target, source) {
return Object.assign(target, source);
}
function filterUndefined(obj) {
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0));
}
function filterUndefinedOrNull(obj) {
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0 && v !== null));
}
function deepFilterUndefined(obj) {
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0).map(([k, v]) => [k, isObjectLike(v) ? deepFilterUndefined(v) : v]));
}
function pick(obj, keys) {
return Object.fromEntries(Object.entries(obj).filter(([k]) => keys.includes(k)));
}
function omit(obj, keys) {
if (!Array.isArray(keys)) throw new StackAssertionError("omit: keys must be an array", { obj, keys });
return Object.fromEntries(Object.entries(obj).filter(([k]) => !keys.includes(k)));
}
function split(obj, keys) {
return [pick(obj, keys), omit(obj, keys)];
}
function mapValues(obj, fn) {
if (Array.isArray(obj)) {
return obj.map((v) => fn(v));
}
return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)]));
}
function sortKeys(obj) {
if (Array.isArray(obj)) {
return [...obj];
}
return Object.fromEntries(Object.entries(obj).sort(([a], [b]) => stringCompare(a, b)));
}
function deepSortKeys(obj) {
return sortKeys(mapValues(obj, (v) => isObjectLike(v) ? deepSortKeys(v) : v));
}
function set(obj, key, value) {
Object.defineProperty(obj, key, { value, writable: true, configurable: true, enumerable: true });
}
function get(obj, key) {
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
if (!descriptor) throw new StackAssertionError(`get: key ${String(key)} does not exist`, { obj, key });
return descriptor.value;
}
function getOrUndefined(obj, key) {
return has(obj, key) ? get(obj, key) : void 0;
}
function has(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
function hasAndNotUndefined(obj, key) {
return has(obj, key) && get(obj, key) !== void 0;
}
function deleteKey(obj, key) {
if (has(obj, key)) {
Reflect.deleteProperty(obj, key);
} else {
throw new StackAssertionError(`deleteKey: key ${String(key)} does not exist`, { obj, key });
}
}
function isObjectLike(value) {
return (typeof value === "object" || typeof value === "function") && value !== null;
}
export {
deepFilterUndefined,
deepMerge,
deepPlainClone,
deepPlainEquals,
deepSortKeys,
deleteKey,
filterUndefined,
filterUndefinedOrNull,
get,
getOrUndefined,
has,
hasAndNotUndefined,
isCloneable,
isNotNull,
isObjectLike,
mapValues,
omit,
pick,
set,
shallowClone,
sortKeys,
split,
typedAssign,
typedEntries,
typedFromEntries,
typedKeys,
typedValues
};
//# sourceMappingURL=objects.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
//# sourceMappingURL=passkey.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}

View file

@ -0,0 +1,233 @@
// src/utils/promises.tsx
import { KnownError } from "..";
import { StackAssertionError, captureError, concatStacktraces } from "./errors";
import { DependenciesMap } from "./maps";
import { Result } from "./results";
import { generateUuid } from "./uuids";
function createPromise(callback) {
let status = "pending";
let valueOrReason = void 0;
let resolve = null;
let reject = null;
const promise = new Promise((res, rej) => {
resolve = (value) => {
if (status !== "pending") return;
status = "fulfilled";
valueOrReason = value;
res(value);
};
reject = (reason) => {
if (status !== "pending") return;
status = "rejected";
valueOrReason = reason;
rej(reason);
};
});
callback(resolve, reject);
return Object.assign(promise, {
status,
...status === "fulfilled" ? { value: valueOrReason } : {},
...status === "rejected" ? { reason: valueOrReason } : {}
});
}
var resolvedCache = null;
function resolved(value) {
resolvedCache ??= new DependenciesMap();
if (resolvedCache.has([value])) {
return resolvedCache.get([value]);
}
const res = Object.assign(Promise.resolve(value), {
status: "fulfilled",
value
});
resolvedCache.set([value], res);
return res;
}
var rejectedCache = null;
function rejected(reason) {
rejectedCache ??= new DependenciesMap();
if (rejectedCache.has([reason])) {
return rejectedCache.get([reason]);
}
const promise = Promise.reject(reason);
ignoreUnhandledRejection(promise);
const res = Object.assign(promise, {
status: "rejected",
reason
});
rejectedCache.set([reason], res);
return res;
}
var neverResolvePromise = pending(new Promise(() => {
}));
function neverResolve() {
return neverResolvePromise;
}
function pending(promise, options = {}) {
const res = promise.then(
(value) => {
res.status = "fulfilled";
res.value = value;
return value;
},
(actualReason) => {
res.status = "rejected";
res.reason = actualReason;
throw actualReason;
}
);
res.status = "pending";
return res;
}
function ignoreUnhandledRejection(promise) {
promise.catch(() => {
});
}
async function wait(ms) {
if (!Number.isFinite(ms) || ms < 0) {
throw new StackAssertionError(`wait() requires a non-negative integer number of milliseconds to wait. (found: ${ms}ms)`);
}
if (ms >= 2 ** 31) {
throw new StackAssertionError("The maximum timeout for wait() is 2147483647ms (2**31 - 1). (found: ${ms}ms)");
}
return await new Promise((resolve) => setTimeout(resolve, ms));
}
async function waitUntil(date) {
return await wait(date.getTime() - Date.now());
}
function runAsynchronouslyWithAlert(...args) {
return runAsynchronously(
args[0],
{
...args[1],
onError: (error) => {
if (KnownError.isKnownError(error) && typeof process !== "undefined" && process.env.NODE_ENV?.includes("production")) {
alert(error.message);
} else {
alert(`An unhandled error occurred. Please ${process.env.NODE_ENV === "development" ? `check the browser console for the full error.` : "report this to the developer."}
${error}`);
}
args[1]?.onError?.(error);
}
},
...args.slice(2)
);
}
function runAsynchronously(promiseOrFunc, options = {}) {
if (typeof promiseOrFunc === "function") {
promiseOrFunc = promiseOrFunc();
}
const duringError = new Error();
promiseOrFunc?.catch((error) => {
options.onError?.(error);
const newError = new StackAssertionError(
"Uncaught error in asynchronous function: " + error.toString(),
{ cause: error }
);
concatStacktraces(newError, duringError);
if (!options.noErrorLogging) {
captureError("runAsynchronously", newError);
}
});
}
var TimeoutError = class extends Error {
constructor(ms) {
super(`Timeout after ${ms}ms`);
this.ms = ms;
this.name = "TimeoutError";
}
};
async function timeout(promise, ms) {
return await Promise.race([
promise.then((value) => Result.ok(value)),
wait(ms).then(() => Result.error(new TimeoutError(ms)))
]);
}
async function timeoutThrow(promise, ms) {
return Result.orThrow(await timeout(promise, ms));
}
function rateLimited(func, options) {
let waitUntil2 = performance.now();
let queue = [];
let addedToQueueCallbacks = /* @__PURE__ */ new Map();
const next = async () => {
while (true) {
if (waitUntil2 > performance.now()) {
await wait(Math.max(1, waitUntil2 - performance.now() + 1));
} else if (queue.length === 0) {
const uuid = generateUuid();
await new Promise((resolve) => {
addedToQueueCallbacks.set(uuid, resolve);
});
addedToQueueCallbacks.delete(uuid);
} else {
break;
}
}
const nextFuncs = options.batchCalls ? queue.splice(0, queue.length) : [queue.shift()];
const start = performance.now();
const value = await Result.fromPromise(func());
const end = performance.now();
waitUntil2 = Math.max(
waitUntil2,
start + (options.throttleMs ?? 0),
end + (options.gapMs ?? 0)
);
for (const nextFunc of nextFuncs) {
if (value.status === "ok") {
nextFunc[0](value.data);
} else {
nextFunc[1](value.error);
}
}
};
runAsynchronously(async () => {
while (true) {
await next();
}
});
return () => {
return new Promise((resolve, reject) => {
waitUntil2 = Math.max(
waitUntil2,
performance.now() + (options.debounceMs ?? 0)
);
queue.push([resolve, reject]);
addedToQueueCallbacks.forEach((cb) => cb());
});
};
}
function throttled(func, delayMs) {
let timeout2 = null;
let nextAvailable = null;
return async (...args) => {
while (nextAvailable !== null) {
await nextAvailable;
}
nextAvailable = new Promise((resolve) => {
timeout2 = setTimeout(() => {
nextAvailable = null;
resolve(func(...args));
}, delayMs);
});
return await nextAvailable;
};
}
export {
createPromise,
ignoreUnhandledRejection,
neverResolve,
pending,
rateLimited,
rejected,
resolved,
runAsynchronously,
runAsynchronouslyWithAlert,
throttled,
timeout,
timeoutThrow,
wait,
waitUntil
};
//# sourceMappingURL=promises.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,128 @@
// src/utils/proxies.tsx
import { nicify } from "./strings";
function logged(name, toLog, options = {}) {
const proxy = new Proxy(toLog, {
get(target, prop, receiver) {
const orig = Reflect.get(target, prop, receiver);
if (typeof orig === "function") {
return function(...args) {
const success = (v, isPromise) => console.debug(`logged(...): Called ${name}.${String(prop)}(${args.map((a) => nicify(a)).join(", ")}) => ${isPromise ? "Promise<" : ""}${nicify(result)}${isPromise ? ">" : ""}`, { this: this, args, promise: isPromise ? result : false, result: v, trace: new Error() });
const error = (e, isPromise) => console.debug(`logged(...): Error in ${name}.${String(prop)}(${args.map((a) => nicify(a)).join(", ")})`, { this: this, args, promise: isPromise ? result : false, error: e, trace: new Error() });
let result;
try {
result = orig.apply(this, args);
} catch (e) {
error(e, false);
throw e;
}
if (result instanceof Promise) {
result.then((v) => success(v, true)).catch((e) => error(e, true));
} else {
success(result, false);
}
return result;
};
}
return orig;
},
set(target, prop, value) {
console.log(`Setting ${name}.${String(prop)} to ${value}`);
return Reflect.set(target, prop, value);
},
apply(target, thisArg, args) {
console.log(`Calling ${name}(${JSON.stringify(args).slice(1, -1)})`);
return Reflect.apply(target, thisArg, args);
},
construct(target, args, newTarget) {
console.log(`Constructing ${name}(${JSON.stringify(args).slice(1, -1)})`);
return Reflect.construct(target, args, newTarget);
},
defineProperty(target, prop, descriptor) {
console.log(`Defining ${name}.${String(prop)} as ${JSON.stringify(descriptor)}`);
return Reflect.defineProperty(target, prop, descriptor);
},
deleteProperty(target, prop) {
console.log(`Deleting ${name}.${String(prop)}`);
return Reflect.deleteProperty(target, prop);
},
setPrototypeOf(target, prototype) {
console.log(`Setting prototype of ${name} to ${prototype}`);
return Reflect.setPrototypeOf(target, prototype);
},
preventExtensions(target) {
console.log(`Preventing extensions of ${name}`);
return Reflect.preventExtensions(target);
}
});
return proxy;
}
function createLazyProxy(factory) {
let cache = void 0;
let initialized = false;
function initializeIfNeeded() {
if (!initialized) {
cache = factory();
initialized = true;
}
return cache;
}
return new Proxy({}, {
get(target, prop, receiver) {
const instance = initializeIfNeeded();
return Reflect.get(instance, prop, receiver);
},
set(target, prop, value, receiver) {
const instance = initializeIfNeeded();
return Reflect.set(instance, prop, value, receiver);
},
has(target, prop) {
const instance = initializeIfNeeded();
return Reflect.has(instance, prop);
},
deleteProperty(target, prop) {
const instance = initializeIfNeeded();
return Reflect.deleteProperty(instance, prop);
},
ownKeys(target) {
const instance = initializeIfNeeded();
return Reflect.ownKeys(instance);
},
getOwnPropertyDescriptor(target, prop) {
const instance = initializeIfNeeded();
return Reflect.getOwnPropertyDescriptor(instance, prop);
},
defineProperty(target, prop, descriptor) {
const instance = initializeIfNeeded();
return Reflect.defineProperty(instance, prop, descriptor);
},
getPrototypeOf(target) {
const instance = initializeIfNeeded();
return Reflect.getPrototypeOf(instance);
},
setPrototypeOf(target, proto) {
const instance = initializeIfNeeded();
return Reflect.setPrototypeOf(instance, proto);
},
isExtensible(target) {
const instance = initializeIfNeeded();
return Reflect.isExtensible(instance);
},
preventExtensions(target) {
const instance = initializeIfNeeded();
return Reflect.preventExtensions(instance);
},
apply(target, thisArg, argumentsList) {
const instance = initializeIfNeeded();
return Reflect.apply(instance, thisArg, argumentsList);
},
construct(target, argumentsList, newTarget) {
const instance = initializeIfNeeded();
return Reflect.construct(instance, argumentsList, newTarget);
}
});
}
export {
createLazyProxy,
logged
};
//# sourceMappingURL=proxies.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,78 @@
// src/utils/react.tsx
import React from "react";
import { isBrowserLike } from "./env";
import { neverResolve } from "./promises";
import { deindent } from "./strings";
function forwardRefIfNeeded(render) {
const version = React.version;
const major = parseInt(version.split(".")[0]);
if (major < 19) {
return React.forwardRef(render);
} else {
return (props) => render(props, props.ref);
}
}
function getNodeText(node) {
if (["number", "string"].includes(typeof node)) {
return `${node}`;
}
if (!node) {
return "";
}
if (Array.isArray(node)) {
return node.map(getNodeText).join("");
}
if (typeof node === "object" && "props" in node) {
return getNodeText(node.props.children);
}
throw new Error(`Unknown node type: ${typeof node}`);
}
function suspend() {
React.use(neverResolve());
throw new Error("Somehow a Promise that never resolves was resolved?");
}
var NoSuspenseBoundaryError = class extends Error {
constructor(options) {
super(deindent`
${options.caller ?? "This code path"} attempted to display a loading indicator, but didn't find a Suspense boundary above it. Please read the error message below carefully.
The fix depends on which of the 3 scenarios caused it:
1. You are missing a loading.tsx file in your app directory. Fix it by adding a loading.tsx file in your app directory.
2. The component is rendered in the root (outermost) layout.tsx or template.tsx file. Next.js does not wrap those files in a Suspense boundary, even if there is a loading.tsx file in the same folder. To fix it, wrap your layout inside a route group like this:
- app
- - layout.tsx // contains <html> and <body>, alongside providers and other components that don't need ${options.caller ?? "this code path"}
- - loading.tsx // required for suspense
- - (main)
- - - layout.tsx // contains the main layout of your app, like a sidebar or a header, and can use ${options.caller ?? "this code path"}
- - - route.tsx // your actual main page
- - - the rest of your app
For more information on this approach, see Next's documentation on route groups: https://nextjs.org/docs/app/building-your-application/routing/route-groups
3. You caught this error with try-catch or a custom error boundary. Fix this by rethrowing the error or not catching it in the first place.
See: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout
More information on SSR and Suspense boundaries: https://react.dev/reference/react/Suspense#providing-a-fallback-for-server-errors-and-client-only-content
`);
this.name = "NoSuspenseBoundaryError";
this.reason = options.caller ?? "suspendIfSsr()";
this.digest = "BAILOUT_TO_CLIENT_SIDE_RENDERING";
}
};
function suspendIfSsr(caller) {
if (!isBrowserLike()) {
throw new NoSuspenseBoundaryError({ caller });
}
}
export {
NoSuspenseBoundaryError,
forwardRefIfNeeded,
getNodeText,
suspend,
suspendIfSsr
};
//# sourceMappingURL=react.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,141 @@
// src/utils/results.tsx
import { wait } from "./promises";
import { deindent, nicify } from "./strings";
var Result = {
fromThrowing,
fromThrowingAsync,
fromPromise: promiseToResult,
ok(data) {
return {
status: "ok",
data
};
},
error(error) {
return {
status: "error",
error
};
},
map: mapResult,
or: (result, fallback) => {
return result.status === "ok" ? result.data : fallback;
},
orThrow: (result) => {
if (result.status === "error") {
throw result.error;
}
return result.data;
},
orThrowAsync: async (result) => {
return Result.orThrow(await result);
},
retry
};
var AsyncResult = {
fromThrowing,
fromPromise: promiseToResult,
ok: Result.ok,
error: Result.error,
pending,
map: mapResult,
or: (result, fallback) => {
if (result.status === "pending") {
return fallback;
}
return Result.or(result, fallback);
},
orThrow: (result) => {
if (result.status === "pending") {
throw new Error("Result still pending");
}
return Result.orThrow(result);
},
retry
};
function pending(progress) {
return {
status: "pending",
progress
};
}
async function promiseToResult(promise) {
try {
const value = await promise;
return Result.ok(value);
} catch (error) {
return Result.error(error);
}
}
function fromThrowing(fn) {
try {
return Result.ok(fn());
} catch (error) {
return Result.error(error);
}
}
async function fromThrowingAsync(fn) {
try {
return Result.ok(await fn());
} catch (error) {
return Result.error(error);
}
}
function mapResult(result, fn) {
if (result.status === "error") return {
status: "error",
error: result.error
};
if (result.status === "pending") return {
status: "pending",
..."progress" in result ? { progress: result.progress } : {}
};
return Result.ok(fn(result.data));
}
var RetryError = class extends AggregateError {
constructor(errors) {
const strings = errors.map((e) => nicify(e));
const isAllSame = strings.length > 1 && strings.every((s) => s === strings[0]);
super(
errors,
deindent`
Error after ${errors.length} attempts.
${isAllSame ? deindent`
Attempts 1-${errors.length}:
${strings[0]}
` : strings.map((s, i) => deindent`
Attempt ${i + 1}:
${s}
`).join("\n\n")}
`,
{ cause: errors[errors.length - 1] }
);
this.errors = errors;
this.name = "RetryError";
}
get attempts() {
return this.errors.length;
}
};
RetryError.prototype.name = "RetryError";
async function retry(fn, totalAttempts, { exponentialDelayBase = 1e3 } = {}) {
const errors = [];
for (let i = 0; i < totalAttempts; i++) {
const res = await fn(i);
if (res.status === "ok") {
return Object.assign(Result.ok(res.data), { attempts: i + 1 });
} else {
errors.push(res.error);
if (i < totalAttempts - 1) {
await wait((Math.random() + 0.5) * exponentialDelayBase * 2 ** i);
}
}
}
return Object.assign(Result.error(new RetryError(errors)), { attempts: totalAttempts });
}
export {
AsyncResult,
Result
};
//# sourceMappingURL=results.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,20 @@
// src/utils/sentry.tsx
var sentryBaseConfig = {
ignoreErrors: [
// React throws these errors when used with some browser extensions (eg. Google Translate)
"NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.",
"NotFoundError: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node."
],
normalizeDepth: 5,
maxValueLength: 5e3,
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1,
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
replaysOnErrorSampleRate: 1,
replaysSessionSampleRate: 1
};
export {
sentryBaseConfig
};
//# sourceMappingURL=sentry.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/utils/sentry.tsx"],"sourcesContent":["import * as Sentry from \"@sentry/nextjs\";\n\nexport const sentryBaseConfig: Sentry.BrowserOptions & Sentry.NodeOptions & Sentry.VercelEdgeOptions = {\n ignoreErrors: [\n // React throws these errors when used with some browser extensions (eg. Google Translate)\n \"NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.\",\n \"NotFoundError: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.\",\n ],\n\n normalizeDepth: 5,\n maxValueLength: 5000,\n\n // Adjust this value in production, or use tracesSampler for greater control\n tracesSampleRate: 1.0,\n\n // Setting this option to true will print useful information to the console while you're setting up Sentry.\n debug: false,\n\n replaysOnErrorSampleRate: 1.0,\n\n replaysSessionSampleRate: 1.0,\n};\n"],"mappings":";AAEO,IAAM,mBAA0F;AAAA,EACrG,cAAc;AAAA;AAAA,IAEZ;AAAA,IACA;AAAA,EACF;AAAA,EAEA,gBAAgB;AAAA,EAChB,gBAAgB;AAAA;AAAA,EAGhB,kBAAkB;AAAA;AAAA,EAGlB,OAAO;AAAA,EAEP,0BAA0B;AAAA,EAE1B,0BAA0B;AAC5B;","names":[]}

View file

@ -0,0 +1,195 @@
// src/utils/stores.tsx
import { ReadWriteLock } from "./locks";
import { pending, rejected, resolved } from "./promises";
import { AsyncResult, Result } from "./results";
import { generateUuid } from "./uuids";
var Store = class {
constructor(_value) {
this._value = _value;
this._callbacks = /* @__PURE__ */ new Map();
}
get() {
return this._value;
}
set(value) {
const oldValue = this._value;
this._value = value;
this._callbacks.forEach((callback) => callback(value, oldValue));
}
update(updater) {
const value = updater(this._value);
this.set(value);
return value;
}
onChange(callback) {
const uuid = generateUuid();
this._callbacks.set(uuid, callback);
return {
unsubscribe: () => {
this._callbacks.delete(uuid);
}
};
}
onceChange(callback) {
const { unsubscribe } = this.onChange((...args) => {
unsubscribe();
callback(...args);
});
return { unsubscribe };
}
};
var storeLock = new ReadWriteLock();
var AsyncStore = class _AsyncStore {
constructor(...args) {
this._mostRecentOkValue = void 0;
this._isRejected = false;
this._waitingRejectFunctions = /* @__PURE__ */ new Map();
this._callbacks = /* @__PURE__ */ new Map();
this._updateCounter = 0;
this._lastSuccessfulUpdate = -1;
if (args.length === 0) {
this._isAvailable = false;
} else {
this._isAvailable = true;
this._mostRecentOkValue = args[0];
}
}
isAvailable() {
return this._isAvailable;
}
isRejected() {
return this._isRejected;
}
get() {
if (this.isRejected()) {
return AsyncResult.error(this._rejectionError);
} else if (this.isAvailable()) {
return AsyncResult.ok(this._mostRecentOkValue);
} else {
return AsyncResult.pending();
}
}
getOrWait() {
const uuid = generateUuid();
if (this.isRejected()) {
return rejected(this._rejectionError);
} else if (this.isAvailable()) {
return resolved(this._mostRecentOkValue);
}
const promise = new Promise((resolve, reject) => {
this.onceChange((value) => {
resolve(value);
});
this._waitingRejectFunctions.set(uuid, reject);
});
const withFinally = promise.finally(() => {
this._waitingRejectFunctions.delete(uuid);
});
return pending(withFinally);
}
_setIfLatest(result, curCounter) {
const oldState = this.get();
const oldValue = this._mostRecentOkValue;
if (curCounter > this._lastSuccessfulUpdate) {
switch (result.status) {
case "ok": {
if (!this._isAvailable || this._isRejected || this._mostRecentOkValue !== result.data) {
this._lastSuccessfulUpdate = curCounter;
this._isAvailable = true;
this._isRejected = false;
this._mostRecentOkValue = result.data;
this._rejectionError = void 0;
this._callbacks.forEach((callback) => callback({
state: this.get(),
oldState,
lastOkValue: oldValue
}));
return true;
}
return false;
}
case "error": {
this._lastSuccessfulUpdate = curCounter;
this._isAvailable = false;
this._isRejected = true;
this._rejectionError = result.error;
this._waitingRejectFunctions.forEach((reject) => reject(result.error));
this._callbacks.forEach((callback) => callback({
state: this.get(),
oldState,
lastOkValue: oldValue
}));
return true;
}
}
}
return false;
}
set(value) {
this._setIfLatest(Result.ok(value), ++this._updateCounter);
}
update(updater) {
const value = updater(this._mostRecentOkValue);
this.set(value);
return value;
}
async setAsync(promise) {
return await storeLock.withReadLock(async () => {
const curCounter = ++this._updateCounter;
const result = await Result.fromPromise(promise);
return this._setIfLatest(result, curCounter);
});
}
setUnavailable() {
this._lastSuccessfulUpdate = ++this._updateCounter;
this._isAvailable = false;
this._isRejected = false;
this._rejectionError = void 0;
}
setRejected(error) {
this._setIfLatest(Result.error(error), ++this._updateCounter);
}
map(mapper) {
const store = new _AsyncStore();
this.onChange((value) => {
store.set(mapper(value));
});
return store;
}
onChange(callback) {
return this.onStateChange(({ state, lastOkValue }) => {
if (state.status === "ok") {
callback(state.data, lastOkValue);
}
});
}
onStateChange(callback) {
const uuid = generateUuid();
this._callbacks.set(uuid, callback);
return {
unsubscribe: () => {
this._callbacks.delete(uuid);
}
};
}
onceChange(callback) {
const { unsubscribe } = this.onChange((...args) => {
unsubscribe();
callback(...args);
});
return { unsubscribe };
}
onceStateChange(callback) {
const { unsubscribe } = this.onStateChange((...args) => {
unsubscribe();
callback(...args);
});
return { unsubscribe };
}
};
export {
AsyncStore,
Store,
storeLock
};
//# sourceMappingURL=stores.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,295 @@
// src/utils/strings.tsx
import { findLastIndex, unique } from "./arrays";
import { StackAssertionError } from "./errors";
import { filterUndefined } from "./objects";
function typedToLowercase(s) {
if (typeof s !== "string") throw new StackAssertionError("Expected a string for typedToLowercase", { s });
return s.toLowerCase();
}
function typedToUppercase(s) {
if (typeof s !== "string") throw new StackAssertionError("Expected a string for typedToUppercase", { s });
return s.toUpperCase();
}
function typedCapitalize(s) {
return s.charAt(0).toUpperCase() + s.slice(1);
}
function stringCompare(a, b) {
if (typeof a !== "string" || typeof b !== "string") throw new StackAssertionError(`Expected two strings for stringCompare, found ${typeof a} and ${typeof b}`, { a, b });
const cmp = (a2, b2) => a2 < b2 ? -1 : a2 > b2 ? 1 : 0;
return cmp(a.toUpperCase(), b.toUpperCase()) || cmp(b, a);
}
function getWhitespacePrefix(s) {
return s.substring(0, s.length - s.trimStart().length);
}
function getWhitespaceSuffix(s) {
return s.substring(s.trimEnd().length);
}
function trimEmptyLinesStart(s) {
const lines = s.split("\n");
const firstNonEmptyLineIndex = lines.findIndex((line) => line.trim() !== "");
if (firstNonEmptyLineIndex === -1) return "";
return lines.slice(firstNonEmptyLineIndex).join("\n");
}
function trimEmptyLinesEnd(s) {
const lines = s.split("\n");
const lastNonEmptyLineIndex = findLastIndex(lines, (line) => line.trim() !== "");
return lines.slice(0, lastNonEmptyLineIndex + 1).join("\n");
}
function trimLines(s) {
return trimEmptyLinesEnd(trimEmptyLinesStart(s));
}
function templateIdentity(strings, ...values) {
if (values.length !== strings.length - 1) throw new StackAssertionError("Invalid number of values; must be one less than strings", { strings, values });
return strings.reduce((result, str, i) => result + str + (values[i] ?? ""), "");
}
function deindent(strings, ...values) {
if (typeof strings === "string") return deindent([strings]);
return templateIdentity(...deindentTemplate(strings, ...values));
}
function deindentTemplate(strings, ...values) {
if (values.length !== strings.length - 1) throw new StackAssertionError("Invalid number of values; must be one less than strings", { strings, values });
const trimmedStrings = [...strings];
trimmedStrings[0] = trimEmptyLinesStart(trimmedStrings[0] + "+").slice(0, -1);
trimmedStrings[trimmedStrings.length - 1] = trimEmptyLinesEnd("+" + trimmedStrings[trimmedStrings.length - 1]).slice(1);
const indentation = trimmedStrings.join("${SOME_VALUE}").split("\n").filter((line) => line.trim() !== "").map((line) => getWhitespacePrefix(line).length).reduce((min, current) => Math.min(min, current), Infinity);
const deindentedStrings = trimmedStrings.map((string, stringIndex) => {
return string.split("\n").map((line, lineIndex) => stringIndex !== 0 && lineIndex === 0 ? line : line.substring(indentation)).join("\n");
});
const indentedValues = values.map((value, i) => {
const firstLineIndentation = getWhitespacePrefix(deindentedStrings[i].split("\n").at(-1));
return `${value}`.replaceAll("\n", `
${firstLineIndentation}`);
});
return [deindentedStrings, ...indentedValues];
}
function extractScopes(scope, removeDuplicates = true) {
const trimmedString = scope.trim();
const scopesArray = trimmedString.split(/\s+/);
const filtered = scopesArray.filter((scope2) => scope2.length > 0);
return removeDuplicates ? [...new Set(filtered)] : filtered;
}
function mergeScopeStrings(...scopes) {
const allScope = scopes.map((s) => extractScopes(s)).flat().join(" ");
return extractScopes(allScope).join(" ");
}
function escapeTemplateLiteral(s) {
return s.replaceAll("`", "\\`").replaceAll("\\", "\\\\").replaceAll("$", "\\$");
}
var nicifiableClassNameOverrides = new Map(Object.entries({
Headers
}).map(([k, v]) => [v, k]));
function nicify(value, options = {}) {
const fullOptions = {
maxDepth: 5,
currentIndent: "",
lineIndent: " ",
multiline: true,
refs: /* @__PURE__ */ new Map(),
path: "value",
parent: null,
overrides: () => null,
keyInParent: null,
hideFields: [],
...filterUndefined(options)
};
const {
maxDepth,
currentIndent,
lineIndent,
multiline,
refs,
path,
overrides,
hideFields
} = fullOptions;
const nl = `
${currentIndent}`;
const overrideResult = overrides(value, options);
if (overrideResult !== null) return overrideResult;
if (["function", "object", "symbol"].includes(typeof value) && value !== null) {
if (refs.has(value)) {
return `Ref<${refs.get(value)}>`;
}
refs.set(value, path);
}
const newOptions = {
maxDepth: maxDepth - 1,
currentIndent,
lineIndent,
multiline,
refs,
path: path + "->[unknown property]",
overrides,
parent: { value, options: fullOptions },
keyInParent: null,
hideFields: []
};
const nestedNicify = (newValue, newPath, keyInParent, options2 = {}) => {
return nicify(newValue, {
...newOptions,
path: newPath,
currentIndent: currentIndent + lineIndent,
keyInParent,
...options2
});
};
switch (typeof value) {
case "boolean":
case "number": {
return JSON.stringify(value);
}
case "string": {
const isDeindentable = (v) => deindent(v) === v && v.includes("\n");
const wrapInDeindent = (v) => deindent`
deindent\`
${currentIndent + lineIndent}${escapeTemplateLiteral(v).replaceAll("\n", nl + lineIndent)}
${currentIndent}\`
`;
if (isDeindentable(value)) {
return wrapInDeindent(value);
} else if (value.endsWith("\n") && isDeindentable(value.slice(0, -1))) {
return wrapInDeindent(value.slice(0, -1)) + ' + "\\n"';
} else {
return JSON.stringify(value);
}
}
case "undefined": {
return "undefined";
}
case "symbol": {
return value.toString();
}
case "bigint": {
return `${value}n`;
}
case "function": {
if (value.name) return `function ${value.name}(...) { ... }`;
return `(...) => { ... }`;
}
case "object": {
if (value === null) return "null";
if (Array.isArray(value)) {
const extraLines2 = getNicifiedObjectExtraLines(value);
const resValueLength2 = value.length + extraLines2.length;
if (maxDepth <= 0 && resValueLength2 === 0) return "[...]";
const resValues2 = value.map((v, i) => nestedNicify(v, `${path}[${i}]`, i));
resValues2.push(...extraLines2);
if (resValues2.length !== resValueLength2) throw new StackAssertionError("nicify of object: resValues.length !== resValueLength", { value, resValues: resValues2, resValueLength: resValueLength2 });
const shouldIndent2 = resValues2.length > 4 || resValues2.some((x) => resValues2.length > 1 && x.length > 4 || x.includes("\n"));
if (shouldIndent2) {
return `[${nl}${resValues2.map((x) => `${lineIndent}${x},${nl}`).join("")}]`;
} else {
return `[${resValues2.join(", ")}]`;
}
}
if (value instanceof URL) {
return `URL(${nestedNicify(value.toString(), `${path}.toString()`, null)})`;
}
if (ArrayBuffer.isView(value)) {
return `${value.constructor.name}([${value.toString()}])`;
}
if (value instanceof Error) {
let stack = value.stack ?? "";
const toString = value.toString();
if (!stack.startsWith(toString)) stack = `${toString}
${stack}`;
stack = stack.trimEnd();
stack = stack.replace(/\n\s+/g, `
${lineIndent}${lineIndent}`);
stack = stack.replace("\n", `
${lineIndent}Stack:
`);
if (Object.keys(value).length > 0) {
stack += `
${lineIndent}Extra properties: ${nestedNicify(Object.fromEntries(Object.entries(value)), path, null)}`;
}
if (value.cause) {
stack += `
${lineIndent}Cause:
${lineIndent}${lineIndent}${nestedNicify(value.cause, path, null, { currentIndent: currentIndent + lineIndent + lineIndent })}`;
}
stack = stack.replaceAll("\n", `
${currentIndent}`);
return stack;
}
const constructorName = [null, Object.prototype].includes(Object.getPrototypeOf(value)) ? null : nicifiableClassNameOverrides.get(value.constructor) ?? value.constructor.name;
const constructorString = constructorName ? `${constructorName} ` : "";
const entries = getNicifiableEntries(value).filter(([k]) => !hideFields.includes(k));
const extraLines = [
...getNicifiedObjectExtraLines(value),
...hideFields.length > 0 ? [`<some fields may have been hidden>`] : []
];
const resValueLength = entries.length + extraLines.length;
if (resValueLength === 0) return `${constructorString}{}`;
if (maxDepth <= 0) return `${constructorString}{ ... }`;
const resValues = entries.map(([k, v], keyIndex) => {
const keyNicified = nestedNicify(k, `Object.keys(${path})[${keyIndex}]`, null);
const keyInObjectLiteral = typeof k === "string" ? nicifyPropertyString(k) : `[${keyNicified}]`;
if (typeof v === "function" && v.name === k) {
return `${keyInObjectLiteral}(...): { ... }`;
} else {
return `${keyInObjectLiteral}: ${nestedNicify(v, `${path}[${keyNicified}]`, k)}`;
}
});
resValues.push(...extraLines);
if (resValues.length !== resValueLength) throw new StackAssertionError("nicify of object: resValues.length !== resValueLength", { value, resValues, resValueLength });
const shouldIndent = resValues.length > 1 || resValues.some((x) => x.includes("\n"));
if (resValues.length === 0) return `${constructorString}{}`;
if (shouldIndent) {
return `${constructorString}{${nl}${resValues.map((x) => `${lineIndent}${x},${nl}`).join("")}}`;
} else {
return `${constructorString}{ ${resValues.join(", ")} }`;
}
}
default: {
return `${typeof value}<${value}>`;
}
}
}
function replaceAll(input, searchValue, replaceValue) {
if (searchValue === "") throw new StackAssertionError("replaceAll: searchValue is empty");
return input.split(searchValue).join(replaceValue);
}
function nicifyPropertyString(str) {
return JSON.stringify(str);
}
function getNicifiableKeys(value) {
const overridden = ("getNicifiableKeys" in value ? value.getNicifiableKeys?.bind(value) : null)?.();
if (overridden != null) return overridden;
const keys = Object.keys(value).sort();
return unique(keys);
}
function getNicifiableEntries(value) {
const recordLikes = [Headers];
function isRecordLike(value2) {
return recordLikes.some((x) => value2 instanceof x);
}
if (isRecordLike(value)) {
return [...value.entries()].sort(([a], [b]) => stringCompare(`${a}`, `${b}`));
}
const keys = getNicifiableKeys(value);
return keys.map((k) => [k, value[k]]);
}
function getNicifiedObjectExtraLines(value) {
return ("getNicifiedObjectExtraLines" in value ? value.getNicifiedObjectExtraLines : null)?.() ?? [];
}
export {
deindent,
deindentTemplate,
escapeTemplateLiteral,
extractScopes,
getWhitespacePrefix,
getWhitespaceSuffix,
mergeScopeStrings,
nicify,
replaceAll,
stringCompare,
templateIdentity,
trimEmptyLinesEnd,
trimEmptyLinesStart,
trimLines,
typedCapitalize,
typedToLowercase,
typedToUppercase
};
//# sourceMappingURL=strings.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,222 @@
// src/utils/strings.nicify.test.ts
import { describe, expect, test } from "vitest";
import { deindent, nicify } from "./strings";
describe("nicify", () => {
describe("primitive values", () => {
test("numbers", () => {
expect(nicify(123)).toBe("123");
expect(nicify(123n)).toBe("123n");
});
test("strings", () => {
expect(nicify("hello")).toBe('"hello"');
});
test("booleans", () => {
expect(nicify(true)).toBe("true");
expect(nicify(false)).toBe("false");
});
test("null and undefined", () => {
expect(nicify(null)).toBe("null");
expect(nicify(void 0)).toBe("undefined");
});
test("symbols", () => {
expect(nicify(Symbol("test"))).toBe("Symbol(test)");
});
});
describe("arrays", () => {
test("empty array", () => {
expect(nicify([])).toBe("[]");
});
test("single-element array", () => {
expect(nicify([1])).toBe("[1]");
});
test("single-element array with long content", () => {
expect(nicify(["123123123123123"])).toBe('["123123123123123"]');
});
test("flat array", () => {
expect(nicify([1, 2, 3])).toBe("[1, 2, 3]");
});
test("longer array", () => {
expect(nicify([1e4, 2, 3])).toBe(deindent`
[
10000,
2,
3,
]
`);
});
test("nested array", () => {
expect(nicify([1, [2, 3]])).toBe(deindent`
[
1,
[2, 3],
]
`);
});
});
describe("objects", () => {
test("empty object", () => {
expect(nicify({})).toBe("{}");
});
test("simple object", () => {
expect(nicify({ a: 1 })).toBe('{ "a": 1 }');
});
test("multiline object", () => {
expect(nicify({ a: 1, b: 2 })).toBe(deindent`
{
"a": 1,
"b": 2,
}
`);
});
});
describe("custom classes", () => {
test("class instance", () => {
class TestClass {
constructor(value) {
this.value = value;
}
}
expect(nicify(new TestClass(42))).toBe('TestClass { "value": 42 }');
});
});
describe("built-in objects", () => {
test("URL", () => {
expect(nicify(new URL("https://example.com"))).toBe('URL("https://example.com/")');
});
test("TypedArrays", () => {
expect(nicify(new Uint8Array([1, 2, 3]))).toBe("Uint8Array([1,2,3])");
expect(nicify(new Int32Array([1, 2, 3]))).toBe("Int32Array([1,2,3])");
});
test("Error objects", () => {
const error = new Error("test error");
const nicifiedError = nicify({ error });
expect(nicifiedError).toMatch(new RegExp(deindent`
^\{
"error": Error: test error
Stack:
at (.|\\n)*
\}$
`));
});
test("Error objects with cause and an extra property", () => {
const error = new Error("test error", { cause: new Error("cause") });
error.extra = "something";
const nicifiedError = nicify(error, { lineIndent: "--" });
expect(nicifiedError).toMatch(new RegExp(deindent`
^Error: test error
--Stack:
----at (.|\\n)+
--Extra properties: \{ "extra": "something" \}
--Cause:
----Error: cause
------Stack:
--------at (.|\\n)+$
`));
});
test("Headers", () => {
const headers = new Headers();
headers.append("Content-Type", "application/json");
headers.append("Accept", "text/plain");
expect(nicify(headers)).toBe(
deindent`
Headers {
"accept": "text/plain",
"content-type": "application/json",
}`
);
});
});
describe("multiline strings", () => {
test("basic multiline", () => {
expect(nicify("line1\nline2")).toBe("deindent`\n line1\n line2\n`");
});
test("multiline with trailing newline", () => {
expect(nicify("line1\nline2\n")).toBe('deindent`\n line1\n line2\n` + "\\n"');
});
});
describe("circular references", () => {
test("object with self reference", () => {
const circular = { a: 1 };
circular.self = circular;
expect(nicify(circular)).toBe(
deindent`
{
"a": 1,
"self": Ref<value>,
}`
);
});
});
describe("configuration options", () => {
test("maxDepth", () => {
const deep = { a: { b: { c: { d: { e: 1 } } } } };
expect(nicify(deep, { maxDepth: 2 })).toBe('{ "a": { "b": { ... } } }');
});
test("lineIndent", () => {
expect(nicify({ a: 1, b: 2 }, { lineIndent: " " })).toBe(deindent`
{
"a": 1,
"b": 2,
}
`);
});
test("hideFields", () => {
expect(nicify({ a: 1, b: 2, secret: "hidden" }, { hideFields: ["secret"] })).toBe(deindent`
{
"a": 1,
"b": 2,
<some fields may have been hidden>,
}
`);
});
});
describe("custom overrides", () => {
test("override with custom type", () => {
expect(nicify({ type: "special" }, {
overrides: (value) => {
if (typeof value === "object" && value && "type" in value && value.type === "special") {
return "SPECIAL";
}
return null;
}
})).toBe("SPECIAL");
});
});
describe("functions", () => {
test("named function", () => {
expect(nicify(function namedFunction() {
})).toBe("function namedFunction(...) { ... }");
});
test("arrow function", () => {
expect(nicify(() => {
})).toBe("(...) => { ... }");
});
});
describe("Nicifiable interface", () => {
test("object implementing Nicifiable", () => {
const nicifiable = {
value: 42,
getNicifiableKeys() {
return ["value"];
},
getNicifiedObjectExtraLines() {
return ["// custom comment"];
}
};
expect(nicify(nicifiable)).toBe(deindent`
{
"value": 42,
// custom comment,
}
`);
});
});
describe("unknown types", () => {
test("object without prototype", () => {
const unknownType = /* @__PURE__ */ Object.create(null);
unknownType.value = "test";
expect(nicify(unknownType)).toBe('{ "value": "test" }');
});
});
});
//# sourceMappingURL=strings.nicify.test.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
//# sourceMappingURL=types.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}

View file

@ -0,0 +1,11 @@
// src/utils/unicode.tsx
import { StackAssertionError } from "./errors";
function getFlagEmoji(twoLetterCountryCode) {
if (!/^[a-zA-Z][a-zA-Z]$/.test(twoLetterCountryCode)) throw new StackAssertionError("Country code must be two alphabetical letters");
const codePoints = twoLetterCountryCode.toUpperCase().split("").map((char) => 127397 + char.charCodeAt(0));
return String.fromCodePoint(...codePoints);
}
export {
getFlagEmoji
};
//# sourceMappingURL=unicode.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/utils/unicode.tsx"],"sourcesContent":["import { StackAssertionError } from \"./errors\";\n\nexport function getFlagEmoji(twoLetterCountryCode: string) {\n if (!/^[a-zA-Z][a-zA-Z]$/.test(twoLetterCountryCode)) throw new StackAssertionError(\"Country code must be two alphabetical letters\");\n const codePoints = twoLetterCountryCode\n .toUpperCase()\n .split('')\n .map(char => 127397 + char.charCodeAt(0));\n return String.fromCodePoint(...codePoints);\n}\nundefined?.test(\"getFlagEmoji\", ({ expect }) => {\n // Test with valid country codes\n expect(getFlagEmoji(\"US\")).toBe(\"🇺🇸\");\n expect(getFlagEmoji(\"us\")).toBe(\"🇺🇸\");\n expect(getFlagEmoji(\"GB\")).toBe(\"🇬🇧\");\n expect(getFlagEmoji(\"JP\")).toBe(\"🇯🇵\");\n\n // Test with invalid country codes\n expect(() => getFlagEmoji(\"\")).toThrow(\"Country code must be two alphabetical letters\");\n expect(() => getFlagEmoji(\"A\")).toThrow(\"Country code must be two alphabetical letters\");\n expect(() => getFlagEmoji(\"ABC\")).toThrow(\"Country code must be two alphabetical letters\");\n expect(() => getFlagEmoji(\"12\")).toThrow(\"Country code must be two alphabetical letters\");\n expect(() => getFlagEmoji(\"A1\")).toThrow(\"Country code must be two alphabetical letters\");\n});\n"],"mappings":";AAAA,SAAS,2BAA2B;AAE7B,SAAS,aAAa,sBAA8B;AACzD,MAAI,CAAC,qBAAqB,KAAK,oBAAoB,EAAG,OAAM,IAAI,oBAAoB,+CAA+C;AACnI,QAAM,aAAa,qBAChB,YAAY,EACZ,MAAM,EAAE,EACR,IAAI,UAAQ,SAAS,KAAK,WAAW,CAAC,CAAC;AAC1C,SAAO,OAAO,cAAc,GAAG,UAAU;AAC3C;","names":[]}

View file

@ -0,0 +1,53 @@
// src/utils/urls.tsx
import { generateSecureRandomString } from "./crypto";
import { templateIdentity } from "./strings";
function createUrlIfValid(...args) {
try {
return new URL(...args);
} catch (e) {
return null;
}
}
function isValidUrl(url2) {
return !!createUrlIfValid(url2);
}
function isValidHostname(hostname) {
const url2 = createUrlIfValid(`https://${hostname}`);
if (!url2) return false;
return url2.hostname === hostname;
}
function isLocalhost(urlOrString) {
const url2 = createUrlIfValid(urlOrString);
if (!url2) return false;
if (url2.hostname === "localhost" || url2.hostname.endsWith(".localhost")) return true;
if (url2.hostname.match(/^127\.\d+\.\d+\.\d+$/)) return true;
return false;
}
function isRelative(url2) {
const randomDomain = `${generateSecureRandomString()}.stack-auth.example.com`;
const u = createUrlIfValid(url2, `https://${randomDomain}`);
if (!u) return false;
if (u.host !== randomDomain) return false;
if (u.protocol !== "https:") return false;
return true;
}
function getRelativePart(url2) {
return url2.pathname + url2.search + url2.hash;
}
function url(strings, ...values) {
return new URL(urlString(strings, ...values));
}
function urlString(strings, ...values) {
return templateIdentity(strings, ...values.map(encodeURIComponent));
}
export {
createUrlIfValid,
getRelativePart,
isLocalhost,
isRelative,
isValidHostname,
isValidUrl,
url,
urlString
};
//# sourceMappingURL=urls.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,16 @@
// src/utils/uuids.tsx
import { generateRandomValues } from "./crypto";
function generateUuid() {
return "10000000-1000-4000-8000-100000000000".replace(
/[018]/g,
(c) => (+c ^ generateRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16)
);
}
function isUuid(str) {
return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(str);
}
export {
generateUuid,
isUuid
};
//# sourceMappingURL=uuids.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/utils/uuids.tsx"],"sourcesContent":["import { generateRandomValues } from \"./crypto\";\n\nexport function generateUuid() {\n // crypto.randomUuid is not supported in all browsers, so this is a polyfill\n return \"10000000-1000-4000-8000-100000000000\".replace(/[018]/g, c =>\n (+c ^ generateRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16)\n );\n}\nundefined?.test(\"generateUuid\", ({ expect }) => {\n // Test that the function returns a valid UUID\n const uuid = generateUuid();\n expect(uuid).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);\n\n // Test that multiple calls generate different UUIDs\n const uuid2 = generateUuid();\n expect(uuid).not.toBe(uuid2);\n\n // Test that the UUID is version 4 (random)\n expect(uuid.charAt(14)).toBe('4');\n\n // Test that the UUID has the correct variant (8, 9, a, or b in position 19)\n expect('89ab').toContain(uuid.charAt(19));\n});\n\nexport function isUuid(str: string) {\n return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(str);\n}\nundefined?.test(\"isUuid\", ({ expect }) => {\n // Test with valid UUIDs\n expect(isUuid(\"123e4567-e89b-42d3-a456-426614174000\")).toBe(true);\n expect(isUuid(\"123e4567-e89b-42d3-8456-426614174000\")).toBe(true);\n expect(isUuid(\"123e4567-e89b-42d3-9456-426614174000\")).toBe(true);\n expect(isUuid(\"123e4567-e89b-42d3-a456-426614174000\")).toBe(true);\n expect(isUuid(\"123e4567-e89b-42d3-b456-426614174000\")).toBe(true);\n\n // Test with invalid UUIDs\n expect(isUuid(\"\")).toBe(false);\n expect(isUuid(\"not-a-uuid\")).toBe(false);\n expect(isUuid(\"123e4567-e89b-12d3-a456-426614174000\")).toBe(false); // Wrong version (not 4)\n expect(isUuid(\"123e4567-e89b-42d3-c456-426614174000\")).toBe(false); // Wrong variant (not 8, 9, a, or b)\n expect(isUuid(\"123e4567-e89b-42d3-a456-42661417400\")).toBe(false); // Too short\n expect(isUuid(\"123e4567-e89b-42d3-a456-4266141740000\")).toBe(false); // Too long\n expect(isUuid(\"123e4567-e89b-42d3-a456_426614174000\")).toBe(false); // Wrong format (underscore instead of dash)\n\n // Test with uppercase letters (should fail as UUID should be lowercase)\n expect(isUuid(\"123E4567-E89B-42D3-A456-426614174000\")).toBe(false);\n});\n"],"mappings":";AAAA,SAAS,4BAA4B;AAE9B,SAAS,eAAe;AAE7B,SAAO,uCAAuC;AAAA,IAAQ;AAAA,IAAU,QAC7D,CAAC,IAAI,qBAAqB,IAAI,WAAW,CAAC,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,IAAI,GAAG,SAAS,EAAE;AAAA,EAC9E;AACF;AAiBO,SAAS,OAAO,KAAa;AAClC,SAAO,wEAAwE,KAAK,GAAG;AACzF;","names":[]}