@ix-xs/node-comfort 1.2.0 → 2.1.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.
Files changed (78) hide show
  1. package/CHANGELOG.md +340 -0
  2. package/README.md +137 -548
  3. package/index.js +80 -35
  4. package/package.json +161 -33
  5. package/src/Arr.js +1050 -418
  6. package/src/Async.js +817 -0
  7. package/src/Cache.js +433 -0
  8. package/src/Checker.js +1202 -539
  9. package/src/Cli.js +1293 -0
  10. package/src/Color.js +556 -0
  11. package/src/Crypto.js +1082 -0
  12. package/src/Emitter.js +356 -0
  13. package/src/Env.js +689 -0
  14. package/src/FS.js +1626 -894
  15. package/src/Func.js +823 -356
  16. package/src/Http.js +700 -0
  17. package/src/Id.js +568 -200
  18. package/src/Logger.js +963 -378
  19. package/src/Num.js +965 -360
  20. package/src/Obj.js +1077 -379
  21. package/src/SQLite.js +1629 -1005
  22. package/src/Schema.js +2101 -0
  23. package/src/Str.js +1209 -432
  24. package/src/Sys.js +654 -0
  25. package/src/Time.js +1533 -440
  26. package/src/Utils.js +342 -325
  27. package/src/errors.js +335 -0
  28. package/src/internal/shared.js +415 -0
  29. package/types/index.d.ts +735 -526
  30. package/types/src/Arr.d.ts +533 -145
  31. package/types/src/Arr.d.ts.map +1 -1
  32. package/types/src/Async.d.ts +372 -0
  33. package/types/src/Async.d.ts.map +1 -0
  34. package/types/src/Cache.d.ts +221 -0
  35. package/types/src/Cache.d.ts.map +1 -0
  36. package/types/src/Checker.d.ts +651 -232
  37. package/types/src/Checker.d.ts.map +1 -1
  38. package/types/src/Cli.d.ts +664 -0
  39. package/types/src/Cli.d.ts.map +1 -0
  40. package/types/src/Color.d.ts +300 -0
  41. package/types/src/Color.d.ts.map +1 -0
  42. package/types/src/Crypto.d.ts +620 -0
  43. package/types/src/Crypto.d.ts.map +1 -0
  44. package/types/src/Emitter.d.ts +189 -0
  45. package/types/src/Emitter.d.ts.map +1 -0
  46. package/types/src/Env.d.ts +412 -0
  47. package/types/src/Env.d.ts.map +1 -0
  48. package/types/src/FS.d.ts +717 -238
  49. package/types/src/FS.d.ts.map +1 -1
  50. package/types/src/Func.d.ts +508 -135
  51. package/types/src/Func.d.ts.map +1 -1
  52. package/types/src/Http.d.ts +444 -0
  53. package/types/src/Http.d.ts.map +1 -0
  54. package/types/src/Id.d.ts +199 -66
  55. package/types/src/Id.d.ts.map +1 -1
  56. package/types/src/Logger.d.ts +414 -46
  57. package/types/src/Logger.d.ts.map +1 -1
  58. package/types/src/Num.d.ts +514 -137
  59. package/types/src/Num.d.ts.map +1 -1
  60. package/types/src/Obj.d.ts +453 -104
  61. package/types/src/Obj.d.ts.map +1 -1
  62. package/types/src/SQLite.d.ts +754 -308
  63. package/types/src/SQLite.d.ts.map +1 -1
  64. package/types/src/Schema.d.ts +1189 -0
  65. package/types/src/Schema.d.ts.map +1 -0
  66. package/types/src/Str.d.ts +617 -166
  67. package/types/src/Str.d.ts.map +1 -1
  68. package/types/src/Sys.d.ts +314 -0
  69. package/types/src/Sys.d.ts.map +1 -0
  70. package/types/src/Time.d.ts +759 -204
  71. package/types/src/Time.d.ts.map +1 -1
  72. package/types/src/Utils.d.ts +158 -102
  73. package/types/src/Utils.d.ts.map +1 -1
  74. package/types/src/errors.d.ts +316 -0
  75. package/types/src/errors.d.ts.map +1 -0
  76. package/types/src/internal/shared.d.ts +123 -0
  77. package/types/src/internal/shared.d.ts.map +1 -0
  78. package/types/index.d.ts.map +0 -1
package/types/src/Id.d.ts CHANGED
@@ -1,119 +1,252 @@
1
1
  /**
2
- * Generates a random UUID v4.
2
+ * Encodings for `token()`.
3
+ */
4
+ export type TokenEncoding = "hex" | "base64" | "base64url" | "base58" | "base32";
5
+ /**
6
+ * A namespace for `uuidv5()`: one of the four standard ones, or any UUID of
7
+ * your own.
8
+ */
9
+ export type UUIDNamespace = "dns" | "url" | "oid" | "x500" | (string & {});
10
+ /**
11
+ * Options for `snowflake()` and `parseSnowflake()`.
12
+ */
13
+ export type SnowflakeOptions = {
14
+ /**
15
+ * Start of time, in ms. Defaults to Discord's (2015-01-01). Twitter uses `1288834974657`.
16
+ */
17
+ epoch?: number | undefined;
18
+ /**
19
+ * 0 to 31. Defaults to `0`.
20
+ */
21
+ workerId?: number | undefined;
22
+ /**
23
+ * 0 to 31. Defaults to `process.pid % 32`.
24
+ */
25
+ processId?: number | undefined;
26
+ };
27
+ /**
28
+ * A decoded Snowflake.
29
+ */
30
+ export type ParsedSnowflake = {
31
+ /**
32
+ * When it was created.
33
+ */
34
+ date: Date;
35
+ /**
36
+ * When it was created, in ms.
37
+ */
38
+ timestamp: number;
39
+ workerId: number;
40
+ processId: number;
41
+ /**
42
+ * Sequence number within the millisecond, 0 to 4095.
43
+ */
44
+ increment: number;
45
+ };
46
+ /**
47
+ * A random UUID (version 4). For ids that sort by date, see `uuidv7()`.
3
48
  *
4
49
  * @example
5
- * nodeComfort.id.uuid(); // "3f2504e0-4f89-41d3-9a0c-0305e82c3301"
50
+ * id.uuid(); // "3f2504e0-4f89-41d3-9a0c-0305e82c3301"
6
51
  *
7
- * @returns {string} A UUID v4 string.
52
+ * @returns {string}
8
53
  */
9
54
  export function uuid(): string;
10
55
  /**
11
- * Generates a URL-safe, collision-resistant ID (nanoid-compatible alphabet).
56
+ * A UUID v5: the same name in the same namespace always gives the same id.
57
+ * Use it to derive a stable id from something you already have, like a URL
58
+ * or an email, without storing a mapping.
12
59
  *
13
- * Uses secure randomness with unbiased rejection sampling.
60
+ * @example
61
+ * id.uuidv5("example.com", "dns"); // "cfbff0d1-9375-5685-968c-48ce8b15ae17", every time
62
+ * id.uuidv5("https://example.com/a", "url");
63
+ * id.uuidv5("ada@example.com", MY_APP_NAMESPACE); // any UUID works as a namespace
64
+ *
65
+ * @param {string | Buffer | Uint8Array} name
66
+ * @param {UUIDNamespace} namespace `"dns"`, `"url"`, `"oid"`, `"x500"` or a UUID.
67
+ * @returns {string}
68
+ */
69
+ export function uuidv5(name: string | Buffer | Uint8Array, namespace: UUIDNamespace): string;
70
+ /**
71
+ * A UUID v7: it starts with a timestamp, so ids sort by creation time.
72
+ * That keeps database indexes compact, which makes them great primary
73
+ * keys. Ids from the same process always increase, even within a
74
+ * millisecond, and even if the system clock steps back.
14
75
  *
15
76
  * @example
16
- * nodeComfort.id.nano(); // "V1StGXR8_Z5jdHi6B-myT"
17
- * nodeComfort.id.nano(10); // "IRFa-VaY2b"
77
+ * id.uuidv7(); // "0190a5c4-7c1e-7b2a-9f3e-1c2d3e4f5a6b"
78
+ * id.timestamp(id.uuidv7()); // when it was created
18
79
  *
19
- * @param {number} [size=21] - Desired length.
20
- * @param {string} [alphabet] - Custom alphabet.
21
- * @returns {string} The generated ID.
80
+ * @returns {string}
22
81
  */
23
- export function nano(size?: number, alphabet?: string): string;
82
+ export function uuidv7(): string;
24
83
  /**
25
- * Generates a ULID • a lexicographically-sortable, timestamp-prefixed ID.
84
+ * A ULID: 26 URL-safe characters that sort by creation time. Ids made in
85
+ * the same millisecond still increase.
26
86
  *
27
- * ULIDs sort by creation time and are a great primary-key alternative to UUIDs.
87
+ * @example
88
+ * id.ulid(); // "01HQ3Z8K9V3N5W7Y2XJ4T6R8PB"
89
+ * id.ulid(Date.UTC(2024, 0, 1)); // for a given time
90
+ *
91
+ * @param {number | Date} [time] The moment to encode. Defaults to now.
92
+ * @returns {string}
93
+ * @throws {RangeError} If the time is not a valid timestamp.
94
+ */
95
+ export function ulid(time?: number | Date): string;
96
+ /**
97
+ * A MongoDB-style ObjectId: 24 hex characters that start with the time, in
98
+ * seconds, and increase within the process.
28
99
  *
29
100
  * @example
30
- * nodeComfort.id.ulid(); // "01ARZ3NDEKTSV4RRFFQ69G5FAV"
101
+ * id.objectId(); // "65f2a9c47b1e4d0c9a3f2b10"
102
+ * id.timestamp(id.objectId()); // when it was created, to the second
103
+ * id.objectId(new Date("2024-01-01")); // for a given time
31
104
  *
32
- * @param {number} [time=Date.now()] - Timestamp in ms to encode.
33
- * @returns {string} A 26-character ULID.
105
+ * @param {number | Date} [time] The moment to encode. Defaults to now.
106
+ * @returns {string}
107
+ * @throws {RangeError} If the time is not a valid timestamp.
34
108
  */
35
- export function ulid(time?: number): string;
109
+ export function objectId(time?: number | Date): string;
36
110
  /**
37
- * Generates a cryptographically-secure random token.
111
+ * A short, URL-safe random id, like `nanoid`. At 21 characters, collisions
112
+ * are as unlikely as with a UUID.
38
113
  *
39
114
  * @example
40
- * nodeComfort.id.token(); // 32-byte hex string
41
- * nodeComfort.id.token(16, "base64url"); // 16 bytes, URL-safe base64
115
+ * id.nano(); // "V1StGXR8_Z5jdHi6B-myT"
116
+ * id.nano(10); // "IRFa-VaY2b"
117
+ * id.nano(8, "0123456789abcdef"); // "4f90d13a"
42
118
  *
43
- * @param {number} [bytes=32] - Number of random bytes.
44
- * @param {"hex"|"base64"|"base64url"|"base58"} [encoding="hex"] - Output encoding.
45
- * @returns {string} The token.
119
+ * @param {number} [size=21]
120
+ * @param {string} [alphabet] 2 to 256 characters. Defaults to letters, digits, `_` and `-`.
121
+ * @returns {string}
46
122
  */
47
- export function token(bytes?: number, encoding?: "hex" | "base64" | "base64url" | "base58"): string;
123
+ export function nano(size?: number, alphabet?: string): string;
48
124
  /**
49
- * Generates a short, human-friendly random code (uppercase + digits by default).
125
+ * Makes an id generator with your own alphabet and length.
50
126
  *
51
- * Ambiguous characters (`0/O`, `1/I`) are excluded, making it suitable for
52
- * coupons, OTP-like codes and invite links.
127
+ * @example
128
+ * const orderId = id.customAlphabet("0123456789ABCDEF", 12);
129
+ * orderId(); // "4F1A09C2BB7E"
130
+ *
131
+ * @param {string} alphabet
132
+ * @param {number} [size=21]
133
+ * @returns {(size?: number) => string}
134
+ */
135
+ export function customAlphabet(alphabet: string, size?: number): (size?: number) => string;
136
+ /**
137
+ * A random id with a prefix that says what it identifies, like Stripe's
138
+ * `cus_...` and `sub_...`. A glance at a log line or a support ticket tells
139
+ * you what kind of thing the id points to.
53
140
  *
54
141
  * @example
55
- * nodeComfort.id.code(); // "K7QF9X"
56
- * nodeComfort.id.code(4, "1234"); // "3142"
142
+ * id.prefixed("user"); // "user_7Hq2mXc0LrT9aZ4bWk1PdE8s"
143
+ * id.prefixed("inv", 12); // "inv_Q0f8ZrT1bK4m"
57
144
  *
58
- * @param {number} [length=6] - Number of characters.
59
- * @param {string} [alphabet="ABCDEFGHJKLMNPQRSTUVWXYZ23456789"] - Characters to pick from.
60
- * @returns {string} The random code.
145
+ * @param {string} prefix
146
+ * @param {number} [size=24] How many random characters come after the prefix (letters and digits).
147
+ * @returns {string}
61
148
  */
62
- export function code(length?: number, alphabet?: string): string;
149
+ export function prefixed(prefix: string, size?: number): string;
63
150
  /**
64
- * Hashes a value with a chosen algorithm.
151
+ * A random token for API keys, session ids, reset links...
65
152
  *
66
153
  * @example
67
- * nodeComfort.id.hash("hello"); // sha256 hex
68
- * nodeComfort.id.hash("hello", { algorithm: "md5" });
154
+ * id.token(); // 64 hex characters
155
+ * id.token(16, "base64url"); // 22 URL-safe characters
156
+ * id.token(16, "base58"); // no look-alike characters
69
157
  *
70
- * @param {string|Buffer} value - Data to hash.
71
- * @param {{ algorithm?: string, encoding?: "hex"|"base64"|"base64url" }} [options] - Hash options.
72
- * @returns {string} The digest.
158
+ * @param {number} [bytes=32] How much randomness, in bytes.
159
+ * @param {TokenEncoding} [encoding="hex"]
160
+ * @returns {string}
73
161
  */
74
- export function hash(value: string | Buffer, options?: {
75
- algorithm?: string;
76
- encoding?: "hex" | "base64" | "base64url";
77
- }): string;
162
+ export function token(bytes?: number, encoding?: TokenEncoding): string;
78
163
  /**
79
- * Computes an HMAC signature for a value using a secret key.
164
+ * A short code that's easy to read aloud and type: no `0/O` or `1/I/L`
165
+ * mix-ups. For invites, coupons and verification codes.
80
166
  *
81
167
  * @example
82
- * nodeComfort.id.hmac("payload", "secret"); // sha256 HMAC hex
168
+ * id.code(); // "K7QF9X"
169
+ * id.code(4, "0123456789"); // "3920"
83
170
  *
84
- * @param {string|Buffer} value - Data to sign.
85
- * @param {string|Buffer} secret - Secret key.
86
- * @param {{ algorithm?: string, encoding?: "hex"|"base64"|"base64url" }} [options] - HMAC options.
87
- * @returns {string} The signature.
171
+ * @param {number} [length=6]
172
+ * @param {string} [alphabet="ABCDEFGHJKMNPQRSTUVWXYZ23456789"]
173
+ * @returns {string}
88
174
  */
89
- export function hmac(value: string | Buffer, secret: string | Buffer, options?: {
90
- algorithm?: string;
91
- encoding?: "hex" | "base64" | "base64url";
92
- }): string;
175
+ export function code(length?: number, alphabet?: string): string;
93
176
  /**
94
- * Compares two strings/buffers in constant time to prevent timing attacks.
177
+ * A Snowflake id, the 64-bit format used by Discord and Twitter. Returned
178
+ * as a string because it doesn't fit in a JavaScript number.
179
+ *
180
+ * @example
181
+ * id.snowflake(); // "1215361932810932224"
95
182
  *
96
- * Use this when comparing secrets, tokens, or signatures.
183
+ * @param {SnowflakeOptions} [options]
184
+ * @returns {string}
185
+ * @throws {RangeError} If the epoch is in the future.
186
+ */
187
+ export function snowflake(options?: SnowflakeOptions): string;
188
+ /**
189
+ * Decodes a Snowflake. With the default epoch, it reads Discord ids.
97
190
  *
98
191
  * @example
99
- * if (nodeComfort.id.safeEqual(provided, expected)) { ... }
192
+ * id.parseSnowflake("175928847299117063").date; // 2016-04-30T11:18:25.796Z
100
193
  *
101
- * @param {string|Buffer} a - First value.
102
- * @param {string|Buffer} b - Second value.
103
- * @returns {boolean} True if equal.
194
+ * @param {string | bigint} id
195
+ * @param {SnowflakeOptions} [options] Use the epoch it was created with.
196
+ * @returns {ParsedSnowflake}
197
+ * @throws {TypeError} If the id is not a string of digits or a bigint.
104
198
  */
105
- export function safeEqual(a: string | Buffer, b: string | Buffer): boolean;
199
+ export function parseSnowflake(id: string | bigint, options?: SnowflakeOptions): ParsedSnowflake;
106
200
  /**
107
- * Returns a monotonically-increasing counter ID prefixed with an optional label.
201
+ * When a UUID v7, a ULID or an ObjectId was created.
108
202
  *
109
- * Useful for readable, unique keys within a single process run.
203
+ * @example
204
+ * id.timestamp(id.ulid()); // now
205
+ * id.timestamp(id.objectId()); // now, to the second
206
+ * id.timestamp(id.uuid()); // undefined, v4 has no time in it
207
+ *
208
+ * @param {string} value
209
+ * @returns {Date | undefined}
210
+ */
211
+ export function timestamp(value: string): Date | undefined;
212
+ /**
213
+ * Readable sequential ids like `"user-1"`, `"user-2"`, counted per prefix.
214
+ * Only unique within the current process; handy in logs and tests.
110
215
  *
111
216
  * @example
112
- * nodeComfort.id.seq("user"); // "user-1"
113
- * nodeComfort.id.seq("user"); // "user-2"
217
+ * id.seq("user"); // "user-1"
218
+ * id.seq("user"); // "user-2"
114
219
  *
115
- * @param {string} [prefix=""] - Optional prefix.
116
- * @returns {string} The sequential ID.
220
+ * @param {string} [prefix=""]
221
+ * @returns {string}
117
222
  */
118
223
  export function seq(prefix?: string): string;
224
+ /**
225
+ * Hashes a value.
226
+ *
227
+ * @deprecated Use `nc.crypto.hash()`. This alias goes away in 3.0.
228
+ * @param {string | Buffer | Uint8Array} value
229
+ * @param {import("./Crypto").HashOptions} [options]
230
+ * @returns {string}
231
+ */
232
+ export function hash(value: string | Buffer | Uint8Array, options?: import("./Crypto").HashOptions): string;
233
+ /**
234
+ * Signs a value with HMAC.
235
+ *
236
+ * @deprecated Use `nc.crypto.hmac()`. This alias goes away in 3.0.
237
+ * @param {string | Buffer | Uint8Array} value
238
+ * @param {string | Buffer | Uint8Array} secret
239
+ * @param {import("./Crypto").HashOptions} [options]
240
+ * @returns {string}
241
+ */
242
+ export function hmac(value: string | Buffer | Uint8Array, secret: string | Buffer | Uint8Array, options?: import("./Crypto").HashOptions): string;
243
+ /**
244
+ * Compares two secrets in constant time.
245
+ *
246
+ * @deprecated Use `nc.crypto.safeEqual()`. This alias goes away in 3.0.
247
+ * @param {string | Buffer | Uint8Array} a
248
+ * @param {string | Buffer | Uint8Array} b
249
+ * @returns {boolean}
250
+ */
251
+ export function safeEqual(a: string | Buffer | Uint8Array, b: string | Buffer | Uint8Array): boolean;
119
252
  //# sourceMappingURL=Id.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Id.d.ts","sourceRoot":"","sources":["../../src/Id.js"],"names":[],"mappings":"AAyDE;;;;;;;GAOG;AACH,wBAFa,MAAM,CAIlB;AAED;;;;;;;;;;;;GAYG;AACH,4BAJW,MAAM,aACN,MAAM,GACJ,MAAM,CAgBlB;AAED;;;;;;;;;;GAUG;AACH,4BAHW,MAAM,GACJ,MAAM,CAgBlB;AAED;;;;;;;;;;GAUG;AACH,8BAJW,MAAM,aACN,KAAK,GAAC,QAAQ,GAAC,WAAW,GAAC,QAAQ,GACjC,MAAM,CAMlB;AAED;;;;;;;;;;;;;GAaG;AACH,8BAJW,MAAM,aACN,MAAM,GACJ,MAAM,CASlB;AAED;;;;;;;;;;GAUG;AACH,4BAJW,MAAM,GAAC,MAAM,YACb;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,KAAK,GAAC,QAAQ,GAAC,WAAW,CAAA;CAAE,GAC3D,MAAM,CAMlB;AAED;;;;;;;;;;GAUG;AACH,4BALW,MAAM,GAAC,MAAM,UACb,MAAM,GAAC,MAAM,YACb;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,KAAK,GAAC,QAAQ,GAAC,WAAW,CAAA;CAAE,GAC3D,MAAM,CAMlB;AAED;;;;;;;;;;;GAWG;AACH,6BAJW,MAAM,GAAC,MAAM,KACb,MAAM,GAAC,MAAM,GACX,OAAO,CAOnB;AAED;;;;;;;;;;;GAWG;AACH,6BAHW,MAAM,GACJ,MAAM,CAKlB"}
1
+ {"version":3,"file":"Id.d.ts","sourceRoot":"","sources":["../../src/Id.js"],"names":[],"mappings":";;;4BAqBa,KAAK,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,QAAQ;;;;;4BAMpD,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;UAc7C,IAAI;;;;eACJ,MAAM;cACN,MAAM;eACN,MAAM;;;;eACN,MAAM;;AA4JpB;;;;;;;GAOG;AACH,wBAFa,MAAM,CAIlB;AAED;;;;;;;;;;;;;GAaG;AACH,6BAJW,MAAM,GAAG,MAAM,GAAG,UAAU,aAC5B,aAAa,GACX,MAAM,CAYlB;AAMD;;;;;;;;;;;GAWG;AACH,0BAFa,MAAM,CAmBlB;AAoBD;;;;;;;;;;;GAWG;AACH,4BAJW,MAAM,GAAG,IAAI,GACX,MAAM,CA6BlB;AAKD;;;;;;;;;;;;GAYG;AACH,gCAJW,MAAM,GAAG,IAAI,GACX,MAAM,CAalB;AAED;;;;;;;;;;;;GAYG;AACH,4BAJW,MAAM,aACN,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;;;;;;GAUG;AACH,yCAJW,MAAM,SACN,MAAM,GACJ,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,MAAM,CAKrC;AAED;;;;;;;;;;;;GAYG;AACH,iCAJW,MAAM,SACN,MAAM,GACJ,MAAM,CAKlB;AAED;;;;;;;;;;;GAWG;AACH,8BAJW,MAAM,aACN,aAAa,GACX,MAAM,CASlB;AAED;;;;;;;;;;;GAWG;AACH,8BAJW,MAAM,aACN,MAAM,GACJ,MAAM,CAIlB;AAKD;;;;;;;;;;GAUG;AACH,oCAJW,gBAAgB,GACd,MAAM,CAiBlB;AAED;;;;;;;;;;GAUG;AACH,mCALW,MAAM,GAAG,MAAM,YACf,gBAAgB,GACd,eAAe,CAkB3B;AAED;;;;;;;;;;GAUG;AACH,iCAHW,MAAM,GACJ,IAAI,GAAG,SAAS,CAU5B;AAKD;;;;;;;;;;GAUG;AACH,6BAHW,MAAM,GACJ,MAAM,CAMlB;AAED;;;;;;;GAOG;AACH,4BAJW,MAAM,GAAG,MAAM,GAAG,UAAU,YAC5B,OAAO,UAAU,EAAE,WAAW,GAC5B,MAAM,CAIlB;AAED;;;;;;;;GAQG;AACH,4BALW,MAAM,GAAG,MAAM,GAAG,UAAU,UAC5B,MAAM,GAAG,MAAM,GAAG,UAAU,YAC5B,OAAO,UAAU,EAAE,WAAW,GAC5B,MAAM,CAIlB;AAED;;;;;;;GAOG;AACH,6BAJW,MAAM,GAAG,MAAM,GAAG,UAAU,KAC5B,MAAM,GAAG,MAAM,GAAG,UAAU,GAC1B,OAAO,CAInB"}