From 91aa3b5bc432f2fdf95723adccfac61259679006 Mon Sep 17 00:00:00 2001 From: MH4GF Date: Thu, 19 Dec 2024 19:57:10 +0900 Subject: [PATCH 1/5] feat: add compression and decompression utilities for UTF-16 strings --- .../src/utils/compressionString.test.ts | Bin 0 -> 1792 bytes .../erd-core/src/utils/compressionString.ts | 74 ++++++++++++++++++ frontend/packages/erd-core/src/utils/index.ts | 1 + 3 files changed, 75 insertions(+) create mode 100644 frontend/packages/erd-core/src/utils/compressionString.test.ts create mode 100644 frontend/packages/erd-core/src/utils/compressionString.ts create mode 100644 frontend/packages/erd-core/src/utils/index.ts diff --git a/frontend/packages/erd-core/src/utils/compressionString.test.ts b/frontend/packages/erd-core/src/utils/compressionString.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..aec8dea2f476222f278c8d634d6095ef90ab227f GIT binary patch literal 1792 zcmdUwO=}ZD7{?11o~FtKYe zGJ`e1cN}bR4G4!K++ubXl-q=3mn#VpRU3BJVc2!2>>E=PBcqyZk3ti&BMcsO>*t~z zvFRjdgr=25B3h^_zh^a2lQ4D(3JH*BsEV7hXDt(=LO|4vZ3F~vI8_!fCU3X$iU!Ed zQzJG9WR|)d2z6#TjDd0$Tb8Xs&SsW*MvZg0lChrN`(s_b z;1LuxnuN_{BJA(rj>mOwUq{@?T%#7Dc#5N@o3%#e!;k92pRXR&%d7RB zl}2@8ceU<$yDvX33{Q7X>W=KKm&@ePE4X)eG29+wn~_W{3^?oXd>3aw5RV17lWlG% zhCX`(0A$%T{TFd#QPE>-)O5cXw?7%x4HiEDt8) zs1Mj3f7aW+KQ2GgxNTxJ^eF&c*Do`M@?laffQ4zAXD-6|3!_k!mVHzHXN0y*l3LjE M&*lCH2b3oG3oH(SvH$=8 literal 0 HcmV?d00001 diff --git a/frontend/packages/erd-core/src/utils/compressionString.ts b/frontend/packages/erd-core/src/utils/compressionString.ts new file mode 100644 index 00000000..beaab9f5 --- /dev/null +++ b/frontend/packages/erd-core/src/utils/compressionString.ts @@ -0,0 +1,74 @@ +function createReadableStreamFromBytes( + bytes: Uint8Array, +): ReadableStream { + return new ReadableStream({ + start(controller) { + controller.enqueue(bytes) + controller.close() + }, + }) +} + +export async function compressToUTF16(input: string): Promise { + const encoder = new TextEncoder() + const inputBytes = encoder.encode(input) + + // Deflate圧縮 + const compressionStream = new CompressionStream('deflate') + const compressedStream = + createReadableStreamFromBytes(inputBytes).pipeThrough(compressionStream) + const compressedBuffer = await new Response(compressedStream).arrayBuffer() + const compressedBytes = new Uint8Array(compressedBuffer) + + // 圧縮後のバイト長を格納するため、先頭4バイトに長さ情報を入れる + const length = compressedBytes.length + const totalLength = 4 + length // 最初の4バイトで長さを格納 + // 2バイト(UTF-16コードユニット)境界に揃えるため、必要なら1バイトパディング + const paddedLength = totalLength % 2 === 0 ? totalLength : totalLength + 1 + + const resultBytes = new Uint8Array(paddedLength) + const dataView = new DataView(resultBytes.buffer) + + // 先頭4バイトに長さ情報を格納 (Little Endian) + dataView.setUint32(0, length, true) + // その後に圧縮データ本体を格納 + resultBytes.set(compressedBytes, 4) + + // UTF-16コードユニットとして解釈するためにUint16Arrayに変換 + const uint16Array = new Uint16Array(resultBytes.buffer) + + // 各Uint16要素をコードユニットとして文字列化 + return String.fromCharCode(...Array.from(uint16Array)) +} + +export async function decompressFromUTF16(input: string): Promise { + const decoder = new TextDecoder() + + // UTF-16コードユニット列をUint16Arrayとして再構成 + const codeUnits = new Uint16Array(input.length) + for (let i = 0; i < input.length; i++) { + codeUnits[i] = input.charCodeAt(i) + } + + // Uint16ArrayからUint8Arrayを参照取得 + const bytes = new Uint8Array(codeUnits.buffer) + const dataView = new DataView(bytes.buffer) + + // 先頭4バイトから圧縮データの実際の長さを取得 + const length = dataView.getUint32(0, true) + + // 圧縮データ部分を切り出し + const compressedBytes = bytes.subarray(4, 4 + length) + + // 解凍 + const decompressionStream = new DecompressionStream('deflate') + const decompressedStream = + createReadableStreamFromBytes(compressedBytes).pipeThrough( + decompressionStream, + ) + const decompressedBuffer = await new Response( + decompressedStream, + ).arrayBuffer() + + return decoder.decode(decompressedBuffer) +} diff --git a/frontend/packages/erd-core/src/utils/index.ts b/frontend/packages/erd-core/src/utils/index.ts new file mode 100644 index 00000000..5c12e8b7 --- /dev/null +++ b/frontend/packages/erd-core/src/utils/index.ts @@ -0,0 +1 @@ +export * from './compressionString' From 4859d37e849546470a87fea2b8bc9b2e989ce267 Mon Sep 17 00:00:00 2001 From: MH4GF Date: Thu, 19 Dec 2024 19:58:51 +0900 Subject: [PATCH 2/5] feat: implement handling for hidden node IDs with compression and decompression --- frontend/.changeset/funny-lemons-decide.md | 6 +++ .../ERDContent/useInitialAutoLayout.ts | 40 +++++++++++-------- .../erd-core/src/stores/userEditing/store.ts | 6 ++- 3 files changed, 34 insertions(+), 18 deletions(-) create mode 100644 frontend/.changeset/funny-lemons-decide.md diff --git a/frontend/.changeset/funny-lemons-decide.md b/frontend/.changeset/funny-lemons-decide.md new file mode 100644 index 00000000..158ba41d --- /dev/null +++ b/frontend/.changeset/funny-lemons-decide.md @@ -0,0 +1,6 @@ +--- +"@liam-hq/erd-core": patch +"@liam-hq/cli": patch +--- + +feat: get hidden nodes via query parameter now compresses diff --git a/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useInitialAutoLayout.ts b/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useInitialAutoLayout.ts index 86e50375..cd4bbdd1 100644 --- a/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useInitialAutoLayout.ts +++ b/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useInitialAutoLayout.ts @@ -1,5 +1,6 @@ import type { QueryParam } from '@/schemas/queryParam' import { addHiddenNodeIds, updateActiveTableName } from '@/stores' +import { decompressFromUTF16 } from '@/utils' import { useNodesInitialized } from '@xyflow/react' import { useEffect } from 'react' import { useERDContentContext } from './ERDContentContext' @@ -13,10 +14,13 @@ const getActiveTableNameFromUrl = (): string | undefined => { return tableName || undefined } -const getHiddenNodeIdsFromUrl = (): string[] => { +const getHiddenNodeIdsFromUrl = async (): Promise => { const urlParams = new URLSearchParams(window.location.search) const hiddenQueryParam: QueryParam = 'hidden' - const hiddenNodeIds = urlParams.get(hiddenQueryParam) + const compressed = urlParams.get(hiddenQueryParam) + const hiddenNodeIds = compressed + ? await decompressFromUTF16(compressed) + : undefined return hiddenNodeIds ? hiddenNodeIds.split(',') : [] } @@ -29,21 +33,25 @@ export const useInitialAutoLayout = () => { const { handleLayout } = useAutoLayout() useEffect(() => { - if (initializeComplete) { - return + const initialize = async () => { + if (initializeComplete) { + return + } + + const tableNameFromUrl = getActiveTableNameFromUrl() + updateActiveTableName(tableNameFromUrl) + const hiddenNodeIds = await getHiddenNodeIdsFromUrl() + addHiddenNodeIds(hiddenNodeIds) + + const fitViewOptions = tableNameFromUrl + ? { maxZoom: 1, duration: 300, nodes: [{ id: tableNameFromUrl }] } + : undefined + + if (nodesInitialized) { + handleLayout(fitViewOptions, hiddenNodeIds) + } } - const tableNameFromUrl = getActiveTableNameFromUrl() - updateActiveTableName(tableNameFromUrl) - const hiddenNodeIds = getHiddenNodeIdsFromUrl() - addHiddenNodeIds(hiddenNodeIds) - - const fitViewOptions = tableNameFromUrl - ? { maxZoom: 1, duration: 300, nodes: [{ id: tableNameFromUrl }] } - : undefined - - if (nodesInitialized) { - handleLayout(fitViewOptions, hiddenNodeIds) - } + initialize() }, [nodesInitialized, initializeComplete, handleLayout]) } diff --git a/frontend/packages/erd-core/src/stores/userEditing/store.ts b/frontend/packages/erd-core/src/stores/userEditing/store.ts index 8f8d547c..89a839ef 100644 --- a/frontend/packages/erd-core/src/stores/userEditing/store.ts +++ b/frontend/packages/erd-core/src/stores/userEditing/store.ts @@ -1,5 +1,6 @@ import type { QueryParam } from '@/schemas/queryParam' import type { ShowMode } from '@/schemas/showMode' +import { compressToUTF16 } from '@/utils' import { proxy, subscribe } from 'valtio' import { proxySet } from 'valtio/utils' @@ -33,14 +34,15 @@ subscribe(userEditingStore.active, () => { window.history.pushState({}, '', url) }) -subscribe(userEditingStore.hiddenNodeIds, () => { +subscribe(userEditingStore.hiddenNodeIds, async () => { const url = new URL(window.location.href) const activeQueryParam: QueryParam = 'hidden' const hiddenNodeIds = Array.from(userEditingStore.hiddenNodeIds).join(',') url.searchParams.delete(activeQueryParam) if (hiddenNodeIds) { - url.searchParams.set(activeQueryParam, hiddenNodeIds) + const compressed = await compressToUTF16(hiddenNodeIds) + url.searchParams.set(activeQueryParam, compressed) } window.history.pushState({}, '', url) From 0e04fd390945d2cde5d8e7c038cdc342de798bb4 Mon Sep 17 00:00:00 2001 From: MH4GF Date: Thu, 19 Dec 2024 20:01:16 +0900 Subject: [PATCH 3/5] feat: handle decompression errors for hidden node IDs from URL --- .../components/ERDRenderer/ERDContent/useInitialAutoLayout.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useInitialAutoLayout.ts b/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useInitialAutoLayout.ts index cd4bbdd1..add72d5f 100644 --- a/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useInitialAutoLayout.ts +++ b/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useInitialAutoLayout.ts @@ -19,7 +19,7 @@ const getHiddenNodeIdsFromUrl = async (): Promise => { const hiddenQueryParam: QueryParam = 'hidden' const compressed = urlParams.get(hiddenQueryParam) const hiddenNodeIds = compressed - ? await decompressFromUTF16(compressed) + ? await decompressFromUTF16(compressed).catch(() => undefined) : undefined return hiddenNodeIds ? hiddenNodeIds.split(',') : [] From 8a2a9c429edca91d9dc8f2c0c09b4a3b8049bbbd Mon Sep 17 00:00:00 2001 From: MH4GF Date: Thu, 19 Dec 2024 20:03:35 +0900 Subject: [PATCH 4/5] chore: translate to English --- .../erd-core/src/utils/compressionString.ts | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/frontend/packages/erd-core/src/utils/compressionString.ts b/frontend/packages/erd-core/src/utils/compressionString.ts index beaab9f5..b7d2f04a 100644 --- a/frontend/packages/erd-core/src/utils/compressionString.ts +++ b/frontend/packages/erd-core/src/utils/compressionString.ts @@ -13,54 +13,54 @@ export async function compressToUTF16(input: string): Promise { const encoder = new TextEncoder() const inputBytes = encoder.encode(input) - // Deflate圧縮 + // Deflate compression const compressionStream = new CompressionStream('deflate') const compressedStream = createReadableStreamFromBytes(inputBytes).pipeThrough(compressionStream) const compressedBuffer = await new Response(compressedStream).arrayBuffer() const compressedBytes = new Uint8Array(compressedBuffer) - // 圧縮後のバイト長を格納するため、先頭4バイトに長さ情報を入れる + // Store the length of the compressed bytes in the first 4 bytes const length = compressedBytes.length - const totalLength = 4 + length // 最初の4バイトで長さを格納 - // 2バイト(UTF-16コードユニット)境界に揃えるため、必要なら1バイトパディング + const totalLength = 4 + length // Store length in the first 4 bytes + // Align to 2-byte (UTF-16 code unit) boundary, add 1 byte padding if necessary const paddedLength = totalLength % 2 === 0 ? totalLength : totalLength + 1 const resultBytes = new Uint8Array(paddedLength) const dataView = new DataView(resultBytes.buffer) - // 先頭4バイトに長さ情報を格納 (Little Endian) + // Store length information in the first 4 bytes (Little Endian) dataView.setUint32(0, length, true) - // その後に圧縮データ本体を格納 + // Store the compressed data after the first 4 bytes resultBytes.set(compressedBytes, 4) - // UTF-16コードユニットとして解釈するためにUint16Arrayに変換 + // Convert to Uint16Array to interpret as UTF-16 code units const uint16Array = new Uint16Array(resultBytes.buffer) - // 各Uint16要素をコードユニットとして文字列化 + // Convert each Uint16 element to a character return String.fromCharCode(...Array.from(uint16Array)) } export async function decompressFromUTF16(input: string): Promise { const decoder = new TextDecoder() - // UTF-16コードユニット列をUint16Arrayとして再構成 + // Reconstruct Uint16Array from UTF-16 code units const codeUnits = new Uint16Array(input.length) for (let i = 0; i < input.length; i++) { codeUnits[i] = input.charCodeAt(i) } - // Uint16ArrayからUint8Arrayを参照取得 + // Reference Uint8Array from Uint16Array const bytes = new Uint8Array(codeUnits.buffer) const dataView = new DataView(bytes.buffer) - // 先頭4バイトから圧縮データの実際の長さを取得 + // Get the actual length of the compressed data from the first 4 bytes const length = dataView.getUint32(0, true) - // 圧縮データ部分を切り出し + // Extract the compressed data part const compressedBytes = bytes.subarray(4, 4 + length) - // 解凍 + // Decompress const decompressionStream = new DecompressionStream('deflate') const decompressedStream = createReadableStreamFromBytes(compressedBytes).pipeThrough( From 0838e34daadbb720e9dd6082dbbf5a09e78476e5 Mon Sep 17 00:00:00 2001 From: MH4GF Date: Thu, 19 Dec 2024 20:25:25 +0900 Subject: [PATCH 5/5] feat: refactor compression utilities to support Base64 and URL-safe encoding --- .../ERDContent/useInitialAutoLayout.ts | 4 +- .../erd-core/src/stores/userEditing/store.ts | 4 +- .../src/utils/compressionString.test.ts | Bin 1792 -> 2493 bytes .../erd-core/src/utils/compressionString.ts | 142 +++++++++++------- 4 files changed, 95 insertions(+), 55 deletions(-) diff --git a/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useInitialAutoLayout.ts b/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useInitialAutoLayout.ts index add72d5f..b167c8b4 100644 --- a/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useInitialAutoLayout.ts +++ b/frontend/packages/erd-core/src/components/ERDRenderer/ERDContent/useInitialAutoLayout.ts @@ -1,6 +1,6 @@ import type { QueryParam } from '@/schemas/queryParam' import { addHiddenNodeIds, updateActiveTableName } from '@/stores' -import { decompressFromUTF16 } from '@/utils' +import { decompressFromEncodedURIComponent } from '@/utils' import { useNodesInitialized } from '@xyflow/react' import { useEffect } from 'react' import { useERDContentContext } from './ERDContentContext' @@ -19,7 +19,7 @@ const getHiddenNodeIdsFromUrl = async (): Promise => { const hiddenQueryParam: QueryParam = 'hidden' const compressed = urlParams.get(hiddenQueryParam) const hiddenNodeIds = compressed - ? await decompressFromUTF16(compressed).catch(() => undefined) + ? await decompressFromEncodedURIComponent(compressed).catch(() => undefined) : undefined return hiddenNodeIds ? hiddenNodeIds.split(',') : [] diff --git a/frontend/packages/erd-core/src/stores/userEditing/store.ts b/frontend/packages/erd-core/src/stores/userEditing/store.ts index 89a839ef..edabad13 100644 --- a/frontend/packages/erd-core/src/stores/userEditing/store.ts +++ b/frontend/packages/erd-core/src/stores/userEditing/store.ts @@ -1,6 +1,6 @@ import type { QueryParam } from '@/schemas/queryParam' import type { ShowMode } from '@/schemas/showMode' -import { compressToUTF16 } from '@/utils' +import { compressToEncodedURIComponent } from '@/utils' import { proxy, subscribe } from 'valtio' import { proxySet } from 'valtio/utils' @@ -41,7 +41,7 @@ subscribe(userEditingStore.hiddenNodeIds, async () => { url.searchParams.delete(activeQueryParam) if (hiddenNodeIds) { - const compressed = await compressToUTF16(hiddenNodeIds) + const compressed = await compressToEncodedURIComponent(hiddenNodeIds) url.searchParams.set(activeQueryParam, compressed) } diff --git a/frontend/packages/erd-core/src/utils/compressionString.test.ts b/frontend/packages/erd-core/src/utils/compressionString.test.ts index aec8dea2f476222f278c8d634d6095ef90ab227f..5fd69d6b5d5a8cbf6d35b2f0562ef249d7520de2 100644 GIT binary patch literal 2493 zcmds2%Z}nk6y5U`mDxZ`G~r9_WYmnW#6h$V1uncMFHqr7XknT(UFw_2w}FSeq{=Rovx2vA89@enKv}|{tdcGQ%$biB$lQ2 z5tuxoZl? zuk{)1!+~%A1NUIIKv0Mv&6SG9;$>%@TjW*sX51f(yLM0(^k&BtgrRSst^FB%jo2)6 zAqJ)c2{b7p(My{1u>W}Y=Hp@i<6-~9VgJ+N!~WNwzyIyKZ-4(~|H}LJI5hxx90xm5 z+BH}6YeigLD-CwUJ9Yx3h~!!kY@~5`gC?GGUAIS#a%;`n{Bf=W@SkVJ!u26ZfCVXh zK{cKhLg8@}HCPoJY}w?V(4a53N^nI*MbTCU6#eqN4ijy}fKhQ11+ zif;!qy{&2!+@9$Ca#R}hr$uX43#PmcB{eeE;d-%dhqu#`2g}PXr+BORVA$qw(PG0P zAJ@>3RQdT%nG1o^7!~`?oh!S++Z)f=4Bb}T8?W#{EArh`CQ4#f`f1&cx^vakaX6jK zlO(#kn^z|#empIW&EmkDR%joVtcqH;mdkpL(}gASIzo43ID*rZWTUHkl4RAi3|p$}Od{XuHAZ%<#;z=zoaS{DnY$VLy_lh}wZt{~I;U-y*D^O; SZu>;42Cbs}ul_8yi2ndWNlpR) delta 313 zcmdlh+`u=%OidvKTW30Cnxfro&ndq~qb5#&;7M z-u3T)+1L1@d-1!z$* { - const encoder = new TextEncoder() - const inputBytes = encoder.encode(input) - - // Deflate compression - const compressionStream = new CompressionStream('deflate') - const compressedStream = - createReadableStreamFromBytes(inputBytes).pipeThrough(compressionStream) - const compressedBuffer = await new Response(compressedStream).arrayBuffer() - const compressedBytes = new Uint8Array(compressedBuffer) - - // Store the length of the compressed bytes in the first 4 bytes - const length = compressedBytes.length - const totalLength = 4 + length // Store length in the first 4 bytes - // Align to 2-byte (UTF-16 code unit) boundary, add 1 byte padding if necessary - const paddedLength = totalLength % 2 === 0 ? totalLength : totalLength + 1 - - const resultBytes = new Uint8Array(paddedLength) - const dataView = new DataView(resultBytes.buffer) +/** + * Perform Deflate compression and return a Uint8Array + */ +async function deflateCompress(inputBytes: Uint8Array): Promise { + const compression = new CompressionStream('deflate') + const stream = + createReadableStreamFromBytes(inputBytes).pipeThrough(compression) + const compressedBuffer = await new Response(stream).arrayBuffer() + return new Uint8Array(compressedBuffer) +} - // Store length information in the first 4 bytes (Little Endian) - dataView.setUint32(0, length, true) - // Store the compressed data after the first 4 bytes - resultBytes.set(compressedBytes, 4) +/** + * Perform Deflate decompression and return a Uint8Array + */ +async function deflateDecompress(inputBytes: Uint8Array): Promise { + const decompression = new DecompressionStream('deflate') + const stream = + createReadableStreamFromBytes(inputBytes).pipeThrough(decompression) + const decompressedBuffer = await new Response(stream).arrayBuffer() + return new Uint8Array(decompressedBuffer) +} - // Convert to Uint16Array to interpret as UTF-16 code units - const uint16Array = new Uint16Array(resultBytes.buffer) +/** + * 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) +} - // Convert each Uint16 element to a character - return String.fromCharCode(...Array.from(uint16Array)) +/** + * 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 } -export async function decompressFromUTF16(input: string): Promise { - const decoder = new TextDecoder() +/** + * Convert Base64 string to URL-safe string (`+` => `-`, `/` => `_`, remove `=`) + */ +function base64ToUrlSafe(base64: string): string { + return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') +} - // Reconstruct Uint16Array from UTF-16 code units - const codeUnits = new Uint16Array(input.length) - for (let i = 0; i < input.length; i++) { - codeUnits[i] = input.charCodeAt(i) +/** + * 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 +} - // Reference Uint8Array from Uint16Array - const bytes = new Uint8Array(codeUnits.buffer) - const dataView = new DataView(bytes.buffer) - - // Get the actual length of the compressed data from the first 4 bytes - const length = dataView.getUint32(0, true) +/** + * Compress string using Deflate and return as Base64 + */ +async function compressToBase64(input: string): Promise { + const textEncoder = new TextEncoder() + const inputBytes = textEncoder.encode(input) + const compressedBytes = await deflateCompress(inputBytes) + return bytesToBase64(compressedBytes) +} - // Extract the compressed data part - const compressedBytes = bytes.subarray(4, 4 + length) +/** + * Decompress Base64 string and return the original string + */ +async function decompressFromBase64(input: string): Promise { + const textDecoder = new TextDecoder() + const compressedBytes = base64ToBytes(input) + const decompressedBytes = await deflateDecompress(compressedBytes) + return textDecoder.decode(decompressedBytes) +} - // Decompress - const decompressionStream = new DecompressionStream('deflate') - const decompressedStream = - createReadableStreamFromBytes(compressedBytes).pipeThrough( - decompressionStream, - ) - const decompressedBuffer = await new Response( - decompressedStream, - ).arrayBuffer() +/** + * Compress string using Deflate and return as URL-safe encoded string (based on Base64) + */ +export async function compressToEncodedURIComponent( + input: string, +): Promise { + const base64 = await compressToBase64(input) + return base64ToUrlSafe(base64) +} - return decoder.decode(decompressedBuffer) +/** + * Restore original string from URL-safe encoded string + */ +export async function decompressFromEncodedURIComponent( + input: string, +): Promise { + const base64 = urlSafeToBase64(input) + return decompressFromBase64(base64) }