@stackframe/stack-shared 2.8.1 → 2.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/interface/adminInterface.d.ts +7 -7
- package/dist/interface/adminInterface.js +4 -4
- package/dist/interface/clientInterface.d.ts +43 -1
- package/dist/interface/clientInterface.js +67 -3
- package/dist/interface/crud/current-user.d.ts +1 -1
- package/dist/interface/crud/{api-keys.d.ts → internal-api-keys.d.ts} +7 -7
- package/dist/interface/crud/{api-keys.js → internal-api-keys.js} +10 -10
- package/dist/interface/crud/project-api-keys.d.ts +188 -0
- package/dist/interface/crud/project-api-keys.js +76 -0
- package/dist/interface/crud/projects.d.ts +28 -0
- package/dist/interface/crud/projects.js +6 -0
- package/dist/interface/crud/team-member-profiles.d.ts +2 -2
- package/dist/interface/crud/users.d.ts +4 -4
- package/dist/interface/serverInterface.d.ts +2 -1
- package/dist/interface/serverInterface.js +4 -0
- package/dist/interface/webhooks.d.ts +34 -2
- package/dist/interface/webhooks.js +3 -0
- package/dist/known-errors.d.ts +24 -1
- package/dist/known-errors.js +30 -4
- package/dist/schema-fields.d.ts +1 -1
- package/dist/schema-fields.js +2 -1
- package/dist/utils/api-keys.d.ts +23 -0
- package/dist/utils/api-keys.js +75 -0
- package/dist/utils/bytes.d.ts +3 -0
- package/dist/utils/bytes.js +55 -6
- package/dist/utils/errors.js +1 -5
- package/dist/utils/hashes.d.ts +1 -1
- package/dist/utils/hashes.js +1 -3
- package/dist/utils/objects.d.ts +8 -0
- package/dist/utils/objects.js +11 -0
- package/dist/utils/results.js +5 -5
- package/dist/utils/strings.d.ts +1 -0
- package/dist/utils/strings.js +73 -17
- package/dist/utils/strings.nicify.test.js +214 -0
- package/dist/utils/types.d.ts +17 -0
- package/package.json +2 -1
- package/dist/utils/strings.test.js +0 -26
- /package/dist/utils/{strings.test.d.ts → strings.nicify.test.d.ts} +0 -0
package/dist/utils/errors.js
CHANGED
|
@@ -68,11 +68,7 @@ StackAssertionError.prototype.name = "StackAssertionError";
|
|
|
68
68
|
export function errorToNiceString(error) {
|
|
69
69
|
if (!(error instanceof Error))
|
|
70
70
|
return `${typeof error}<${nicify(error)}>`;
|
|
71
|
-
|
|
72
|
-
const toString = error.toString();
|
|
73
|
-
if (!stack.startsWith(toString))
|
|
74
|
-
stack = `${toString}\n${stack}`; // some browsers don't include the error message in the stack, some do
|
|
75
|
-
return `${stack} ${nicify(Object.fromEntries(Object.entries(error)), { maxDepth: 8 })}`;
|
|
71
|
+
return nicify(error, { maxDepth: 8 });
|
|
76
72
|
}
|
|
77
73
|
const errorSinks = new Set();
|
|
78
74
|
export function registerErrorSink(sink) {
|
package/dist/utils/hashes.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare function sha512(input: Uint8Array | string): Promise<
|
|
1
|
+
export declare function sha512(input: Uint8Array | string): Promise<Uint8Array>;
|
|
2
2
|
export declare function hashPassword(password: string): Promise<string>;
|
|
3
3
|
export declare function comparePassword(password: string, hash: string): Promise<boolean>;
|
|
4
4
|
export declare function isPasswordHashValid(hash: string): Promise<boolean>;
|
package/dist/utils/hashes.js
CHANGED
|
@@ -2,9 +2,7 @@ import bcrypt from 'bcrypt';
|
|
|
2
2
|
import { StackAssertionError } from './errors';
|
|
3
3
|
export async function sha512(input) {
|
|
4
4
|
const bytes = typeof input === "string" ? new TextEncoder().encode(input) : input;
|
|
5
|
-
return await crypto.subtle.digest("SHA-512", bytes)
|
|
6
|
-
return Array.prototype.map.call(new Uint8Array(buf), x => (('00' + x.toString(16)).slice(-2))).join('');
|
|
7
|
-
});
|
|
5
|
+
return new Uint8Array(await crypto.subtle.digest("SHA-512", bytes));
|
|
8
6
|
}
|
|
9
7
|
export async function hashPassword(password) {
|
|
10
8
|
const passwordBytes = new TextEncoder().encode(password);
|
package/dist/utils/objects.d.ts
CHANGED
|
@@ -26,6 +26,14 @@ export type FilterUndefined<T> = {
|
|
|
26
26
|
* TypeScript's `Partial<XYZ>` type allows `undefined` values.
|
|
27
27
|
*/
|
|
28
28
|
export declare function filterUndefined<T extends {}>(obj: T): FilterUndefined<T>;
|
|
29
|
+
export type FilterUndefinedOrNull<T> = FilterUndefined<{
|
|
30
|
+
[k in keyof T]: null extends T[k] ? NonNullable<T[k]> | undefined : T[k];
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* Returns a new object with all undefined and null values removed. Useful when spreading optional parameters on an object, as
|
|
34
|
+
* TypeScript's `Partial<XYZ>` type allows `undefined` values.
|
|
35
|
+
*/
|
|
36
|
+
export declare function filterUndefinedOrNull<T extends {}>(obj: T): FilterUndefinedOrNull<T>;
|
|
29
37
|
export declare function pick<T extends {}, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>;
|
|
30
38
|
export declare function omit<T extends {}, K extends keyof T>(obj: T, keys: K[]): Omit<T, K>;
|
|
31
39
|
export declare function split<T extends {}, K extends keyof T>(obj: T, keys: K[]): [Pick<T, K>, Omit<T, K>];
|
package/dist/utils/objects.js
CHANGED
|
@@ -215,6 +215,17 @@ import.meta.vitest?.test("filterUndefined", ({ expect }) => {
|
|
|
215
215
|
expect(filterUndefined({ a: null, b: undefined })).toEqual({ a: null });
|
|
216
216
|
expect(filterUndefined({ a: 0, b: "", c: false, d: undefined })).toEqual({ a: 0, b: "", c: false });
|
|
217
217
|
});
|
|
218
|
+
/**
|
|
219
|
+
* Returns a new object with all undefined and null values removed. Useful when spreading optional parameters on an object, as
|
|
220
|
+
* TypeScript's `Partial<XYZ>` type allows `undefined` values.
|
|
221
|
+
*/
|
|
222
|
+
export function filterUndefinedOrNull(obj) {
|
|
223
|
+
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined && v !== null));
|
|
224
|
+
}
|
|
225
|
+
import.meta.vitest?.test("filterUndefinedOrNull", ({ expect }) => {
|
|
226
|
+
expect(filterUndefinedOrNull({})).toEqual({});
|
|
227
|
+
expect(filterUndefinedOrNull({ a: 1, b: 2 })).toEqual({ a: 1, b: 2 });
|
|
228
|
+
});
|
|
218
229
|
export function pick(obj, keys) {
|
|
219
230
|
return Object.fromEntries(Object.entries(obj).filter(([k]) => keys.includes(k)));
|
|
220
231
|
}
|
package/dist/utils/results.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { wait } from "./promises";
|
|
2
|
-
import { deindent } from "./strings";
|
|
2
|
+
import { deindent, nicify } from "./strings";
|
|
3
3
|
export const Result = {
|
|
4
4
|
fromThrowing,
|
|
5
5
|
fromThrowingAsync,
|
|
@@ -255,17 +255,17 @@ import.meta.vitest?.test("mapResult", ({ expect }) => {
|
|
|
255
255
|
});
|
|
256
256
|
class RetryError extends AggregateError {
|
|
257
257
|
constructor(errors) {
|
|
258
|
-
const strings = errors.map(e =>
|
|
258
|
+
const strings = errors.map(e => nicify(e));
|
|
259
259
|
const isAllSame = strings.length > 1 && strings.every(s => s === strings[0]);
|
|
260
260
|
super(errors, deindent `
|
|
261
261
|
Error after ${errors.length} attempts.
|
|
262
262
|
|
|
263
263
|
${isAllSame ? deindent `
|
|
264
264
|
Attempts 1-${errors.length}:
|
|
265
|
-
${
|
|
266
|
-
` :
|
|
265
|
+
${strings[0]}
|
|
266
|
+
` : strings.map((s, i) => deindent `
|
|
267
267
|
Attempt ${i + 1}:
|
|
268
|
-
${
|
|
268
|
+
${s}
|
|
269
269
|
`).join("\n\n")}
|
|
270
270
|
`, { cause: errors[errors.length - 1] });
|
|
271
271
|
this.errors = errors;
|
package/dist/utils/strings.d.ts
CHANGED
|
@@ -43,6 +43,7 @@ export declare function trimLines(s: string): string;
|
|
|
43
43
|
export declare function templateIdentity(strings: TemplateStringsArray | readonly string[], ...values: string[]): string;
|
|
44
44
|
export declare function deindent(code: string): string;
|
|
45
45
|
export declare function deindent(strings: TemplateStringsArray | readonly string[], ...values: any[]): string;
|
|
46
|
+
export declare function deindentTemplate(strings: TemplateStringsArray | readonly string[], ...values: any[]): [string[], ...string[]];
|
|
46
47
|
export declare function extractScopes(scope: string, removeDuplicates?: boolean): string[];
|
|
47
48
|
export declare function mergeScopeStrings(...scopes: string[]): string;
|
|
48
49
|
export declare function escapeTemplateLiteral(s: string): string;
|
package/dist/utils/strings.js
CHANGED
|
@@ -207,8 +207,9 @@ Line2`).toBe("Line1\nLine2");
|
|
|
207
207
|
export function deindent(strings, ...values) {
|
|
208
208
|
if (typeof strings === "string")
|
|
209
209
|
return deindent([strings]);
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
return templateIdentity(...deindentTemplate(strings, ...values));
|
|
211
|
+
}
|
|
212
|
+
export function deindentTemplate(strings, ...values) {
|
|
212
213
|
if (values.length !== strings.length - 1)
|
|
213
214
|
throw new StackAssertionError("Invalid number of values; must be one less than strings", { strings, values });
|
|
214
215
|
const trimmedStrings = [...strings];
|
|
@@ -231,7 +232,7 @@ export function deindent(strings, ...values) {
|
|
|
231
232
|
const firstLineIndentation = getWhitespacePrefix(deindentedStrings[i].split("\n").at(-1));
|
|
232
233
|
return `${value}`.replaceAll("\n", `\n${firstLineIndentation}`);
|
|
233
234
|
});
|
|
234
|
-
return
|
|
235
|
+
return [deindentedStrings, ...indentedValues];
|
|
235
236
|
}
|
|
236
237
|
import.meta.vitest?.test("deindent", ({ expect }) => {
|
|
237
238
|
// Test with string input
|
|
@@ -241,7 +242,6 @@ import.meta.vitest?.test("deindent", ({ expect }) => {
|
|
|
241
242
|
expect(deindent("\n hello\n world\n")).toBe("hello\nworld");
|
|
242
243
|
// Test with empty input
|
|
243
244
|
expect(deindent("")).toBe("");
|
|
244
|
-
expect(deindent([])).toBe("");
|
|
245
245
|
// Test with template literal
|
|
246
246
|
expect(deindent `
|
|
247
247
|
hello
|
|
@@ -414,12 +414,13 @@ export function nicify(value, options = {}) {
|
|
|
414
414
|
keyInParent: null,
|
|
415
415
|
hideFields: [],
|
|
416
416
|
};
|
|
417
|
-
const nestedNicify = (newValue, newPath, keyInParent) => {
|
|
417
|
+
const nestedNicify = (newValue, newPath, keyInParent, options = {}) => {
|
|
418
418
|
return nicify(newValue, {
|
|
419
419
|
...newOptions,
|
|
420
420
|
path: newPath,
|
|
421
421
|
currentIndent: currentIndent + lineIndent,
|
|
422
422
|
keyInParent,
|
|
423
|
+
...options,
|
|
423
424
|
});
|
|
424
425
|
};
|
|
425
426
|
switch (typeof value) {
|
|
@@ -431,7 +432,7 @@ export function nicify(value, options = {}) {
|
|
|
431
432
|
const isDeindentable = (v) => deindent(v) === v && v.includes("\n");
|
|
432
433
|
const wrapInDeindent = (v) => deindent `
|
|
433
434
|
deindent\`
|
|
434
|
-
${currentIndent + lineIndent}${escapeTemplateLiteral(
|
|
435
|
+
${currentIndent + lineIndent}${escapeTemplateLiteral(v).replaceAll("\n", nl + lineIndent)}
|
|
435
436
|
${currentIndent}\`
|
|
436
437
|
`;
|
|
437
438
|
if (isDeindentable(value)) {
|
|
@@ -470,7 +471,7 @@ export function nicify(value, options = {}) {
|
|
|
470
471
|
resValues.push(...extraLines);
|
|
471
472
|
if (resValues.length !== resValueLength)
|
|
472
473
|
throw new StackAssertionError("nicify of object: resValues.length !== resValueLength", { value, resValues, resValueLength });
|
|
473
|
-
const shouldIndent = resValues.length >
|
|
474
|
+
const shouldIndent = resValues.length > 4 || resValues.some(x => (resValues.length > 1 && x.length > 4) || x.includes("\n"));
|
|
474
475
|
if (shouldIndent) {
|
|
475
476
|
return `[${nl}${resValues.map(x => `${lineIndent}${x},${nl}`).join("")}]`;
|
|
476
477
|
}
|
|
@@ -479,13 +480,30 @@ export function nicify(value, options = {}) {
|
|
|
479
480
|
}
|
|
480
481
|
}
|
|
481
482
|
if (value instanceof URL) {
|
|
482
|
-
return `URL(${
|
|
483
|
+
return `URL(${nestedNicify(value.toString(), `${path}.toString()`, null)})`;
|
|
483
484
|
}
|
|
484
485
|
if (ArrayBuffer.isView(value)) {
|
|
485
486
|
return `${value.constructor.name}([${value.toString()}])`;
|
|
486
487
|
}
|
|
488
|
+
if (value instanceof Error) {
|
|
489
|
+
let stack = value.stack ?? "";
|
|
490
|
+
const toString = value.toString();
|
|
491
|
+
if (!stack.startsWith(toString))
|
|
492
|
+
stack = `${toString}\n${stack}`; // some browsers don't include the error message in the stack, some do
|
|
493
|
+
stack = stack.trimEnd();
|
|
494
|
+
stack = stack.replace(/\n\s+/g, `\n${lineIndent}${lineIndent}`);
|
|
495
|
+
stack = stack.replace("\n", `\n${lineIndent}Stack:\n`);
|
|
496
|
+
if (Object.keys(value).length > 0) {
|
|
497
|
+
stack += `\n${lineIndent}Extra properties: ${nestedNicify(Object.fromEntries(Object.entries(value)), path, null)}`;
|
|
498
|
+
}
|
|
499
|
+
if (value.cause) {
|
|
500
|
+
stack += `\n${lineIndent}Cause:\n${lineIndent}${lineIndent}${nestedNicify(value.cause, path, null, { currentIndent: currentIndent + lineIndent + lineIndent })}`;
|
|
501
|
+
}
|
|
502
|
+
stack = stack.replaceAll("\n", `\n${currentIndent}`);
|
|
503
|
+
return stack;
|
|
504
|
+
}
|
|
487
505
|
const constructorName = [null, Object.prototype].includes(Object.getPrototypeOf(value)) ? null : (nicifiableClassNameOverrides.get(value.constructor) ?? value.constructor.name);
|
|
488
|
-
const constructorString = constructorName ? `${
|
|
506
|
+
const constructorString = constructorName ? `${constructorName} ` : "";
|
|
489
507
|
const entries = getNicifiableEntries(value).filter(([k]) => !hideFields.includes(k));
|
|
490
508
|
const extraLines = [
|
|
491
509
|
...getNicifiedObjectExtraLines(value),
|
|
@@ -498,7 +516,7 @@ export function nicify(value, options = {}) {
|
|
|
498
516
|
return `${constructorString}{ ... }`;
|
|
499
517
|
const resValues = entries.map(([k, v], keyIndex) => {
|
|
500
518
|
const keyNicified = nestedNicify(k, `Object.keys(${path})[${keyIndex}]`, null);
|
|
501
|
-
const keyInObjectLiteral = typeof k === "string" ?
|
|
519
|
+
const keyInObjectLiteral = typeof k === "string" ? nicifyPropertyString(k) : `[${keyNicified}]`;
|
|
502
520
|
if (typeof v === "function" && v.name === k) {
|
|
503
521
|
return `${keyInObjectLiteral}(...): { ... }`;
|
|
504
522
|
}
|
|
@@ -525,25 +543,63 @@ export function nicify(value, options = {}) {
|
|
|
525
543
|
}
|
|
526
544
|
}
|
|
527
545
|
export function replaceAll(input, searchValue, replaceValue) {
|
|
546
|
+
if (searchValue === "")
|
|
547
|
+
throw new StackAssertionError("replaceAll: searchValue is empty");
|
|
528
548
|
return input.split(searchValue).join(replaceValue);
|
|
529
549
|
}
|
|
550
|
+
import.meta.vitest?.test("replaceAll", ({ expect }) => {
|
|
551
|
+
expect(replaceAll("hello world", "o", "x")).toBe("hellx wxrld");
|
|
552
|
+
expect(replaceAll("aaa", "a", "b")).toBe("bbb");
|
|
553
|
+
expect(replaceAll("", "a", "b")).toBe("");
|
|
554
|
+
expect(replaceAll("abc", "b", "")).toBe("ac");
|
|
555
|
+
expect(replaceAll("test.test.test", ".", "_")).toBe("test_test_test");
|
|
556
|
+
expect(replaceAll("a.b*c", ".", "x")).toBe("axb*c");
|
|
557
|
+
expect(replaceAll("a*b*c", "*", "x")).toBe("axbxc");
|
|
558
|
+
expect(replaceAll("hello hello", "hello", "hi")).toBe("hi hi");
|
|
559
|
+
});
|
|
530
560
|
function nicifyPropertyString(str) {
|
|
531
|
-
if (/^[_a-zA-Z][_a-zA-Z0-9]*$/.test(str))
|
|
532
|
-
return str;
|
|
533
561
|
return JSON.stringify(str);
|
|
534
562
|
}
|
|
563
|
+
import.meta.vitest?.test("nicifyPropertyString", ({ expect }) => {
|
|
564
|
+
// Test valid identifiers
|
|
565
|
+
expect(nicifyPropertyString("validName")).toBe('"validName"');
|
|
566
|
+
expect(nicifyPropertyString("_validName")).toBe('"_validName"');
|
|
567
|
+
expect(nicifyPropertyString("valid123Name")).toBe('"valid123Name"');
|
|
568
|
+
// Test invalid identifiers
|
|
569
|
+
expect(nicifyPropertyString("123invalid")).toBe('"123invalid"');
|
|
570
|
+
expect(nicifyPropertyString("invalid-name")).toBe('"invalid-name"');
|
|
571
|
+
expect(nicifyPropertyString("invalid space")).toBe('"invalid space"');
|
|
572
|
+
expect(nicifyPropertyString("$invalid")).toBe('"$invalid"');
|
|
573
|
+
expect(nicifyPropertyString("")).toBe('""');
|
|
574
|
+
// Test with special characters
|
|
575
|
+
expect(nicifyPropertyString("property!")).toBe('"property!"');
|
|
576
|
+
expect(nicifyPropertyString("property.name")).toBe('"property.name"');
|
|
577
|
+
// Test with escaped characters
|
|
578
|
+
expect(nicifyPropertyString("\\")).toBe('"\\\\"');
|
|
579
|
+
expect(nicifyPropertyString('"')).toBe('"\\""');
|
|
580
|
+
});
|
|
535
581
|
function getNicifiableKeys(value) {
|
|
536
582
|
const overridden = ("getNicifiableKeys" in value ? value.getNicifiableKeys?.bind(value) : null)?.();
|
|
537
583
|
if (overridden != null)
|
|
538
584
|
return overridden;
|
|
539
585
|
const keys = Object.keys(value).sort();
|
|
540
|
-
if (value instanceof Error) {
|
|
541
|
-
if (value.cause)
|
|
542
|
-
keys.unshift("cause");
|
|
543
|
-
keys.unshift("message", "stack");
|
|
544
|
-
}
|
|
545
586
|
return unique(keys);
|
|
546
587
|
}
|
|
588
|
+
import.meta.vitest?.test("getNicifiableKeys", ({ expect }) => {
|
|
589
|
+
// Test regular object
|
|
590
|
+
expect(getNicifiableKeys({ b: 1, a: 2, c: 3 })).toEqual(["a", "b", "c"]);
|
|
591
|
+
// Test empty object
|
|
592
|
+
expect(getNicifiableKeys({})).toEqual([]);
|
|
593
|
+
// Test object with custom getNicifiableKeys
|
|
594
|
+
const customObject = {
|
|
595
|
+
a: 1,
|
|
596
|
+
b: 2,
|
|
597
|
+
getNicifiableKeys() {
|
|
598
|
+
return ["customKey1", "customKey2"];
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
expect(getNicifiableKeys(customObject)).toEqual(["customKey1", "customKey2"]);
|
|
602
|
+
});
|
|
547
603
|
function getNicifiableEntries(value) {
|
|
548
604
|
const recordLikes = [Headers];
|
|
549
605
|
function isRecordLike(value) {
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { deindent, nicify } from "./strings";
|
|
3
|
+
describe("nicify", () => {
|
|
4
|
+
describe("primitive values", () => {
|
|
5
|
+
test("numbers", () => {
|
|
6
|
+
expect(nicify(123)).toBe("123");
|
|
7
|
+
expect(nicify(123n)).toBe("123n");
|
|
8
|
+
});
|
|
9
|
+
test("strings", () => {
|
|
10
|
+
expect(nicify("hello")).toBe('"hello"');
|
|
11
|
+
});
|
|
12
|
+
test("booleans", () => {
|
|
13
|
+
expect(nicify(true)).toBe("true");
|
|
14
|
+
expect(nicify(false)).toBe("false");
|
|
15
|
+
});
|
|
16
|
+
test("null and undefined", () => {
|
|
17
|
+
expect(nicify(null)).toBe("null");
|
|
18
|
+
expect(nicify(undefined)).toBe("undefined");
|
|
19
|
+
});
|
|
20
|
+
test("symbols", () => {
|
|
21
|
+
expect(nicify(Symbol("test"))).toBe("Symbol(test)");
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
describe("arrays", () => {
|
|
25
|
+
test("empty array", () => {
|
|
26
|
+
expect(nicify([])).toBe("[]");
|
|
27
|
+
});
|
|
28
|
+
test("single-element array", () => {
|
|
29
|
+
expect(nicify([1])).toBe("[1]");
|
|
30
|
+
});
|
|
31
|
+
test("single-element array with long content", () => {
|
|
32
|
+
expect(nicify(["123123123123123"])).toBe('["123123123123123"]');
|
|
33
|
+
});
|
|
34
|
+
test("flat array", () => {
|
|
35
|
+
expect(nicify([1, 2, 3])).toBe("[1, 2, 3]");
|
|
36
|
+
});
|
|
37
|
+
test("longer array", () => {
|
|
38
|
+
expect(nicify([10000, 2, 3])).toBe(deindent `
|
|
39
|
+
[
|
|
40
|
+
10000,
|
|
41
|
+
2,
|
|
42
|
+
3,
|
|
43
|
+
]
|
|
44
|
+
`);
|
|
45
|
+
});
|
|
46
|
+
test("nested array", () => {
|
|
47
|
+
expect(nicify([1, [2, 3]])).toBe(deindent `
|
|
48
|
+
[
|
|
49
|
+
1,
|
|
50
|
+
[2, 3],
|
|
51
|
+
]
|
|
52
|
+
`);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe("objects", () => {
|
|
56
|
+
test("empty object", () => {
|
|
57
|
+
expect(nicify({})).toBe("{}");
|
|
58
|
+
});
|
|
59
|
+
test("simple object", () => {
|
|
60
|
+
expect(nicify({ a: 1 })).toBe('{ "a": 1 }');
|
|
61
|
+
});
|
|
62
|
+
test("multiline object", () => {
|
|
63
|
+
expect(nicify({ a: 1, b: 2 })).toBe(deindent `
|
|
64
|
+
{
|
|
65
|
+
"a": 1,
|
|
66
|
+
"b": 2,
|
|
67
|
+
}
|
|
68
|
+
`);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe("custom classes", () => {
|
|
72
|
+
test("class instance", () => {
|
|
73
|
+
class TestClass {
|
|
74
|
+
constructor(value) {
|
|
75
|
+
this.value = value;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
expect(nicify(new TestClass(42))).toBe('TestClass { "value": 42 }');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
describe("built-in objects", () => {
|
|
82
|
+
test("URL", () => {
|
|
83
|
+
expect(nicify(new URL("https://example.com"))).toBe('URL("https://example.com/")');
|
|
84
|
+
});
|
|
85
|
+
test("TypedArrays", () => {
|
|
86
|
+
expect(nicify(new Uint8Array([1, 2, 3]))).toBe("Uint8Array([1,2,3])");
|
|
87
|
+
expect(nicify(new Int32Array([1, 2, 3]))).toBe("Int32Array([1,2,3])");
|
|
88
|
+
});
|
|
89
|
+
test("Error objects", () => {
|
|
90
|
+
const error = new Error("test error");
|
|
91
|
+
const nicifiedError = nicify({ error });
|
|
92
|
+
expect(nicifiedError).toMatch(new RegExp(deindent `
|
|
93
|
+
^\{
|
|
94
|
+
"error": Error: test error
|
|
95
|
+
Stack:
|
|
96
|
+
at (.|\\n)*
|
|
97
|
+
\}$
|
|
98
|
+
`));
|
|
99
|
+
});
|
|
100
|
+
test("Error objects with cause and an extra property", () => {
|
|
101
|
+
const error = new Error("test error", { cause: new Error("cause") });
|
|
102
|
+
error.extra = "something";
|
|
103
|
+
const nicifiedError = nicify(error, { lineIndent: "--" });
|
|
104
|
+
expect(nicifiedError).toMatch(new RegExp(deindent `
|
|
105
|
+
^Error: test error
|
|
106
|
+
--Stack:
|
|
107
|
+
----at (.|\\n)+
|
|
108
|
+
--Extra properties: \{ "extra": "something" \}
|
|
109
|
+
--Cause:
|
|
110
|
+
----Error: cause
|
|
111
|
+
------Stack:
|
|
112
|
+
--------at (.|\\n)+$
|
|
113
|
+
`));
|
|
114
|
+
});
|
|
115
|
+
test("Headers", () => {
|
|
116
|
+
const headers = new Headers();
|
|
117
|
+
headers.append("Content-Type", "application/json");
|
|
118
|
+
headers.append("Accept", "text/plain");
|
|
119
|
+
expect(nicify(headers)).toBe(deindent `
|
|
120
|
+
Headers {
|
|
121
|
+
"accept": "text/plain",
|
|
122
|
+
"content-type": "application/json",
|
|
123
|
+
}`);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
describe("multiline strings", () => {
|
|
127
|
+
test("basic multiline", () => {
|
|
128
|
+
expect(nicify("line1\nline2")).toBe('deindent`\n line1\n line2\n`');
|
|
129
|
+
});
|
|
130
|
+
test("multiline with trailing newline", () => {
|
|
131
|
+
expect(nicify("line1\nline2\n")).toBe('deindent`\n line1\n line2\n` + "\\n"');
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
describe("circular references", () => {
|
|
135
|
+
test("object with self reference", () => {
|
|
136
|
+
const circular = { a: 1 };
|
|
137
|
+
circular.self = circular;
|
|
138
|
+
expect(nicify(circular)).toBe(deindent `
|
|
139
|
+
{
|
|
140
|
+
"a": 1,
|
|
141
|
+
"self": Ref<value>,
|
|
142
|
+
}`);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
describe("configuration options", () => {
|
|
146
|
+
test("maxDepth", () => {
|
|
147
|
+
const deep = { a: { b: { c: { d: { e: 1 } } } } };
|
|
148
|
+
expect(nicify(deep, { maxDepth: 2 })).toBe('{ "a": { "b": { ... } } }');
|
|
149
|
+
});
|
|
150
|
+
test("lineIndent", () => {
|
|
151
|
+
expect(nicify({ a: 1, b: 2 }, { lineIndent: " " })).toBe(deindent `
|
|
152
|
+
{
|
|
153
|
+
"a": 1,
|
|
154
|
+
"b": 2,
|
|
155
|
+
}
|
|
156
|
+
`);
|
|
157
|
+
});
|
|
158
|
+
test("hideFields", () => {
|
|
159
|
+
expect(nicify({ a: 1, b: 2, secret: "hidden" }, { hideFields: ["secret"] })).toBe(deindent `
|
|
160
|
+
{
|
|
161
|
+
"a": 1,
|
|
162
|
+
"b": 2,
|
|
163
|
+
<some fields may have been hidden>,
|
|
164
|
+
}
|
|
165
|
+
`);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
describe("custom overrides", () => {
|
|
169
|
+
test("override with custom type", () => {
|
|
170
|
+
expect(nicify({ type: "special" }, {
|
|
171
|
+
overrides: ((value) => {
|
|
172
|
+
if (typeof value === "object" && value && "type" in value && value.type === "special") {
|
|
173
|
+
return "SPECIAL";
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
})
|
|
177
|
+
})).toBe("SPECIAL");
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
describe("functions", () => {
|
|
181
|
+
test("named function", () => {
|
|
182
|
+
expect(nicify(function namedFunction() { })).toBe("function namedFunction(...) { ... }");
|
|
183
|
+
});
|
|
184
|
+
test("arrow function", () => {
|
|
185
|
+
expect(nicify(() => { })).toBe("(...) => { ... }");
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
describe("Nicifiable interface", () => {
|
|
189
|
+
test("object implementing Nicifiable", () => {
|
|
190
|
+
const nicifiable = {
|
|
191
|
+
value: 42,
|
|
192
|
+
getNicifiableKeys() {
|
|
193
|
+
return ["value"];
|
|
194
|
+
},
|
|
195
|
+
getNicifiedObjectExtraLines() {
|
|
196
|
+
return ["// custom comment"];
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
expect(nicify(nicifiable)).toBe(deindent `
|
|
200
|
+
{
|
|
201
|
+
"value": 42,
|
|
202
|
+
// custom comment,
|
|
203
|
+
}
|
|
204
|
+
`);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
describe("unknown types", () => {
|
|
208
|
+
test("object without prototype", () => {
|
|
209
|
+
const unknownType = Object.create(null);
|
|
210
|
+
unknownType.value = "test";
|
|
211
|
+
expect(nicify(unknownType)).toBe('{ "value": "test" }');
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
});
|
package/dist/utils/types.d.ts
CHANGED
|
@@ -2,3 +2,20 @@ export type IsAny<T> = 0 extends (1 & T) ? true : false;
|
|
|
2
2
|
export type isNullish<T> = T extends null | undefined ? true : false;
|
|
3
3
|
export type NullishCoalesce<T, U> = T extends null | undefined ? U : T;
|
|
4
4
|
export type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never;
|
|
5
|
+
/**
|
|
6
|
+
* A variation of TypeScript's conditionals with slightly different semantics. It is the perfect type for cases where:
|
|
7
|
+
*
|
|
8
|
+
* - If all possible values are contained in `Extends`, then it will be mapped to `Then`.
|
|
9
|
+
* - If all possible values are not contained in `Extends`, then it will be mapped to `Otherwise`.
|
|
10
|
+
* - If some possible values are contained in `Extends` and some are not, then it will be mapped to `Then | Otherwise`.
|
|
11
|
+
*
|
|
12
|
+
* This is different from TypeScript's built-in conditional types (`Value extends Extends ? Then : Otherwise`), which
|
|
13
|
+
* returns `Otherwise` for the third case (causing unsoundness in many real-world cases).
|
|
14
|
+
*/
|
|
15
|
+
export type IfAndOnlyIf<Value, Extends, Then, Otherwise> = (Value extends Extends ? never : Otherwise) | (Value & Extends extends never ? never : Then);
|
|
16
|
+
/**
|
|
17
|
+
* Can be used to prettify a type in the IDE; for example, some complicated intersected types can be flattened into a single type.
|
|
18
|
+
*/
|
|
19
|
+
export type PrettifyType<T> = {
|
|
20
|
+
[K in keyof T]: T[K];
|
|
21
|
+
} & {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackframe/stack-shared",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.3",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"@simplewebauthn/browser": "^11.0.0",
|
|
46
46
|
"async-mutex": "^0.5.0",
|
|
47
47
|
"bcrypt": "^5.1.1",
|
|
48
|
+
"crc": "^4.3.2",
|
|
48
49
|
"elliptic": "^6.5.7",
|
|
49
50
|
"ip-regex": "^5.0.0",
|
|
50
51
|
"jose": "^5.2.2",
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { templateIdentity } from "./strings";
|
|
3
|
-
describe("templateIdentity", () => {
|
|
4
|
-
it("should be equivalent to a regular template string", () => {
|
|
5
|
-
const adjective = "scientific";
|
|
6
|
-
const noun = "railgun";
|
|
7
|
-
expect(templateIdentity `a certain scientific railgun`).toBe("a certain scientific railgun");
|
|
8
|
-
expect(templateIdentity `a certain ${adjective} railgun`).toBe(`a certain scientific railgun`);
|
|
9
|
-
expect(templateIdentity `a certain ${adjective} ${noun}`).toBe(`a certain scientific railgun`);
|
|
10
|
-
expect(templateIdentity `${adjective}${noun}`).toBe(`scientificrailgun`);
|
|
11
|
-
});
|
|
12
|
-
it("should work with empty strings", () => {
|
|
13
|
-
expect(templateIdentity ``).toBe("");
|
|
14
|
-
expect(templateIdentity `${""}`).toBe("");
|
|
15
|
-
expect(templateIdentity `${""}${""}`).toBe("");
|
|
16
|
-
});
|
|
17
|
-
it("should work with normal arrays", () => {
|
|
18
|
-
expect(templateIdentity(["a ", " scientific ", "gun"], "certain", "rail")).toBe("a certain scientific railgun");
|
|
19
|
-
expect(templateIdentity(["a"])).toBe("a");
|
|
20
|
-
});
|
|
21
|
-
it("should throw an error with wrong number of value arguments", () => {
|
|
22
|
-
expect(() => templateIdentity([])).toThrow();
|
|
23
|
-
expect(() => templateIdentity(["a", "b"])).toThrow();
|
|
24
|
-
expect(() => templateIdentity(["a", "b", "c"], "a", "b", "c")).toThrow();
|
|
25
|
-
});
|
|
26
|
-
});
|
|
File without changes
|