@dxos/util 0.7.4 → 0.7.5-labs.071a3e2
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/dist/lib/browser/index.mjs +108 -46
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +106 -44
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +108 -46
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/array-to-hex.d.ts +1 -1
- package/dist/types/src/array-to-hex.d.ts.map +1 -1
- package/dist/types/src/array.d.ts +12 -0
- package/dist/types/src/array.d.ts.map +1 -1
- package/dist/types/src/bitfield.d.ts +1 -1
- package/dist/types/src/deep.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +6 -4
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/no-infer.d.ts +2 -0
- package/dist/types/src/no-infer.d.ts.map +1 -0
- package/dist/types/src/position.d.ts +15 -0
- package/dist/types/src/position.d.ts.map +1 -0
- package/dist/types/src/position.test.d.ts +2 -0
- package/dist/types/src/position.test.d.ts.map +1 -0
- package/dist/types/src/safe-parse.d.ts +7 -0
- package/dist/types/src/safe-parse.d.ts.map +1 -0
- package/dist/types/src/string.d.ts +5 -0
- package/dist/types/src/string.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +22 -4
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +6 -6
- package/src/array-to-hex.ts +1 -1
- package/src/array.test.ts +83 -1
- package/src/array.ts +29 -0
- package/src/deep.ts +5 -3
- package/src/index.ts +7 -5
- package/src/instance-id.ts +1 -1
- package/src/no-infer.ts +5 -0
- package/src/position.test.ts +98 -0
- package/src/position.ts +30 -0
- package/src/safe-parse.ts +32 -0
- package/src/string.ts +14 -0
- package/src/types.ts +31 -13
- package/dist/types/src/safe-parse-json.d.ts +0 -5
- package/dist/types/src/safe-parse-json.d.ts.map +0 -1
- package/src/safe-parse-json.ts +0 -15
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
|
|
7
|
+
import { byPosition, type Position } from './position';
|
|
8
|
+
|
|
9
|
+
type TestItem = {
|
|
10
|
+
id: number;
|
|
11
|
+
position?: Position;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
describe('byPosition', () => {
|
|
15
|
+
it('should keep items with same position in their original order', () => {
|
|
16
|
+
const items: TestItem[] = [
|
|
17
|
+
{ id: 1, position: 'static' },
|
|
18
|
+
{ id: 2, position: 'static' },
|
|
19
|
+
{ id: 3, position: 'hoist' },
|
|
20
|
+
{ id: 4, position: 'hoist' },
|
|
21
|
+
{ id: 5, position: 'fallback' },
|
|
22
|
+
{ id: 6, position: 'fallback' },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const sorted = [...items].sort(byPosition);
|
|
26
|
+
|
|
27
|
+
// Check that items with the same position maintain relative order
|
|
28
|
+
expect(sorted.findIndex((item) => item.id === 3)).toBeLessThan(sorted.findIndex((item) => item.id === 4));
|
|
29
|
+
expect(sorted.findIndex((item) => item.id === 1)).toBeLessThan(sorted.findIndex((item) => item.id === 2));
|
|
30
|
+
expect(sorted.findIndex((item) => item.id === 5)).toBeLessThan(sorted.findIndex((item) => item.id === 6));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should place "hoist" items before "static" items', () => {
|
|
34
|
+
const items: TestItem[] = [
|
|
35
|
+
{ id: 1, position: 'static' },
|
|
36
|
+
{ id: 2, position: 'hoist' },
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const sorted = [...items].sort(byPosition);
|
|
40
|
+
expect(sorted[0].position).toBe('hoist');
|
|
41
|
+
expect(sorted[1].position).toBe('static');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should place "fallback" items after "static" items', () => {
|
|
45
|
+
const items: TestItem[] = [
|
|
46
|
+
{ id: 1, position: 'fallback' },
|
|
47
|
+
{ id: 2, position: 'static' },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const sorted = [...items].sort(byPosition);
|
|
51
|
+
expect(sorted[0].position).toBe('static');
|
|
52
|
+
expect(sorted[1].position).toBe('fallback');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should treat items without position as "static"', () => {
|
|
56
|
+
const items: TestItem[] = [{ id: 1 }, { id: 2, position: 'hoist' }, { id: 3, position: 'fallback' }];
|
|
57
|
+
|
|
58
|
+
const sorted = [...items].sort(byPosition);
|
|
59
|
+
expect(sorted[0].position).toBe('hoist');
|
|
60
|
+
expect(sorted[1].position).toBeUndefined();
|
|
61
|
+
expect(sorted[2].position).toBe('fallback');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should correctly sort mixed positions', () => {
|
|
65
|
+
const items: TestItem[] = [
|
|
66
|
+
{ id: 1, position: 'fallback' },
|
|
67
|
+
{ id: 2, position: 'static' },
|
|
68
|
+
{ id: 3, position: 'hoist' },
|
|
69
|
+
{ id: 4 }, // implicit static
|
|
70
|
+
{ id: 5, position: 'hoist' },
|
|
71
|
+
{ id: 6, position: 'fallback' },
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
const sorted = [...items].sort(byPosition);
|
|
75
|
+
|
|
76
|
+
// All hoisted items should come first
|
|
77
|
+
expect(sorted[0].position).toBe('hoist');
|
|
78
|
+
expect(sorted[1].position).toBe('hoist');
|
|
79
|
+
|
|
80
|
+
// Static items (including undefined) should be in the middle
|
|
81
|
+
expect(sorted[2].position).toBe('static');
|
|
82
|
+
expect(sorted[3].position).toBeUndefined();
|
|
83
|
+
|
|
84
|
+
// Fallback items should be last
|
|
85
|
+
expect(sorted[4].position).toBe('fallback');
|
|
86
|
+
expect(sorted[5].position).toBe('fallback');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should handle empty arrays', () => {
|
|
90
|
+
const items: TestItem[] = [];
|
|
91
|
+
expect(() => [...items].sort(byPosition)).not.toThrow();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should handle single item arrays', () => {
|
|
95
|
+
const items: TestItem[] = [{ id: 1, position: 'static' }];
|
|
96
|
+
expect(() => [...items].sort(byPosition)).not.toThrow();
|
|
97
|
+
});
|
|
98
|
+
});
|
package/src/position.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Determines priority order.
|
|
7
|
+
*
|
|
8
|
+
* - `static` - Remain in natural order.
|
|
9
|
+
* - `hoist` - Placed before `static`.
|
|
10
|
+
* - `fallback` - Placed after `static`.
|
|
11
|
+
*/
|
|
12
|
+
export type Position = 'static' | 'hoist' | 'fallback';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Sorting function for sorting by position.
|
|
16
|
+
*/
|
|
17
|
+
export const byPosition = <T extends { position?: Position }>(a: T, b: T) => {
|
|
18
|
+
const aPosition = a.position ?? 'static';
|
|
19
|
+
const bPosition = b.position ?? 'static';
|
|
20
|
+
|
|
21
|
+
if (aPosition === bPosition) {
|
|
22
|
+
return 0;
|
|
23
|
+
} else if (aPosition === 'hoist' || bPosition === 'fallback') {
|
|
24
|
+
return -1;
|
|
25
|
+
} else if (bPosition === 'hoist' || aPosition === 'fallback') {
|
|
26
|
+
return 1;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return 0;
|
|
30
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
export const safeParseInt = (value: string | undefined, defaultValue?: number) => {
|
|
6
|
+
try {
|
|
7
|
+
const n = parseInt(value ?? '');
|
|
8
|
+
return isNaN(n) ? defaultValue : n;
|
|
9
|
+
} catch (err) {
|
|
10
|
+
return defaultValue;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const safeParseFloat = (str: string, defaultValue?: number): number | undefined => {
|
|
15
|
+
try {
|
|
16
|
+
return parseFloat(str);
|
|
17
|
+
} catch {
|
|
18
|
+
return defaultValue ?? 0;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const safeParseJson: {
|
|
23
|
+
<T extends object>(data: string | undefined | null, defaultValue: T): T;
|
|
24
|
+
<T extends object>(data: string | undefined | null): T | undefined;
|
|
25
|
+
} = <T extends object>(data: string | undefined | null, defaultValue?: T) => {
|
|
26
|
+
if (data && data.length > 0) {
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(data);
|
|
29
|
+
} catch (err) {}
|
|
30
|
+
}
|
|
31
|
+
return defaultValue;
|
|
32
|
+
};
|
package/src/string.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Capitalizes the first letter of a string.
|
|
7
|
+
*/
|
|
8
|
+
export const capitalize = (str: string): string => {
|
|
9
|
+
if (str.length === 0) {
|
|
10
|
+
return '';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
14
|
+
};
|
package/src/types.ts
CHANGED
|
@@ -10,7 +10,34 @@ export type MaybeProvider<T, V = void> = T | ((arg: V) => T);
|
|
|
10
10
|
|
|
11
11
|
export type MaybePromise<T> = T | Promise<T>;
|
|
12
12
|
|
|
13
|
-
export type
|
|
13
|
+
export type GuardedType<T> = T extends (value: any) => value is infer R ? R : never;
|
|
14
|
+
|
|
15
|
+
export type DeepReadonly<T> = {
|
|
16
|
+
readonly [P in keyof T]: T[P] extends Record<string, any>
|
|
17
|
+
? DeepReadonly<T[P]>
|
|
18
|
+
: T[P] extends Array<infer U>
|
|
19
|
+
? ReadonlyArray<DeepReadonly<U>>
|
|
20
|
+
: T[P];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type DeepWriteable<T> = { -readonly [K in keyof T]: T[K] extends object ? DeepWriteable<T[K]> : T[K] };
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Simplifies type (copied from effect).
|
|
27
|
+
*/
|
|
28
|
+
export type Simplify<A> = { [K in keyof A]: A[K] } extends infer B ? B : never;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Replace types of specified keys.
|
|
32
|
+
*/
|
|
33
|
+
export type Specialize<T, U> = Simplify<Omit<T, keyof U> & U>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Make specified keys optional.
|
|
37
|
+
*/
|
|
38
|
+
// TODO(burdon): Wrapping with Simplify fails.
|
|
39
|
+
// export type MakeOptional<T, K extends keyof T> = Simplify<Omit<T, K> & Partial<Pick<T, K>>>;
|
|
40
|
+
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
|
14
41
|
|
|
15
42
|
/**
|
|
16
43
|
* All types that evaluate to false when cast to a boolean.
|
|
@@ -27,7 +54,7 @@ export const isNotFalsy = <T>(value: T): value is Exclude<T, Falsy> => !!value;
|
|
|
27
54
|
export const nonNullable = <T>(value: T): value is NonNullable<T> => value !== null && value !== undefined;
|
|
28
55
|
export const isNotNullOrUndefined = <T>(value: T): value is Exclude<T, null | undefined> => value != null;
|
|
29
56
|
// export const isNotNullish = <T>(value: T | null | undefined): value is T => value !== undefined && value !== null;
|
|
30
|
-
export const boolGuard = <T>(value: T | null | undefined): value is T => Boolean(value);
|
|
57
|
+
// export const boolGuard = <T>(value: T | null | undefined): value is T => Boolean(value);
|
|
31
58
|
|
|
32
59
|
// TODO(burdon): Replace use of setTimeout everywhere?
|
|
33
60
|
// Would remove the need to cancel (and associated errors), but would change the operation of the code
|
|
@@ -55,14 +82,14 @@ export const getAsyncProviderValue = <T, V = void>(
|
|
|
55
82
|
/**
|
|
56
83
|
* Remove keys with undefined values.
|
|
57
84
|
*/
|
|
58
|
-
export const
|
|
85
|
+
export const stripUndefined = <T extends { [index: string]: any }>(obj: T): T => {
|
|
59
86
|
if (typeof obj === 'object') {
|
|
60
87
|
Object.keys(obj).forEach((key) => {
|
|
61
88
|
const value = obj[key];
|
|
62
89
|
if (value === undefined) {
|
|
63
90
|
delete obj[key];
|
|
64
91
|
} else if (value !== null && typeof value === 'object') {
|
|
65
|
-
|
|
92
|
+
stripUndefined(value); // TODO(burdon): Test recursion.
|
|
66
93
|
}
|
|
67
94
|
});
|
|
68
95
|
}
|
|
@@ -88,12 +115,3 @@ export const arrayMove = <T>(array: T[], from: number, to: number): Array<T> =>
|
|
|
88
115
|
array.splice(to < 0 ? array.length + to : to, 0, array.splice(from, 1)[0]);
|
|
89
116
|
return array;
|
|
90
117
|
};
|
|
91
|
-
|
|
92
|
-
export const safeParseInt = (value: string | undefined, defaultValue?: number) => {
|
|
93
|
-
try {
|
|
94
|
-
const n = parseInt(value ?? '');
|
|
95
|
-
return isNaN(n) ? defaultValue : n;
|
|
96
|
-
} catch (err) {
|
|
97
|
-
return defaultValue;
|
|
98
|
-
}
|
|
99
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"safe-parse-json.d.ts","sourceRoot":"","sources":["../../../src/safe-parse-json.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,aAAa,EAAE;IAC1B,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EAAE,YAAY,EAAE,CAAC,GAAG,CAAC,CAAC;IACxE,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,SAAS,CAAC;CAQpE,CAAC"}
|
package/src/safe-parse-json.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2024 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
export const safeParseJson: {
|
|
6
|
-
<T extends object>(data: string | undefined | null, defaultValue: T): T;
|
|
7
|
-
<T extends object>(data: string | undefined | null): T | undefined;
|
|
8
|
-
} = <T extends object>(data: string | undefined | null, defaultValue?: T) => {
|
|
9
|
-
if (data) {
|
|
10
|
-
try {
|
|
11
|
-
return JSON.parse(data);
|
|
12
|
-
} catch (err) {}
|
|
13
|
-
}
|
|
14
|
-
return defaultValue;
|
|
15
|
-
};
|