@dxos/util 0.4.9 → 0.4.10-main.068c3d8

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.
@@ -0,0 +1,50 @@
1
+ /// <reference types="node" />
2
+ import { inspect } from 'node:util';
3
+ export declare const symbolSingleton: unique symbol;
4
+ export type SingletonFactory<T> = () => T;
5
+ export declare class SymbolDiKey<T> {
6
+ symbol: symbol;
7
+ constructor(name: string);
8
+ [symbolSingleton]?: SingletonFactory<T>;
9
+ toString(): string;
10
+ [inspect.custom](): string;
11
+ }
12
+ export type ConstructorDiKey<T> = {
13
+ new (...args: any[]): T;
14
+ [symbolSingleton]?: SingletonFactory<T>;
15
+ };
16
+ /**
17
+ * Represents a key for an entry in a DI container.
18
+ * Class constructors can be used as is.
19
+ * Multiple keys can be combined into a single key to represent parameterized types.
20
+ * Composite keys maintain referential equality.
21
+ * Keys can optionally have a singleton factory attached to them.
22
+ */
23
+ export type DiKey<T> = {
24
+ new (...args: any[]): T;
25
+ } | SymbolDiKey<T>;
26
+ export declare const DiKey: {
27
+ /**
28
+ * Needed to ensure referential equality of combined keys.
29
+ *
30
+ * This is a multilevel map of maps, where the maps are also used as keys.
31
+ *
32
+ * Good luck :)
33
+ */
34
+ "__#1@#combinedRegistry": WeakMap<WeakKey, any>;
35
+ define<T>(name: string): DiKey<T>;
36
+ singleton<T_1>(name: string, factory: SingletonFactory<T_1>): DiKey<T_1>;
37
+ stringify(id: DiKey<any>): string;
38
+ /**
39
+ * Create composite keys to represent parameterized types.
40
+ *
41
+ * @example DiKey.combine(A, B, C) => "A<B, C>"
42
+ *
43
+ * Maintains referential equality: `DiKey.combine(A, B) === DiKey.combine(A, B)`
44
+ */
45
+ combine(...ids: DiKey<any>[]): any;
46
+ getSingletonFactory<T_2>(id: DiKey<T_2>): SingletonFactory<T_2> | undefined;
47
+ "__#1@#lookupCombined"(map: WeakMap<any, any>, [first, ...rest]: DiKey<any>[]): WeakMap<any, any>;
48
+ "__#1@#combinedDescription"([first, ...rest]: DiKey<any>[]): string;
49
+ };
50
+ //# sourceMappingURL=di-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"di-key.d.ts","sourceRoot":"","sources":["../../../src/di-key.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,eAAO,MAAM,eAAe,eAAsB,CAAC;AAEnD,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC;AAE1C,qBAAa,WAAW,CAAC,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;gBAEH,IAAI,EAAE,MAAM;IAIjB,CAAC,eAAe,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAa;IAE3D,QAAQ;IAIR,CAAC,OAAO,CAAC,MAAM,CAAC;CAGjB;AAED,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI;IAChC,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IAExB,CAAC,eAAe,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;CACzC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,KAAK,CAAC,CAAC,IACf;IACE,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;CACzB,GACD,WAAW,CAAC,CAAC,CAAC,CAAC;AAEnB,eAAO,MAAM,KAAK;IAChB;;;;;;OAMG;;oBAKa,MAAM;yBAIH,MAAM;kBAMX,MAAM,GAAG,CAAC,GAAG,MAAM;IAWjC;;;;;;OAMG;oBACa,MAAM,GAAG,CAAC,EAAE;;gCASP,QAAQ,GAAG,EAAE,GAAG,CAAC,oBAAoB,MAAM,GAAG,CAAC,EAAE,GAAG,QAAQ,GAAG,EAAE,GAAG,CAAC;kDASnD,MAAM,GAAG,CAAC,EAAE;CAGjD,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=di-key.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"di-key.test.d.ts","sourceRoot":"","sources":["../../../src/di-key.test.ts"],"names":[],"mappings":""}
@@ -1,3 +1,6 @@
1
+ export type JsonValue = string | number | boolean | null | JsonValue[] | {
2
+ [key: string]: JsonValue;
3
+ };
1
4
  /**
2
5
  * JSON.stringify replacer.
3
6
  */
@@ -6,6 +9,10 @@ export declare function jsonReplacer(this: any, key: string, value: any): any;
6
9
  * Recursively converts an object into a JSON-compatible object.
7
10
  */
8
11
  export declare const jsonify: (value: any, depth?: number, visitedObjects?: WeakSet<any>) => any;
12
+ /**
13
+ * Recursively converts an object into a JSON-compatible object appropriate for logging.
14
+ */
15
+ export declare const jsonlogify: (value: any, depth?: number, visitedObjects?: WeakSet<any>) => any;
9
16
  export type JsonKeyOptions = {
10
17
  truncate?: boolean;
11
18
  humanize?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../../src/json.ts"],"names":[],"mappings":"AAaA;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,GAAG,CAqBpE;AAED;;GAEG;AACH,eAAO,MAAM,OAAO,UAAW,GAAG,oDAAmD,GA+BpF,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,eAAO,MAAM,eAAe,aAChB,cAAc,WAClB,MAAM,SAAS,GAAG,QAUvB,CAAC"}
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../../src/json.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,EAAE,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAKtG;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,GAAG,CAqBpE;AAED;;GAEG;AACH,eAAO,MAAM,OAAO,UAAW,GAAG,oDAAmD,GA+BpF,CAAC;AAEF;;GAEG;AAGH,eAAO,MAAM,UAAU,UAAW,GAAG,oDAAmD,GAiCvF,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,eAAO,MAAM,eAAe,aAChB,cAAc,WAClB,MAAM,SAAS,GAAG,QAUvB,CAAC"}
@@ -1,5 +1,10 @@
1
+ interface IMap<K, V> {
2
+ get(key: K): V | undefined;
3
+ set(key: K, value: V): this;
4
+ }
1
5
  /**
2
6
  * Get or set map value.
3
7
  */
4
- export declare const defaultMap: <K, V>(map: Map<K, V>, key: K, def: V | (() => V)) => V;
8
+ export declare const defaultMap: <K, V>(map: IMap<K, V>, key: K, def: V | (() => V)) => V;
9
+ export {};
5
10
  //# sourceMappingURL=map.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"map.d.ts","sourceRoot":"","sources":["../../../src/map.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,eAAO,MAAM,UAAU,yDAQtB,CAAC"}
1
+ {"version":3,"file":"map.d.ts","sourceRoot":"","sources":["../../../src/map.ts"],"names":[],"mappings":"AAIA,UAAU,IAAI,CAAC,CAAC,EAAE,CAAC;IACjB,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;IAC3B,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;CAC7B;AAED;;GAEG;AACH,eAAO,MAAM,UAAU,0DAQtB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/util",
3
- "version": "0.4.9",
3
+ "version": "0.4.10-main.068c3d8",
4
4
  "description": "Temporary bucket for misc functions, which should graduate into separate packages.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -16,13 +16,13 @@
16
16
  "src"
17
17
  ],
18
18
  "dependencies": {
19
- "@dxos/debug": "0.4.9",
20
- "@dxos/invariant": "0.4.9",
21
- "@dxos/keys": "0.4.9",
22
- "@dxos/node-std": "0.4.9"
19
+ "@dxos/invariant": "0.4.10-main.068c3d8",
20
+ "@dxos/debug": "0.4.10-main.068c3d8",
21
+ "@dxos/keys": "0.4.10-main.068c3d8",
22
+ "@dxos/node-std": "0.4.10-main.068c3d8"
23
23
  },
24
24
  "devDependencies": {
25
- "@dxos/crypto": "0.4.9"
25
+ "@dxos/crypto": "0.4.10-main.068c3d8"
26
26
  },
27
27
  "publishConfig": {
28
28
  "access": "public"
@@ -0,0 +1,32 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { test } from 'vitest';
6
+
7
+ import { DiContainer } from './di-container';
8
+ import { DiKey } from './di-key';
9
+
10
+ test('DiContainer', ({ expect }) => {
11
+ const A = DiKey.define<string>('A');
12
+ class B {}
13
+
14
+ const generic = DiKey.define('Generic');
15
+ type Generic<T> = { foo: T };
16
+ const Generic = <T>(param: DiKey<T>): DiKey<Generic<T>> => DiKey.combine(generic, param);
17
+
18
+ const Singleton = DiKey.singleton('Singleton', () => 'singleton');
19
+
20
+ const b = new B();
21
+ const container = new DiContainer()
22
+ .provide(B, b)
23
+ .provide(Generic(A), {
24
+ foo: 'Generic A',
25
+ })
26
+ .provide(Generic(B), { foo: b });
27
+
28
+ expect(container.get(B)).toBe(b);
29
+ expect(container.get(Generic(A)).foo).toBe('Generic A');
30
+ expect(container.get(Generic(B)).foo).toBe(b);
31
+ expect(container.get(Singleton)).toBe('singleton');
32
+ });
@@ -0,0 +1,44 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { DiKey } from './di-key';
6
+
7
+ export class DiContainer {
8
+ #entries = new Map<DiKey<any>, any>();
9
+
10
+ get<T>(key: DiKey<T>): T {
11
+ const value = this.#entries.get(key);
12
+ if (value === undefined) {
13
+ const factory = DiKey.getSingletonFactory(key);
14
+ if (factory != null) {
15
+ const singletonValue = factory();
16
+ this.#entries.set(key, singletonValue);
17
+ return singletonValue;
18
+ }
19
+
20
+ throw new Error(`Key ${DiKey.stringify(key)} does not exist in the container`);
21
+ }
22
+
23
+ return value;
24
+ }
25
+
26
+ provide<T>(key: DiKey<T>, value: T): this {
27
+ if (DiKey.getSingletonFactory(key) != null) {
28
+ throw new Error(`Key ${DiKey.stringify(key)} is a singleton key and cannot be provided directly`);
29
+ }
30
+ if (this.#entries.has(key)) {
31
+ throw new Error(`Key ${DiKey.stringify(key)} already exists in the container`);
32
+ }
33
+
34
+ this.#entries.set(key, value);
35
+
36
+ return this;
37
+ }
38
+
39
+ delete<T>(key: DiKey<T>): this {
40
+ this.#entries.delete(key);
41
+
42
+ return this;
43
+ }
44
+ }
@@ -0,0 +1,16 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { test } from 'vitest';
6
+
7
+ import { DiKey } from './di-key';
8
+
9
+ test('DiKey', ({ expect }) => {
10
+ const A = DiKey.define('A');
11
+ class B {}
12
+
13
+ // eslint-disable-next-line no-self-compare
14
+ expect(DiKey.combine(A, B) === DiKey.combine(A, B)).toBeTruthy();
15
+ expect(DiKey.stringify(DiKey.combine(A, B))).toEqual('A<B>');
16
+ });
package/src/di-key.ts ADDED
@@ -0,0 +1,111 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { inspect } from 'node:util';
6
+
7
+ import { defaultMap } from './map';
8
+
9
+ export const symbolSingleton = Symbol('singleton');
10
+
11
+ export type SingletonFactory<T> = () => T;
12
+
13
+ export class SymbolDiKey<T> {
14
+ symbol: symbol;
15
+
16
+ constructor(name: string) {
17
+ this.symbol = Symbol(name);
18
+ }
19
+
20
+ public [symbolSingleton]?: SingletonFactory<T> = undefined;
21
+
22
+ toString() {
23
+ return String(this.symbol).slice(7, -1);
24
+ }
25
+
26
+ [inspect.custom]() {
27
+ return this.toString();
28
+ }
29
+ }
30
+
31
+ export type ConstructorDiKey<T> = {
32
+ new (...args: any[]): T;
33
+
34
+ [symbolSingleton]?: SingletonFactory<T>;
35
+ };
36
+
37
+ /**
38
+ * Represents a key for an entry in a DI container.
39
+ * Class constructors can be used as is.
40
+ * Multiple keys can be combined into a single key to represent parameterized types.
41
+ * Composite keys maintain referential equality.
42
+ * Keys can optionally have a singleton factory attached to them.
43
+ */
44
+ export type DiKey<T> =
45
+ | {
46
+ new (...args: any[]): T;
47
+ }
48
+ | SymbolDiKey<T>;
49
+
50
+ export const DiKey = new (class DiKeyConstructor {
51
+ /**
52
+ * Needed to ensure referential equality of combined keys.
53
+ *
54
+ * This is a multilevel map of maps, where the maps are also used as keys.
55
+ *
56
+ * Good luck :)
57
+ */
58
+ // TODO(dmaretskyi): Disable private members lowering for dev env.
59
+ // TODO(dmaretskyi): Could be a weak map after a NodeJS upgrade.
60
+ #combinedRegistry = new WeakMap();
61
+
62
+ define<T>(name: string): DiKey<T> {
63
+ return new SymbolDiKey(name);
64
+ }
65
+
66
+ singleton<T>(name: string, factory: SingletonFactory<T>): DiKey<T> {
67
+ const id = new SymbolDiKey(name);
68
+ id[symbolSingleton] = factory;
69
+ return id as any;
70
+ }
71
+
72
+ stringify(id: DiKey<any>): string {
73
+ switch (typeof id) {
74
+ case 'symbol':
75
+ return String(id).slice(7, -1);
76
+ case 'function':
77
+ return id.name;
78
+ default:
79
+ return String(id);
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Create composite keys to represent parameterized types.
85
+ *
86
+ * @example DiKey.combine(A, B, C) => "A<B, C>"
87
+ *
88
+ * Maintains referential equality: `DiKey.combine(A, B) === DiKey.combine(A, B)`
89
+ */
90
+ combine(...ids: DiKey<any>[]) {
91
+ const map = this.#lookupCombined(this.#combinedRegistry, ids);
92
+ return defaultMap(this.#combinedRegistry, map, () => this.#combinedDescription(ids));
93
+ }
94
+
95
+ getSingletonFactory<T>(id: DiKey<T>): SingletonFactory<T> | undefined {
96
+ return (id as any)[symbolSingleton];
97
+ }
98
+
99
+ #lookupCombined(map: WeakMap<any, any>, [first, ...rest]: DiKey<any>[]): WeakMap<any, any> {
100
+ const value = defaultMap(map, first, () => new Map());
101
+ if (rest.length > 0) {
102
+ return this.#lookupCombined(value, rest);
103
+ } else {
104
+ return value;
105
+ }
106
+ }
107
+
108
+ #combinedDescription([first, ...rest]: DiKey<any>[]) {
109
+ return `${this.stringify(first)}<${rest.map(this.stringify).join(', ')}>`;
110
+ }
111
+ })();
package/src/json.ts CHANGED
@@ -9,7 +9,10 @@ import { PublicKey } from '@dxos/keys';
9
9
  import { humanize } from './human-hash';
10
10
  import { arrayToBuffer } from './uint8array';
11
11
 
12
+ export type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
13
+
12
14
  const MAX_DEPTH = 5;
15
+ const LOG_MAX_DEPTH = 7;
13
16
 
14
17
  /**
15
18
  * JSON.stringify replacer.
@@ -73,6 +76,46 @@ export const jsonify = (value: any, depth = 0, visitedObjects = new WeakSet<any>
73
76
  }
74
77
  };
75
78
 
79
+ /**
80
+ * Recursively converts an object into a JSON-compatible object appropriate for logging.
81
+ */
82
+
83
+ // TODO(nf): use util.inspect/[util.inspect.custom] instead?
84
+ export const jsonlogify = (value: any, depth = 0, visitedObjects = new WeakSet<any>()): any => {
85
+ if (depth > LOG_MAX_DEPTH) {
86
+ return null;
87
+ } else if (typeof value === 'function') {
88
+ return null;
89
+ } else if (typeof value === 'object' && value !== null) {
90
+ if (visitedObjects.has(value)) {
91
+ return null;
92
+ }
93
+ visitedObjects.add(value);
94
+
95
+ try {
96
+ if (value instanceof Uint8Array) {
97
+ return arrayToBuffer(value).toString('hex');
98
+ } else if (Array.isArray(value)) {
99
+ return value.map((x) => jsonlogify(x, depth + 1, visitedObjects));
100
+ } else if (typeof value.toJSONL === 'function') {
101
+ return value.toJSONL();
102
+ } else if (typeof value.toJSON === 'function') {
103
+ return value.toJSON();
104
+ } else {
105
+ const res: any = {};
106
+ for (const key of Object.keys(value)) {
107
+ res[key] = jsonlogify(value[key], depth + 1, visitedObjects);
108
+ }
109
+ return res;
110
+ }
111
+ } finally {
112
+ visitedObjects.delete(value);
113
+ }
114
+ } else {
115
+ return value;
116
+ }
117
+ };
118
+
76
119
  export type JsonKeyOptions = {
77
120
  truncate?: boolean;
78
121
  humanize?: boolean;
package/src/map.ts CHANGED
@@ -2,10 +2,15 @@
2
2
  // Copyright 2020 DXOS.org
3
3
  //
4
4
 
5
+ interface IMap<K, V> {
6
+ get(key: K): V | undefined;
7
+ set(key: K, value: V): this;
8
+ }
9
+
5
10
  /**
6
11
  * Get or set map value.
7
12
  */
8
- export const defaultMap = <K, V>(map: Map<K, V>, key: K, def: V | (() => V)) => {
13
+ export const defaultMap = <K, V>(map: IMap<K, V>, key: K, def: V | (() => V)) => {
9
14
  let value = map.get(key);
10
15
  if (value === undefined) {
11
16
  value = typeof def === 'function' ? (def as () => V)() : def;