base60-codec 0.1.10 โ 0.2.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 +23 -14
- package/dist/index.cjs +56 -16
- package/dist/index.d.cts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +42 -15
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -27,67 +27,76 @@ Ideal for generating compact, URL-safe IDs with predictable ordering.
|
|
|
27
27
|
|
|
28
28
|
## ๐ Installation
|
|
29
29
|
|
|
30
|
-
```
|
|
30
|
+
```shell
|
|
31
31
|
npm install base60-codec
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
## ๐งฉ Quick Usage
|
|
35
35
|
|
|
36
|
-
```
|
|
37
|
-
import {
|
|
36
|
+
```javascript
|
|
37
|
+
import { encodeUUID, decodeUUID } from "base60-codec";
|
|
38
38
|
|
|
39
39
|
const uuid = "550e8400-e29b-41d4-a716-446655440000";
|
|
40
40
|
|
|
41
41
|
// Encode as 22-char Base60
|
|
42
|
-
const encoded =
|
|
42
|
+
const encoded = encodeUUID(uuid);
|
|
43
43
|
console.log(encoded); // e.g. "09EzBRW... (22 chars)"
|
|
44
44
|
|
|
45
45
|
// Decode back to UUID
|
|
46
|
-
console.log(
|
|
46
|
+
console.log(decodeUUID(encoded));
|
|
47
47
|
// โ "550e8400-e29b-41d4-a716-446655440000"
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
๐ก Tip
|
|
51
|
+
If you prefer grouped APIs, you can also import the base60 namespace:
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
import base60 from "base60-codec";
|
|
55
|
+
|
|
56
|
+
base60.encodeUUID(uuid);
|
|
57
|
+
```
|
|
58
|
+
|
|
50
59
|
## โจ Features
|
|
51
60
|
|
|
52
61
|
### โ
UUID (128-bit) โ 22 chars
|
|
53
62
|
|
|
54
|
-
```
|
|
63
|
+
```javascript
|
|
55
64
|
encodeUUID(uuid: string): string
|
|
56
65
|
decodeUUID(id: Base60String): string
|
|
57
66
|
```
|
|
58
67
|
|
|
59
68
|
### โ
ULID (26 chars Base32) โ 22 chars
|
|
60
69
|
|
|
61
|
-
```
|
|
70
|
+
```javascript
|
|
62
71
|
encodeULID(ulid: string): Base60String
|
|
63
72
|
decodeULID(id: Base60String): string
|
|
64
73
|
```
|
|
65
74
|
|
|
66
75
|
### โ
Int64 โ 11 chars
|
|
67
76
|
|
|
68
|
-
```
|
|
77
|
+
```javascript
|
|
69
78
|
encodeInt64(num: number | bigint): string
|
|
70
79
|
decodeInt64(id: Base60String): bigint
|
|
71
80
|
```
|
|
72
81
|
|
|
73
82
|
### โ
BigInt encoding
|
|
74
83
|
|
|
75
|
-
```
|
|
84
|
+
```javascript
|
|
76
85
|
encodeBigInt(value: bigint, padLength?: number): string
|
|
77
86
|
decodeToBigInt(text: Base60String): bigint
|
|
78
87
|
```
|
|
79
88
|
|
|
80
89
|
### โ
Safe comparison
|
|
81
90
|
|
|
82
|
-
```
|
|
91
|
+
```javascript
|
|
83
92
|
compareAsBigInt(a: Base60String, b: Base60String): -1 | 0 | 1
|
|
84
93
|
```
|
|
85
94
|
|
|
86
95
|
### โ
Type-safe Base60 string guard
|
|
87
96
|
|
|
88
|
-
```
|
|
97
|
+
```javascript
|
|
89
98
|
if (base60.isValidBase60(text)) {
|
|
90
|
-
// text is now typed as Base60String
|
|
99
|
+
// text is now typed as Base60String
|
|
91
100
|
}
|
|
92
101
|
```
|
|
93
102
|
|
|
@@ -107,7 +116,7 @@ if (base60.isValidBase60(text)) {
|
|
|
107
116
|
|
|
108
117
|
Leading zero bytes are dropped:
|
|
109
118
|
|
|
110
|
-
```
|
|
119
|
+
```javascript
|
|
111
120
|
encodeBytes(Uint8Array([0,1,2]))
|
|
112
121
|
โ
|
|
113
122
|
decodeToBytes(...) โ [1,2]
|
|
@@ -119,7 +128,7 @@ UUID / ULID / Int64 are unaffected because they use fixed 16-byte / 8-byte decod
|
|
|
119
128
|
|
|
120
129
|
## ๐งช Testing
|
|
121
130
|
|
|
122
|
-
```
|
|
131
|
+
```shell
|
|
123
132
|
npm test
|
|
124
133
|
```
|
|
125
134
|
|
package/dist/index.cjs
CHANGED
|
@@ -20,8 +20,21 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
base60: () => base60,
|
|
24
|
+
compareAsBigInt: () => compareAsBigInt,
|
|
23
25
|
createBase60Codec: () => createBase60Codec,
|
|
24
|
-
|
|
26
|
+
decodeInt64: () => decodeInt64,
|
|
27
|
+
decodeToBigInt: () => decodeToBigInt,
|
|
28
|
+
decodeToBytes: () => decodeToBytes,
|
|
29
|
+
decodeULID: () => decodeULID,
|
|
30
|
+
decodeUUID: () => decodeUUID,
|
|
31
|
+
default: () => index_default,
|
|
32
|
+
encodeBigInt: () => encodeBigInt,
|
|
33
|
+
encodeBytes: () => encodeBytes,
|
|
34
|
+
encodeInt64: () => encodeInt64,
|
|
35
|
+
encodeULID: () => encodeULID,
|
|
36
|
+
encodeUUID: () => encodeUUID,
|
|
37
|
+
isValidBase60: () => isValidBase60
|
|
25
38
|
});
|
|
26
39
|
module.exports = __toCommonJS(index_exports);
|
|
27
40
|
var FIXED_LEN_UUID = 22;
|
|
@@ -70,7 +83,7 @@ function createBase60Codec() {
|
|
|
70
83
|
}
|
|
71
84
|
return bytes;
|
|
72
85
|
}
|
|
73
|
-
function
|
|
86
|
+
function encodeBigInt2(value, padLength) {
|
|
74
87
|
if (value === 0n) {
|
|
75
88
|
return padLength != null ? leftPad(alphabet[0], padLength) : alphabet[0];
|
|
76
89
|
}
|
|
@@ -83,7 +96,7 @@ function createBase60Codec() {
|
|
|
83
96
|
}
|
|
84
97
|
return padLength != null ? leftPad(out, padLength) : out;
|
|
85
98
|
}
|
|
86
|
-
function
|
|
99
|
+
function decodeToBigInt2(text) {
|
|
87
100
|
let v = 0n;
|
|
88
101
|
for (const ch of text) {
|
|
89
102
|
const d = charToValue.get(ch);
|
|
@@ -97,16 +110,16 @@ function createBase60Codec() {
|
|
|
97
110
|
return {
|
|
98
111
|
alphabet,
|
|
99
112
|
encodeBytes(bytes) {
|
|
100
|
-
return
|
|
113
|
+
return encodeBigInt2(bytesToBigInt(bytes));
|
|
101
114
|
},
|
|
102
115
|
decodeToBytes(text) {
|
|
103
|
-
return bigIntToBytes(
|
|
116
|
+
return bigIntToBytes(decodeToBigInt2(text));
|
|
104
117
|
},
|
|
105
|
-
encodeBigInt,
|
|
106
|
-
decodeToBigInt,
|
|
118
|
+
encodeBigInt: encodeBigInt2,
|
|
119
|
+
decodeToBigInt: decodeToBigInt2,
|
|
107
120
|
encodeInt64(value) {
|
|
108
121
|
return leftPad(
|
|
109
|
-
|
|
122
|
+
encodeBigInt2(BigInt(value)),
|
|
110
123
|
FIXED_LEN_INT64
|
|
111
124
|
);
|
|
112
125
|
},
|
|
@@ -114,7 +127,7 @@ function createBase60Codec() {
|
|
|
114
127
|
if (text.length !== FIXED_LEN_INT64) {
|
|
115
128
|
throw new Error(`Expected ${FIXED_LEN_INT64} chars for Base60 Int64`);
|
|
116
129
|
}
|
|
117
|
-
return
|
|
130
|
+
return decodeToBigInt2(text);
|
|
118
131
|
},
|
|
119
132
|
encodeUUID(uuid) {
|
|
120
133
|
const hex = uuid.replace(/-/g, "");
|
|
@@ -124,12 +137,12 @@ function createBase60Codec() {
|
|
|
124
137
|
bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
|
|
125
138
|
}
|
|
126
139
|
return leftPad(
|
|
127
|
-
|
|
140
|
+
encodeBigInt2(bytesToBigInt(bytes)),
|
|
128
141
|
FIXED_LEN_UUID
|
|
129
142
|
);
|
|
130
143
|
},
|
|
131
144
|
decodeUUID(text) {
|
|
132
|
-
const bytes = bigIntToBytes(
|
|
145
|
+
const bytes = bigIntToBytes(decodeToBigInt2(text), 16);
|
|
133
146
|
const hex = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
134
147
|
return hex.slice(0, 8) + "-" + hex.slice(8, 12) + "-" + hex.slice(12, 16) + "-" + hex.slice(16, 20) + "-" + hex.slice(20);
|
|
135
148
|
},
|
|
@@ -147,14 +160,14 @@ function createBase60Codec() {
|
|
|
147
160
|
if (v === void 0) throw new Error(`Invalid ULID char: ${ch}`);
|
|
148
161
|
value = (value << 5n) + BigInt(v);
|
|
149
162
|
}
|
|
150
|
-
const raw =
|
|
163
|
+
const raw = encodeBigInt2(value);
|
|
151
164
|
return leftPad(raw, FIXED_LEN_ULID);
|
|
152
165
|
},
|
|
153
166
|
decodeULID(text) {
|
|
154
167
|
if (text.length !== 22) {
|
|
155
168
|
throw new Error("Expected 22-char Base60 ULID");
|
|
156
169
|
}
|
|
157
|
-
const value =
|
|
170
|
+
const value = decodeToBigInt2(text);
|
|
158
171
|
const bytes = bigIntToBytes(value, 16);
|
|
159
172
|
let bits = "";
|
|
160
173
|
for (const b of bytes) {
|
|
@@ -169,8 +182,8 @@ function createBase60Codec() {
|
|
|
169
182
|
return ulid;
|
|
170
183
|
},
|
|
171
184
|
compareAsBigInt(a, b) {
|
|
172
|
-
const ai =
|
|
173
|
-
const bi =
|
|
185
|
+
const ai = decodeToBigInt2(a);
|
|
186
|
+
const bi = decodeToBigInt2(b);
|
|
174
187
|
if (ai < bi) return -1;
|
|
175
188
|
if (ai > bi) return 1;
|
|
176
189
|
return 0;
|
|
@@ -184,8 +197,35 @@ function createBase60Codec() {
|
|
|
184
197
|
};
|
|
185
198
|
}
|
|
186
199
|
var base60 = createBase60Codec();
|
|
200
|
+
var {
|
|
201
|
+
encodeBytes,
|
|
202
|
+
decodeToBytes,
|
|
203
|
+
encodeBigInt,
|
|
204
|
+
decodeToBigInt,
|
|
205
|
+
encodeInt64,
|
|
206
|
+
decodeInt64,
|
|
207
|
+
encodeUUID,
|
|
208
|
+
decodeUUID,
|
|
209
|
+
encodeULID,
|
|
210
|
+
decodeULID,
|
|
211
|
+
compareAsBigInt,
|
|
212
|
+
isValidBase60
|
|
213
|
+
} = base60;
|
|
187
214
|
var index_default = base60;
|
|
188
215
|
// Annotate the CommonJS export names for ESM import in node:
|
|
189
216
|
0 && (module.exports = {
|
|
190
|
-
|
|
217
|
+
base60,
|
|
218
|
+
compareAsBigInt,
|
|
219
|
+
createBase60Codec,
|
|
220
|
+
decodeInt64,
|
|
221
|
+
decodeToBigInt,
|
|
222
|
+
decodeToBytes,
|
|
223
|
+
decodeULID,
|
|
224
|
+
decodeUUID,
|
|
225
|
+
encodeBigInt,
|
|
226
|
+
encodeBytes,
|
|
227
|
+
encodeInt64,
|
|
228
|
+
encodeULID,
|
|
229
|
+
encodeUUID,
|
|
230
|
+
isValidBase60
|
|
191
231
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -18,5 +18,17 @@ interface Base60Codec {
|
|
|
18
18
|
}
|
|
19
19
|
declare function createBase60Codec(): Base60Codec;
|
|
20
20
|
declare const base60: Base60Codec;
|
|
21
|
+
declare const encodeBytes: (bytes: Uint8Array) => string;
|
|
22
|
+
declare const decodeToBytes: (text: Base60String) => Uint8Array;
|
|
23
|
+
declare const encodeBigInt: (value: bigint, padLength?: number) => Base60String;
|
|
24
|
+
declare const decodeToBigInt: (text: Base60String) => bigint;
|
|
25
|
+
declare const encodeInt64: (value: number | bigint) => Base60String;
|
|
26
|
+
declare const decodeInt64: (text: Base60String) => bigint;
|
|
27
|
+
declare const encodeUUID: (uuid: string) => Base60String;
|
|
28
|
+
declare const decodeUUID: (text: Base60String) => string;
|
|
29
|
+
declare const encodeULID: (ulid: string) => Base60String;
|
|
30
|
+
declare const decodeULID: (text: Base60String) => string;
|
|
31
|
+
declare const compareAsBigInt: (a: Base60String, b: Base60String) => number;
|
|
32
|
+
declare const isValidBase60: (text: string) => text is Base60String;
|
|
21
33
|
|
|
22
|
-
export { type Base60Codec, type Base60String, createBase60Codec, base60 as default };
|
|
34
|
+
export { type Base60Codec, type Base60String, base60, compareAsBigInt, createBase60Codec, decodeInt64, decodeToBigInt, decodeToBytes, decodeULID, decodeUUID, base60 as default, encodeBigInt, encodeBytes, encodeInt64, encodeULID, encodeUUID, isValidBase60 };
|
package/dist/index.d.ts
CHANGED
|
@@ -18,5 +18,17 @@ interface Base60Codec {
|
|
|
18
18
|
}
|
|
19
19
|
declare function createBase60Codec(): Base60Codec;
|
|
20
20
|
declare const base60: Base60Codec;
|
|
21
|
+
declare const encodeBytes: (bytes: Uint8Array) => string;
|
|
22
|
+
declare const decodeToBytes: (text: Base60String) => Uint8Array;
|
|
23
|
+
declare const encodeBigInt: (value: bigint, padLength?: number) => Base60String;
|
|
24
|
+
declare const decodeToBigInt: (text: Base60String) => bigint;
|
|
25
|
+
declare const encodeInt64: (value: number | bigint) => Base60String;
|
|
26
|
+
declare const decodeInt64: (text: Base60String) => bigint;
|
|
27
|
+
declare const encodeUUID: (uuid: string) => Base60String;
|
|
28
|
+
declare const decodeUUID: (text: Base60String) => string;
|
|
29
|
+
declare const encodeULID: (ulid: string) => Base60String;
|
|
30
|
+
declare const decodeULID: (text: Base60String) => string;
|
|
31
|
+
declare const compareAsBigInt: (a: Base60String, b: Base60String) => number;
|
|
32
|
+
declare const isValidBase60: (text: string) => text is Base60String;
|
|
21
33
|
|
|
22
|
-
export { type Base60Codec, type Base60String, createBase60Codec, base60 as default };
|
|
34
|
+
export { type Base60Codec, type Base60String, base60, compareAsBigInt, createBase60Codec, decodeInt64, decodeToBigInt, decodeToBytes, decodeULID, decodeUUID, base60 as default, encodeBigInt, encodeBytes, encodeInt64, encodeULID, encodeUUID, isValidBase60 };
|
package/dist/index.js
CHANGED
|
@@ -45,7 +45,7 @@ function createBase60Codec() {
|
|
|
45
45
|
}
|
|
46
46
|
return bytes;
|
|
47
47
|
}
|
|
48
|
-
function
|
|
48
|
+
function encodeBigInt2(value, padLength) {
|
|
49
49
|
if (value === 0n) {
|
|
50
50
|
return padLength != null ? leftPad(alphabet[0], padLength) : alphabet[0];
|
|
51
51
|
}
|
|
@@ -58,7 +58,7 @@ function createBase60Codec() {
|
|
|
58
58
|
}
|
|
59
59
|
return padLength != null ? leftPad(out, padLength) : out;
|
|
60
60
|
}
|
|
61
|
-
function
|
|
61
|
+
function decodeToBigInt2(text) {
|
|
62
62
|
let v = 0n;
|
|
63
63
|
for (const ch of text) {
|
|
64
64
|
const d = charToValue.get(ch);
|
|
@@ -72,16 +72,16 @@ function createBase60Codec() {
|
|
|
72
72
|
return {
|
|
73
73
|
alphabet,
|
|
74
74
|
encodeBytes(bytes) {
|
|
75
|
-
return
|
|
75
|
+
return encodeBigInt2(bytesToBigInt(bytes));
|
|
76
76
|
},
|
|
77
77
|
decodeToBytes(text) {
|
|
78
|
-
return bigIntToBytes(
|
|
78
|
+
return bigIntToBytes(decodeToBigInt2(text));
|
|
79
79
|
},
|
|
80
|
-
encodeBigInt,
|
|
81
|
-
decodeToBigInt,
|
|
80
|
+
encodeBigInt: encodeBigInt2,
|
|
81
|
+
decodeToBigInt: decodeToBigInt2,
|
|
82
82
|
encodeInt64(value) {
|
|
83
83
|
return leftPad(
|
|
84
|
-
|
|
84
|
+
encodeBigInt2(BigInt(value)),
|
|
85
85
|
FIXED_LEN_INT64
|
|
86
86
|
);
|
|
87
87
|
},
|
|
@@ -89,7 +89,7 @@ function createBase60Codec() {
|
|
|
89
89
|
if (text.length !== FIXED_LEN_INT64) {
|
|
90
90
|
throw new Error(`Expected ${FIXED_LEN_INT64} chars for Base60 Int64`);
|
|
91
91
|
}
|
|
92
|
-
return
|
|
92
|
+
return decodeToBigInt2(text);
|
|
93
93
|
},
|
|
94
94
|
encodeUUID(uuid) {
|
|
95
95
|
const hex = uuid.replace(/-/g, "");
|
|
@@ -99,12 +99,12 @@ function createBase60Codec() {
|
|
|
99
99
|
bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
|
|
100
100
|
}
|
|
101
101
|
return leftPad(
|
|
102
|
-
|
|
102
|
+
encodeBigInt2(bytesToBigInt(bytes)),
|
|
103
103
|
FIXED_LEN_UUID
|
|
104
104
|
);
|
|
105
105
|
},
|
|
106
106
|
decodeUUID(text) {
|
|
107
|
-
const bytes = bigIntToBytes(
|
|
107
|
+
const bytes = bigIntToBytes(decodeToBigInt2(text), 16);
|
|
108
108
|
const hex = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
109
109
|
return hex.slice(0, 8) + "-" + hex.slice(8, 12) + "-" + hex.slice(12, 16) + "-" + hex.slice(16, 20) + "-" + hex.slice(20);
|
|
110
110
|
},
|
|
@@ -122,14 +122,14 @@ function createBase60Codec() {
|
|
|
122
122
|
if (v === void 0) throw new Error(`Invalid ULID char: ${ch}`);
|
|
123
123
|
value = (value << 5n) + BigInt(v);
|
|
124
124
|
}
|
|
125
|
-
const raw =
|
|
125
|
+
const raw = encodeBigInt2(value);
|
|
126
126
|
return leftPad(raw, FIXED_LEN_ULID);
|
|
127
127
|
},
|
|
128
128
|
decodeULID(text) {
|
|
129
129
|
if (text.length !== 22) {
|
|
130
130
|
throw new Error("Expected 22-char Base60 ULID");
|
|
131
131
|
}
|
|
132
|
-
const value =
|
|
132
|
+
const value = decodeToBigInt2(text);
|
|
133
133
|
const bytes = bigIntToBytes(value, 16);
|
|
134
134
|
let bits = "";
|
|
135
135
|
for (const b of bytes) {
|
|
@@ -144,8 +144,8 @@ function createBase60Codec() {
|
|
|
144
144
|
return ulid;
|
|
145
145
|
},
|
|
146
146
|
compareAsBigInt(a, b) {
|
|
147
|
-
const ai =
|
|
148
|
-
const bi =
|
|
147
|
+
const ai = decodeToBigInt2(a);
|
|
148
|
+
const bi = decodeToBigInt2(b);
|
|
149
149
|
if (ai < bi) return -1;
|
|
150
150
|
if (ai > bi) return 1;
|
|
151
151
|
return 0;
|
|
@@ -159,8 +159,35 @@ function createBase60Codec() {
|
|
|
159
159
|
};
|
|
160
160
|
}
|
|
161
161
|
var base60 = createBase60Codec();
|
|
162
|
+
var {
|
|
163
|
+
encodeBytes,
|
|
164
|
+
decodeToBytes,
|
|
165
|
+
encodeBigInt,
|
|
166
|
+
decodeToBigInt,
|
|
167
|
+
encodeInt64,
|
|
168
|
+
decodeInt64,
|
|
169
|
+
encodeUUID,
|
|
170
|
+
decodeUUID,
|
|
171
|
+
encodeULID,
|
|
172
|
+
decodeULID,
|
|
173
|
+
compareAsBigInt,
|
|
174
|
+
isValidBase60
|
|
175
|
+
} = base60;
|
|
162
176
|
var index_default = base60;
|
|
163
177
|
export {
|
|
178
|
+
base60,
|
|
179
|
+
compareAsBigInt,
|
|
164
180
|
createBase60Codec,
|
|
165
|
-
|
|
181
|
+
decodeInt64,
|
|
182
|
+
decodeToBigInt,
|
|
183
|
+
decodeToBytes,
|
|
184
|
+
decodeULID,
|
|
185
|
+
decodeUUID,
|
|
186
|
+
index_default as default,
|
|
187
|
+
encodeBigInt,
|
|
188
|
+
encodeBytes,
|
|
189
|
+
encodeInt64,
|
|
190
|
+
encodeULID,
|
|
191
|
+
encodeUUID,
|
|
192
|
+
isValidBase60
|
|
166
193
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "base60-codec",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A tiny, deterministic Base60 encoder/decoder for UUID, ULID, Int64, and BigInt.",
|
|
5
5
|
"author": "Shinnosuke Hagiwara <hagiwara000@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,8 +11,14 @@
|
|
|
11
11
|
"exports": {
|
|
12
12
|
".": {
|
|
13
13
|
"types": "./dist/index.d.ts",
|
|
14
|
-
"import":
|
|
15
|
-
|
|
14
|
+
"import": {
|
|
15
|
+
"default": "./dist/index.js",
|
|
16
|
+
"base60": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"require": {
|
|
19
|
+
"default": "./dist/index.cjs",
|
|
20
|
+
"base60": "./dist/index.cjs"
|
|
21
|
+
}
|
|
16
22
|
}
|
|
17
23
|
},
|
|
18
24
|
"files": [
|