@oscarpalmer/atoms 0.5.2 → 0.6.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/dist/js/atoms.js +90 -15
- package/package.json +10 -31
- package/src/js/string.ts +5 -3
- package/src/js/value.ts +148 -8
- package/types/value.d.ts +23 -3
package/dist/js/atoms.js
CHANGED
|
@@ -1,35 +1,110 @@
|
|
|
1
|
+
// src/js/string.ts
|
|
2
|
+
function createUuid() {
|
|
3
|
+
return uuidTemplate.replace(/[018]/g, (substring) => (substring ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> substring / 4).toString(16));
|
|
4
|
+
}
|
|
5
|
+
function getString(value) {
|
|
6
|
+
return typeof value === "string" ? value : String(value);
|
|
7
|
+
}
|
|
8
|
+
function isNullableOrWhitespace(value) {
|
|
9
|
+
return value === undefined || value === null || getString(value).trim().length === 0;
|
|
10
|
+
}
|
|
11
|
+
var uuidTemplate = "10000000-1000-4000-8000-100000000000";
|
|
1
12
|
// src/js/value.ts
|
|
13
|
+
var _getValue = function(data, key) {
|
|
14
|
+
if (typeof data !== "object" || data === null || badProperties.has(key)) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (data instanceof Map) {
|
|
18
|
+
return data.get(key);
|
|
19
|
+
}
|
|
20
|
+
if (data instanceof Set) {
|
|
21
|
+
return Array.from(data)[key];
|
|
22
|
+
}
|
|
23
|
+
return data[key];
|
|
24
|
+
};
|
|
25
|
+
var _setValue = function(data, key, value) {
|
|
26
|
+
if (typeof data !== "object" || data === null || badProperties.has(key)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (data instanceof Map) {
|
|
30
|
+
data.set(key, value);
|
|
31
|
+
} else if (data instanceof Set) {
|
|
32
|
+
_setValueInSet(data, key, value);
|
|
33
|
+
} else {
|
|
34
|
+
data[key] = value;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var _setValueInSet = function(data, key, value) {
|
|
38
|
+
const index = numberExpression.test(key) ? Number.parseInt(key, 10) : -1;
|
|
39
|
+
if (index === -1 || index >= data.size) {
|
|
40
|
+
data.add(value);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const array = Array.from(data);
|
|
44
|
+
array.splice(index, 1, value);
|
|
45
|
+
data.clear();
|
|
46
|
+
const { length } = array;
|
|
47
|
+
let position = Number(length);
|
|
48
|
+
while (position--) {
|
|
49
|
+
data.add(array[length - position - 1]);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
2
52
|
function getValue(data, key) {
|
|
3
53
|
if (typeof data !== "object" || data === null || isNullableOrWhitespace(key)) {
|
|
4
54
|
return;
|
|
5
55
|
}
|
|
6
|
-
const parts = getString(key).split(".");
|
|
7
|
-
|
|
8
|
-
let index = 0;
|
|
56
|
+
const parts = getString(key).split(".").reverse();
|
|
57
|
+
let position = parts.length;
|
|
9
58
|
let value = data;
|
|
10
|
-
while (
|
|
11
|
-
value = value
|
|
59
|
+
while (position--) {
|
|
60
|
+
value = _getValue(value, parts[position]);
|
|
61
|
+
if (value === undefined) {
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
12
64
|
}
|
|
13
65
|
return value;
|
|
14
66
|
}
|
|
67
|
+
function isArrayOrObject(value) {
|
|
68
|
+
return constructors.has(value?.constructor?.name);
|
|
69
|
+
}
|
|
15
70
|
function isNullable(value) {
|
|
16
71
|
return value === undefined || value === null;
|
|
17
72
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
function createUuid() {
|
|
21
|
-
return uuidTemplate.replace(/[018]/g, (substring) => (substring ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> substring / 4).toString(16));
|
|
22
|
-
}
|
|
23
|
-
function getString(value2) {
|
|
24
|
-
return typeof value2 === "string" ? value2 : String(value2);
|
|
73
|
+
function isObject(value) {
|
|
74
|
+
return value?.constructor?.name === objectConstructor;
|
|
25
75
|
}
|
|
26
|
-
function
|
|
27
|
-
|
|
76
|
+
function setValue(data, key, value) {
|
|
77
|
+
if (typeof data !== "object" || data === null || isNullableOrWhitespace(key)) {
|
|
78
|
+
return data;
|
|
79
|
+
}
|
|
80
|
+
const parts = getString(key).split(".").reverse();
|
|
81
|
+
let position = parts.length;
|
|
82
|
+
let target = data;
|
|
83
|
+
while (position--) {
|
|
84
|
+
const key2 = parts[position];
|
|
85
|
+
if (position === 0) {
|
|
86
|
+
_setValue(target, key2, value);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
let next = _getValue(target, key2);
|
|
90
|
+
if (typeof next !== "object" || next === null) {
|
|
91
|
+
next = numberExpression.test(parts[position - 1]) ? [] : {};
|
|
92
|
+
target[key2] = next;
|
|
93
|
+
}
|
|
94
|
+
target = next;
|
|
95
|
+
}
|
|
96
|
+
return data;
|
|
28
97
|
}
|
|
29
|
-
var
|
|
98
|
+
var badProperties = new Set(["__proto__", "constructor", "prototype"]);
|
|
99
|
+
var objectConstructor = "Object";
|
|
100
|
+
var constructors = new Set(["Array", objectConstructor]);
|
|
101
|
+
var numberExpression = /^\d+$/;
|
|
30
102
|
export {
|
|
103
|
+
setValue,
|
|
104
|
+
isObject,
|
|
31
105
|
isNullableOrWhitespace,
|
|
32
106
|
isNullable,
|
|
107
|
+
isArrayOrObject,
|
|
33
108
|
getValue,
|
|
34
109
|
getString,
|
|
35
110
|
createUuid
|
package/package.json
CHANGED
|
@@ -5,13 +5,10 @@
|
|
|
5
5
|
},
|
|
6
6
|
"description": "Sweet little atomic goodies…",
|
|
7
7
|
"devDependencies": {
|
|
8
|
-
"@
|
|
8
|
+
"@biomejs/biome": "^1.4",
|
|
9
9
|
"bun": "^1.0",
|
|
10
|
-
"prettier": "^3.1",
|
|
11
|
-
"rollup": "^4.5",
|
|
12
10
|
"sass": "^1.69",
|
|
13
|
-
"typescript": "^5.3"
|
|
14
|
-
"xo": "^0.56"
|
|
11
|
+
"typescript": "^5.3"
|
|
15
12
|
},
|
|
16
13
|
"exports": {
|
|
17
14
|
".": {
|
|
@@ -30,38 +27,20 @@
|
|
|
30
27
|
"main": "./dist/js/atoms.js",
|
|
31
28
|
"module": "./dist/js/atoms.js",
|
|
32
29
|
"name": "@oscarpalmer/atoms",
|
|
33
|
-
"prettier": {
|
|
34
|
-
"arrowParens": "avoid",
|
|
35
|
-
"bracketSpacing": false,
|
|
36
|
-
"singleQuote": true,
|
|
37
|
-
"switchIndent": true,
|
|
38
|
-
"trailingComma": "all",
|
|
39
|
-
"useTabs": true
|
|
40
|
-
},
|
|
41
30
|
"repository": {
|
|
42
31
|
"type": "git",
|
|
43
32
|
"url": "git+https://github.com/oscarpalmer/atoms.git"
|
|
44
33
|
},
|
|
45
34
|
"scripts": {
|
|
46
|
-
"build": "
|
|
47
|
-
"build:css": "
|
|
48
|
-
"build:js": "
|
|
49
|
-
"test": "
|
|
50
|
-
"types": "
|
|
51
|
-
"watch:css": "
|
|
52
|
-
"watch:js": "
|
|
35
|
+
"build": "bun run build:css && bun run build:js && bun run types",
|
|
36
|
+
"build:css": "bunx sass ./src/css:./dist/css --no-source-map",
|
|
37
|
+
"build:js": "bunx bun build ./src/js/index.ts --outfile ./dist/js/atoms.js",
|
|
38
|
+
"test": "bun test --coverage",
|
|
39
|
+
"types": "bunx tsc -p ./tsconfig.json",
|
|
40
|
+
"watch:css": "bunx sass ./src/css:./dist/css --no-source-map --watch",
|
|
41
|
+
"watch:js": "bun build ./src/js/index.ts --outfile ./dist/js/atoms.js --watch"
|
|
53
42
|
},
|
|
54
43
|
"type": "module",
|
|
55
44
|
"types": "./types/index.d.ts",
|
|
56
|
-
"version": "0.
|
|
57
|
-
"xo": {
|
|
58
|
-
"envs": [
|
|
59
|
-
"browser"
|
|
60
|
-
],
|
|
61
|
-
"prettier": true,
|
|
62
|
-
"rules": {
|
|
63
|
-
"import/extensions": "off",
|
|
64
|
-
"import/no-cycle": "off"
|
|
65
|
-
}
|
|
66
|
-
}
|
|
45
|
+
"version": "0.6.0"
|
|
67
46
|
}
|
package/src/js/string.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import {isNullable} from './value';
|
|
2
|
-
|
|
3
1
|
const uuidTemplate = '10000000-1000-4000-8000-100000000000';
|
|
4
2
|
|
|
5
3
|
/**
|
|
@@ -27,5 +25,9 @@ export function getString(value: unknown): string {
|
|
|
27
25
|
export function isNullableOrWhitespace(
|
|
28
26
|
value: unknown,
|
|
29
27
|
): value is undefined | null | '' {
|
|
30
|
-
return
|
|
28
|
+
return (
|
|
29
|
+
value === undefined ||
|
|
30
|
+
value === null ||
|
|
31
|
+
getString(value).trim().length === 0
|
|
32
|
+
);
|
|
31
33
|
}
|
package/src/js/value.ts
CHANGED
|
@@ -1,10 +1,86 @@
|
|
|
1
1
|
import {getString, isNullableOrWhitespace} from './string';
|
|
2
2
|
|
|
3
|
+
export type ArrayOrObject = unknown[] | GenericObject;
|
|
4
|
+
export type GenericObject = Record<string, unknown>;
|
|
5
|
+
export type Key = number | string;
|
|
6
|
+
export type ValueObject = ArrayOrObject | Map<unknown, unknown> | Set<unknown>;
|
|
7
|
+
|
|
8
|
+
const badProperties = new Set(['__proto__', 'constructor', 'prototype']);
|
|
9
|
+
const objectConstructor = 'Object';
|
|
10
|
+
const constructors = new Set(['Array', objectConstructor]);
|
|
11
|
+
const numberExpression = /^\d+$/;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Internal function to get a value from an object
|
|
15
|
+
*/
|
|
16
|
+
function _getValue(data: ValueObject, key: string): unknown {
|
|
17
|
+
if (typeof data !== 'object' || data === null || badProperties.has(key)) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (data instanceof Map) {
|
|
22
|
+
return data.get(key as never);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (data instanceof Set) {
|
|
26
|
+
return Array.from(data)[key as never];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return data[key as never];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Internal function to set a value in an object
|
|
34
|
+
*/
|
|
35
|
+
function _setValue(data: ValueObject, key: string, value: unknown): void {
|
|
36
|
+
if (typeof data !== 'object' || data === null || badProperties.has(key)) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (data instanceof Map) {
|
|
41
|
+
data.set(key as never, value);
|
|
42
|
+
} else if (data instanceof Set) {
|
|
43
|
+
_setValueInSet(data, key, value);
|
|
44
|
+
} else {
|
|
45
|
+
data[key as never] = value as never;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* - Internal function to set a value in a `Set`
|
|
51
|
+
* - If key is not a valid index or if it is greater than or equal to the length of the set, we simply append it to the set
|
|
52
|
+
* - If the index is less than the size of the set, we convert the set to an array, splice the value into the array, and then convert the array back to a set
|
|
53
|
+
*/
|
|
54
|
+
function _setValueInSet(data: Set<unknown>, key: string, value: unknown): void {
|
|
55
|
+
const index = numberExpression.test(key) ? Number.parseInt(key, 10) : -1;
|
|
56
|
+
|
|
57
|
+
if (index === -1 || index >= data.size) {
|
|
58
|
+
data.add(value);
|
|
59
|
+
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const array = Array.from(data);
|
|
64
|
+
|
|
65
|
+
array.splice(index, 1, value);
|
|
66
|
+
|
|
67
|
+
data.clear();
|
|
68
|
+
|
|
69
|
+
const {length} = array;
|
|
70
|
+
|
|
71
|
+
let position = Number(length);
|
|
72
|
+
|
|
73
|
+
while (position--) {
|
|
74
|
+
data.add(array[length - position - 1]);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
3
78
|
/**
|
|
4
|
-
* - Get the value from an object using a path
|
|
5
|
-
* - You can retrieve a nested value by using
|
|
79
|
+
* - Get the value from an object using a key path
|
|
80
|
+
* - You can retrieve a nested value by using dot notation, e.g., `foo.bar.baz`
|
|
81
|
+
* - Returns `undefined` if the value is not found
|
|
6
82
|
*/
|
|
7
|
-
export function getValue(data:
|
|
83
|
+
export function getValue(data: ValueObject, key: Key): unknown {
|
|
8
84
|
if (
|
|
9
85
|
typeof data !== 'object' ||
|
|
10
86
|
data === null ||
|
|
@@ -13,22 +89,86 @@ export function getValue(data: unknown, key: unknown): unknown {
|
|
|
13
89
|
return undefined;
|
|
14
90
|
}
|
|
15
91
|
|
|
16
|
-
const parts = getString(key).split('.');
|
|
17
|
-
const length = parts.length;
|
|
92
|
+
const parts = getString(key).split('.').reverse();
|
|
18
93
|
|
|
19
|
-
let
|
|
94
|
+
let position = parts.length;
|
|
20
95
|
let value = data;
|
|
21
96
|
|
|
22
|
-
while (
|
|
23
|
-
value = value
|
|
97
|
+
while (position--) {
|
|
98
|
+
value = _getValue(value, parts[position]) as ValueObject;
|
|
99
|
+
|
|
100
|
+
if (value === undefined) {
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
24
103
|
}
|
|
25
104
|
|
|
26
105
|
return value;
|
|
27
106
|
}
|
|
28
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Is the value an array or a generic object?
|
|
110
|
+
*/
|
|
111
|
+
export function isArrayOrObject(value: unknown): value is ArrayOrObject {
|
|
112
|
+
return constructors.has((value as ArrayOrObject)?.constructor?.name);
|
|
113
|
+
}
|
|
114
|
+
|
|
29
115
|
/**
|
|
30
116
|
* Is the value undefined or null?
|
|
31
117
|
*/
|
|
32
118
|
export function isNullable(value: unknown): value is undefined | null {
|
|
33
119
|
return value === undefined || value === null;
|
|
34
120
|
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Is the value a generic object?
|
|
124
|
+
*/
|
|
125
|
+
export function isObject(value: unknown): value is GenericObject {
|
|
126
|
+
return (value as GenericObject)?.constructor?.name === objectConstructor;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* - Set the value in an object using a key path
|
|
131
|
+
* - You can set a nested value by using dot notation, e.g., `foo.bar.baz`
|
|
132
|
+
* - If a part of the path does not exist, it will be created, either as an array or a generic object, depending on the key
|
|
133
|
+
* - Returns the original object
|
|
134
|
+
*/
|
|
135
|
+
export function setValue<Model extends ValueObject>(
|
|
136
|
+
data: Model,
|
|
137
|
+
key: Key,
|
|
138
|
+
value: unknown,
|
|
139
|
+
): Model {
|
|
140
|
+
if (
|
|
141
|
+
typeof data !== 'object' ||
|
|
142
|
+
data === null ||
|
|
143
|
+
isNullableOrWhitespace(key)
|
|
144
|
+
) {
|
|
145
|
+
return data;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const parts = getString(key).split('.').reverse();
|
|
149
|
+
|
|
150
|
+
let position = parts.length;
|
|
151
|
+
let target: ValueObject = data;
|
|
152
|
+
|
|
153
|
+
while (position--) {
|
|
154
|
+
const key = parts[position] as never;
|
|
155
|
+
|
|
156
|
+
if (position === 0) {
|
|
157
|
+
_setValue(target, key, value);
|
|
158
|
+
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let next = _getValue(target, key);
|
|
163
|
+
|
|
164
|
+
if (typeof next !== 'object' || next === null) {
|
|
165
|
+
next = numberExpression.test(parts[position - 1]) ? [] : {};
|
|
166
|
+
|
|
167
|
+
target[key] = next as never;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
target = next as ValueObject;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return data;
|
|
174
|
+
}
|
package/types/value.d.ts
CHANGED
|
@@ -1,9 +1,29 @@
|
|
|
1
|
+
export type ArrayOrObject = unknown[] | GenericObject;
|
|
2
|
+
export type GenericObject = Record<string, unknown>;
|
|
3
|
+
export type Key = number | string;
|
|
4
|
+
export type ValueObject = ArrayOrObject | Map<unknown, unknown> | Set<unknown>;
|
|
1
5
|
/**
|
|
2
|
-
* - Get the value from an object using a path
|
|
3
|
-
* - You can retrieve a nested value by using
|
|
6
|
+
* - Get the value from an object using a key path
|
|
7
|
+
* - You can retrieve a nested value by using dot notation, e.g., `foo.bar.baz`
|
|
8
|
+
* - Returns `undefined` if the value is not found
|
|
4
9
|
*/
|
|
5
|
-
export declare function getValue(data:
|
|
10
|
+
export declare function getValue(data: ValueObject, key: Key): unknown;
|
|
11
|
+
/**
|
|
12
|
+
* Is the value an array or a generic object?
|
|
13
|
+
*/
|
|
14
|
+
export declare function isArrayOrObject(value: unknown): value is ArrayOrObject;
|
|
6
15
|
/**
|
|
7
16
|
* Is the value undefined or null?
|
|
8
17
|
*/
|
|
9
18
|
export declare function isNullable(value: unknown): value is undefined | null;
|
|
19
|
+
/**
|
|
20
|
+
* Is the value a generic object?
|
|
21
|
+
*/
|
|
22
|
+
export declare function isObject(value: unknown): value is GenericObject;
|
|
23
|
+
/**
|
|
24
|
+
* - Set the value in an object using a key path
|
|
25
|
+
* - You can set a nested value by using dot notation, e.g., `foo.bar.baz`
|
|
26
|
+
* - If a part of the path does not exist, it will be created, either as an array or a generic object, depending on the key
|
|
27
|
+
* - Returns the original object
|
|
28
|
+
*/
|
|
29
|
+
export declare function setValue<Model extends ValueObject>(data: Model, key: Key, value: unknown): Model;
|