@demicodes/utils 0.1.0 → 0.2.1
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/index.d.mts +21 -1
- package/dist/index.mjs +104 -2
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -34,6 +34,8 @@ declare function throwIfAborted(signal: AbortSignal): void;
|
|
|
34
34
|
* promise is not canceled; this only stops the caller from awaiting it past the abort.
|
|
35
35
|
*/
|
|
36
36
|
declare function abortable<T>(promise: Promise<T>, signal: AbortSignal): Promise<T>;
|
|
37
|
+
/** Returns the string `code` of an errno-style error (`ENOENT`, `EEXIST`, …), or null. */
|
|
38
|
+
declare function errorCode(error: unknown): string | null;
|
|
37
39
|
/** Reports whether an unknown thrown value is a file-not-found error (ENOENT). */
|
|
38
40
|
declare function isFileNotFoundError(error: unknown): boolean;
|
|
39
41
|
//#endregion
|
|
@@ -68,10 +70,20 @@ declare function encodeUtf8(text: string): Uint8Array;
|
|
|
68
70
|
declare function decodeUtf8(data: Uint8Array): string;
|
|
69
71
|
/** Returns the UTF-8 byte length of a string. */
|
|
70
72
|
declare function utf8Bytes(text: string): number;
|
|
73
|
+
/** Packs a latin1 byte-string (each char = one byte, 0–255) into bytes. */
|
|
74
|
+
declare function encodeLatin1(text: string): Uint8Array;
|
|
75
|
+
/** Unpacks bytes into a latin1 byte-string (each char = one byte). */
|
|
76
|
+
declare function decodeLatin1(bytes: Uint8Array): string;
|
|
77
|
+
/** Strictly decodes UTF-8; returns null when the bytes are not valid UTF-8. */
|
|
78
|
+
declare function decodeUtf8Strict(bytes: Uint8Array): string | null;
|
|
71
79
|
/** Slices a string by UTF-8 byte offsets, returning the decoded substring. */
|
|
72
80
|
declare function utf8Slice(text: string, start: number, end: number): string;
|
|
73
81
|
/** Concatenates byte chunks into a single `Uint8Array`. */
|
|
74
82
|
declare function concatBytes(chunks: readonly Uint8Array[]): Uint8Array;
|
|
83
|
+
/** Encodes bytes as standard base64 (platform-neutral, no Node or DOM globals). */
|
|
84
|
+
declare function bytesToBase64(bytes: Uint8Array): string;
|
|
85
|
+
/** Decodes standard base64 to bytes, throwing on malformed payloads. */
|
|
86
|
+
declare function base64ToBytes(base64: string): Uint8Array;
|
|
75
87
|
//#endregion
|
|
76
88
|
//#region src/strings.d.ts
|
|
77
89
|
/** Clamps a number into the inclusive `[min, max]` range. */
|
|
@@ -96,6 +108,14 @@ declare function parseJsonObject(value: string): Record<string, unknown> | null;
|
|
|
96
108
|
* "[Circular]". Returns undefined when the top-level value has no JSON form.
|
|
97
109
|
*/
|
|
98
110
|
declare function safeJsonStringify(value: unknown): string | undefined;
|
|
111
|
+
/**
|
|
112
|
+
* JSON.stringify that round-trips values plain JSON cannot: `Uint8Array` is
|
|
113
|
+
* encoded as a `__demiUint8Array`-marked base64 object and `bigint` as a
|
|
114
|
+
* `__demiBigInt`-marked string. Decode with `parsePortableJson`.
|
|
115
|
+
*/
|
|
116
|
+
declare function stringifyPortableJson(value: unknown, space?: number): string;
|
|
117
|
+
/** Parses JSON produced by `stringifyPortableJson`, reviving marked `Uint8Array` and `bigint` values. */
|
|
118
|
+
declare function parsePortableJson<T>(text: string): T;
|
|
99
119
|
//#endregion
|
|
100
120
|
//#region src/paths.d.ts
|
|
101
121
|
/**
|
|
@@ -113,4 +133,4 @@ declare function isAbsolutePath(path: string): boolean;
|
|
|
113
133
|
/** Generates a random UUID. */
|
|
114
134
|
declare function createId(): string;
|
|
115
135
|
//#endregion
|
|
116
|
-
export { AbortError, Deferred, abortable, asError, asRecord, asString, clamp, concatBytes, createId, decodeUtf8, deferred, delay, dirnamePath, encodeUtf8, errorMessage, isAbortError, isAbsolutePath, isFileNotFoundError, isRecord, nonEmptyString, noop, normalizeBaseUrl, normalizePath, numberOrNull, numberOrZero, parseJsonObject, parseJsonOrString, safeJsonStringify, shortHash, stringOrNull, tail, throwIfAborted, truncate, utf8Bytes, utf8Slice, waitFor, withTimeout };
|
|
136
|
+
export { AbortError, Deferred, abortable, asError, asRecord, asString, base64ToBytes, bytesToBase64, clamp, concatBytes, createId, decodeLatin1, decodeUtf8, decodeUtf8Strict, deferred, delay, dirnamePath, encodeLatin1, encodeUtf8, errorCode, errorMessage, isAbortError, isAbsolutePath, isFileNotFoundError, isRecord, nonEmptyString, noop, normalizeBaseUrl, normalizePath, numberOrNull, numberOrZero, parseJsonObject, parseJsonOrString, parsePortableJson, safeJsonStringify, shortHash, stringOrNull, stringifyPortableJson, tail, throwIfAborted, truncate, utf8Bytes, utf8Slice, waitFor, withTimeout };
|
package/dist/index.mjs
CHANGED
|
@@ -84,9 +84,15 @@ function abortable(promise, signal) {
|
|
|
84
84
|
});
|
|
85
85
|
});
|
|
86
86
|
}
|
|
87
|
+
/** Returns the string `code` of an errno-style error (`ENOENT`, `EEXIST`, …), or null. */
|
|
88
|
+
function errorCode(error) {
|
|
89
|
+
if (typeof error !== "object" || error === null) return null;
|
|
90
|
+
const code = error.code;
|
|
91
|
+
return typeof code === "string" ? code : null;
|
|
92
|
+
}
|
|
87
93
|
/** Reports whether an unknown thrown value is a file-not-found error (ENOENT). */
|
|
88
94
|
function isFileNotFoundError(error) {
|
|
89
|
-
return
|
|
95
|
+
return errorCode(error) === "ENOENT";
|
|
90
96
|
}
|
|
91
97
|
//#endregion
|
|
92
98
|
//#region src/async.ts
|
|
@@ -154,6 +160,27 @@ function decodeUtf8(data) {
|
|
|
154
160
|
function utf8Bytes(text) {
|
|
155
161
|
return encoder.encode(text).byteLength;
|
|
156
162
|
}
|
|
163
|
+
/** Packs a latin1 byte-string (each char = one byte, 0–255) into bytes. */
|
|
164
|
+
function encodeLatin1(text) {
|
|
165
|
+
const out = new Uint8Array(text.length);
|
|
166
|
+
for (let i = 0; i < text.length; i += 1) out[i] = text.charCodeAt(i) & 255;
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
/** Unpacks bytes into a latin1 byte-string (each char = one byte). */
|
|
170
|
+
function decodeLatin1(bytes) {
|
|
171
|
+
let out = "";
|
|
172
|
+
const chunk = 32768;
|
|
173
|
+
for (let i = 0; i < bytes.length; i += chunk) out += String.fromCharCode(...bytes.subarray(i, i + chunk));
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
/** Strictly decodes UTF-8; returns null when the bytes are not valid UTF-8. */
|
|
177
|
+
function decodeUtf8Strict(bytes) {
|
|
178
|
+
try {
|
|
179
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
180
|
+
} catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
157
184
|
/** Slices a string by UTF-8 byte offsets, returning the decoded substring. */
|
|
158
185
|
function utf8Slice(text, start, end) {
|
|
159
186
|
if (start <= 0 && end >= utf8Bytes(text)) return text;
|
|
@@ -170,6 +197,47 @@ function concatBytes(chunks) {
|
|
|
170
197
|
}
|
|
171
198
|
return combined;
|
|
172
199
|
}
|
|
200
|
+
const BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
201
|
+
/** Encodes bytes as standard base64 (platform-neutral, no Node or DOM globals). */
|
|
202
|
+
function bytesToBase64(bytes) {
|
|
203
|
+
let output = "";
|
|
204
|
+
for (let index = 0; index < bytes.byteLength; index += 3) {
|
|
205
|
+
const first = bytes[index];
|
|
206
|
+
const second = bytes[index + 1];
|
|
207
|
+
const third = bytes[index + 2];
|
|
208
|
+
output += BASE64_ALPHABET[first >> 2];
|
|
209
|
+
output += BASE64_ALPHABET[(first & 3) << 4 | (second ?? 0) >> 4];
|
|
210
|
+
output += second === void 0 ? "=" : BASE64_ALPHABET[(second & 15) << 2 | (third ?? 0) >> 6];
|
|
211
|
+
output += third === void 0 ? "=" : BASE64_ALPHABET[third & 63];
|
|
212
|
+
}
|
|
213
|
+
return output;
|
|
214
|
+
}
|
|
215
|
+
/** Decodes standard base64 to bytes, throwing on malformed payloads. */
|
|
216
|
+
function base64ToBytes(base64) {
|
|
217
|
+
const clean = base64.replace(/\s+/g, "");
|
|
218
|
+
if (clean.length === 0) return /* @__PURE__ */ new Uint8Array();
|
|
219
|
+
if (clean.length % 4 !== 0) throw new Error("Invalid base64 payload length");
|
|
220
|
+
const padding = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
|
|
221
|
+
const bytes = new Uint8Array(clean.length / 4 * 3 - padding);
|
|
222
|
+
let offset = 0;
|
|
223
|
+
for (let index = 0; index < clean.length; index += 4) {
|
|
224
|
+
const first = base64Value(clean[index]);
|
|
225
|
+
const second = base64Value(clean[index + 1]);
|
|
226
|
+
const third = clean[index + 2] === "=" ? 0 : base64Value(clean[index + 2]);
|
|
227
|
+
const fourth = clean[index + 3] === "=" ? 0 : base64Value(clean[index + 3]);
|
|
228
|
+
const triple = first << 18 | second << 12 | third << 6 | fourth;
|
|
229
|
+
if (offset < bytes.byteLength) bytes[offset++] = triple >> 16 & 255;
|
|
230
|
+
if (offset < bytes.byteLength) bytes[offset++] = triple >> 8 & 255;
|
|
231
|
+
if (offset < bytes.byteLength) bytes[offset++] = triple & 255;
|
|
232
|
+
}
|
|
233
|
+
return bytes;
|
|
234
|
+
}
|
|
235
|
+
function base64Value(char) {
|
|
236
|
+
if (!char || char === "=") throw new Error("Invalid base64 payload");
|
|
237
|
+
const value = BASE64_ALPHABET.indexOf(char);
|
|
238
|
+
if (value === -1) throw new Error("Invalid base64 payload");
|
|
239
|
+
return value;
|
|
240
|
+
}
|
|
173
241
|
//#endregion
|
|
174
242
|
//#region src/strings.ts
|
|
175
243
|
/** Clamps a number into the inclusive `[min, max]` range. */
|
|
@@ -240,6 +308,40 @@ function safeJsonStringify(value) {
|
|
|
240
308
|
return;
|
|
241
309
|
}
|
|
242
310
|
}
|
|
311
|
+
const BINARY_MARKER = "__demiUint8Array";
|
|
312
|
+
const BIGINT_MARKER = "__demiBigInt";
|
|
313
|
+
/**
|
|
314
|
+
* JSON.stringify that round-trips values plain JSON cannot: `Uint8Array` is
|
|
315
|
+
* encoded as a `__demiUint8Array`-marked base64 object and `bigint` as a
|
|
316
|
+
* `__demiBigInt`-marked string. Decode with `parsePortableJson`.
|
|
317
|
+
*/
|
|
318
|
+
function stringifyPortableJson(value, space) {
|
|
319
|
+
return JSON.stringify(value, (_key, nested) => {
|
|
320
|
+
if (nested instanceof Uint8Array) return {
|
|
321
|
+
[BINARY_MARKER]: true,
|
|
322
|
+
base64: bytesToBase64(nested)
|
|
323
|
+
};
|
|
324
|
+
if (typeof nested === "bigint") return {
|
|
325
|
+
[BIGINT_MARKER]: true,
|
|
326
|
+
value: nested.toString()
|
|
327
|
+
};
|
|
328
|
+
return nested;
|
|
329
|
+
}, space);
|
|
330
|
+
}
|
|
331
|
+
/** Parses JSON produced by `stringifyPortableJson`, reviving marked `Uint8Array` and `bigint` values. */
|
|
332
|
+
function parsePortableJson(text) {
|
|
333
|
+
return JSON.parse(text, (_key, nested) => {
|
|
334
|
+
if (isEncodedUint8Array(nested)) return base64ToBytes(nested.base64);
|
|
335
|
+
if (isEncodedBigInt(nested)) return BigInt(nested.value);
|
|
336
|
+
return nested;
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
function isEncodedUint8Array(value) {
|
|
340
|
+
return isRecord(value) && value[BINARY_MARKER] === true && typeof value.base64 === "string";
|
|
341
|
+
}
|
|
342
|
+
function isEncodedBigInt(value) {
|
|
343
|
+
return isRecord(value) && value[BIGINT_MARKER] === true && typeof value.value === "string";
|
|
344
|
+
}
|
|
243
345
|
//#endregion
|
|
244
346
|
//#region src/paths.ts
|
|
245
347
|
/**
|
|
@@ -287,4 +389,4 @@ function createId() {
|
|
|
287
389
|
return globalThis.crypto.randomUUID();
|
|
288
390
|
}
|
|
289
391
|
//#endregion
|
|
290
|
-
export { AbortError, abortable, asError, asRecord, asString, clamp, concatBytes, createId, decodeUtf8, deferred, delay, dirnamePath, encodeUtf8, errorMessage, isAbortError, isAbsolutePath, isFileNotFoundError, isRecord, nonEmptyString, noop, normalizeBaseUrl, normalizePath, numberOrNull, numberOrZero, parseJsonObject, parseJsonOrString, safeJsonStringify, shortHash, stringOrNull, tail, throwIfAborted, truncate, utf8Bytes, utf8Slice, waitFor, withTimeout };
|
|
392
|
+
export { AbortError, abortable, asError, asRecord, asString, base64ToBytes, bytesToBase64, clamp, concatBytes, createId, decodeLatin1, decodeUtf8, decodeUtf8Strict, deferred, delay, dirnamePath, encodeLatin1, encodeUtf8, errorCode, errorMessage, isAbortError, isAbsolutePath, isFileNotFoundError, isRecord, nonEmptyString, noop, normalizeBaseUrl, normalizePath, numberOrNull, numberOrZero, parseJsonObject, parseJsonOrString, parsePortableJson, safeJsonStringify, shortHash, stringOrNull, stringifyPortableJson, tail, throwIfAborted, truncate, utf8Bytes, utf8Slice, waitFor, withTimeout };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@demicodes/utils",
|
|
3
3
|
"description": "Shared zero-dependency utilities for Demi (type guards, errors, async, bytes, strings, paths, JSON).",
|
|
4
|
-
"version": "0.1
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.mjs",
|