scxq2-cc 1.0.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 +340 -0
- package/dist/base64.js +83 -0
- package/dist/canon.js +60 -0
- package/dist/cli.mjs +192 -0
- package/dist/engine.js +753 -0
- package/dist/index.d.ts +426 -0
- package/dist/index.js +48 -0
- package/dist/sha.js +71 -0
- package/dist/verify.js +480 -0
- package/dist/wasm-decoder.js +232 -0
- package/package.json +64 -0
- package/src/base64.js +83 -0
- package/src/canon.js +60 -0
- package/src/engine.js +753 -0
- package/src/index.js +48 -0
- package/src/sha.js +71 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @asx/scxq2-cc - SCXQ2 Compression Calculus Engine
|
|
3
|
+
* TypeScript Type Definitions
|
|
4
|
+
*
|
|
5
|
+
* @version 1.0.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* =============================================================================
|
|
9
|
+
Engine Constants
|
|
10
|
+
============================================================================= */
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* CC Engine identity object (frozen)
|
|
14
|
+
*/
|
|
15
|
+
export declare const CC_ENGINE: Readonly<{
|
|
16
|
+
"@id": "asx://cc/engine/scxq2.v1";
|
|
17
|
+
"@type": "cc.engine";
|
|
18
|
+
"@version": "1.0.0";
|
|
19
|
+
"@status": "frozen";
|
|
20
|
+
"$schema": "xjson://schema/core/v1";
|
|
21
|
+
}>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* SCXQ2 encoding mode constants (frozen)
|
|
25
|
+
*/
|
|
26
|
+
export declare const SCXQ2_ENCODING: Readonly<{
|
|
27
|
+
mode: "SCXQ2-DICT16-B64";
|
|
28
|
+
encoding: "SCXQ2-1";
|
|
29
|
+
}>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* CC operator identifiers (frozen)
|
|
33
|
+
*/
|
|
34
|
+
export declare const CC_OPS: Readonly<{
|
|
35
|
+
NORM: "cc.norm.v1";
|
|
36
|
+
DICT: "cc.dict.v1";
|
|
37
|
+
FIELD: "cc.field.v1";
|
|
38
|
+
LANE: "cc.lane.v1";
|
|
39
|
+
EDGE: "cc.edge.v1";
|
|
40
|
+
}>;
|
|
41
|
+
|
|
42
|
+
/* =============================================================================
|
|
43
|
+
Compression Options
|
|
44
|
+
============================================================================= */
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Options for compression operations
|
|
48
|
+
*/
|
|
49
|
+
export interface CCCompressOptions {
|
|
50
|
+
/** Maximum dictionary entries (1-65535, default: 1024) */
|
|
51
|
+
maxDict?: number;
|
|
52
|
+
/** Minimum token length (2-128, default: 3) */
|
|
53
|
+
minLen?: number;
|
|
54
|
+
/** Skip string literal tokens */
|
|
55
|
+
noStrings?: boolean;
|
|
56
|
+
/** Skip whitespace tokens */
|
|
57
|
+
noWS?: boolean;
|
|
58
|
+
/** Skip punctuation tokens */
|
|
59
|
+
noPunct?: boolean;
|
|
60
|
+
/** Enable JSON key extraction (CC.FIELD operator) */
|
|
61
|
+
enableFieldOps?: boolean;
|
|
62
|
+
/** Enable edge witnesses (CC.EDGE operator) */
|
|
63
|
+
enableEdgeOps?: boolean;
|
|
64
|
+
/** ISO timestamp (auto-generated if omitted) */
|
|
65
|
+
created_utc?: string;
|
|
66
|
+
/** Source file identifier */
|
|
67
|
+
source_file?: string | null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Flags indicating which token types were processed
|
|
72
|
+
*/
|
|
73
|
+
export interface SCXQ2Flags {
|
|
74
|
+
noStrings: boolean;
|
|
75
|
+
noWS: boolean;
|
|
76
|
+
noPunct: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* =============================================================================
|
|
80
|
+
SCXQ2 Dictionary
|
|
81
|
+
============================================================================= */
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* SCXQ2 Dictionary object
|
|
85
|
+
*/
|
|
86
|
+
export interface SCXQ2Dict {
|
|
87
|
+
"@type": "scxq2.dict";
|
|
88
|
+
"@version": string;
|
|
89
|
+
mode: "SCXQ2-DICT16-B64";
|
|
90
|
+
encoding: "SCXQ2-1";
|
|
91
|
+
created_utc: string;
|
|
92
|
+
source_sha256_utf8: string;
|
|
93
|
+
max_dict: number;
|
|
94
|
+
min_len: number;
|
|
95
|
+
flags: SCXQ2Flags;
|
|
96
|
+
dict: string[];
|
|
97
|
+
dict_sha256_canon: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* =============================================================================
|
|
101
|
+
SCXQ2 Block
|
|
102
|
+
============================================================================= */
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Edge witness tuple [from_index, to_index]
|
|
106
|
+
*/
|
|
107
|
+
export type EdgeWitness = [number, number];
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* SCXQ2 Block object
|
|
111
|
+
*/
|
|
112
|
+
export interface SCXQ2Block {
|
|
113
|
+
"@type": "scxq2.block";
|
|
114
|
+
"@version": string;
|
|
115
|
+
mode: "SCXQ2-DICT16-B64";
|
|
116
|
+
encoding: "SCXQ2-1";
|
|
117
|
+
created_utc: string;
|
|
118
|
+
source_sha256_utf8: string;
|
|
119
|
+
dict_sha256_canon: string;
|
|
120
|
+
original_bytes_utf8: number;
|
|
121
|
+
b64: string;
|
|
122
|
+
block_sha256_canon: string;
|
|
123
|
+
/** Lane identifier (multi-lane packs only) */
|
|
124
|
+
lane_id?: string;
|
|
125
|
+
/** Edge witnesses (when enableEdgeOps is true) */
|
|
126
|
+
edges?: EdgeWitness[];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* =============================================================================
|
|
130
|
+
CC Proof
|
|
131
|
+
============================================================================= */
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Proof step in the compression trace
|
|
135
|
+
*/
|
|
136
|
+
export interface CCProofStep {
|
|
137
|
+
op: string;
|
|
138
|
+
sha?: string;
|
|
139
|
+
dict_entries?: number;
|
|
140
|
+
block_sha?: string;
|
|
141
|
+
roundtrip_sha?: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Compression Calculus proof object
|
|
146
|
+
*/
|
|
147
|
+
export interface CCProof {
|
|
148
|
+
"@type": "cc.proof";
|
|
149
|
+
"@version": string;
|
|
150
|
+
engine: string;
|
|
151
|
+
created_utc: string;
|
|
152
|
+
source_sha256_utf8: string;
|
|
153
|
+
dict_sha256_canon: string;
|
|
154
|
+
block_sha256_canon: string;
|
|
155
|
+
roundtrip_sha256_utf8: string;
|
|
156
|
+
ok: boolean;
|
|
157
|
+
steps: CCProofStep[];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Multi-lane proof object
|
|
162
|
+
*/
|
|
163
|
+
export interface CCLanesProof {
|
|
164
|
+
"@type": "cc.lanes.proof";
|
|
165
|
+
"@version": string;
|
|
166
|
+
engine: string;
|
|
167
|
+
created_utc: string;
|
|
168
|
+
dict_sha256_canon: string;
|
|
169
|
+
lanes: Array<{
|
|
170
|
+
lane_id: string;
|
|
171
|
+
source_sha256_utf8: string;
|
|
172
|
+
block_sha256_canon: string;
|
|
173
|
+
}>;
|
|
174
|
+
ok: boolean;
|
|
175
|
+
steps?: CCProofStep[];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* =============================================================================
|
|
179
|
+
CC Audit
|
|
180
|
+
============================================================================= */
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Top token info for audit
|
|
184
|
+
*/
|
|
185
|
+
export interface TokenInfo {
|
|
186
|
+
tok: string;
|
|
187
|
+
count: number;
|
|
188
|
+
totalSavings: number;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Compression audit object
|
|
193
|
+
*/
|
|
194
|
+
export interface CCAudit {
|
|
195
|
+
"@type": "cc.audit";
|
|
196
|
+
"@version": string;
|
|
197
|
+
engine: string;
|
|
198
|
+
created_utc: string;
|
|
199
|
+
sizes: {
|
|
200
|
+
original_bytes_utf8: number;
|
|
201
|
+
encoded_b64_bytes_utf8: number;
|
|
202
|
+
ratio: number | null;
|
|
203
|
+
};
|
|
204
|
+
dict: {
|
|
205
|
+
entries: number;
|
|
206
|
+
max_dict: number;
|
|
207
|
+
min_len: number;
|
|
208
|
+
flags: SCXQ2Flags;
|
|
209
|
+
};
|
|
210
|
+
top_tokens: TokenInfo[];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Multi-lane audit object
|
|
215
|
+
*/
|
|
216
|
+
export interface CCLanesAudit {
|
|
217
|
+
"@type": "cc.lanes.audit";
|
|
218
|
+
"@version": string;
|
|
219
|
+
engine: string;
|
|
220
|
+
created_utc: string;
|
|
221
|
+
dict_entries: number;
|
|
222
|
+
lane_count: number;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* =============================================================================
|
|
226
|
+
Compression Results
|
|
227
|
+
============================================================================= */
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Single-lane compression result
|
|
231
|
+
*/
|
|
232
|
+
export interface CCResult {
|
|
233
|
+
dict: SCXQ2Dict;
|
|
234
|
+
block: SCXQ2Block;
|
|
235
|
+
proof: CCProof;
|
|
236
|
+
audit: CCAudit;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Multi-lane compression result
|
|
241
|
+
*/
|
|
242
|
+
export interface CCLanesResult {
|
|
243
|
+
dict: SCXQ2Dict;
|
|
244
|
+
lanes: SCXQ2Block[];
|
|
245
|
+
proof: CCLanesProof;
|
|
246
|
+
audit: CCLanesAudit;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Lane input for multi-lane compression
|
|
251
|
+
*/
|
|
252
|
+
export interface LaneInput {
|
|
253
|
+
lane_id: string;
|
|
254
|
+
text: string | Uint8Array;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Multi-lane compression input
|
|
259
|
+
*/
|
|
260
|
+
export interface LanesInput {
|
|
261
|
+
lanes: LaneInput[];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/* =============================================================================
|
|
265
|
+
Main API Functions
|
|
266
|
+
============================================================================= */
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Compresses input text into an SCXQ2 language pack.
|
|
270
|
+
* Uses async hashing for universal compatibility (Node.js, Browser, Worker).
|
|
271
|
+
*
|
|
272
|
+
* @param input - Source text or bytes to compress
|
|
273
|
+
* @param opts - Compression options
|
|
274
|
+
* @returns Promise resolving to compression result
|
|
275
|
+
*/
|
|
276
|
+
export declare function ccCompress(
|
|
277
|
+
input: string | Uint8Array,
|
|
278
|
+
opts?: CCCompressOptions
|
|
279
|
+
): Promise<CCResult>;
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Synchronous compression (Node.js only).
|
|
283
|
+
*
|
|
284
|
+
* @param input - Source text or bytes to compress
|
|
285
|
+
* @param opts - Compression options
|
|
286
|
+
* @returns Compression result
|
|
287
|
+
* @throws If not running in Node.js
|
|
288
|
+
*/
|
|
289
|
+
export declare function ccCompressSync(
|
|
290
|
+
input: string | Uint8Array,
|
|
291
|
+
opts?: CCCompressOptions
|
|
292
|
+
): CCResult;
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Compresses multiple lanes with a shared dictionary.
|
|
296
|
+
*
|
|
297
|
+
* @param laneInput - Object containing array of lanes
|
|
298
|
+
* @param opts - Compression options
|
|
299
|
+
* @returns Promise resolving to multi-lane compression result
|
|
300
|
+
*/
|
|
301
|
+
export declare function ccCompressLanes(
|
|
302
|
+
laneInput: LanesInput,
|
|
303
|
+
opts?: CCCompressOptions
|
|
304
|
+
): Promise<CCLanesResult>;
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Synchronous multi-lane compression (Node.js only).
|
|
308
|
+
*
|
|
309
|
+
* @param laneInput - Object containing array of lanes
|
|
310
|
+
* @param opts - Compression options
|
|
311
|
+
* @returns Multi-lane compression result
|
|
312
|
+
* @throws If not running in Node.js
|
|
313
|
+
*/
|
|
314
|
+
export declare function ccCompressLanesSync(
|
|
315
|
+
laneInput: LanesInput,
|
|
316
|
+
opts?: CCCompressOptions
|
|
317
|
+
): CCLanesResult;
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Decompresses an SCXQ2 block using its dictionary.
|
|
321
|
+
*
|
|
322
|
+
* @param dictJson - SCXQ2 dictionary object
|
|
323
|
+
* @param blockJson - SCXQ2 block object
|
|
324
|
+
* @returns Decompressed text
|
|
325
|
+
* @throws On invalid pack or decoding error
|
|
326
|
+
*/
|
|
327
|
+
export declare function ccDecompress(
|
|
328
|
+
dictJson: SCXQ2Dict,
|
|
329
|
+
blockJson: SCXQ2Block
|
|
330
|
+
): string;
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Verifies structural validity of an SCXQ2 pack.
|
|
334
|
+
*
|
|
335
|
+
* @param dictJson - Dictionary object
|
|
336
|
+
* @param blockJson - Block object
|
|
337
|
+
* @returns Success indicator
|
|
338
|
+
* @throws On verification failure
|
|
339
|
+
*/
|
|
340
|
+
export declare function verifyPack(
|
|
341
|
+
dictJson: SCXQ2Dict,
|
|
342
|
+
blockJson: SCXQ2Block
|
|
343
|
+
): { ok: true };
|
|
344
|
+
|
|
345
|
+
/* =============================================================================
|
|
346
|
+
Utility Functions
|
|
347
|
+
============================================================================= */
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Produces canonical JSON string with sorted keys.
|
|
351
|
+
*
|
|
352
|
+
* @param obj - Object to serialize
|
|
353
|
+
* @returns Canonical JSON string
|
|
354
|
+
*/
|
|
355
|
+
export declare function canon(obj: unknown): string;
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Recursively sorts object keys.
|
|
359
|
+
*
|
|
360
|
+
* @param value - Any value
|
|
361
|
+
* @returns Value with sorted object keys
|
|
362
|
+
*/
|
|
363
|
+
export declare function sortKeysDeep<T>(value: T): T;
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Creates shallow copy excluding specified fields.
|
|
367
|
+
*
|
|
368
|
+
* @param obj - Source object
|
|
369
|
+
* @param fields - Fields to exclude
|
|
370
|
+
* @returns New object without excluded fields
|
|
371
|
+
*/
|
|
372
|
+
export declare function strip<T extends object>(
|
|
373
|
+
obj: T,
|
|
374
|
+
fields: string[]
|
|
375
|
+
): Partial<T>;
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Computes SHA-256 hash of UTF-8 text (async).
|
|
379
|
+
*
|
|
380
|
+
* @param text - Text to hash
|
|
381
|
+
* @returns Promise resolving to hex-encoded hash
|
|
382
|
+
*/
|
|
383
|
+
export declare function sha256HexUtf8(text: string): Promise<string>;
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Computes SHA-256 hash of UTF-8 text (sync, Node.js only).
|
|
387
|
+
*
|
|
388
|
+
* @param text - Text to hash
|
|
389
|
+
* @param nodeCrypto - Optional pre-imported crypto module
|
|
390
|
+
* @returns Hex-encoded hash
|
|
391
|
+
*/
|
|
392
|
+
export declare function sha256HexUtf8Sync(
|
|
393
|
+
text: string,
|
|
394
|
+
nodeCrypto?: unknown
|
|
395
|
+
): string;
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Gets Node.js crypto module if available.
|
|
399
|
+
*
|
|
400
|
+
* @returns Crypto module or null
|
|
401
|
+
*/
|
|
402
|
+
export declare function getNodeCrypto(): unknown | null;
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Encodes bytes to base64 string.
|
|
406
|
+
*
|
|
407
|
+
* @param bytes - Bytes to encode
|
|
408
|
+
* @returns Base64-encoded string
|
|
409
|
+
*/
|
|
410
|
+
export declare function bytesToBase64(bytes: Uint8Array | number[]): string;
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Decodes base64 string to bytes.
|
|
414
|
+
*
|
|
415
|
+
* @param b64 - Base64-encoded string
|
|
416
|
+
* @returns Decoded bytes
|
|
417
|
+
*/
|
|
418
|
+
export declare function base64ToBytes(b64: string): Uint8Array;
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Validates that a string is valid base64.
|
|
422
|
+
*
|
|
423
|
+
* @param b64 - String to validate
|
|
424
|
+
* @returns True if valid base64
|
|
425
|
+
*/
|
|
426
|
+
export declare function isValidBase64(b64: string): boolean;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @asx/scxq2-cc - SCXQ2 Compression Calculus Engine
|
|
3
|
+
*
|
|
4
|
+
* A deterministic, proof-generating compression engine that produces
|
|
5
|
+
* content-addressable language packs following the frozen SCXQ2 specification.
|
|
6
|
+
*
|
|
7
|
+
* @module @asx/scxq2-cc
|
|
8
|
+
* @version 1.0.0
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* // Basic compression
|
|
12
|
+
* import { ccCompress, ccDecompress } from '@asx/scxq2-cc';
|
|
13
|
+
*
|
|
14
|
+
* const pack = await ccCompress('function hello() { console.log("Hello"); }');
|
|
15
|
+
* console.log(pack.proof.ok); // true
|
|
16
|
+
*
|
|
17
|
+
* const roundtrip = ccDecompress(pack.dict, pack.block);
|
|
18
|
+
* // roundtrip === original input
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* // Multi-lane compression
|
|
22
|
+
* import { ccCompressLanes } from '@asx/scxq2-cc';
|
|
23
|
+
*
|
|
24
|
+
* const pack = await ccCompressLanes({
|
|
25
|
+
* lanes: [
|
|
26
|
+
* { lane_id: 'main', text: 'function main() {}' },
|
|
27
|
+
* { lane_id: 'util', text: 'function util() {}' }
|
|
28
|
+
* ]
|
|
29
|
+
* });
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
// Re-export engine API
|
|
33
|
+
export {
|
|
34
|
+
CC_ENGINE,
|
|
35
|
+
SCXQ2_ENCODING,
|
|
36
|
+
CC_OPS,
|
|
37
|
+
ccCompress,
|
|
38
|
+
ccCompressSync,
|
|
39
|
+
ccCompressLanes,
|
|
40
|
+
ccCompressLanesSync,
|
|
41
|
+
ccDecompress,
|
|
42
|
+
verifyPack
|
|
43
|
+
} from "./engine.js";
|
|
44
|
+
|
|
45
|
+
// Re-export utilities for advanced use
|
|
46
|
+
export { canon, sortKeysDeep, strip } from "./canon.js";
|
|
47
|
+
export { sha256HexUtf8, sha256HexUtf8Sync, getNodeCrypto } from "./sha.js";
|
|
48
|
+
export { bytesToBase64, base64ToBytes, isValidBase64 } from "./base64.js";
|
package/dist/sha.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SCXQ2 SHA-256 Utilities
|
|
3
|
+
*
|
|
4
|
+
* Universal SHA-256 hashing that works in Node.js, browsers, and workers.
|
|
5
|
+
* Provides both async (WebCrypto) and sync (Node crypto) implementations.
|
|
6
|
+
*
|
|
7
|
+
* @module @asx/scxq2-cc/sha
|
|
8
|
+
* @version 1.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Computes SHA-256 hash of UTF-8 text, returning hex string.
|
|
13
|
+
* Uses WebCrypto when available, falls back to Node crypto.
|
|
14
|
+
*
|
|
15
|
+
* @param {string} text - UTF-8 text to hash
|
|
16
|
+
* @returns {Promise<string>} Hex-encoded SHA-256 hash
|
|
17
|
+
*/
|
|
18
|
+
export async function sha256HexUtf8(text) {
|
|
19
|
+
// WebCrypto (browser / worker)
|
|
20
|
+
if (globalThis.crypto?.subtle) {
|
|
21
|
+
const data = new TextEncoder().encode(text);
|
|
22
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", data);
|
|
23
|
+
return [...new Uint8Array(digest)]
|
|
24
|
+
.map(b => b.toString(16).padStart(2, "0"))
|
|
25
|
+
.join("");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Node.js
|
|
29
|
+
const { createHash } = await import("crypto");
|
|
30
|
+
return createHash("sha256").update(text, "utf8").digest("hex");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Synchronous SHA-256 hash (Node.js only).
|
|
35
|
+
* Throws if crypto module is not available.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} text - UTF-8 text to hash
|
|
38
|
+
* @param {Object} [nodeCrypto] - Pre-imported crypto module (optional)
|
|
39
|
+
* @returns {string} Hex-encoded SHA-256 hash
|
|
40
|
+
*/
|
|
41
|
+
export function sha256HexUtf8Sync(text, nodeCrypto = null) {
|
|
42
|
+
if (nodeCrypto) {
|
|
43
|
+
return nodeCrypto.createHash("sha256").update(text, "utf8").digest("hex");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Dynamic require for Node.js environments
|
|
47
|
+
if (typeof process !== "undefined" && process.versions?.node) {
|
|
48
|
+
// Use dynamic import trick for sync context
|
|
49
|
+
const crypto = require("crypto");
|
|
50
|
+
return crypto.createHash("sha256").update(text, "utf8").digest("hex");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
throw new Error("SCXQ2: sync hashing requires Node.js crypto module");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Gets Node.js crypto module if available.
|
|
58
|
+
* Returns null in browser/worker environments.
|
|
59
|
+
*
|
|
60
|
+
* @returns {Object|null} Node crypto module or null
|
|
61
|
+
*/
|
|
62
|
+
export function getNodeCrypto() {
|
|
63
|
+
if (typeof process !== "undefined" && process.versions?.node) {
|
|
64
|
+
try {
|
|
65
|
+
return require("crypto");
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|