@stackframe/stack-shared 2.8.1 → 2.8.2
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 +6 -0
- package/dist/interface/clientInterface.d.ts +1 -1
- package/dist/interface/clientInterface.js +1 -1
- package/dist/interface/webhooks.d.ts +32 -0
- package/dist/interface/webhooks.js +3 -0
- package/dist/known-errors.d.ts +3 -0
- package/dist/known-errors.js +8 -0
- package/dist/utils/errors.js +1 -5
- 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/package.json +1 -1
- package/dist/utils/strings.test.js +0 -26
- /package/dist/utils/{strings.test.d.ts → strings.nicify.test.d.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,11 +6,11 @@ import { Result } from "../utils/results";
|
|
|
6
6
|
import { ContactChannelsCrud } from './crud/contact-channels';
|
|
7
7
|
import { CurrentUserCrud } from './crud/current-user';
|
|
8
8
|
import { ConnectedAccountAccessTokenCrud } from './crud/oauth';
|
|
9
|
+
import { ProjectPermissionsCrud } from './crud/project-permissions';
|
|
9
10
|
import { InternalProjectsCrud, ProjectsCrud } from './crud/projects';
|
|
10
11
|
import { SessionsCrud } from './crud/sessions';
|
|
11
12
|
import { TeamInvitationCrud } from './crud/team-invitation';
|
|
12
13
|
import { TeamMemberProfilesCrud } from './crud/team-member-profiles';
|
|
13
|
-
import { ProjectPermissionsCrud } from './crud/project-permissions';
|
|
14
14
|
import { TeamPermissionsCrud } from './crud/team-permissions';
|
|
15
15
|
import { TeamsCrud } from './crud/teams';
|
|
16
16
|
export type ClientInterfaceOptions = {
|
|
@@ -85,7 +85,7 @@ export class StackClientInterface {
|
|
|
85
85
|
// try to diagnose the error for the user
|
|
86
86
|
if (retriedResult.status === "error") {
|
|
87
87
|
if (globalVar.navigator && !globalVar.navigator.onLine) {
|
|
88
|
-
throw new Error("Failed to send Stack network request. It seems like you are offline. (window.navigator.onLine is falsy)", { cause: retriedResult.error });
|
|
88
|
+
throw new Error("Failed to send Stack network request. It seems like you are offline, please check your internet connection and try again. This is not an error with Stack Auth. (window.navigator.onLine is falsy)", { cause: retriedResult.error });
|
|
89
89
|
}
|
|
90
90
|
throw await this._createNetworkError(retriedResult.error, session, requestType);
|
|
91
91
|
}
|
|
@@ -254,4 +254,36 @@ export declare const webhookEvents: readonly [{
|
|
|
254
254
|
description: string;
|
|
255
255
|
tags: string[];
|
|
256
256
|
};
|
|
257
|
+
}, {
|
|
258
|
+
type: string;
|
|
259
|
+
schema: yup.ObjectSchema<{
|
|
260
|
+
id: string;
|
|
261
|
+
user_id: string;
|
|
262
|
+
team_id: string;
|
|
263
|
+
}, yup.AnyObject, {
|
|
264
|
+
id: undefined;
|
|
265
|
+
user_id: undefined;
|
|
266
|
+
team_id: undefined;
|
|
267
|
+
}, "">;
|
|
268
|
+
metadata: {
|
|
269
|
+
summary: string;
|
|
270
|
+
description: string;
|
|
271
|
+
tags: string[];
|
|
272
|
+
};
|
|
273
|
+
}, {
|
|
274
|
+
type: string;
|
|
275
|
+
schema: yup.ObjectSchema<{
|
|
276
|
+
id: string;
|
|
277
|
+
user_id: string;
|
|
278
|
+
team_id: string;
|
|
279
|
+
}, yup.AnyObject, {
|
|
280
|
+
id: undefined;
|
|
281
|
+
user_id: undefined;
|
|
282
|
+
team_id: undefined;
|
|
283
|
+
}, "">;
|
|
284
|
+
metadata: {
|
|
285
|
+
summary: string;
|
|
286
|
+
description: string;
|
|
287
|
+
tags: string[];
|
|
288
|
+
};
|
|
257
289
|
}];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { teamMembershipCreatedWebhookEvent, teamMembershipDeletedWebhookEvent } from "./crud/team-memberships";
|
|
2
|
+
import { teamPermissionCreatedWebhookEvent, teamPermissionDeletedWebhookEvent } from "./crud/team-permissions";
|
|
2
3
|
import { teamCreatedWebhookEvent, teamDeletedWebhookEvent, teamUpdatedWebhookEvent } from "./crud/teams";
|
|
3
4
|
import { userCreatedWebhookEvent, userDeletedWebhookEvent, userUpdatedWebhookEvent } from "./crud/users";
|
|
4
5
|
export const webhookEvents = [
|
|
@@ -10,4 +11,6 @@ export const webhookEvents = [
|
|
|
10
11
|
teamDeletedWebhookEvent,
|
|
11
12
|
teamMembershipCreatedWebhookEvent,
|
|
12
13
|
teamMembershipDeletedWebhookEvent,
|
|
14
|
+
teamPermissionCreatedWebhookEvent,
|
|
15
|
+
teamPermissionDeletedWebhookEvent,
|
|
13
16
|
];
|
package/dist/known-errors.d.ts
CHANGED
|
@@ -66,6 +66,9 @@ export declare const KnownErrors: {
|
|
|
66
66
|
ProjectAuthenticationError: KnownErrorConstructor<KnownError & KnownErrorBrand<"PROJECT_AUTHENTICATION_ERROR">, [statusCode: number, humanReadableMessage: string, details?: Json | undefined]> & {
|
|
67
67
|
errorCode: "PROJECT_AUTHENTICATION_ERROR";
|
|
68
68
|
};
|
|
69
|
+
PermissionIdAlreadyExists: KnownErrorConstructor<KnownError & KnownErrorBrand<"PERMISSION_ID_ALREADY_EXISTS">, [permissionId: string]> & {
|
|
70
|
+
errorCode: "PERMISSION_ID_ALREADY_EXISTS";
|
|
71
|
+
};
|
|
69
72
|
InvalidProjectAuthentication: KnownErrorConstructor<KnownError & KnownErrorBrand<"PROJECT_AUTHENTICATION_ERROR"> & {
|
|
70
73
|
constructorArgs: [statusCode: number, humanReadableMessage: string, details?: Json | undefined];
|
|
71
74
|
} & KnownErrorBrand<"INVALID_PROJECT_AUTHENTICATION">, [statusCode: number, humanReadableMessage: string, details?: Json | undefined]> & {
|
package/dist/known-errors.js
CHANGED
|
@@ -570,6 +570,13 @@ const InvalidPollingCodeError = createKnownErrorConstructor(KnownError, "INVALID
|
|
|
570
570
|
"The polling code is invalid or does not exist.",
|
|
571
571
|
details,
|
|
572
572
|
], (json) => [json]);
|
|
573
|
+
const PermissionIdAlreadyExists = createKnownErrorConstructor(KnownError, "PERMISSION_ID_ALREADY_EXISTS", (permissionId) => [
|
|
574
|
+
400,
|
|
575
|
+
`Permission with ID "${permissionId}" already exists. Choose a different ID.`,
|
|
576
|
+
{
|
|
577
|
+
permission_id: permissionId,
|
|
578
|
+
},
|
|
579
|
+
], (json) => [json.permission_id]);
|
|
573
580
|
export const KnownErrors = {
|
|
574
581
|
CannotDeleteCurrentSession,
|
|
575
582
|
UnsupportedError,
|
|
@@ -577,6 +584,7 @@ export const KnownErrors = {
|
|
|
577
584
|
SchemaError,
|
|
578
585
|
AllOverloadsFailed,
|
|
579
586
|
ProjectAuthenticationError,
|
|
587
|
+
PermissionIdAlreadyExists,
|
|
580
588
|
InvalidProjectAuthentication,
|
|
581
589
|
ProjectKeyWithoutAccessType,
|
|
582
590
|
InvalidAccessType,
|
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/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/package.json
CHANGED
|
@@ -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
|