@esposter/shared 4.3.3 → 4.4.0

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/README.md CHANGED
@@ -5,12 +5,14 @@
5
5
  [![NPM downloads][badge-npm-downloads]][url-npm]
6
6
  [![NPM Unpacked Size (with version)][badge-npm-unpacked-size]][url-npm]
7
7
 
8
- Shared TypeScript utilities, types, and error classes for the Esposter platform. Works in both browser and server environments.
8
+ Shared TypeScript utilities, types, and error classes for the Esposter platform.
9
+
10
+ Design notes live in [the specs](https://github.com/Esposter/Esposter/tree/main/openspec/specs/shared).
9
11
 
10
12
  ## Table of Contents
11
13
 
12
14
  - 🚀 [Getting Started](#getting-started)
13
- - 📖 [Documentation](#documentation)
15
+ - 🧩 [Key Exports](#key-exports)
14
16
  - ⚖️ [License](#license)
15
17
 
16
18
  ---
@@ -21,11 +23,9 @@ Shared TypeScript utilities, types, and error classes for the Esposter platform.
21
23
  pnpm i @esposter/shared zod
22
24
  ```
23
25
 
24
- ## <a name="documentation">📖 Documentation</a>
25
-
26
- We highly recommend you take a look at the [documentation](https://github.com/Esposter/Esposter/tree/main/openspec/specs/shared) to level up.
26
+ Works in both browser and server environments.
27
27
 
28
- ### Key Exports
28
+ ## <a name="key-exports">🧩 Key Exports</a>
29
29
 
30
30
  #### Error Classes
31
31
 
@@ -40,7 +40,7 @@ throw new InvalidOperationError(Operation.Read, file.name, zodError.message);
40
40
  ```ts
41
41
  import { takeOne } from "@esposter/shared";
42
42
 
43
- // Safe index access — throws on out-of-bounds instead of returning undefined
43
+ // The first item, typed without `undefined`; an out-of-bounds index still yields `undefined` at runtime
44
44
  const first = takeOne(items);
45
45
  ```
46
46
 
@@ -49,7 +49,7 @@ const first = takeOne(items);
49
49
  ```ts
50
50
  import type { ToData, DeepOmit, GetPaths } from "@esposter/shared";
51
51
 
52
- // ToData<T> — strips class methods, returns plain data shape
52
+ // ToData<T> — the plain data shape of T, with its `toJSON` method removed at every depth
53
53
  type RawColumn = ToData<Column>;
54
54
  ```
55
55
 
@@ -74,17 +74,6 @@ import { capitalize, truncate, toKebabCase } from "@esposter/shared";
74
74
  import { getResult, getResultAsync, withFinalizer } from "@esposter/shared";
75
75
  ```
76
76
 
77
- ### Commands
78
-
79
- Run from `packages/shared/`:
80
-
81
- ```bash
82
- pnpm build # compile to dist/
83
- pnpm test # vitest watch mode (coverage is run from the repo root)
84
- pnpm lint:fix # auto-fix lint
85
- pnpm typecheck # type check
86
- ```
87
-
88
77
  ## <a name="license">⚖️ License</a>
89
78
 
90
79
  This project is licensed under the [Apache-2.0 license](https://github.com/Esposter/Esposter/blob/main/LICENSE).
package/dist/index.d.ts CHANGED
@@ -188,6 +188,7 @@ export type AttributesTransformer = (attributes: Record<string, string>) => Reco
188
188
  export interface SanitizeHtmlOptions {
189
189
  allowedAttributes?: Record<string, string[]>;
190
190
  allowedClasses?: Record<string, string[]>;
191
+ allowedSources?: Record<string, Record<string, RegExp[]>>;
191
192
  allowedStyles?: Record<string, Record<string, RegExp[]>>;
192
193
  allowedTags?: string[];
193
194
  transformAttributes?: Record<string, AttributesTransformer>;
@@ -196,10 +197,12 @@ export interface SanitizePolicy {
196
197
  allowedAttributes: Record<string, string[]>;
197
198
  allowedClasses: Record<string, string[]>;
198
199
  allowedSchemes: string[];
200
+ allowedSources: Record<string, Record<string, RegExp[]>>;
199
201
  allowedStyles: Record<string, Record<string, RegExp[]>>;
200
202
  allowedTags: string[];
201
203
  transformAttributes: Record<string, AttributesTransformer>;
202
204
  }
205
+ export type TextHtmlAllowance = Pick<SanitizeHtmlOptions, "allowedAttributes" | "allowedSources" | "allowedTags">;
203
206
  export interface ItemEntityType<T extends string> {
204
207
  type: T;
205
208
  }
@@ -1870,6 +1873,29 @@ export interface ItemMetadataClass<TBase extends Class<object>> {
1870
1873
  new (...args: ConstructorParameters<TBase>): InstanceType<TBase> & ItemMetadata;
1871
1874
  prototype: InstanceType<TBase> & ItemMetadata;
1872
1875
  }
1876
+ export type GetArrayProps<T extends unknown[], P extends string, D extends unknown[], R extends boolean> = (D extends [unknown, ...infer Rest] ? GetProperties<T[number], R extends true ? `${P}[number]` : `${P}.[number]`, Rest, false> : never) | {
1877
+ path: R extends true ? "length" : `${P}.length`;
1878
+ value: T["length"];
1879
+ };
1880
+ export type FunctionLike = ((...args: never) => unknown) | (abstract new (...args: never) => unknown);
1881
+ export type KnownKeys<T> = { [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K]; };
1882
+ export type GetObjectProps<T, P extends string, D extends unknown[], R extends boolean> = { [K in keyof KnownKeys<T> & (number | string)]: K extends `${number}` ? never : NonNullable<T[K]> extends FunctionLike ? never : (D extends [unknown, ...infer Rest] ? GetProperties<T[K], R extends true ? `${K}` : `${P}.${K}`, Rest, false> : never) | {
1883
+ path: R extends true ? `${K}` : `${P}.${K}`;
1884
+ value: T[K];
1885
+ }; }[keyof KnownKeys<T> & (number | string)];
1886
+ export type GetPrimitiveProps<T, P extends string, D extends unknown[], R extends boolean> = T extends string ? {
1887
+ path: R extends true ? "length" : `${P}.length`;
1888
+ value: number;
1889
+ } : T extends symbol ? (D extends [unknown, ...infer Rest] ? GetProperties<string | undefined, R extends true ? "description" : `${P}.description`, Rest, false> : never) | {
1890
+ path: R extends true ? "description" : `${P}.description`;
1891
+ value: string | undefined;
1892
+ } : never;
1893
+ export type GetProperties<T, P extends string = "", D extends unknown[] = [unknown, unknown, unknown, unknown, unknown], R extends boolean = true> = [T] extends [never] ? never : D extends [] ? never : NonNullable<T> extends (infer O) ? O extends unknown[] ? GetArrayProps<O, P, D, R> : O extends object ? GetObjectProps<O, P, D, R> : GetPrimitiveProps<O, P, D, R> : never;
1894
+ export type GetPaths<T> = GetProperties<T> extends (infer U) ? (U extends {
1895
+ path: infer Path;
1896
+ } ? Path : never) : never;
1897
+ export type PropertyNames<T> = { [P in GetPaths<T>]: P; };
1898
+ export declare const ItemMetadataPropertyNames: PropertyNames<ItemMetadata>;
1873
1899
  export declare abstract class Serializable {
1874
1900
  toJSON(): this;
1875
1901
  }
@@ -1877,7 +1903,6 @@ export interface TakeOne {
1877
1903
  <T extends readonly unknown[]>(values: T, index?: number): T[number];
1878
1904
  <T extends object>(values: T, index: keyof T): T[keyof T];
1879
1905
  }
1880
- export type FunctionLike = ((...args: never) => unknown) | (abstract new (...args: never) => unknown);
1881
1906
  export type DeepOmit<T, TKey extends PropertyKey> = T extends DeepOmitKeptValue ? T : T extends readonly unknown[] ? { [K in keyof T]: DeepOmit<T[K], TKey>; } : T extends object ? [keyof T] extends [never] ? T : { [P in keyof T as P extends symbol ? never : P extends TKey ? never : P]: DeepOmit<T[P], TKey>; } : T;
1882
1907
  type DeepOmitKeptValue = ArrayBufferView | Blob | Date | FunctionLike | Map<unknown, unknown> | Primitive | Promise<unknown> | RegExp | Set<unknown>;
1883
1908
  export type DeepOptionalUndefined<T> = T extends Date | FunctionLike | Primitive ? T : T extends unknown[] ? { [K in keyof T]: DeepOptionalUndefined<T[K]>; } : T extends object ? { [K in keyof T as undefined extends T[K] ? K : never]?: DeepOptionalUndefined<T[K]>; } & { [K in keyof T as undefined extends T[K] ? never : K]: DeepOptionalUndefined<T[K]>; } extends (infer O) ? { [K in keyof O]: O[K]; } : never : T;
@@ -1941,6 +1966,8 @@ export declare const CUSTOM_EMOJI_ID_ATTRIBUTE = "data-custom-emoji-id";
1941
1966
  export declare const CUSTOM_EMOJI_NAME_ATTRIBUTE = "data-custom-emoji-name";
1942
1967
  export declare const SPOILER_ATTRIBUTE = "data-spoiler";
1943
1968
  export declare const SPOILER_SELECTOR: string;
1969
+ export declare const checkIsOnlyReactor: (userIds: string[], userId: string) => boolean;
1970
+ export declare const getUpdatedUserIds: (userIds: string[], newUserId: string) => string[];
1944
1971
  export declare const getMentions: (message: string) => HTMLElement[];
1945
1972
  export declare const charset: (type: string) => false | string;
1946
1973
  export declare const MEDIA_TYPE_REGEX: RegExp;
@@ -1966,6 +1993,7 @@ export declare const html: typeof String.raw;
1966
1993
  export declare const checkContainsProfanity: (text: unknown, { isWholeWord }?: Partial<ProfanityOptions>) => boolean;
1967
1994
  export declare const englishProfaneWords: readonly string[];
1968
1995
  export declare const checkIsAllowedUrl: (url: string, allowedSchemes: string[]) => boolean;
1996
+ export declare const checkIsSourceAllowed: (element: DefaultTreeAdapterTypes.Element, allowedSources: SanitizePolicy["allowedSources"]) => boolean;
1969
1997
  export declare const checkIsUnsafeUrlCharacter: (character: string) => boolean;
1970
1998
  export declare const checkIsWhitespaceCharacter: (character: string) => boolean;
1971
1999
  export declare const collectText: (nodes: DefaultTreeAdapterTypes.ChildNode[], depth: number) => string;
@@ -1983,7 +2011,7 @@ export declare const MAX_NESTING_DEPTH = 256;
1983
2011
  export declare const SRCSET_DESCRIPTOR_REGEX: RegExp;
1984
2012
  export declare const ESCAPED_TEXT_CHARACTERS_REGEX: RegExp;
1985
2013
  export declare const ESCAPED_ATTRIBUTE_CHARACTERS_REGEX: RegExp;
1986
- export declare const SANITIZER_ADVISORY_WATCH_SINCE = "2026-10-21";
2014
+ export declare const SANITIZER_ADVISORY_WATCH_SINCE = "2026-10-23";
1987
2015
  export declare const escapeAttribute: (value: string) => string;
1988
2016
  export declare const escapeCharacters: (value: string, charactersRegex: RegExp, entities: Record<string, string>) => string;
1989
2017
  export declare const escapeText: (text: string) => string;
@@ -1993,10 +2021,11 @@ export declare const filterHtml: (html: string, policy: SanitizePolicy) => strin
1993
2021
  export declare const filterSrcset: (value: string, allowedSchemes: string[]) => string | undefined;
1994
2022
  export declare const filterStyle: (value: string, rules: Record<string, RegExp[]> | undefined) => string | undefined;
1995
2023
  export declare const getChildNodes: (element: DefaultTreeAdapterTypes.Element) => DefaultTreeAdapterTypes.ChildNode[];
2024
+ export declare const getTagPolicy: <T>(tagPolicies: Record<string, T>, tagName: string) => T | undefined;
1996
2025
  export declare const matchesClassPattern: (className: string, pattern: string) => boolean;
1997
2026
  export declare const renderNode: (node: DefaultTreeAdapterTypes.ChildNode, policy: SanitizePolicy, depth: number) => string;
1998
2027
  export declare const sanitizeHtml: (html: string, options?: SanitizeHtmlOptions) => string;
1999
- export declare const sanitizeTextHtml: (html: string) => string;
2028
+ export declare const sanitizeTextHtml: (html: string, allowance?: TextHtmlAllowance) => string;
2000
2029
  export declare const applyItemMetadataMixin: <TBase extends Class<object>>(Base: TBase) => ItemMetadataClass<TBase>;
2001
2030
  export declare const createNormalizedStringSchema: (maxLength: number, schema?: z.ZodString) => z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>, z.ZodString>;
2002
2031
  export declare const createUniqueArraySchema: CreateUniqueArraySchema;
@@ -2032,27 +2061,6 @@ export declare const checkIsSameBytes: (first: ArrayBuffer | ArrayBufferView | S
2032
2061
  export declare const checkIsSameValueZero: (first: unknown, second: unknown) => boolean;
2033
2062
  export declare const clonePlainDeep: <T>(data: T) => T;
2034
2063
  export declare const getEnumerableKeys: (value: object) => PropertyKey[];
2035
- export type GetArrayProps<T extends unknown[], P extends string, D extends unknown[], R extends boolean> = (D extends [unknown, ...infer Rest] ? GetProperties<T[number], R extends true ? `${P}[number]` : `${P}.[number]`, Rest, false> : never) | {
2036
- path: R extends true ? "length" : `${P}.length`;
2037
- value: T["length"];
2038
- };
2039
- export type KnownKeys<T> = { [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K]; };
2040
- export type GetObjectProps<T, P extends string, D extends unknown[], R extends boolean> = { [K in keyof KnownKeys<T> & (number | string)]: K extends `${number}` ? never : NonNullable<T[K]> extends FunctionLike ? never : (D extends [unknown, ...infer Rest] ? GetProperties<T[K], R extends true ? `${K}` : `${P}.${K}`, Rest, false> : never) | {
2041
- path: R extends true ? `${K}` : `${P}.${K}`;
2042
- value: T[K];
2043
- }; }[keyof KnownKeys<T> & (number | string)];
2044
- export type GetPrimitiveProps<T, P extends string, D extends unknown[], R extends boolean> = T extends string ? {
2045
- path: R extends true ? "length" : `${P}.length`;
2046
- value: number;
2047
- } : T extends symbol ? (D extends [unknown, ...infer Rest] ? GetProperties<string | undefined, R extends true ? "description" : `${P}.description`, Rest, false> : never) | {
2048
- path: R extends true ? "description" : `${P}.description`;
2049
- value: string | undefined;
2050
- } : never;
2051
- export type GetProperties<T, P extends string = "", D extends unknown[] = [unknown, unknown, unknown, unknown, unknown], R extends boolean = true> = [T] extends [never] ? never : D extends [] ? never : NonNullable<T> extends (infer O) ? O extends unknown[] ? GetArrayProps<O, P, D, R> : O extends object ? GetObjectProps<O, P, D, R> : GetPrimitiveProps<O, P, D, R> : never;
2052
- export type GetPaths<T> = GetProperties<T> extends (infer U) ? (U extends {
2053
- path: infer Path;
2054
- } ? Path : never) : never;
2055
- export type PropertyNames<T> = { [P in GetPaths<T>]: P; };
2056
2064
  export declare const getPropertyNames: <T>() => PropertyNames<T>;
2057
2065
  export declare const jsonDateParse: <T = any>(text: string) => T;
2058
2066
  export type MergeObjectsStrict<T extends object[]> = T extends [infer TFirst, infer TSecond, ...infer TRemaining] ? TSecond extends { [K in keyof TSecond]: K extends keyof TFirst ? never : TSecond[K]; } ? TRemaining extends object[] ? MergeObjectsStrict<[TSecond, ...TRemaining]> & TFirst : TFirst & TSecond : never : T extends [infer TFirst] ? TFirst : never;
@@ -2072,6 +2080,8 @@ export declare const toKebabCase: <T extends string>(string: T) => KebabCase<T>;
2072
2080
  export declare const truncate: (string: string, length: number) => string;
2073
2081
  export declare const uncapitalize: <T extends string>(string: T) => Uncapitalize<T>;
2074
2082
  export declare const MILLISECONDS_PER_SECOND = 1e3;
2083
+ export declare const createExponentialBackoff: (baseDelayMs: number, maxDelayMs: number) => (<T>(operation: () => Promise<T>) => Promise<T>);
2084
+ export declare const getExponentialBackoffDelayMs: (attempt: number, baseDelayMs: number, maxDelayMs: number) => number;
2075
2085
  export declare const hrtime: (previousHrTime?: [number, number]) => [number, number];
2076
2086
  export declare const now: () => string;
2077
2087
  export declare const sleep: (durationMs: number) => Promise<void>;
package/dist/index.js CHANGED
@@ -157,6 +157,10 @@ var ItemMetadata = class {
157
157
  deletedAt = null;
158
158
  updatedAt = /* @__PURE__ */ new Date();
159
159
  };
160
+ // src/util/object/getPropertyNames.ts
161
+ const getPropertyNames = () => new Proxy({}, { get: (_target, property) => property });
162
+ // src/models/shared/ItemMetadataPropertyNames.ts
163
+ const ItemMetadataPropertyNames = getPropertyNames();
160
164
  // src/util/reactivity/getRawData.ts
161
165
  const getRawData = (data) => {
162
166
  const raw = data && data.__v_raw;
@@ -209,8 +213,12 @@ const withFinalizer = (callback, finalizer) => {
209
213
  };
210
214
  // src/services/error/withFinalizerAsync.ts
211
215
  const withFinalizerAsync = async (callback, finalizer) => {
212
- const callbackResult = await getResultAsync(async () => callback());
213
- if (finalizer) await getResultAsync(async () => finalizer()).match(noop, (error) => {
216
+ const callbackResult = await getResultAsync(async () => {
217
+ return await callback();
218
+ });
219
+ if (finalizer) await getResultAsync(async () => {
220
+ await finalizer();
221
+ }).match(noop, (error) => {
214
222
  reportFinalizerError(callbackResult, error);
215
223
  });
216
224
  return callbackResult.match((value) => value, (error) => {
@@ -336,12 +344,23 @@ const wrapUpgradeDatabase = (database) => ({
336
344
  ...wrapDatabase(database),
337
345
  createObjectStore: (name, options) => wrapObjectStore(database.createObjectStore(name, options))
338
346
  });
347
+ // src/util/function/getSynchronizedFunction.ts
348
+ const pendingPromises = /* @__PURE__ */ new Set();
349
+ const getSynchronizedFunction = (asyncFunction) => (...args) => {
350
+ const promise = withFinalizerAsync(() => asyncFunction(...args), () => {
351
+ pendingPromises.delete(promise);
352
+ });
353
+ pendingPromises.add(promise);
354
+ };
355
+ const waitForSynchronizedFunctions = async () => {
356
+ await Promise.allSettled(pendingPromises);
357
+ };
339
358
  // src/services/indexedDb/wrapUpgradeTransaction.ts
340
359
  const wrapUpgradeTransaction = (transaction) => {
341
360
  const done = getTransactionDone(transaction);
342
- getResultAsync(() => done).match(noop, (error) => {
361
+ getSynchronizedFunction(() => getResultAsync(() => done).match(noop, (error) => {
343
362
  if (transaction.error) console.error(error);
344
- });
363
+ }))();
345
364
  return {
346
365
  get done() {
347
366
  return done;
@@ -610,6 +629,10 @@ const CUSTOM_EMOJI_ID_ATTRIBUTE = "data-custom-emoji-id";
610
629
  const CUSTOM_EMOJI_NAME_ATTRIBUTE = "data-custom-emoji-name";
611
630
  const SPOILER_ATTRIBUTE = "data-spoiler";
612
631
  const SPOILER_SELECTOR = `span[${SPOILER_ATTRIBUTE}]`;
632
+ // src/services/message/emoji/checkIsOnlyReactor.ts
633
+ const checkIsOnlyReactor = (userIds, userId) => userIds.length === 1 && userIds[0] === userId;
634
+ // src/services/message/emoji/getUpdatedUserIds.ts
635
+ const getUpdatedUserIds = (userIds, newUserId) => userIds.includes(newUserId) ? userIds.filter((id) => id !== newUserId) : [...userIds, newUserId];
613
636
  // src/services/message/getMentions.ts
614
637
  const getMentions = (message) => parse(message).querySelectorAll(MENTION_SELECTOR);
615
638
  // src/services/mime/constants.ts
@@ -1193,6 +1216,17 @@ const checkIsAllowedUrl = (url, allowedSchemes) => {
1193
1216
  const scheme = beforePath.slice(0, separatorIndex).replaceAll(NON_SCHEME_CHARACTERS_REGEX, "").toLowerCase();
1194
1217
  return allowedSchemes.includes(scheme);
1195
1218
  };
1219
+ // src/services/sanitizeHtml/getTagPolicy.ts
1220
+ const getTagPolicy = (tagPolicies, tagName) => Object.hasOwn(tagPolicies, tagName) ? tagPolicies[tagName] : void 0;
1221
+ // src/services/sanitizeHtml/checkIsSourceAllowed.ts
1222
+ const checkIsSourceAllowed = (element, allowedSources) => {
1223
+ const sourceRules = getTagPolicy(allowedSources, element.tagName);
1224
+ if (!sourceRules) return true;
1225
+ return Object.entries(sourceRules).every(([name, patterns]) => {
1226
+ const attribute = element.attrs.find((attr) => attr.name === name);
1227
+ return attribute !== void 0 && patterns.some((pattern) => pattern.test(attribute.value));
1228
+ });
1229
+ };
1196
1230
  // src/services/sanitizeHtml/constants.ts
1197
1231
  const HTML_NAMESPACE = html$1.NS.HTML;
1198
1232
  const DEFAULT_ALLOWED_TAGS = [
@@ -1327,7 +1361,7 @@ const MAX_NESTING_DEPTH = 256;
1327
1361
  const SRCSET_DESCRIPTOR_REGEX = /^(?:\d+w|\d*\.?\d+x|\d+h)$/u;
1328
1362
  const ESCAPED_TEXT_CHARACTERS_REGEX = /[&<>]/gu;
1329
1363
  const ESCAPED_ATTRIBUTE_CHARACTERS_REGEX = /[&"<>]/gu;
1330
- const SANITIZER_ADVISORY_WATCH_SINCE = "2026-10-21";
1364
+ const SANITIZER_ADVISORY_WATCH_SINCE = "2026-10-23";
1331
1365
  // src/services/sanitizeHtml/getChildNodes.ts
1332
1366
  const getChildNodes = (element) => "content" in element ? element.content.childNodes : element.childNodes;
1333
1367
  // src/services/sanitizeHtml/collectText.ts
@@ -1420,7 +1454,6 @@ const filterStyle = (value, rules) => {
1420
1454
  return Array.from(declarations, ([property, { important, propertyValue }]) => `${property}${PROPERTY_SEPARATOR}${propertyValue}${important ? IMPORTANT_SUFFIX : ""}`).join(DECLARATION_SEPARATOR);
1421
1455
  };
1422
1456
  // src/services/sanitizeHtml/filterAttributes.ts
1423
- const getTagPolicy = (tagPolicies, tagName) => Object.hasOwn(tagPolicies, tagName) ? tagPolicies[tagName] : void 0;
1424
1457
  const checkIsMetaRefresh = (tagName, attributes) => tagName === "meta" && attributes["http-equiv"]?.trim().toLowerCase() === "refresh";
1425
1458
  const filterAttributes = (element, policy) => {
1426
1459
  const { tagName } = element;
@@ -1451,7 +1484,7 @@ const renderNode = (node, policy, depth) => {
1451
1484
  if (!("tagName" in node) || depth > 256) return "";
1452
1485
  const element = node;
1453
1486
  const children = getChildNodes(element).map((child) => renderNode(child, policy, depth + 1)).join("");
1454
- if (!(element.namespaceURI === HTML_NAMESPACE && !NON_TEXT_TAGS.includes(element.tagName) && policy.allowedTags.includes(element.tagName))) return NON_TEXT_TAGS.includes(element.tagName) ? "" : children;
1487
+ if (!(element.namespaceURI === HTML_NAMESPACE && !NON_TEXT_TAGS.includes(element.tagName) && policy.allowedTags.includes(element.tagName) && checkIsSourceAllowed(element, policy.allowedSources))) return NON_TEXT_TAGS.includes(element.tagName) ? "" : children;
1455
1488
  const attributes = Object.entries(filterAttributes(element, policy)).map(([name, value]) => ` ${name}="${escapeAttribute(value)}"`).join("");
1456
1489
  const opening = `<${element.tagName}${attributes}>`;
1457
1490
  return VOID_TAGS.includes(element.tagName) ? opening : `${opening}${children}</${element.tagName}>`;
@@ -1502,6 +1535,7 @@ const sanitizeHtml = (html, options) => filterHtml(html, {
1502
1535
  },
1503
1536
  allowedClasses: options?.allowedClasses ?? {},
1504
1537
  allowedSchemes: DEFAULT_ALLOWED_SCHEMES,
1538
+ allowedSources: options?.allowedSources ?? {},
1505
1539
  allowedStyles: {
1506
1540
  ...options?.allowedStyles,
1507
1541
  ...TABLE_ALLOWED_STYLES
@@ -1521,47 +1555,54 @@ const COLOR_REGEXES = [
1521
1555
  /^rgba?\(\d{1,3},\s*\d{1,3},\s*\d{1,3}(?:,\s*[\d.]+)?\)$/u,
1522
1556
  /^[a-z]+$/iu
1523
1557
  ];
1524
- const sanitizeTextHtml = (html) => sanitizeHtml(html, {
1525
- allowedAttributes: {
1526
- input: [
1527
- "checked",
1528
- "disabled",
1529
- "type"
1530
- ],
1531
- li: ["data-checked", "data-type"],
1532
- span: [
1533
- CUSTOM_EMOJI_ID_ATTRIBUTE,
1534
- CUSTOM_EMOJI_NAME_ATTRIBUTE,
1535
- MENTION_ID_ATTRIBUTE,
1536
- MENTION_ITEM_TYPE_ATTRIBUTE,
1537
- MENTION_LABEL_ATTRIBUTE,
1538
- MENTION_TYPE_ATTRIBUTE,
1539
- SPOILER_ATTRIBUTE,
1540
- "style"
1541
- ],
1542
- ul: ["data-type"]
1543
- },
1544
- allowedClasses: {
1545
- code: ["language-*"],
1546
- pre: ["language-*"],
1547
- span: ["hljs*"]
1548
- },
1549
- allowedStyles: { span: {
1550
- "background-color": COLOR_REGEXES,
1551
- "border-radius": [/^[\d.]+(?<unit>px|em|rem|%)$/u],
1552
- color: COLOR_REGEXES
1553
- } },
1554
- allowedTags: [
1555
- ...DEFAULT_ALLOWED_TAGS,
1556
- "input",
1557
- "label"
1558
+ const KIT_ALLOWED_ATTRIBUTES = {
1559
+ input: [
1560
+ "checked",
1561
+ "disabled",
1562
+ "type"
1558
1563
  ],
1559
- transformAttributes: { input: ({ checked }) => ({
1560
- ...checked === void 0 ? {} : { checked },
1561
- disabled: "",
1562
- type: "checkbox"
1563
- }) }
1564
- });
1564
+ li: ["data-checked", "data-type"],
1565
+ span: [
1566
+ CUSTOM_EMOJI_ID_ATTRIBUTE,
1567
+ CUSTOM_EMOJI_NAME_ATTRIBUTE,
1568
+ MENTION_ID_ATTRIBUTE,
1569
+ MENTION_ITEM_TYPE_ATTRIBUTE,
1570
+ MENTION_LABEL_ATTRIBUTE,
1571
+ MENTION_TYPE_ATTRIBUTE,
1572
+ SPOILER_ATTRIBUTE,
1573
+ "style"
1574
+ ],
1575
+ ul: ["data-type"]
1576
+ };
1577
+ const sanitizeTextHtml = (html, allowance = {}) => {
1578
+ const allowedAttributes = { ...KIT_ALLOWED_ATTRIBUTES };
1579
+ for (const [tagName, attributes] of Object.entries(allowance.allowedAttributes ?? {})) allowedAttributes[tagName] = [.../* @__PURE__ */ new Set([...allowedAttributes[tagName] ?? [], ...attributes])];
1580
+ return sanitizeHtml(html, {
1581
+ allowedAttributes,
1582
+ allowedClasses: {
1583
+ code: ["language-*"],
1584
+ pre: ["language-*"],
1585
+ span: ["hljs*"]
1586
+ },
1587
+ allowedSources: allowance.allowedSources,
1588
+ allowedStyles: { span: {
1589
+ "background-color": COLOR_REGEXES,
1590
+ "border-radius": [/^[\d.]+(?<unit>px|em|rem|%)$/u],
1591
+ color: COLOR_REGEXES
1592
+ } },
1593
+ allowedTags: [
1594
+ ...DEFAULT_ALLOWED_TAGS,
1595
+ "input",
1596
+ "label",
1597
+ ...allowance.allowedTags ?? []
1598
+ ],
1599
+ transformAttributes: { input: ({ checked }) => ({
1600
+ ...checked === void 0 ? {} : { checked },
1601
+ disabled: "",
1602
+ type: "checkbox"
1603
+ }) }
1604
+ });
1605
+ };
1565
1606
  // src/services/shared/applyItemMetadataMixin.ts
1566
1607
  const applyItemMetadataMixin = (Base) => class ItemWithMetadata extends Base {
1567
1608
  createdAt = /* @__PURE__ */ new Date();
@@ -1714,17 +1755,6 @@ const downloadFile = (filename, blobPart, type) => {
1714
1755
  };
1715
1756
  // src/util/environment/checkIsServer.ts
1716
1757
  const checkIsServer = () => typeof window === "undefined";
1717
- // src/util/function/getSynchronizedFunction.ts
1718
- const pendingPromises = /* @__PURE__ */ new Set();
1719
- const getSynchronizedFunction = (asyncFunction) => (...args) => {
1720
- const promise = asyncFunction(...args).finally(() => {
1721
- pendingPromises.delete(promise);
1722
- });
1723
- pendingPromises.add(promise);
1724
- };
1725
- const waitForSynchronizedFunctions = async () => {
1726
- await Promise.allSettled(pendingPromises);
1727
- };
1728
1758
  // src/util/id/uuid/constants.ts
1729
1759
  const UUIDV4_REGEX = /^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/iu;
1730
1760
  // src/util/map/getOrCreate.ts
@@ -1751,8 +1781,6 @@ const clone = (data, copies) => {
1751
1781
  }
1752
1782
  };
1753
1783
  const clonePlainDeep = (data) => clone(data, /* @__PURE__ */ new WeakMap());
1754
- // src/util/object/getPropertyNames.ts
1755
- const getPropertyNames = () => new Proxy({}, { get: (_target, property) => property });
1756
1784
  // src/util/object/jsonDateParse.ts
1757
1785
  const ISO_DATE_REGEX = /^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})T(?<hours>\d{2}):(?<minutes>\d{2}):(?<seconds>\d{2}(?:\.{0,1}\d*))(?:Z|(?<sign>\+|-)(?<offset>[\d|:]*))?$/u;
1758
1786
  const MS_AJAX_DATE_REGEX = /^\/Date\((?<timestamp>-?\d+(?:[-+]\d+)?)\)[/\\]$/u;
@@ -2035,6 +2063,28 @@ const truncate = (string, length) => {
2035
2063
  const uncapitalize = (string) => `${string.charAt(0).toLowerCase()}${string.slice(1)}`;
2036
2064
  // src/util/time/constants.ts
2037
2065
  const MILLISECONDS_PER_SECOND = 1e3;
2066
+ // src/util/time/getExponentialBackoffDelayMs.ts
2067
+ const getExponentialBackoffDelayMs = (attempt, baseDelayMs, maxDelayMs) => Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);
2068
+ // src/util/time/sleep.ts
2069
+ const sleep = (durationMs) => new Promise((resolve) => {
2070
+ setTimeout(() => {
2071
+ resolve();
2072
+ }, durationMs);
2073
+ });
2074
+ // src/util/time/createExponentialBackoff.ts
2075
+ const createExponentialBackoff = (baseDelayMs, maxDelayMs) => {
2076
+ let failureCount = 0;
2077
+ return async (operation) => {
2078
+ if (failureCount > 0) await sleep(getExponentialBackoffDelayMs(failureCount - 1, baseDelayMs, maxDelayMs));
2079
+ return getResultAsync(operation).match((value) => {
2080
+ failureCount = 0;
2081
+ return value;
2082
+ }, (error) => {
2083
+ failureCount += 1;
2084
+ throw error;
2085
+ });
2086
+ };
2087
+ };
2038
2088
  // src/util/time/hrtime.ts
2039
2089
  const hrtime = (previousHrTime) => {
2040
2090
  if (checkIsServer()) return process.hrtime(previousHrTime);
@@ -2058,10 +2108,4 @@ const now = () => {
2058
2108
  const [seconds, nanoseconds] = hrtime(loadNanoseconds);
2059
2109
  return (BigInt(loadMilliseconds) * 10n ** 6n + (BigInt(seconds) * 10n ** 9n + BigInt(nanoseconds))).toString();
2060
2110
  };
2061
- // src/util/time/sleep.ts
2062
- const sleep = (durationMs) => new Promise((resolve) => {
2063
- setTimeout(() => {
2064
- resolve();
2065
- }, durationMs);
2066
- });
2067
- export { ALL_SPECIAL_VALUES, APPEND_SEGMENT, CHARSET_PARAMETER_REGEX, CUSTOM_EMOJI_ID_ATTRIBUTE, CUSTOM_EMOJI_NAME_ATTRIBUTE, DEFAULT_ALLOWED_ATTRIBUTES, DEFAULT_ALLOWED_SCHEMES, DEFAULT_ALLOWED_TAGS, DEFAULT_COMPRESSION_LEVEL, DEFAULT_FACET_SCORE, DEFAULT_LINK_ATTRIBUTES, DEFAULT_SOURCE_SCORE, DEFAULT_TEXT_CHARSET, DEFAULT_TYPE_SCORE, ESCAPED_ATTRIBUTE_CHARACTERS_REGEX, ESCAPED_TEXT_CHARACTERS_REGEX, FacetScoreMap, ForbiddenError, GIBIBYTE, HTML_NAMESPACE, ID_SEPARATOR, IMPORTANT_REGEX, InvalidOperationError, ItemMetadata, JSON_POINTER_REGEX, JsonPatchOperationType, KIBIBYTE, MAX_NESTING_DEPTH, MAX_READ_LIMIT, MAX_WINDOW_LOG, MEDIA_TYPE_REGEX, MENTION_EVERYONE_ID, MENTION_HERE_ID, MENTION_ID_ATTRIBUTE, MENTION_ITEM_TYPE_ATTRIBUTE, MENTION_LABEL_ATTRIBUTE, MENTION_SELECTOR, MENTION_TYPE, MENTION_TYPE_ATTRIBUTE, MILLISECONDS_PER_SECOND, MIN_WINDOW_LOG, MentionType, NON_TEXT_TAGS, NotFoundError, NotInitializedError, ObjectMethod, Operation, PATH_SEPARATOR_REGEX, RoutePath, SANITIZER_ADVISORY_WATCH_SINCE, SITE_NAME, SPOILER_ATTRIBUTE, SPOILER_SELECTOR, SRCSET_ATTRIBUTE, SRCSET_DESCRIPTOR_REGEX, Serializable, SourceScoreMap, TEXT_MEDIA_TYPE_REGEX, TypeScoreMap, TypedEventEmitter, URL_ATTRIBUTES, UUIDV4_REGEX, VOID_TAGS, WordFilteredError, addJsonValue, applyItemMetadataMixin, applyPatch, applyPatches, capitalize, charset, checkContainsProfanity, checkIsAllowedUrl, checkIsCustomMethod, checkIsDeepEqual, checkIsNestedControl, checkIsNestedInteraction, checkIsPlainObject, checkIsSameBytes, checkIsSameValueZero, checkIsServer, checkIsUnsafeUrlCharacter, checkIsWhitespaceCharacter, chunk, chunkBySerializedSize, clonePlainDeep, collectText, contentType, createEvictingPromiseCache, createHistory, createItemEntityTypeSchema, createNormalizedStringSchema, createUniqueArraySchema, css, dedent, downloadFile, downloadUrl, encodeUrlSubDelimiters, englishProfaneWords, escapeAttribute, escapeCharacters, escapeText, exhaustiveGuard, extension, filterAttributes, filterClasses, filterHtml, filterSrcset, filterStyle, getChildNodes, getDecodedUriComponent, getEndOfDay, getEnumerableKeys, getExtensionMimeTypeMap, getMediaType, getMentions, getMimeScore, getMimeTypeEntryMap, getOrCreate, getPropertyNames, getRawData, getRequestError, getResult, getResultAsync, getSynchronizedFunction, getTransactionDone, getTransactionObjectStore, getWindowLog, getZonedDateTime, hrtime, html, jsonDateParse, lookup, matchesClassPattern, mergeObjectsStrict, noop, normalizeString, now, omitDeep, openDatabase, parseArrayIndex, parseJsonPointer, pushHistory, readJsonValue, redoHistory, removeJsonValue, renderNode, reportFinalizerError, requestToPromise, sanitizeHtml, sanitizeTextHtml, settleAll, sleep, streamToText, takeOne, takeRandom, toAppError, toJsonPointer, toKebabCase, toRawDeep, truncate, uncapitalize, undoHistory, updateJsonValue, waitForSynchronizedFunctions, withFinalizer, withFinalizerAsync, wrapDatabase, wrapIndex, wrapObjectStore, wrapTransaction, wrapUpgradeDatabase, wrapUpgradeTransaction };
2111
+ export { ALL_SPECIAL_VALUES, APPEND_SEGMENT, CHARSET_PARAMETER_REGEX, CUSTOM_EMOJI_ID_ATTRIBUTE, CUSTOM_EMOJI_NAME_ATTRIBUTE, DEFAULT_ALLOWED_ATTRIBUTES, DEFAULT_ALLOWED_SCHEMES, DEFAULT_ALLOWED_TAGS, DEFAULT_COMPRESSION_LEVEL, DEFAULT_FACET_SCORE, DEFAULT_LINK_ATTRIBUTES, DEFAULT_SOURCE_SCORE, DEFAULT_TEXT_CHARSET, DEFAULT_TYPE_SCORE, ESCAPED_ATTRIBUTE_CHARACTERS_REGEX, ESCAPED_TEXT_CHARACTERS_REGEX, FacetScoreMap, ForbiddenError, GIBIBYTE, HTML_NAMESPACE, ID_SEPARATOR, IMPORTANT_REGEX, InvalidOperationError, ItemMetadata, ItemMetadataPropertyNames, JSON_POINTER_REGEX, JsonPatchOperationType, KIBIBYTE, MAX_NESTING_DEPTH, MAX_READ_LIMIT, MAX_WINDOW_LOG, MEDIA_TYPE_REGEX, MENTION_EVERYONE_ID, MENTION_HERE_ID, MENTION_ID_ATTRIBUTE, MENTION_ITEM_TYPE_ATTRIBUTE, MENTION_LABEL_ATTRIBUTE, MENTION_SELECTOR, MENTION_TYPE, MENTION_TYPE_ATTRIBUTE, MILLISECONDS_PER_SECOND, MIN_WINDOW_LOG, MentionType, NON_TEXT_TAGS, NotFoundError, NotInitializedError, ObjectMethod, Operation, PATH_SEPARATOR_REGEX, RoutePath, SANITIZER_ADVISORY_WATCH_SINCE, SITE_NAME, SPOILER_ATTRIBUTE, SPOILER_SELECTOR, SRCSET_ATTRIBUTE, SRCSET_DESCRIPTOR_REGEX, Serializable, SourceScoreMap, TEXT_MEDIA_TYPE_REGEX, TypeScoreMap, TypedEventEmitter, URL_ATTRIBUTES, UUIDV4_REGEX, VOID_TAGS, WordFilteredError, addJsonValue, applyItemMetadataMixin, applyPatch, applyPatches, capitalize, charset, checkContainsProfanity, checkIsAllowedUrl, checkIsCustomMethod, checkIsDeepEqual, checkIsNestedControl, checkIsNestedInteraction, checkIsOnlyReactor, checkIsPlainObject, checkIsSameBytes, checkIsSameValueZero, checkIsServer, checkIsSourceAllowed, checkIsUnsafeUrlCharacter, checkIsWhitespaceCharacter, chunk, chunkBySerializedSize, clonePlainDeep, collectText, contentType, createEvictingPromiseCache, createExponentialBackoff, createHistory, createItemEntityTypeSchema, createNormalizedStringSchema, createUniqueArraySchema, css, dedent, downloadFile, downloadUrl, encodeUrlSubDelimiters, englishProfaneWords, escapeAttribute, escapeCharacters, escapeText, exhaustiveGuard, extension, filterAttributes, filterClasses, filterHtml, filterSrcset, filterStyle, getChildNodes, getDecodedUriComponent, getEndOfDay, getEnumerableKeys, getExponentialBackoffDelayMs, getExtensionMimeTypeMap, getMediaType, getMentions, getMimeScore, getMimeTypeEntryMap, getOrCreate, getPropertyNames, getRawData, getRequestError, getResult, getResultAsync, getSynchronizedFunction, getTagPolicy, getTransactionDone, getTransactionObjectStore, getUpdatedUserIds, getWindowLog, getZonedDateTime, hrtime, html, jsonDateParse, lookup, matchesClassPattern, mergeObjectsStrict, noop, normalizeString, now, omitDeep, openDatabase, parseArrayIndex, parseJsonPointer, pushHistory, readJsonValue, redoHistory, removeJsonValue, renderNode, reportFinalizerError, requestToPromise, sanitizeHtml, sanitizeTextHtml, settleAll, sleep, streamToText, takeOne, takeRandom, toAppError, toJsonPointer, toKebabCase, toRawDeep, truncate, uncapitalize, undoHistory, updateJsonValue, waitForSynchronizedFunctions, withFinalizer, withFinalizerAsync, wrapDatabase, wrapIndex, wrapObjectStore, wrapTransaction, wrapUpgradeDatabase, wrapUpgradeTransaction };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@esposter/shared",
3
- "version": "4.3.3",
3
+ "version": "4.4.0",
4
4
  "description": "A library that contains shared typescript code.",
5
5
  "keywords": [
6
6
  "error-handling",
@@ -43,7 +43,7 @@
43
43
  "lint": "eslint .",
44
44
  "lint:fix": "eslint --fix .",
45
45
  "test": "vitest",
46
- "typecheck": "tsc"
46
+ "typecheck": "tsrs --fast"
47
47
  },
48
48
  "dependencies": {
49
49
  "mime-db": "^1.54.0",
@@ -53,11 +53,11 @@
53
53
  },
54
54
  "devDependencies": {
55
55
  "@azure/data-tables": "^13.3.2",
56
- "@esposter/configuration": "4.3.3",
57
- "@types/node": "^26.6.5",
58
- "fake-indexeddb": "^6.2.6",
56
+ "@esposter/configuration": "4.4.0",
57
+ "@types/node": "^26.6.6",
58
+ "fake-indexeddb": "^6.2.7",
59
59
  "happy-dom": "^21.0.4",
60
- "tsdown": "^0.23.1",
60
+ "tsdown": "0.23.2",
61
61
  "type-fest": "^5.10.0",
62
62
  "vue": "^3.5.44"
63
63
  },
@@ -67,5 +67,5 @@
67
67
  "inlinedDependencies": {
68
68
  "type-fest": "5.10.0"
69
69
  },
70
- "gitHead": "955bbc130ad9b28877fd4ae8efc73a045141ce01"
70
+ "gitHead": "e1a416c4b91da69cc2aa259b50a4d656f204d272"
71
71
  }