-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #329 from liam-hq/compress-query-parameter
feat: get hidden nodes via query parameter now compresses
- Loading branch information
Showing
6 changed files
with
207 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
"@liam-hq/erd-core": patch | ||
"@liam-hq/cli": patch | ||
--- | ||
|
||
feat: get hidden nodes via query parameter now compresses |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
frontend/packages/erd-core/src/utils/compressionString.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { describe, expect, it } from 'vitest' | ||
import { | ||
compressToEncodedURIComponent, | ||
decompressFromEncodedURIComponent, | ||
} from './compressionString' | ||
|
||
describe('compressionString', () => { | ||
it('should compress and decompress a string correctly', async () => { | ||
const input = 'Hello, world!' | ||
const compressed = await compressToEncodedURIComponent(input) | ||
const decompressed = await decompressFromEncodedURIComponent(compressed) | ||
|
||
expect(compressed).toMatchInlineSnapshot(`"eJzzSM3JyddRKM8vyklRBAAgXgSK"`) | ||
expect(decompressed).toBe(input) | ||
}) | ||
|
||
it('should handle empty string', async () => { | ||
const input = '' | ||
const compressed = await compressToEncodedURIComponent(input) | ||
const decompressed = await decompressFromEncodedURIComponent(compressed) | ||
|
||
expect(compressed).toMatchInlineSnapshot(`"eJwDAAAAAAE"`) | ||
expect(decompressed).toBe(input) | ||
}) | ||
|
||
it('should handle long string', async () => { | ||
const input = 'a'.repeat(1000) | ||
const compressed = await compressToEncodedURIComponent(input) | ||
const decompressed = await decompressFromEncodedURIComponent(compressed) | ||
|
||
expect(compressed).toMatchInlineSnapshot(`"eJxLTBwFo2AUDHcAAPnYevg"`) | ||
expect(decompressed).toBe(input) | ||
}) | ||
|
||
it('should handle special characters', async () => { | ||
const input = 'こんにちは、世界!' | ||
const compressed = await compressToEncodedURIComponent(input) | ||
const decompressed = await decompressFromEncodedURIComponent(compressed) | ||
|
||
expect(compressed).toMatchInlineSnapshot( | ||
`"eJwBGwDk_-OBk-OCk-OBq-OBoeOBr-OAgeS4lueVjO-8gQC2EmE"`, | ||
) | ||
expect(decompressed).toBe(input) | ||
}) | ||
|
||
it('should handle binary data', async () => { | ||
const input = String.fromCharCode( | ||
...Array.from({ length: 256 }, (_, i) => i), | ||
) | ||
const compressed = await compressToEncodedURIComponent(input) | ||
const decompressed = await decompressFromEncodedURIComponent(compressed) | ||
|
||
expect(compressed).toMatchInlineSnapshot( | ||
`"eJwFwQNXHQAABtBse9mu1bKWbbuWFpbN9arXy7Zt23XO9_2x7hUTl5CUkpaRlZNXUFRSVlFVU9fQ1NLW0dX7oW9gaGRsYmpmbmFpZW1ja2fv4Ojk_NPF9Zebu4enl7ePr59_wO_AoOCQ0LDwiMio6JjYuPiExKTklNS09IzMrOyc3Lz8gsI_RcUlpWV_yysqq_5V19TW1Tc0NjW3tLa1d3R2dff874UAfejHAIQYhAhDGMYIRjGGcUxgElOYxgxmMYd5LGARS1jGClaxhnVsYBNb2MYOdrGHfRzgEEc4xglOcYZzXOASV7jGDW5xh3s84BFPeMYLXvGGd3zgE18UsI_9HKCQgxRxiMMc4SjHOM4JTnKK05zhLOc4zwUuconLXOEq17jODW5yi9vc4S73uM8DHvKIxzzhKc94zgte8orXvOEt73jPBz7yic984Svf-M4PfvLrG5oE0ME"`, | ||
) | ||
expect(decompressed).toBe(input) | ||
}) | ||
}) |
114 changes: 114 additions & 0 deletions
114
frontend/packages/erd-core/src/utils/compressionString.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
function createReadableStreamFromBytes( | ||
bytes: Uint8Array, | ||
): ReadableStream<Uint8Array> { | ||
return new ReadableStream({ | ||
start(controller) { | ||
controller.enqueue(bytes) | ||
controller.close() | ||
}, | ||
}) | ||
} | ||
|
||
/** | ||
* Perform Deflate compression and return a Uint8Array | ||
*/ | ||
async function deflateCompress(inputBytes: Uint8Array): Promise<Uint8Array> { | ||
const compression = new CompressionStream('deflate') | ||
const stream = | ||
createReadableStreamFromBytes(inputBytes).pipeThrough(compression) | ||
const compressedBuffer = await new Response(stream).arrayBuffer() | ||
return new Uint8Array(compressedBuffer) | ||
} | ||
|
||
/** | ||
* Perform Deflate decompression and return a Uint8Array | ||
*/ | ||
async function deflateDecompress(inputBytes: Uint8Array): Promise<Uint8Array> { | ||
const decompression = new DecompressionStream('deflate') | ||
const stream = | ||
createReadableStreamFromBytes(inputBytes).pipeThrough(decompression) | ||
const decompressedBuffer = await new Response(stream).arrayBuffer() | ||
return new Uint8Array(decompressedBuffer) | ||
} | ||
|
||
/** | ||
* Encode byte array to Base64 string | ||
*/ | ||
function bytesToBase64(bytes: Uint8Array): string { | ||
let binaryString = '' | ||
for (const b of Array.from(bytes)) { | ||
binaryString += String.fromCharCode(b) | ||
} | ||
return btoa(binaryString) | ||
} | ||
|
||
/** | ||
* Decode Base64 string to byte array | ||
*/ | ||
function base64ToBytes(base64: string): Uint8Array { | ||
const binaryString = atob(base64) | ||
const length = binaryString.length | ||
const bytes = new Uint8Array(length) | ||
for (let i = 0; i < length; i++) { | ||
bytes[i] = binaryString.charCodeAt(i) | ||
} | ||
return bytes | ||
} | ||
|
||
/** | ||
* Convert Base64 string to URL-safe string (`+` => `-`, `/` => `_`, remove `=`) | ||
*/ | ||
function base64ToUrlSafe(base64: string): string { | ||
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') | ||
} | ||
|
||
/** | ||
* Convert URL-safe string back to Base64 | ||
*/ | ||
function urlSafeToBase64(urlSafe: string): string { | ||
let base64 = urlSafe.replace(/-/g, '+').replace(/_/g, '/') | ||
while (base64.length % 4) { | ||
base64 += '=' | ||
} | ||
return base64 | ||
} | ||
|
||
/** | ||
* Compress string using Deflate and return as Base64 | ||
*/ | ||
async function compressToBase64(input: string): Promise<string> { | ||
const textEncoder = new TextEncoder() | ||
const inputBytes = textEncoder.encode(input) | ||
const compressedBytes = await deflateCompress(inputBytes) | ||
return bytesToBase64(compressedBytes) | ||
} | ||
|
||
/** | ||
* Decompress Base64 string and return the original string | ||
*/ | ||
async function decompressFromBase64(input: string): Promise<string> { | ||
const textDecoder = new TextDecoder() | ||
const compressedBytes = base64ToBytes(input) | ||
const decompressedBytes = await deflateDecompress(compressedBytes) | ||
return textDecoder.decode(decompressedBytes) | ||
} | ||
|
||
/** | ||
* Compress string using Deflate and return as URL-safe encoded string (based on Base64) | ||
*/ | ||
export async function compressToEncodedURIComponent( | ||
input: string, | ||
): Promise<string> { | ||
const base64 = await compressToBase64(input) | ||
return base64ToUrlSafe(base64) | ||
} | ||
|
||
/** | ||
* Restore original string from URL-safe encoded string | ||
*/ | ||
export async function decompressFromEncodedURIComponent( | ||
input: string, | ||
): Promise<string> { | ||
const base64 = urlSafeToBase64(input) | ||
return decompressFromBase64(base64) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './compressionString' |