@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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @stackframe/stack-shared
2
2
 
3
+ ## 2.8.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Various changes
8
+
3
9
  ## 2.8.1
4
10
 
5
11
  ## 2.8.0
@@ -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
  ];
@@ -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]> & {
@@ -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,
@@ -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
- let stack = error.stack ?? "";
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) {
@@ -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 => String(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
- ${errors[0]}
266
- ` : errors.map((e, i) => deindent `
265
+ ${strings[0]}
266
+ ` : strings.map((s, i) => deindent `
267
267
  Attempt ${i + 1}:
268
- ${e}
268
+ ${s}
269
269
  `).join("\n\n")}
270
270
  `, { cause: errors[errors.length - 1] });
271
271
  this.errors = errors;
@@ -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;
@@ -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
- if (strings.length === 0)
211
- return "";
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 templateIdentity(deindentedStrings, ...indentedValues);
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(value).replaceAll("\n", nl + lineIndent)}
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 > 1 || resValues.some(x => x.includes("\n"));
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(${nicify(value.toString())})`;
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 ? `${nicifyPropertyString(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" ? JSON.stringify(k) : `[${keyNicified}]`;
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackframe/stack-shared",
3
- "version": "2.8.1",
3
+ "version": "2.8.2",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "type": "module",
@@ -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
- });