diff --git a/.changeset/good-jobs-type.md b/.changeset/good-jobs-type.md new file mode 100644 index 0000000..cb50d04 --- /dev/null +++ b/.changeset/good-jobs-type.md @@ -0,0 +1,6 @@ +--- +"@urlpack/base10": major +"@urlpack/qrjson": major +--- + +New codecs for QR Codes URL (See https://huonw.github.io/blog/2024/03/qr-base10-base64/) diff --git a/packages/base10/.gitignore b/packages/base10/.gitignore new file mode 100644 index 0000000..12c18d4 --- /dev/null +++ b/packages/base10/.gitignore @@ -0,0 +1 @@ +/lib/ diff --git a/packages/base10/LICENSE b/packages/base10/LICENSE new file mode 100644 index 0000000..fcb15cf --- /dev/null +++ b/packages/base10/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Danggeun Market Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/base10/README.md b/packages/base10/README.md new file mode 100644 index 0000000..4caeb63 --- /dev/null +++ b/packages/base10/README.md @@ -0,0 +1,15 @@ +# @urlpack/base10 + +[![Package Version](https://img.shields.io/npm/v/@urlpack/base10)](https://npm.im/@urlpack/base10) +[![License](https://img.shields.io/npm/l/@urlpack/base10)](#License) +[![Bundle Size](https://img.shields.io/bundlephobia/minzip/@urlpack/base10)](https://bundlephobia.com/package/@urlpack/base10) + +Pure JavaScript implementation of the Base10 codec. + +- Zero dependencies +- ES Modules & Browser compatible +- Tree-shakable encoder and decoder + +## License + +MIT diff --git a/packages/base10/package.json b/packages/base10/package.json new file mode 100644 index 0000000..068802a --- /dev/null +++ b/packages/base10/package.json @@ -0,0 +1,48 @@ +{ + "name": "@urlpack/base10", + "version": "0.0.0", + "license": "MIT", + "homepage": "https://github.com/daangn/urlpack/tree/main/packages/base10", + "repository": { + "type": "git", + "url": "https://github.com/daangn/urlpack.git", + "directory": "packages/base10" + }, + "source": "./src/index.ts", + "type": "module", + "main": "./src/index.ts", + "module": "./lib/index.mjs", + "types": "./lib/index.d.ts", + "exports": { + ".": { + "types": "./lib/index.d.ts", + "import": "./lib/index.mjs", + "require": "./lib/index.cjs" + }, + "./package.json": "./package.json" + }, + "publishConfig": { + "access": "public", + "main": "./lib/index.mjs" + }, + "files": [ + "src", + "lib" + ], + "scripts": { + "prepack": "yarn build", + "build": "nanobundle build", + "test": "uvu -r tsm", + "test:watch": "yarn test || true && watchlist src tests -- yarn test" + }, + "dependencies": { + "@urlpack/base-codec": "workspace:^2.0.0" + }, + "devDependencies": { + "nanobundle": "^2.0.0", + "tsm": "^2.3.0", + "typescript": "^5.4.3", + "uvu": "^0.5.6", + "watchlist": "^0.3.1" + } +} diff --git a/packages/base10/src/decode.ts b/packages/base10/src/decode.ts new file mode 100644 index 0000000..0e84af9 --- /dev/null +++ b/packages/base10/src/decode.ts @@ -0,0 +1,6 @@ +import { makeBaseDecoder } from '@urlpack/base-codec'; + +import { baseAlphabet } from './util'; + +const defaultDecoder = makeBaseDecoder(baseAlphabet); +export const decode: (encoding: string) => Uint8Array = defaultDecoder.decode; diff --git a/packages/base10/src/encode.ts b/packages/base10/src/encode.ts new file mode 100644 index 0000000..9264dea --- /dev/null +++ b/packages/base10/src/encode.ts @@ -0,0 +1,6 @@ +import { makeBaseEncoder } from '@urlpack/base-codec'; + +import { baseAlphabet } from './util'; + +const defaultEncoder = makeBaseEncoder(baseAlphabet); +export const encode: (binary: Uint8Array) => string = defaultEncoder.encode; diff --git a/packages/base10/src/index.ts b/packages/base10/src/index.ts new file mode 100644 index 0000000..2366832 --- /dev/null +++ b/packages/base10/src/index.ts @@ -0,0 +1,2 @@ +export * from './encode'; +export * from './decode'; diff --git a/packages/base10/src/util.ts b/packages/base10/src/util.ts new file mode 100644 index 0000000..87f9d7a --- /dev/null +++ b/packages/base10/src/util.ts @@ -0,0 +1 @@ +export const baseAlphabet = '0123456789'; diff --git a/packages/base10/tests/codec.spec.ts b/packages/base10/tests/codec.spec.ts new file mode 100644 index 0000000..2be1d68 --- /dev/null +++ b/packages/base10/tests/codec.spec.ts @@ -0,0 +1,35 @@ +import { test } from 'uvu'; +import * as assert from 'uvu/assert'; + +import { encode, decode } from '@urlpack/base10'; + +const textEncoder = new TextEncoder(); + +const cases: Array<[binary: Uint8Array, text: string]> = [ + [ + textEncoder.encode('안녕하세요!'), + '314474236304828881015048610782331442209', + ], + [ + textEncoder.encode( + 'The quick brown fox jumps over the lazy dog.', + ), + '3024830571690175283291907639196436031967763819210983988162282536502237781693262640684650930677706176554798', + ], + [ + new Uint8Array([0x00, 0x00, 0x28, 0x7f, 0xb4, 0xcd]), + '00679457997', + ], +]; + +for (const [binary, text] of cases) { + test('base10.encode', () => { + assert.equal(encode(binary), text); + }); + + test('base10.decode', () => { + assert.equal(decode(text), binary); + }); +} + +test.run(); diff --git a/packages/base10/tsconfig.json b/packages/base10/tsconfig.json new file mode 100644 index 0000000..f4d09e2 --- /dev/null +++ b/packages/base10/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "declarationDir": "lib" + }, + "include": [ + "src" + ] +} diff --git a/packages/qrjson/.gitignore b/packages/qrjson/.gitignore new file mode 100644 index 0000000..12c18d4 --- /dev/null +++ b/packages/qrjson/.gitignore @@ -0,0 +1 @@ +/lib/ diff --git a/packages/qrjson/LICENSE b/packages/qrjson/LICENSE new file mode 100644 index 0000000..fcb15cf --- /dev/null +++ b/packages/qrjson/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Danggeun Market Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/qrjson/README.md b/packages/qrjson/README.md new file mode 100644 index 0000000..a33f81d --- /dev/null +++ b/packages/qrjson/README.md @@ -0,0 +1,35 @@ +# @urlpack/qrjson + +[![Package Version](https://img.shields.io/npm/v/@urlpack/qrjson)](https://npm.im/@urlpack/qrjson) +[![License](https://img.shields.io/npm/l/@urlpack/qrjson)](#License) +[![Bundle Size](https://img.shields.io/bundlephobia/minzip/@urlpack/qrjson)](https://bundlephobia.com/package/@urlpack/qrjson) + +Compress JSON data into compact & optimized to URL for QR Codes + +- ES Modules & Browser compatible +- Compact output using [MessagePack](https://msgpack.org/) +- Use Base10 encoding to get clear QR Code image + +## Usage + +```ts +import { makeQrJsonEncoder } from '@urlpack/qrjson'; + +const encoder = makeQrJsonEncoder(); + +encoder.encode({ + href: 'http://daangn.com', + uid: 1234567, + context: { + foo: 'bar', + baz: [1, 2, 3, 4, 5], + }, +}); +// => 'QL3sGqgSwhebCV6jsPsxSCG6DPGZUAo7qtLbEFxFN3bequ3qABcg6pxvpvr36FveMxCtD4zNSWSpHmxgz8' +// +// Only 82 characters, 35% smaller output than JSON.stringify + lz-string +``` + +## LICENSE + +MIT diff --git a/packages/qrjson/package.json b/packages/qrjson/package.json new file mode 100644 index 0000000..8394c96 --- /dev/null +++ b/packages/qrjson/package.json @@ -0,0 +1,50 @@ +{ + "name": "@urlpack/qrjson", + "version": "0.0.0", + "license": "MIT", + "homepage": "https://github.com/daangn/urlpack/tree/main/packages/qrjson", + "repository": { + "type": "git", + "url": "https://github.com/daangn/urlpack.git", + "directory": "packages/qrjson" + }, + "source": "./src/index.ts", + "type": "module", + "main": "./src/index.ts", + "module": "./lib/index.mjs", + "types": "./lib/index.d.ts", + "exports": { + ".": { + "types": "./lib/index.d.ts", + "import": "./lib/index.mjs", + "require": "./lib/index.cjs" + }, + "./package.json": "./package.json" + }, + "publishConfig": { + "access": "public", + "main": "./lib/index.mjs" + }, + "files": [ + "src", + "lib" + ], + "scripts": { + "prepack": "yarn build", + "build": "nanobundle build", + "test": "uvu -r tsm", + "test:watch": "yarn test || true && watchlist src tests -- yarn test" + }, + "dependencies": { + "@urlpack/base10": "workspace:^0.0.0", + "@urlpack/msgpack": "workspace:^2.0.0" + }, + "devDependencies": { + "@urlpack/base-codec": "workspace:^2.0.0", + "nanobundle": "^2.0.0", + "tsm": "^2.3.0", + "typescript": "^5.4.3", + "uvu": "^0.5.6", + "watchlist": "^0.3.1" + } +} diff --git a/packages/qrjson/src/decoder.ts b/packages/qrjson/src/decoder.ts new file mode 100644 index 0000000..060d268 --- /dev/null +++ b/packages/qrjson/src/decoder.ts @@ -0,0 +1,12 @@ +import { decode as decodeBase10 } from '@urlpack/base10'; +import { makeMessagePackDecoder } from '@urlpack/msgpack'; + +export function makeQrJsonDecoder(): { + decode: (str: string) => Data, +} { + const decodeString = decodeBase10; + const decodeBinary = makeMessagePackDecoder().decode; + return { + decode: str => decodeBinary(decodeString(str)) as Data, + }; +} diff --git a/packages/qrjson/src/encoder.ts b/packages/qrjson/src/encoder.ts new file mode 100644 index 0000000..27fd07e --- /dev/null +++ b/packages/qrjson/src/encoder.ts @@ -0,0 +1,12 @@ +import { encode as encodeToBase10 } from '@urlpack/base10'; +import { makeMessagePackEncoder } from '@urlpack/msgpack'; + +export function makeQrJsonEncoder(): { + encode: (data: Data) => string, +} { + const encodeData = makeMessagePackEncoder().encode; + const encodeBinary = encodeToBase10; + return { + encode: data => encodeBinary(encodeData(data as any)), + }; +} diff --git a/packages/qrjson/src/index.ts b/packages/qrjson/src/index.ts new file mode 100644 index 0000000..33d824d --- /dev/null +++ b/packages/qrjson/src/index.ts @@ -0,0 +1,2 @@ +export * from './encoder'; +export * from './decoder'; diff --git a/packages/qrjson/tests/codec.spec.ts b/packages/qrjson/tests/codec.spec.ts new file mode 100644 index 0000000..4101146 --- /dev/null +++ b/packages/qrjson/tests/codec.spec.ts @@ -0,0 +1,26 @@ +import { test } from 'uvu'; +import * as assert from 'uvu/assert'; + +import { makeBaseEncoder, makeBaseDecoder } from '@urlpack/base-codec'; +import { makeQrJsonEncoder, makeQrJsonDecoder } from '@urlpack/qrjson'; + +test('pack json', () => { + const data = { + href: 'http://daangn.com', + uid: 1234567, + context: { + foo: 'bar', + baz: [1, 2, 3, 4, 5], + }, + }; + + const { encode } = makeQrJsonEncoder(); + const { decode } = makeQrJsonDecoder(); + + const stored = encode(data); + console.log(stored); + + assert.equal(decode(stored), data); +}); + +test.run(); diff --git a/packages/qrjson/tsconfig.json b/packages/qrjson/tsconfig.json new file mode 100644 index 0000000..f4d09e2 --- /dev/null +++ b/packages/qrjson/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "declarationDir": "lib" + }, + "include": [ + "src" + ] +} diff --git a/yarn.lock b/yarn.lock index c6b1d24..1897100 100644 --- a/yarn.lock +++ b/yarn.lock @@ -820,6 +820,19 @@ __metadata: languageName: unknown linkType: soft +"@urlpack/base10@workspace:^0.0.0, @urlpack/base10@workspace:packages/base10": + version: 0.0.0-use.local + resolution: "@urlpack/base10@workspace:packages/base10" + dependencies: + "@urlpack/base-codec": "workspace:^2.0.0" + nanobundle: "npm:^2.0.0" + tsm: "npm:^2.3.0" + typescript: "npm:^5.4.3" + uvu: "npm:^0.5.6" + watchlist: "npm:^0.3.1" + languageName: unknown + linkType: soft + "@urlpack/base58@workspace:^2.0.0, @urlpack/base58@workspace:packages/base58": version: 0.0.0-use.local resolution: "@urlpack/base58@workspace:packages/base58" @@ -899,6 +912,21 @@ __metadata: languageName: unknown linkType: soft +"@urlpack/qrjson@workspace:packages/qrjson": + version: 0.0.0-use.local + resolution: "@urlpack/qrjson@workspace:packages/qrjson" + dependencies: + "@urlpack/base-codec": "workspace:^2.0.0" + "@urlpack/base10": "workspace:^0.0.0" + "@urlpack/msgpack": "workspace:^2.0.0" + nanobundle: "npm:^2.0.0" + tsm: "npm:^2.3.0" + typescript: "npm:^5.4.3" + uvu: "npm:^0.5.6" + watchlist: "npm:^0.3.1" + languageName: unknown + linkType: soft + "abbrev@npm:1": version: 1.1.1 resolution: "abbrev@npm:1.1.1"