diff --git a/.github/logo_dark.svg b/.github/logo_dark.svg new file mode 100644 index 0000000..96320bb --- /dev/null +++ b/.github/logo_dark.svg @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/logo_light.svg b/.github/logo_light.svg new file mode 100644 index 0000000..e3ca4df --- /dev/null +++ b/.github/logo_light.svg @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9cfa4b7..576f120 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,32 +2,29 @@ name: CI on: push: - branches: [main] - pull_request: {} + workflow_call: jobs: - test: - name: Node.js v${{ matrix.node }} + deno: runs-on: ubuntu-latest - strategy: - matrix: - node: [16, 18, 20] steps: - - uses: actions/checkout@main + - uses: actions/checkout@v4 + - uses: denoland/setup-deno@v1 - - name: (env) setup pnpm - uses: pnpm/action-setup@master - with: - version: 8.6.5 + - run: deno fmt --check + - run: deno lint + - run: deno check **/*.ts - - name: (env) setup node v${{ matrix.node }} - uses: actions/setup-node@main - with: - node-version: ${{ matrix.node }} - cache: pnpm - check-latest: true + - name: Tests + run: |- + deno test --coverage=cov/ + deno coverage cov/ - - run: pnpm install --ignore-scripts - - run: pnpm run build - - run: pnpm run test - - run: pnpm run typecheck + npm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: denoland/setup-deno@v1 + + - run: mkdir -p npm + - run: deno task build diff --git a/.gitignore b/.gitignore index bbb492a..6b0cded 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,5 @@ -node_modules .DS_Store -*-lock.json -*.lock -*.log - -/coverage -/.nyc_output - -# Editors -*.iml -/.idea -/.vscode - -# Code -/node.* -/browser.* -/json.* -/utils.* +node_modules +/npm +*.lcov +/cov \ No newline at end of file diff --git a/TEMP/bench.ts b/TEMP/bench.ts new file mode 100644 index 0000000..e92f893 --- /dev/null +++ b/TEMP/bench.ts @@ -0,0 +1,18 @@ +import * as v4 from 'npm:diary@^0.4'; +import * as v5 from '../mod.ts'; + +v4.enable('*'); + +let log = v4.diary('test', (e) => { + JSON.stringify(e); +}); +Deno.bench('v4', () => { + log.info('hello', { name: 'world' }); +}); + +let logv5 = v5.diary((e) => { + JSON.stringify(e); +}); +Deno.bench('v5', () => { + logv5('info', 'hello {name}', { name: 'world' }); +}); diff --git a/TEMP/migration.md b/TEMP/migration.md new file mode 100644 index 0000000..27226e6 --- /dev/null +++ b/TEMP/migration.md @@ -0,0 +1,6 @@ +Removed the `enable` function from the `diary` package. + +```diff +- import { enable } from 'diary'; +- enable('*'); +``` diff --git a/TEMP/migration.ts b/TEMP/migration.ts new file mode 100644 index 0000000..39a3580 --- /dev/null +++ b/TEMP/migration.ts @@ -0,0 +1,42 @@ +import * as v4 from 'npm:diary@^0.4'; +import * as v5 from '../mod.ts'; + +let v4Events: any[] = []; +let v5Events: any[] = []; + +{ + v4.enable('*'); + const log = v4.diary('v0.4', (event) => { + v4Events.push(event); + }); + + log.log('hello %s', 'world', 'extra', 'props'); + log.debug('hello %s', 'world', 'extra', 'props'); + log.info('hello %s', 'world', 'extra', 'props'); + log.warn('hello %s', 'world', 'extra', 'props'); + log.error('hello %s', 'world', 'extra', 'props'); + log.fatal('hello %s', 'world', 'extra', 'props'); +} + +{ + const log = v5.diary((level, event, props) => { + v5Events.push({ name: 'v0.5', level, messages: [event, props] }); + }); + + log('debug', 'hello'); + log('log', 'hello {phrase}', { phrase: 'world', extra: ['extra', 'props'] }); + log('debug', 'hello {phrase}', { phrase: 'world', extra: ['extra', 'props'] }); + log('info', 'hello {phrase}', { phrase: 'world', extra: ['extra', 'props'] }); + log('warn', 'hello {phrase}', { phrase: 'world', extra: ['extra', 'props'] }); + log('error', 'hello {phrase}', { phrase: 'world', extra: ['extra', 'props'] }); + log('fatal', 'hello {phrase} the time is {time}', { + phrase: 'world', + extra: ['extra', 'props'], + time: 123, + }); +} + +console.log({ + v4Events, + v5Events, +}); diff --git a/bench/index.ts b/bench/index.ts deleted file mode 100644 index c8415b7..0000000 --- a/bench/index.ts +++ /dev/null @@ -1,112 +0,0 @@ -// @ts-nocheck - -import { suite } from '@marais/bench'; -import bunyan from 'bunyan'; -import debug from 'debug'; -import pino from 'pino'; -import fs from 'fs'; -import { diary } from '../diary/node/index.js'; - -console.log('JIT'); -await suite({ - diary() { - const ws = fs.createWriteStream('/dev/null'); - const sink = (event) => { - ws.write(JSON.stringify(event)); - }; - - return () => { - const suite = diary('standard', sink); - suite.info('info message'); - }; - }, - pino() { - const sink = pino.destination({ - dest: '/dev/null', - minLength: 0, - sync: true, - }); - - return () => { - const suite = pino(sink); - suite.info('info message'); - }; - }, - bunyan() { - const sink = fs.createWriteStream('/dev/null'); - - return () => { - const suite = bunyan.createLogger({ - name: 'standard', - stream: sink, - }); - suite.info('info message'); - }; - }, - debug() { - const ws = fs.createWriteStream('/dev/null'); - const sink = (event) => { - ws.write(event); - }; - - return () => { - const suite = debug('standard'); - suite.enabled = true; - suite.log = sink; - suite('info message'); - }; - }, -}); - -console.log('\nAOT'); -await suite({ - diary() { - const ws = fs.createWriteStream('/dev/null'); - const sink = (event) => { - ws.write(JSON.stringify(event)); - }; - const suite = diary('standard', sink); - - return () => { - suite.info('info message'); - }; - }, - pino() { - const sink = pino.destination({ - dest: '/dev/null', - minLength: 0, - sync: true, - }); - - const suite = pino(sink); - - return () => { - suite.info('info message'); - }; - }, - bunyan() { - const sink = fs.createWriteStream('/dev/null'); - const suite = bunyan.createLogger({ - name: 'standard', - stream: sink, - }); - - return () => { - suite.info('info message'); - }; - }, - debug() { - const ws = fs.createWriteStream('/dev/null'); - const sink = (event) => { - ws.write(event); - }; - - const suite = debug('standard'); - suite.enabled = true; - suite.log = sink; - - return () => { - suite('info message'); - }; - }, -}); diff --git a/bench/package.json b/bench/package.json deleted file mode 100644 index 68375c8..0000000 --- a/bench/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "private": true, - "type": "modue", - "dependencies": { - "@graphile/logger": "0.2.0", - "@marais/bench": "0.0.6", - "bunyan": "1.8.15", - "debug": "4.3.4", - "pino": "8.14.1" - } -} diff --git a/bench/pnpm-lock.yaml b/bench/pnpm-lock.yaml deleted file mode 100644 index 1021b8a..0000000 --- a/bench/pnpm-lock.yaml +++ /dev/null @@ -1,328 +0,0 @@ -lockfileVersion: '6.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -dependencies: - '@graphile/logger': - specifier: 0.2.0 - version: 0.2.0 - '@marais/bench': - specifier: 0.0.6 - version: 0.0.6 - bunyan: - specifier: 1.8.15 - version: 1.8.15 - debug: - specifier: 4.3.4 - version: 4.3.4 - pino: - specifier: 8.14.1 - version: 8.14.1 - -packages: - - /@graphile/logger@0.2.0: - resolution: {integrity: sha512-jjcWBokl9eb1gVJ85QmoaQ73CQ52xAaOCF29ukRbYNl6lY+ts0ErTaDYOBlejcbUs2OpaiqYLO5uDhyLFzWw4w==} - dev: false - - /@marais/bench@0.0.6: - resolution: {integrity: sha512-LGrWUEmT+S+szQUxlTJjkthUfHThlzLECinaCmjYAZ4sadEgWlFhLlMu0P040A9H4V9jP6zHUw5aFmb3yoAbww==} - dependencies: - '@thi.ng/bench': 3.2.11 - dev: false - - /@thi.ng/api@8.8.1: - resolution: {integrity: sha512-ugTtl3dvOuRsLAF9hZcd/ULBXDG0cAacEQ26jRY00JEEwdy24WR1DOO4iL2mHei0vm2HyvfJ8IlJoRR7mSSqUA==} - engines: {node: '>=12.7'} - dev: false - - /@thi.ng/bench@3.2.11: - resolution: {integrity: sha512-1SSCfwbIXJ9KUIjAaLZEmKSC5OCQf5hDpmzLXlhDFr1SlOaBYXgtu+k8GrSRqmu2kezTV0u23nfW7LSJ5YX2ZA==} - engines: {node: '>=12.7'} - dependencies: - '@thi.ng/api': 8.8.1 - dev: false - - /abort-controller@3.0.0: - resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} - engines: {node: '>=6.5'} - dependencies: - event-target-shim: 5.0.1 - dev: false - - /atomic-sleep@1.0.0: - resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} - engines: {node: '>=8.0.0'} - dev: false - - /balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: false - optional: true - - /base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - dev: false - - /brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - dev: false - optional: true - - /buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - dev: false - - /bunyan@1.8.15: - resolution: {integrity: sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==} - engines: {'0': node >=0.10.0} - hasBin: true - optionalDependencies: - dtrace-provider: 0.8.8 - moment: 2.29.4 - mv: 2.1.1 - safe-json-stringify: 1.2.0 - dev: false - - /concat-map@0.0.1: - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} - dev: false - optional: true - - /debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - dev: false - - /dtrace-provider@0.8.8: - resolution: {integrity: sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==} - engines: {node: '>=0.10'} - requiresBuild: true - dependencies: - nan: 2.15.0 - dev: false - optional: true - - /event-target-shim@5.0.1: - resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} - engines: {node: '>=6'} - dev: false - - /events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} - dev: false - - /fast-redact@3.2.0: - resolution: {integrity: sha512-zaTadChr+NekyzallAMXATXLOR8MNx3zqpZ0MUF2aGf4EathnG0f32VLODNlY8IuGY3HoRO2L6/6fSzNsLaHIw==} - engines: {node: '>=6'} - dev: false - - /glob@6.0.4: - resolution: {integrity: sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=} - dependencies: - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.0.4 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: false - optional: true - - /ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - dev: false - - /inflight@1.0.6: - resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=} - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - dev: false - optional: true - - /inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: false - optional: true - - /minimatch@3.0.4: - resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==} - dependencies: - brace-expansion: 1.1.11 - dev: false - optional: true - - /minimist@1.2.5: - resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==} - dev: false - optional: true - - /mkdirp@0.5.5: - resolution: {integrity: sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==} - hasBin: true - dependencies: - minimist: 1.2.5 - dev: false - optional: true - - /moment@2.29.4: - resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} - requiresBuild: true - dev: false - optional: true - - /ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - dev: false - - /mv@2.1.1: - resolution: {integrity: sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==} - engines: {node: '>=0.8.0'} - requiresBuild: true - dependencies: - mkdirp: 0.5.5 - ncp: 2.0.0 - rimraf: 2.4.5 - dev: false - optional: true - - /nan@2.15.0: - resolution: {integrity: sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==} - dev: false - optional: true - - /ncp@2.0.0: - resolution: {integrity: sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=} - hasBin: true - dev: false - optional: true - - /on-exit-leak-free@2.1.0: - resolution: {integrity: sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==} - dev: false - - /once@1.4.0: - resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} - dependencies: - wrappy: 1.0.2 - dev: false - optional: true - - /path-is-absolute@1.0.1: - resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} - engines: {node: '>=0.10.0'} - dev: false - optional: true - - /pino-abstract-transport@1.0.0: - resolution: {integrity: sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==} - dependencies: - readable-stream: 4.4.0 - split2: 4.2.0 - dev: false - - /pino-std-serializers@6.2.1: - resolution: {integrity: sha512-wHuWB+CvSVb2XqXM0W/WOYUkVSPbiJb9S5fNB7TBhd8s892Xq910bRxwHtC4l71hgztObTjXL6ZheZXFjhDrDQ==} - dev: false - - /pino@8.14.1: - resolution: {integrity: sha512-8LYNv7BKWXSfS+k6oEc6occy5La+q2sPwU3q2ljTX5AZk7v+5kND2o5W794FyRaqha6DJajmkNRsWtPpFyMUdw==} - hasBin: true - dependencies: - atomic-sleep: 1.0.0 - fast-redact: 3.2.0 - on-exit-leak-free: 2.1.0 - pino-abstract-transport: 1.0.0 - pino-std-serializers: 6.2.1 - process-warning: 2.2.0 - quick-format-unescaped: 4.0.4 - real-require: 0.2.0 - safe-stable-stringify: 2.4.3 - sonic-boom: 3.3.0 - thread-stream: 2.3.0 - dev: false - - /process-warning@2.2.0: - resolution: {integrity: sha512-/1WZ8+VQjR6avWOgHeEPd7SDQmFQ1B5mC1eRXsCm5TarlNmx/wCsa5GEaxGm05BORRtyG/Ex/3xq3TuRvq57qg==} - dev: false - - /process@0.11.10: - resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} - engines: {node: '>= 0.6.0'} - dev: false - - /quick-format-unescaped@4.0.4: - resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} - dev: false - - /readable-stream@4.4.0: - resolution: {integrity: sha512-kDMOq0qLtxV9f/SQv522h8cxZBqNZXuXNyjyezmfAAuribMyVXziljpQ/uQhfE1XLg2/TLTW2DsnoE4VAi/krg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - abort-controller: 3.0.0 - buffer: 6.0.3 - events: 3.3.0 - process: 0.11.10 - dev: false - - /real-require@0.2.0: - resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} - engines: {node: '>= 12.13.0'} - dev: false - - /rimraf@2.4.5: - resolution: {integrity: sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=} - hasBin: true - dependencies: - glob: 6.0.4 - dev: false - optional: true - - /safe-json-stringify@1.2.0: - resolution: {integrity: sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==} - requiresBuild: true - dev: false - optional: true - - /safe-stable-stringify@2.4.3: - resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==} - engines: {node: '>=10'} - dev: false - - /sonic-boom@3.3.0: - resolution: {integrity: sha512-LYxp34KlZ1a2Jb8ZQgFCK3niIHzibdwtwNUWKg0qQRzsDoJ3Gfgkf8KdBTFU3SkejDEIlWwnSnpVdOZIhFMl/g==} - dependencies: - atomic-sleep: 1.0.0 - dev: false - - /split2@4.2.0: - resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} - engines: {node: '>= 10.x'} - dev: false - - /thread-stream@2.3.0: - resolution: {integrity: sha512-kaDqm1DET9pp3NXwR8382WHbnpXnRkN9xGN9dQt3B2+dmXiW8X1SOwmFOxAErEQ47ObhZ96J6yhZNXuyCOL7KA==} - dependencies: - real-require: 0.2.0 - dev: false - - /wrappy@1.0.2: - resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} - dev: false - optional: true diff --git a/bundt.config.ts b/bundt.config.ts deleted file mode 100644 index 49d9be4..0000000 --- a/bundt.config.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { define } from 'bundt/config'; - -const is_node = (condition: string) => !~condition.indexOf('.'); - -export default define((input, options) => { - options.treeShaking = true; - - if (input.export === '.') { - const target = is_node(input.condition) - ? 'node' - : input.condition.split('.')[0]; - - options.define = { - __TARGET__: `"${target}"`, - }; - - if (target === 'node') { - options.banner = { - js: "import { format as _FORMAT } from 'util';", - }; - } - } - - return options; -}); diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..8104bb4 --- /dev/null +++ b/deno.json @@ -0,0 +1,54 @@ +{ + "version": "0.5.0", + "name": "@mr/log", + "lock": false, + "tasks": { + "build": "deno run -A scripts/build.ts" + }, + "exports": { + ".": "./lib/mod.ts", + "./using": "./lib/using.ts", + "./stream": "./lib/stream.ts", + "./output.console": "./lib/output.console.ts", + "./utils": "./lib/utils.ts" + }, + "imports": { + "@deno/dnt": "jsr:@deno/dnt@^0.41", + "@std/assert": "jsr:@std/assert@^0.224.0", + "@std/async": "jsr:@std/async@^0.224.0", + "@std/testing": "jsr:@std/testing@^0.224.0" + }, + "lint": { + "rules": { + "exclude": [ + "no-var", + "prefer-const", + "no-cond-assign", + "no-inner-declarations", + "no-explicit-any", + "require-await" + ] + } + }, + "fmt": { + "lineWidth": 100, + "singleQuote": true, + "useTabs": true + }, + "exclude": [ + "npm", + "coverage" + ], + "publish": { + "include": [ + "lib/*.ts", + "license", + "readme.md", + ".github/logo*.svg" + ], + "exclude": [ + "**/*.test.ts", + "**/*.bench.ts" + ] + } +} diff --git a/examples/_journey.ts b/examples/_journey.ts new file mode 100644 index 0000000..81b33dc --- /dev/null +++ b/examples/_journey.ts @@ -0,0 +1,71 @@ +import type { Diary } from '../mod.ts'; + +import { delay } from '@std/async'; +import { faker } from 'npm:@faker-js/faker'; + +export async function log_journey(log: Diary) { + type Order = any; + + let db = { + orders: [] as Order[], + prices: { + Latte: 4.0, + Cappuccino: 3.5, + Espresso: 2.5, + Americano: 3.0, + Mocha: 4.5, + }, + }; + + async function save_order(order: Order) { + await delay(faker.number.int({ min: 500, max: 1000 })); + db.orders.push(order); + log('info', 'Order {id} saved to database', order); + } + + async function update_order(order: Order) { + await delay(faker.number.int({ min: 500, max: 1000 })); + const index = db.orders.findIndex((o) => o.id === order.id); + if (index !== -1) { + db.orders[index] = order; + log('info', 'Order {id} updated in database', order); + } + } + + async function process(order: Order) { + order.status = 'in progress'; + log('debug', 'Order {id} is being prepared', order); + await delay(faker.number.int({ min: 1000, max: 3000 })); + order.status = 'completed'; + log('info', 'Order {id} is completed for {customer}', order); + } + + function gen_order(): Order { + const drink: keyof typeof db.prices = faker.helpers.arrayElement([ + 'Latte', + 'Cappuccino', + 'Espresso', + 'Americano', + 'Mocha', + ]); + + const order = { + id: faker.string.uuid(), + customer: faker.person.firstName(), + drink, + size: faker.helpers.arrayElement(['Small', 'Medium', 'Large']), + price: db.prices[drink], + status: 'new', + }; + log('info', 'New order received: {id}, {customer}, {drink}, {size} ${price}', order); + return order; + } + + while (true) { + const newOrder = gen_order(); + await save_order(newOrder); + await process(newOrder); + await update_order(newOrder); + await delay(faker.number.int({ min: 500, max: 1500 })); + } +} diff --git a/examples/example.ts b/examples/example.ts new file mode 100644 index 0000000..9ab134f --- /dev/null +++ b/examples/example.ts @@ -0,0 +1,80 @@ +import { type Diary, diary, type OnEmitFn } from '../mod.ts'; +import { browser, plain, pretty } from '../output.console.ts'; +import { interpolate } from '../utils.ts'; + +class User { + id = 123; + name = 'Actor'; + + // Interpolate calls .toString() on anything it recieves + toString() { + return `[User id: ${this.id} name: ${this.name}]`; + } +} + +function example(oe: OnEmitFn) { + let user = new User(); + + // Example where you may want to create a standard logger and output fn for your application + let createLogger = (ctx: { + loggerName: string; + pid: number; + }): Diary => { + return diary((level, message, props = {}) => { + return oe(level, message, { ...ctx, ...props }); + }); + }; + + // then use that in your code like this: + let log = createLogger({ pid: Deno.pid, loggerName: 'example' }); + log('debug', 'hello from {loggerName} with {pid}'); + log('info', 'hello {user} with {pid}', { user }); + + log('log', 'this is a log message'); + log('info', 'this is an info message'); + log('debug', 'this is a debug message'); + log('warn', 'this is a warning message'); + log('error', 'this is an error message'); + log('fatal', 'this is a fatal message'); + + log('fatal', 'some {user} had an {error}', { + user: 'Actor', + error: new Error('boom!'), + }); + + log('info', 'this {user} exists', { user }); + log('info', 'we call that user {name} with {id}', user); + + log('info', 'we also have inherited props like {pid}', { + ...user, + pid: Deno.pid, + }); + + log('info', 'this {user} can send more properties than we want', { + user, + pid: Deno.pid, + }); +} + +console.log('============ PRETTY ============\n'); +example(pretty); + +console.log('\n\n============ PLAIN ============\n'); +example(plain); + +console.log('\n\n============ BROWSER ============'); +console.log('NOTE: we omit the level, as that is presented in the DevTools.\n'); +example(browser); + +console.log('\n\n============ CLEF (custom) ============\n'); +example((level, message, props) => { + let event = { + '@t': new Date().toISOString(), + '@l': level, + '@m': interpolate(message, props || {}), + '@mt': message, + ...props, + }; + + console.log(JSON.stringify(event)); +}); diff --git a/examples/stream.ts b/examples/stream.ts new file mode 100644 index 0000000..fca6a6a --- /dev/null +++ b/examples/stream.ts @@ -0,0 +1,28 @@ +import type { LogEvent } from '../mod.ts'; +import { diary } from '../stream.ts'; +import { interpolate } from '../utils.ts'; +import { log_journey } from './_journey.ts'; + +let log_file = await Deno.open('./log.log', { + write: true, + create: true, + truncate: true, +}); + +let log_transform = new TransformStream({ + transform([level, message, props = {}], controller) { + controller.enqueue(`${Date.now()} [${level}] ${interpolate(message, props)}\n`); + }, +}); + +let log = diary((readable) => { + let [a, b] = readable + .pipeThrough(log_transform) + .pipeThrough(new TextEncoderStream()) + .tee(); + + a.pipeTo(log_file.writable); + b.pipeTo(Deno.stdout.writable); +}); + +log_journey(log); diff --git a/lib/mod.test.ts b/lib/mod.test.ts new file mode 100644 index 0000000..7302062 --- /dev/null +++ b/lib/mod.test.ts @@ -0,0 +1,71 @@ +import { assertInstanceOf } from '@std/assert'; +import { assertSpyCall, spy } from '@std/testing/mock'; + +import type { Level } from '../lib/mod.ts'; +import * as lib from '../lib/mod.ts'; + +Deno.test('api', () => { + assertInstanceOf(lib.diary, Function); + + let emit = spy(); + let log = lib.diary(emit); + assertInstanceOf(log, Function); + log('debug', 'hello'); + log('info', 'hello {name}', { name: 'world' }); + // @ts-expect-error - wrong type "name" should be "foo" + log('debug', 'hello {name}', { foo: 'world' }); +}); + +Deno.test('calls onEmit', () => { + let emit = spy(); + let log = lib.diary(emit); + log('info', 'hello', { name: 'world' }); + assertSpyCall(emit, 0, { + args: ['info', 'hello', { name: 'world' }], + }); +}); + +Deno.test('calls onEmit for every log', () => { + let emit = spy(); + let log = lib.diary(emit); + log('debug', 'debug message'); + log('info', 'hello', { name: 'world' }); + log('debug', 'hello {phrase}', { phrase: 'world' }); + + assertSpyCall(emit, 0, { + args: ['debug', 'debug message'], + }); + assertSpyCall(emit, 1, { + args: ['info', 'hello', { name: 'world' }], + }); + assertSpyCall(emit, 2, { + args: ['debug', 'hello {phrase}', { phrase: 'world' }], + }); +}); + +Deno.test('calls with correct level', () => { + let emit = spy(); + let log = lib.diary(emit); + + let i = 0; + for (let level of ['log', 'debug', 'info', 'warn', 'error', 'fatal']) { + log(level as Level, 'hello', { name: 'world' }); + assertSpyCall(emit, i++, { + args: [level, 'hello', { name: 'world' }], + }); + } +}); + +Deno.test('should allow anything as prop value', () => { + class Test {} + + let t = new Test(); + + let emit = spy(); + let log = lib.diary(emit); + log('info', 'hello {phrase}', { phrase: t }); + + assertSpyCall(emit, 0, { + args: ['info', 'hello {phrase}', { phrase: t }], + }); +}); diff --git a/lib/mod.ts b/lib/mod.ts new file mode 100644 index 0000000..352b2c9 --- /dev/null +++ b/lib/mod.ts @@ -0,0 +1,21 @@ +import type { Dict, Pretty, Props } from './utils.ts'; + +export type Level = 'log' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'; + +export type LogEvent = Parameters; + +export interface OnEmitFn { + (level: Level, message: string, props?: Dict | undefined): any; +} + +export interface Diary { + , keyof Ctx>>>( + level: Level, + template: T, + ...args: keyof Params extends never ? [] : Params extends object ? [properties: Params] : [] + ): void; +} + +export function diary(onEmit: OnEmitFn): Diary { + return onEmit; +} diff --git a/lib/output.console.test.ts b/lib/output.console.test.ts new file mode 100644 index 0000000..aa244d5 --- /dev/null +++ b/lib/output.console.test.ts @@ -0,0 +1,73 @@ +import { assertInstanceOf } from '@std/assert'; +import { assertSpyCall, stub } from '@std/testing/mock'; + +import * as lib from './output.console.ts'; + +Deno.test('api', () => { + assertInstanceOf(lib.browser, Function); + assertInstanceOf(lib.plain, Function); + assertInstanceOf(lib.pretty, Function); +}); + +Deno.test('browser :: interpolates values', () => { + let log = stub(console, 'log'); + lib.browser('log', 'hello {phrase}', { phrase: 'world' }); + + assertSpyCall(log, 0, { + args: ['hello %o', 'world'], + }); + + log.restore(); +}); + +Deno.test('browser :: calls console.log', () => { + let log = stub(console, 'log'); + lib.browser('log', 'hello', { name: 'world' }); + assertSpyCall(log, 0, { + args: ['hello'], + }); + + log.restore(); +}); + +Deno.test('browser :: see the value as an argument', () => { + class T {} + let t = new T(); + let log = stub(console, 'log'); + lib.browser('log', 'hello {T}', { T: t }); + assertSpyCall(log, 0, { + args: ['hello %o', t], + }); + + log.restore(); +}); + +Deno.test('browser :: ignores unused values', () => { + let log = stub(console, 'log'); + lib.browser('log', 'log {phrase}', { phrase: 'world', foo: 'bar' }); + assertSpyCall(log, 0, { + args: ['log %o', 'world'], + }); + + log.restore(); +}); + +Deno.test('browser :: if no props given still log', () => { + let log = stub(console, 'log'); + lib.browser('log', 'hello'); + assertSpyCall(log, 0, { + args: ['hello'], + }); + + log.restore(); +}); + +Deno.test('plain :: includes level', () => { + let log = stub(console, 'log'); + lib.plain('log', 'log {phrase}', { phrase: 'world' }); + assertSpyCall(log, 0, { + args: ['◆ log log %o', 'world'], + }); + + log.restore(); +}); diff --git a/lib/output.console.ts b/lib/output.console.ts new file mode 100644 index 0000000..8f61b60 --- /dev/null +++ b/lib/output.console.ts @@ -0,0 +1,47 @@ +import type { Level } from './mod.ts'; +import { interpolate } from './utils.ts'; + +import { blue, bold, gray, magenta, red, yellow } from 'npm:kleur@^4/colors'; + +const LEVELS = { + log: '◆ log ' as const, + debug: '● debug ' as const, + info: 'ℹ info ' as const, + warn: '‼ warn ' as const, + error: '✗ error ' as const, + fatal: '✗ fatal ' as const, +} as const; + +function log(out: string, level: Level, message: string, props = {}) { + let args: unknown[] = []; + out += interpolate(message, props, (value) => { + args.push(value); + return '%o'; + }); + + console[level === 'fatal' ? 'error' : level](out, ...args); +} + +export function browser(level: Level, message: string, props?: object | undefined) { + log('', level, message, props); +} + +export function plain(level: Level, message: string, props?: object | undefined) { + log(LEVELS[level], level, message, props); +} + +export function pretty(level: Level, message: string, props?: object | undefined) { + let l = LEVELS[level] as string; + + // deno-fmt-ignore + switch (level) { + case 'log': l = gray(l); break; + case 'debug': l = magenta(l); break; + case 'warn': l = yellow(l); break; + case 'info': l = blue(l); break; + case 'error': l = red(l); break; + case 'fatal': l = bold(red(l)); break; + } + + log(l, level, message, props); +} diff --git a/lib/stream.test.ts b/lib/stream.test.ts new file mode 100644 index 0000000..40db017 --- /dev/null +++ b/lib/stream.test.ts @@ -0,0 +1,26 @@ +import { assertEquals, assertInstanceOf } from '@std/assert'; +import { delay } from '@std/async'; +import * as stream from './stream.ts'; +import * as lib from './mod.ts'; + +Deno.test('api', () => { + assertInstanceOf(lib.diary, Function); +}); + +Deno.test('streams', async () => { + let events: lib.LogEvent[] = []; + let log = stream.diary(async (stream) => { + assertInstanceOf(stream, ReadableStream); + for await (let event of stream) events.push(event); + }); + + log('info', 'hello', { name: 'world' }); + log('debug', 'hello', { name: 'world' }); + + await delay(1); + + assertEquals(events, [ + ['info', 'hello', { name: 'world' }], + ['debug', 'hello', { name: 'world' }], + ]); +}); diff --git a/lib/stream.ts b/lib/stream.ts new file mode 100644 index 0000000..5bd9767 --- /dev/null +++ b/lib/stream.ts @@ -0,0 +1,14 @@ +import * as lib from './mod.ts'; + +export function diary( + cb: (r: ReadableStream) => any, +): lib.Diary { + let stream = new TransformStream(); + let writer = stream.writable.getWriter(); + cb(stream.readable); + return lib.diary( + function () { + writer.write(Array.from(arguments) as lib.LogEvent); + }, + ); +} diff --git a/lib/using.test.ts b/lib/using.test.ts new file mode 100644 index 0000000..6a58fd3 --- /dev/null +++ b/lib/using.test.ts @@ -0,0 +1,46 @@ +import { assertInstanceOf } from '@std/assert'; +import * as lib from './using.ts'; +import { assertSpyCall, spy } from '@std/testing/mock'; +import { delay } from '@std/async'; + +Deno.test('api', () => { + assertInstanceOf(lib.diary, Function); +}); + +Deno.test('calls onEmit', () => { + let emit = spy(); + + { + using log = lib.diary(emit); + log('info', 'hello {name}', { name: 'world' }); + log('debug', 'hello {name}', { name: 'world' }); + } + + assertSpyCall(emit, 0, { + args: [ + [ + ['info', 'hello {name}', { name: 'world' }], + ['debug', 'hello {name}', { name: 'world' }], + ], + ], + }); +}); + +Deno.test('allows async disposal', async () => { + let emit = spy(() => delay(1)); + + { + await using log = lib.diary(emit); + log('info', 'hello {name}', { name: 'world' }); + log('debug', 'hello {name}', { name: 'world' }); + } + + assertSpyCall(emit, 0, { + args: [ + [ + ['info', 'hello {name}', { name: 'world' }], + ['debug', 'hello {name}', { name: 'world' }], + ], + ], + }); +}); diff --git a/lib/using.ts b/lib/using.ts new file mode 100644 index 0000000..f2db3f2 --- /dev/null +++ b/lib/using.ts @@ -0,0 +1,20 @@ +import * as lib from './mod.ts'; + +export interface Diary extends lib.Diary { + [Symbol.dispose](): void; +} + +export function diary(flush: (events: lib.LogEvent[]) => any): Diary { + const events: lib.LogEvent[] = []; + + let log = lib.diary( + function () { + events.push(Array.from(arguments) as lib.LogEvent); + }, + ) as Diary; + log[Symbol.dispose] = function () { + return flush(events); + }; + + return log; +} diff --git a/lib/utils.test.ts b/lib/utils.test.ts new file mode 100644 index 0000000..7d6dd35 --- /dev/null +++ b/lib/utils.test.ts @@ -0,0 +1,109 @@ +import { assertEquals } from '@std/assert'; +import * as lib from './utils.ts'; + +Deno.test('api', () => { + assertEquals(lib.interpolate('hello {name}', { name: 'world' }), 'hello world'); + // @ts-expect-error - wrong type "name" should be "foo" + assertEquals(lib.interpolate('hello {name}', { foo: 'world' }), 'hello undefined'); +}); + +Deno.test('the same key twice', () => { + let s = lib.interpolate('hello {name} {name}', { name: 'world' }); + assertEquals(s, 'hello world world'); +}); + +Deno.test('allows more than one value', () => { + let s = lib.interpolate('hello {name}', { name: 'world', foo: 'bar' }); + assertEquals(s, 'hello world'); +}); + +Deno.test('allows brackets to wrap a value', () => { + let s = lib.interpolate('hello {name}', { name: '{world}' }); + assertEquals(s, 'hello {world}'); +}); + +Deno.test('with a lot of replacements', () => { + let s = lib.interpolate('hello {name} {name} {name} {name}', { name: 'world' }); + assertEquals(s, 'hello world world world world'); +}); + +Deno.test('should allow spaces in the keys', () => { + let s = lib.interpolate('hello {first name}', { 'first name': 'world' }); + assertEquals(s, 'hello world'); +}); + +Deno.test('should allow multiple keys', () => { + let s = lib.interpolate('a {phrase} b {name} c {phrase} d', { phrase: 'hello', name: 'world' }); + assertEquals(s, 'a hello b world c hello d'); +}); + +// TODO: Maybe this should just "pick the next key??" +Deno.test('should allow zero-width keys to mean empty string', () => { + let s = lib.interpolate('{name} {}', { name: 'world', '': 'test' }); + assertEquals(s, 'world test'); +}); + +Deno.test('interpolates empty string', () => { + assertEquals(lib.interpolate('', { name: 'world' }), ''); +}); + +Deno.test('interpolates array as the value', () => { + let s = lib.interpolate('{0} {1}', ['hello', 123]); + assertEquals(s, 'hello 123'); +}); + +Deno.test('interpolates with no matching keys', () => { + // @ts-expect-error the key wont exist, that is the point + assertEquals(lib.interpolate('hello {name}', { foo: 'bar' }), 'hello undefined'); +}); + +Deno.test('interpolates with null values', () => { + assertEquals(lib.interpolate('hello {name}', { name: null }), 'hello null'); +}); + +Deno.test('interpolates with undefined values', () => { + assertEquals(lib.interpolate('hello {name}', { name: undefined }), 'hello undefined'); +}); + +Deno.test('interpolates with numeric values', () => { + assertEquals(lib.interpolate('hello {name}', { name: 123 }), 'hello 123'); +}); + +Deno.test('interpolates with boolean values', () => { + assertEquals(lib.interpolate('hello {name}', { name: true }), 'hello true'); +}); + +Deno.test('allows custom seralizer', () => { + assertEquals( + lib.interpolate('hello {name}', { name: 'world' }, (v) => v?.toUpperCase() ?? '?'), + 'hello WORLD', + ); +}); + +Deno.test('allows custom seralizer multiple', () => { + assertEquals( + lib.interpolate('hello {name} {name}', { name: 'world' }, (v) => v?.toUpperCase() ?? '?'), + 'hello WORLD WORLD', + ); +}); + +Deno.test('the value should be cached in the single interpolation', () => { + let i = 0; + let obj = { + get name() { + return i++; + }, + }; + assertEquals(obj.name, 0); + assertEquals(obj.name, 1); + i = 0; + assertEquals(lib.interpolate('{name} {name}', obj), '0 0'); + assertEquals(i, 1); + assertEquals(lib.interpolate('{name} {name}', obj), '1 1'); + assertEquals(i, 2); +}); + +// props written between {} brackets +// Only valid names are allowed +// brackets can be escaped +// numbers are indexs for an array diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000..78e2612 --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,35 @@ +export type Props = T extends `${string}{${infer S}}${infer Rest}` + ? { [K in S]: unknown } & Props + : unknown; + +let CURLY = /{([\s\S]*?)}/g; +let cache: Record = {}; +export function interpolate>>( + message: T, + props: O, + to_string: (v: Value | undefined, key: string, props: O) => string = String, +): string { + let kv = cache[message] ||= message.split(CURLY); + let s, k, i = 0, len = kv.length - 1, v_cache: Dict = {}; + + for (s = k = ''; i < len;) { + s += kv[i++]; + k = kv[i++]; + // TODO: if the v_cache value is nullish, we don't reuse the cached value — and we should + s += v_cache[k] || (v_cache[k] = to_string(props[k as keyof O] as Value, k, props)); + } + + return s + kv[i]; +} + +// --- + +type Value = T extends Array ? T[number] + : T extends Record ? T[keyof T] + : unknown; + +/* @internal */ +export type Dict = Record; + +/* @internal */ +export type Pretty = { [K in keyof T]: T[K] } & unknown; diff --git a/package.json b/package.json deleted file mode 100644 index 8da5b34..0000000 --- a/package.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "name": "diary", - "version": "0.4.5", - "description": "Fast effective logging library for both Node, the Browser, and Workers!", - "keywords": [ - "fast", - "logging", - "utility", - "middleware", - "debug", - "logger" - ], - "repository": "maraisr/diary", - "license": "MIT", - "author": "Marais Rossow (https://marais.io)", - "sideEffects": false, - "exports": { - ".": { - "browser": { - "types": "./browser.d.ts", - "import": "./browser.mjs", - "require": "./browser.js" - }, - "types": "./node.d.ts", - "import": "./node.mjs", - "require": "./node.js" - }, - "./json": { - "types": "./json.d.ts", - "import": "./json.mjs", - "require": "./json.js" - }, - "./utils": { - "types": "./utils.d.ts", - "import": "./utils.mjs", - "require": "./utils.js" - }, - "./package.json": "./package.json" - }, - "main": "node.js", - "module": "node.mjs", - "types": "node.d.ts", - "files": [ - "browser.*", - "node.*", - "json.*", - "utils.*" - ], - "scripts": { - "bench": "cross-env DEBUG=standard ROARR_LOG=true tsm bench/index.ts", - "build": "bundt --minify", - "format": "prettier --write --list-different \"{*,bench/**/*,.github/**/*,test/**/*,src/*.spec}.+(ts|json|yml|md)\"", - "test": "uvu src \".test.ts$\" -r tsm -r test/helpers/setup.js", - "typecheck": "tsc --noEmit" - }, - "prettier": "@marais/prettier", - "devDependencies": { - "@marais/prettier": "0.0.4", - "@marais/tsconfig": "0.0.4", - "@types/node": "20.3.2", - "bundt": "2.0.0-next.5", - "cross-env": "7.0.3", - "nanospy": "1.0.0", - "prettier": "2.8.8", - "tsm": "2.3.0", - "typescript": "5.1.3", - "uvu": "0.5.4" - }, - "volta": { - "node": "18.16.1" - } -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index f2964c0..0000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,699 +0,0 @@ -lockfileVersion: '6.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -devDependencies: - '@marais/prettier': - specifier: 0.0.4 - version: 0.0.4 - '@marais/tsconfig': - specifier: 0.0.4 - version: 0.0.4 - '@types/node': - specifier: 20.3.2 - version: 20.3.2 - bundt: - specifier: 2.0.0-next.5 - version: 2.0.0-next.5 - cross-env: - specifier: 7.0.3 - version: 7.0.3 - nanospy: - specifier: 1.0.0 - version: 1.0.0 - prettier: - specifier: 2.8.8 - version: 2.8.8 - tsm: - specifier: 2.3.0 - version: 2.3.0 - typescript: - specifier: 5.1.3 - version: 5.1.3 - uvu: - specifier: 0.5.4 - version: 0.5.4 - -packages: - - /@esbuild/android-arm@0.15.18: - resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-loong64@0.14.54: - resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-loong64@0.15.18: - resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@jridgewell/gen-mapping@0.3.3: - resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.18 - dev: true - - /@jridgewell/resolve-uri@3.1.0: - resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} - engines: {node: '>=6.0.0'} - dev: true - - /@jridgewell/set-array@1.1.2: - resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} - engines: {node: '>=6.0.0'} - dev: true - - /@jridgewell/source-map@0.3.3: - resolution: {integrity: sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==} - dependencies: - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.18 - dev: true - - /@jridgewell/sourcemap-codec@1.4.14: - resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} - dev: true - - /@jridgewell/sourcemap-codec@1.4.15: - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - dev: true - - /@jridgewell/trace-mapping@0.3.18: - resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} - dependencies: - '@jridgewell/resolve-uri': 3.1.0 - '@jridgewell/sourcemap-codec': 1.4.14 - dev: true - - /@marais/prettier@0.0.4: - resolution: {integrity: sha512-fcJgHALkAkmOyMEioqMaikXlUQLy9jj+SZjlI2AD9V0vEO1EjR3ZI5vz3y6A0Bz/PgskbyM9+F/A44850UWrhQ==} - dev: true - - /@marais/tsconfig@0.0.4: - resolution: {integrity: sha512-b6KCal22xP6E8wgl52rxdf8MXuffI4oJ9aTosucX4aVb97yl01wU0PzGF67oMA/i9KdzLa0rjQ0zVdZ+1pvVAg==} - dev: true - - /@types/node@20.3.2: - resolution: {integrity: sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw==} - dev: true - - /acorn@8.9.0: - resolution: {integrity: sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true - - /buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - dev: true - - /bundt@2.0.0-next.5: - resolution: {integrity: sha512-uoMMvvZUGRVyVbd0tls6ZU3bASc0lZt3b0iD3AE2J9sKgnsKJoWAWe4uUcCkla+Dx+T006ZERBvq0PY3iNuXlw==} - engines: {node: '>=12'} - hasBin: true - dependencies: - esbuild: 0.14.54 - rewrite-imports: 2.0.3 - terser: 5.18.1 - dev: true - - /commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - dev: true - - /cross-env@7.0.3: - resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} - engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} - hasBin: true - dependencies: - cross-spawn: 7.0.3 - dev: true - - /cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - dev: true - - /dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - dev: true - - /diff@5.1.0: - resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} - engines: {node: '>=0.3.1'} - dev: true - - /esbuild-android-64@0.14.54: - resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /esbuild-android-64@0.15.18: - resolution: {integrity: sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /esbuild-android-arm64@0.14.54: - resolution: {integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /esbuild-android-arm64@0.15.18: - resolution: {integrity: sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /esbuild-darwin-64@0.14.54: - resolution: {integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /esbuild-darwin-64@0.15.18: - resolution: {integrity: sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /esbuild-darwin-arm64@0.14.54: - resolution: {integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /esbuild-darwin-arm64@0.15.18: - resolution: {integrity: sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /esbuild-freebsd-64@0.14.54: - resolution: {integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-freebsd-64@0.15.18: - resolution: {integrity: sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-freebsd-arm64@0.14.54: - resolution: {integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-freebsd-arm64@0.15.18: - resolution: {integrity: sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-32@0.14.54: - resolution: {integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-32@0.15.18: - resolution: {integrity: sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-64@0.14.54: - resolution: {integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-64@0.15.18: - resolution: {integrity: sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-arm64@0.14.54: - resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-arm64@0.15.18: - resolution: {integrity: sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-arm@0.14.54: - resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-arm@0.15.18: - resolution: {integrity: sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-mips64le@0.14.54: - resolution: {integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-mips64le@0.15.18: - resolution: {integrity: sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-ppc64le@0.14.54: - resolution: {integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-ppc64le@0.15.18: - resolution: {integrity: sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-riscv64@0.14.54: - resolution: {integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-riscv64@0.15.18: - resolution: {integrity: sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-s390x@0.14.54: - resolution: {integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-s390x@0.15.18: - resolution: {integrity: sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-netbsd-64@0.14.54: - resolution: {integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-netbsd-64@0.15.18: - resolution: {integrity: sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-openbsd-64@0.14.54: - resolution: {integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-openbsd-64@0.15.18: - resolution: {integrity: sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-sunos-64@0.14.54: - resolution: {integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: true - optional: true - - /esbuild-sunos-64@0.15.18: - resolution: {integrity: sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-32@0.14.54: - resolution: {integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-32@0.15.18: - resolution: {integrity: sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-64@0.14.54: - resolution: {integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-64@0.15.18: - resolution: {integrity: sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-arm64@0.14.54: - resolution: {integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-arm64@0.15.18: - resolution: {integrity: sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild@0.14.54: - resolution: {integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/linux-loong64': 0.14.54 - esbuild-android-64: 0.14.54 - esbuild-android-arm64: 0.14.54 - esbuild-darwin-64: 0.14.54 - esbuild-darwin-arm64: 0.14.54 - esbuild-freebsd-64: 0.14.54 - esbuild-freebsd-arm64: 0.14.54 - esbuild-linux-32: 0.14.54 - esbuild-linux-64: 0.14.54 - esbuild-linux-arm: 0.14.54 - esbuild-linux-arm64: 0.14.54 - esbuild-linux-mips64le: 0.14.54 - esbuild-linux-ppc64le: 0.14.54 - esbuild-linux-riscv64: 0.14.54 - esbuild-linux-s390x: 0.14.54 - esbuild-netbsd-64: 0.14.54 - esbuild-openbsd-64: 0.14.54 - esbuild-sunos-64: 0.14.54 - esbuild-windows-32: 0.14.54 - esbuild-windows-64: 0.14.54 - esbuild-windows-arm64: 0.14.54 - dev: true - - /esbuild@0.15.18: - resolution: {integrity: sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/android-arm': 0.15.18 - '@esbuild/linux-loong64': 0.15.18 - esbuild-android-64: 0.15.18 - esbuild-android-arm64: 0.15.18 - esbuild-darwin-64: 0.15.18 - esbuild-darwin-arm64: 0.15.18 - esbuild-freebsd-64: 0.15.18 - esbuild-freebsd-arm64: 0.15.18 - esbuild-linux-32: 0.15.18 - esbuild-linux-64: 0.15.18 - esbuild-linux-arm: 0.15.18 - esbuild-linux-arm64: 0.15.18 - esbuild-linux-mips64le: 0.15.18 - esbuild-linux-ppc64le: 0.15.18 - esbuild-linux-riscv64: 0.15.18 - esbuild-linux-s390x: 0.15.18 - esbuild-netbsd-64: 0.15.18 - esbuild-openbsd-64: 0.15.18 - esbuild-sunos-64: 0.15.18 - esbuild-windows-32: 0.15.18 - esbuild-windows-64: 0.15.18 - esbuild-windows-arm64: 0.15.18 - dev: true - - /isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - dev: true - - /kleur@4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} - dev: true - - /mri@1.2.0: - resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} - engines: {node: '>=4'} - dev: true - - /nanospy@1.0.0: - resolution: {integrity: sha512-wvmmALNstRRhLhy7RV11NCRY2k1zxstImiju4VyyKNNRIKDVjyBtmEd/Q4G82/3dN4VSTe+0PRR3DUAASSbEEQ==} - engines: {node: ^8.0.0 || ^10.0.0 || ^12.0.0 || ^14.0.0 || ^16.0.0 || ^18.0.0 || >=20.0.0} - dev: true - - /path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - dev: true - - /prettier@2.8.8: - resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} - engines: {node: '>=10.13.0'} - hasBin: true - dev: true - - /rewrite-imports@2.0.3: - resolution: {integrity: sha512-R7ICJEeP3y+d/q4C8YEJj9nRP0JyiSqG07uc0oQh8JvAe706dDFVL95GBZYCjADqmhArZWWjfM/5EcmVu4/B+g==} - engines: {node: '>=6'} - dev: true - - /sade@1.8.1: - resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} - engines: {node: '>=6'} - dependencies: - mri: 1.2.0 - dev: true - - /shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - dependencies: - shebang-regex: 3.0.0 - dev: true - - /shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - dev: true - - /source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - dev: true - - /source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - dev: true - - /terser@5.18.1: - resolution: {integrity: sha512-j1n0Ao919h/Ai5r43VAnfV/7azUYW43GPxK7qSATzrsERfW7+y2QW9Cp9ufnRF5CQUWbnLSo7UJokSWCqg4tsQ==} - engines: {node: '>=10'} - hasBin: true - dependencies: - '@jridgewell/source-map': 0.3.3 - acorn: 8.9.0 - commander: 2.20.3 - source-map-support: 0.5.21 - dev: true - - /tsm@2.3.0: - resolution: {integrity: sha512-++0HFnmmR+gMpDtKTnW3XJ4yv9kVGi20n+NfyQWB9qwJvTaIWY9kBmzek2YUQK5APTQ/1DTrXmm4QtFPmW9Rzw==} - engines: {node: '>=12'} - hasBin: true - dependencies: - esbuild: 0.15.18 - dev: true - - /typescript@5.1.3: - resolution: {integrity: sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==} - engines: {node: '>=14.17'} - hasBin: true - dev: true - - /uvu@0.5.4: - resolution: {integrity: sha512-x1CyUjcP9VKaNPhjeB3FIc/jqgLsz2Q9LFhRzUTu/jnaaHILEGNuE0XckQonl8ISLcwyk9I2EZvWlYsQnwxqvQ==} - engines: {node: '>=8'} - hasBin: true - dependencies: - dequal: 2.0.3 - diff: 5.1.0 - kleur: 4.1.5 - sade: 1.8.1 - dev: true - - /which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - dependencies: - isexe: 2.0.0 - dev: true diff --git a/readme.md b/readme.md index 9107faa..ae8b80f 100644 --- a/readme.md +++ b/readme.md @@ -1,181 +1,71 @@ -
- - - -# ![diary](./shots/logo.png) - - - -
- -
+
-**Dear diary, you make my logging so easy** +
- - js downloads - - - licenses - - - gzip size - - - brotli size + + + + diary logo + -
+[![npm downloads](https://img.shields.io/npm/dw/diary?colorA=f6f8fa&colorB=f6f8fa&style=flat&label=npm%20downloads)](https://npm-stat.com/charts.html?package=diary) +[![size](https://img.shields.io/bundlephobia/minzip/diary?colorA=f6f8fa&colorB=f6f8fa&style=flat)](https://bundlephobia.com/package/diary) +[![licenses](https://licenses.dev/b/npm/diary?style=light)](https://licenses.dev/npm/diary) +
This is free to use software, but if you do like it, consisder supporting me ❤️ -[![sponsor me](https://badgen.net/badge/icon/sponsor?icon=github&label&color=gray)](https://github.com/sponsors/maraisr) -[![buy me a coffee](https://badgen.net/badge/icon/buymeacoffee?icon=buymeacoffee&label&color=gray)](https://www.buymeacoffee.com/marais) +[![sponsor me](https://img.shields.io/badge/sponsor-f6f8fa?style=flat&logo=github&logoColor=21262d)](https://github.com/sponsors/maraisr) +[![buy me a coffee](https://img.shields.io/badge/buy_me_a_coffee-f6f8fa?style=flat&logo=buymeacoffee&logoColor=21262d)](https://www.buymeacoffee.com/marais)
-## ⚡ Features - -- No [dependencies](https://npm.anvaka.com/#/view/2d/diary) -- Outstanding [performance](#-benchmark) -- Support for [`debug`'s filter](https://www.npmjs.com/package/debug#wildcards) - ## ⚙️ Install -```sh +```shell npm add diary ``` +_Avaliable on [jsr](https://jsr.io/@mr/log), [NPM](https://npmjs.com/package/diary) and +[deno.land](https://deno.land/x/diary)_ + ## 🚀 Usage ```ts -import { info, diary, enable } from 'diary'; +import { diary } from 'diary'; +import { pretty } from 'diary/output.console'; -// 1️⃣ Choose to enable the emission of logs, or not. -enable('*'); +// 1️⃣ create a diary +let log = diary(pretty); // 2️⃣ log something -info('this important thing happened'); -// ~> ℹ info this important thing happened - -// Maybe setup a scoped logger -const scopedDiary = diary('my-module', (event) => { - if (event.level === 'error') { - Sentry.captureException(event.error); - } -}); - -// 3️⃣ log more things -scopedDiary.info('this other important thing happened'); -// ~> ℹ info [my-module] this other important thing happened -``` +log('info', '{name} is now {type}', { name: 'marais', type: 'admin' }); +// ~> ℹ info marais is now admin -
Node users - -The `enable` function is executed for you from the `DEBUG` environment variable. And as a drop in replacement for -`debug`. - -```shell -DEBUG=client:db,server:* node example.js +// 💡 log message as completely typesafe +log('debug', '{name} was created {at}', { name: 'marais' }); +// ^? Error: 'at' is not defined ```
-## 🔎 API - -### diary(name: string, onEmit?: Reporter) - -Returns: [log functions](#log-functions) - -> A default diary is exported, accessible through simply importing any [log function](#log-functions). -> ->
-> Example of default diary -> -> ```ts -> import { info } from 'diary'; -> -> info("i'll be logged under the default diary"); -> ``` -> ->
+:construction: Talk about structured logging -#### name +:construction: Talk about fragments vs sentences -Type: `string` +:construction: Talk about onEmit -The name given to this _diary_—and will also be available in all logEvents. +:construction: Show /using and /stream -#### onEmit (optional) +:construction: Complete examples -Type: `Reporter` - -A reporter is run on every log message (provided its [enabled](#enablequery-string)). A reporter gets given the -`LogEvent` interface: - -```ts -interface LogEvent { - name: string; - level: LogLevels; - - messages: any[]; -} -``` - -> _Note_: you can attach any other context in middleware. -> ->
Example -> -> ```ts -> import { diary, default_reporter } from 'diary'; -> const scope = diary('scope', (event) => { -> event.ts = new Date(); -> return default_reporter(event); -> }); -> ``` -> ->
- -Errors (for `error` and `fatal`) there is also an `error: Error` property. - -### _log functions_ - -A set of functions that map to `console.error`, `console.warn`, `console.debug`, `console.info` and `console.info`. -Aptly named; - -`fatal`, `error`, `warn`, `debug`, `info`, and `log`. All of which follow the same api signature: - -```ts -declare logFunction(message: object | Error | string, ...args: unknown[]): void; -``` - -All parameters are simply spread onto the function and reported. Node/browser's built-in formatters will format any -objects (by default). - -```ts -info('hi there'); // ℹ info hi there -info('hi %s', 'there'); // ℹ info hi there -info('hi %j', { foo: 'bar' }); // ℹ info hi { "foo": "bar" } -info('hi %o', { foo: 'bar' }); // ℹ info hi { foo: 'bar' } -info({ foo: 'bar' }); // ℹ info { foo: 'bar' } -``` - -#### diary (optional) - -Type: `Diary` - -The result of a calling [diary](#diary-name-string); - -### enable(query: string) - -Type: `Function` - -Opts certain log messages into being output. See more [here](#programmatic). +:construction: What does production look like? ## 💨 Benchmark @@ -195,14 +85,9 @@ AOT ✔ debug ~ 1,287,846 ops/sec ± 0.24% ``` -> AOT: The logger is setup a head of time, and ops/sec is the result of calling the log fn. Simulates long running -> process, with a single logger. JIT: The logger is setup right before the log fn is called per op. Simulates setting up -> a logger per request for example. - -## Related - -- [workers-logger](https://github.com/maraisr/workers-logger) — fast and effective logging for - [Cloudflare Workers](https://workers.cloudflare.com/) +> AOT: The logger is setup a head of time, and ops/sec is the result of calling the log fn. +> Simulates long running process, with a single logger. JIT: The logger is setup right before the +> log fn is called per op. Simulates setting up a logger per request for example. ## License diff --git a/scripts/build.ts b/scripts/build.ts new file mode 100644 index 0000000..83392cb --- /dev/null +++ b/scripts/build.ts @@ -0,0 +1,83 @@ +import { build, emptyDir } from '@deno/dnt'; + +await emptyDir('./npm'); + +await build({ + entryPoints: [ + './lib/mod.ts', + { + name: './stream', + path: './lib/stream.ts', + }, + { + name: './using', + path: './lib/using.ts', + }, + { + name: './output.console', + path: './lib/output.console.ts', + }, + { + name: './utils', + path: './lib/utils.ts', + }, + ], + outDir: './npm', + shims: { + deno: 'dev', + }, + + esModule: true, + scriptModule: 'cjs', + + declaration: 'inline', + declarationMap: false, + + typeCheck: 'both', + skipSourceOutput: true, + test: true, + + importMap: 'deno.json', + + package: { + name: 'diary', + version: Deno.args[0], + description: 'Fast effective logging library for just about everything.', + repository: 'maraisr/diary', + license: 'MIT', + author: { + name: 'Marais Rososuw', + email: 'me@marais.dev', + url: 'https://marais.io', + }, + sideEffects: false, + keywords: [ + 'fast', + 'logging', + 'utility', + 'middleware', + 'debug', + 'logger', + ], + }, + + compilerOptions: { + target: 'ES2022', + lib: ['ES2022', 'WebWorker'], + }, + + filterDiagnostic(diag) { + let txt = diag.messageText.toString(); + // ignore type error for missing Deno built-in information + + return ( + !/Type 'ReadableStream<.*>' must have a/.test(txt) && + !txt.includes(`Type 'Timeout' is not assignable to type 'number'.`) + ); + }, + + async postBuild() { + await Deno.copyFile('license', 'npm/license'); + await Deno.copyFile('readme.md', 'npm/readme.md'); + }, +}); diff --git a/shots/logo.png b/shots/logo.png deleted file mode 100644 index 871f509..0000000 Binary files a/shots/logo.png and /dev/null differ diff --git a/src/generic.ts b/src/generic.ts deleted file mode 100644 index cc01646..0000000 --- a/src/generic.ts +++ /dev/null @@ -1,24 +0,0 @@ -/// - -import { diary as _diary, type LogEvent, type Reporter, type Diary } from './logger'; - -function reporter(event: LogEvent) { - let label = ''; - const fn = console[event.level === 'fatal' ? 'error' : event.level]; - - if (event.name) label += `[${event.name}] `; - - if (typeof event.messages[0] === 'object') { - return void fn(label, ...event.messages); - } else { - const message = event.messages.shift(); - return void fn(label + message, ...event.messages); - } -} - -export const diary = (name: string, onEmit: Reporter = reporter): Diary => _diary(name, onEmit); - -const { fatal, error, warn, debug, info, log } = diary('', reporter); -export { fatal, error, warn, debug, info, log }; -export type { Diary, LogEvent, LogLevels, Reporter } from './logger'; -export { enable } from './logger'; \ No newline at end of file diff --git a/src/index.test.ts b/src/index.test.ts deleted file mode 100644 index df400aa..0000000 --- a/src/index.test.ts +++ /dev/null @@ -1,161 +0,0 @@ -import {suite, test} from 'uvu'; -import * as assert from 'uvu/assert'; -import { restoreAll, spy, spyOn } from 'nanospy'; - -import * as diary from './logger' -import type {Reporter} from './logger' - -import * as GENERIC from './generic'; -import * as NODE from './node'; - -const levels = ['fatal', 'error', 'warn', 'debug', 'info', 'log'] as const; - -function before() { - diary.enable('*'); - restoreAll(); -} - -test.before.each(before); - -Object.entries({ generic: GENERIC, node: NODE }).forEach(([name, mod]) => { - const s = suite(`mod :: ${name}`); - s.before.each(before); - - s('exports', () => { - [...levels, 'diary'].forEach((verb) => { - assert.type( - // @ts-ignore - mod[verb], - 'function', - `Expected diary to have #${verb} function`, - ); - }); - }); - - s.run(); -}); - -test('should allow object logging', () => { - const reporter = spy(); - const scope = diary.diary('error', reporter); - - scope.info('info'); - assert.equal(reporter.callCount, 1); - assert.equal(reporter.calls[0][0], 'ℹ info info'); - scope.info({ foo: 'bar' }); - - assert.equal(reporter.calls[1][0], "ℹ info { foo: 'bar' }"); -}); - -const allows = suite('allows'); -allows.before.each(before); - -allows('should only allow some scopes', () => { - const reporter = spy(); - const scopeA = diary.diary('scope:a', reporter); - const scopeB = diary.diary('scope:b', reporter); - - diary.enable('scope:a'); - - scopeA.info('info a'); - scopeB.info('info b'); - scopeB.info('info b'); - scopeA.info('info a'); - - assert.equal( - reporter.calls.flatMap((i) => i[0].messages), - ['info a', 'info a'], - ); -}); - -allows('should allow nested scopes', () => { - const reporter = spy(); - const scopeA = diary.diary('scope:a', reporter); - const scopeB = diary.diary('scope:b', reporter); - - diary.enable('scope:*'); - - scopeA.info('info a'); - scopeB.info('info b'); - - assert.equal( - reporter.calls.flatMap((i) => i[0].messages), - ['info a', 'info b'], - ); -}); - -allows('should allow multiple allows per enable', () => { - const reporter = spy(); - - const scopeA = diary.diary('scope:a', reporter); - const scopeB = diary.diary('scope:b', reporter); - - diary.enable('scope:a,blah'); - - scopeA.info('info a'); - scopeB.info('info b'); - - diary.enable('blah,scope:a'); - - scopeA.info('info a'); - scopeB.info('info b'); - scopeB.info('info b'); - scopeA.info('info a'); - - diary.enable('foo,bar:*,scope:,scope:*'); - - scopeA.info('info a'); - scopeB.info('info b'); - - assert.equal( - reporter.calls.flatMap((i) => i[0].messages), - ['info a', 'info a', 'info a', 'info a', 'info b'], - ); -}); - -allows.run(); - -levels.forEach((level) => { - const l = suite(`level :: ${level}`); - l.before.each(before); - - l('should log something', () => { - const reporter = spy(); - const scope = diary.diary(level, reporter); - - scope[level]('something'); - scope[level]('something else'); - scope[level]('object else', { foo: 'bar' }); - scope[level]({ foo: 'bar' }); - - assert.equal(reporter.callCount, 4); - - assert.equal( - reporter.calls.map((i) => i[0]), - [ - { - name: level, - level: level, - messages: ['something'], - }, - { - name: level, - level: level, - messages: ['something else'], - }, - { - name: level, - level: level, - messages: ['object else', { foo: 'bar' }], - }, - { - name: level, - level: level, - messages: [{ foo: 'bar' }], - }, - ], - ); - }); - - l.run(); -}); diff --git a/src/json.test.ts b/src/json.test.ts deleted file mode 100644 index d71503d..0000000 --- a/src/json.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import {test, suite} from 'uvu'; -import * as assert from 'uvu/assert'; -import * as diary from './logger'; -import * as json from './json'; -import { restoreAll, spyOn } from 'nanospy'; - -test('api', () => { - assert.type(json.reporter, 'function'); -}); - -const output = suite('output'); - -output.before.each(() => { - diary.enable('*'); - restoreAll(); -}); - -output('simple', () => { - const log_output = spyOn(console, 'log', () => {}); - - const scope = diary.diary('json', json.reporter); - scope.info('foo %s', 'bar'); - - assert.equal(log_output.callCount, 1); - assert.equal( - log_output.calls[0][0], - '{"name":"json","level":"info","message":"foo bar"}', - ); -}); - -output('with rest', () => { - const log_output = spyOn(console, 'log', () => {}); - - const scope = diary.diary('json', (event) => { - event.context = { sequence: 0 }; - json.reporter(event); - }); - - scope.info('foo %s', 'bar'); - - assert.equal(log_output.callCount, 1); - assert.equal( - log_output.calls[0][0], - '{"name":"json","level":"info","message":"foo bar","context":{"sequence":0}}', - ); -}); - -output.run(); diff --git a/src/json.ts b/src/json.ts deleted file mode 100644 index f45c248..0000000 --- a/src/json.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { Reporter } from './logger'; -import { sprintf } from './utils'; - -export const reporter: Reporter = ({ name, level, messages, ...rest }) => { - if (typeof messages[0] === 'object') { - return console.log( - JSON.stringify({ - name, - level, - ...messages, - ...rest, - }) - ); - } else { - const message = messages.shift() as string; - return console.log( - JSON.stringify({ - name, - level, - message: sprintf(message, ...messages), - ...rest, - }) - ); - } -}; diff --git a/src/levels.ts b/src/levels.ts deleted file mode 100644 index 07fa6e3..0000000 --- a/src/levels.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const fatal = '✗ fatal' as const; -export const error = '✗ error' as const; -export const warn = '‼ warn ' as const; -export const debug = '● debug' as const; -export const info = 'ℹ info ' as const; -export const log = '◆ log ' as const; diff --git a/src/logger.ts b/src/logger.ts deleted file mode 100644 index 27a2fcc..0000000 --- a/src/logger.ts +++ /dev/null @@ -1,87 +0,0 @@ -export type Reporter = (event: LogEvent) => void; - -interface LogFn { - (message: T, ...args: unknown[]): void; - (message: T, ...args: unknown[]): void; - (message: unknown, ...args: unknown[]): void; - (message: string, ...args: unknown[]): void; -} - -export type LogLevels = 'fatal' | 'error' | 'warn' | 'debug' | 'info' | 'log'; - -export interface LogEvent { - name: string; - level: LogLevels; - - messages: unknown[]; - - [other: string]: any; -} - -let allows: RegExp[] = []; - -const to_reg_exp = (x: string) => new RegExp(x.replace(/\*/g, '.*') + '$'); - -/** - * Configure what logs to emit. Follows the colon delimited scheme. - * - * @example - * ```ts - * import { diary, enable } from 'diary'; - * - * enable('scope:A'); - * - * const scopeA = diary('scope:A'); - * const scopeB = diary('scope:B'); - * - * scopeA.log('foo bar'); // => 'foo bar' - * scopeB.log('foo bar'); // => na - * - * enable('scope:*'); - * - * scopeA.log('foo bar'); // => 'foo bar' - * scopeB.log('foo bar'); // => 'foo bar' - * ``` - */ -export const enable = (allows_query: string) => { - allows = allows_query.split(/[\s,]+/).map(to_reg_exp); -}; - -const logger = ( - name: string, - reporter: Reporter, - level: LogLevels, - ...messages: unknown[] -): void => { - for (let len = allows.length; len--;) - if (allows[len].test(name)) return reporter({ name, level, messages }); -}; - -export type Diary = Record; - -/** - * Creates a new diary logging instance. - * - * @example - * ```ts - * import { diary } from 'diary'; - * - * const log = diary('my-fancy-app'); - * - * log.info('app has started'); - * ``` - * - * @param name A name to give this diary instance this can be unique to your application, or not. - * When logged, it'll exist after the level string, eg: `ℹ info [my-fancy-app] app has started` - * @param onEmit The reporter that handles the output of the log messages - */ -export const diary = (name: string, onEmit?: Reporter): Diary => { - return { - fatal: logger.bind(0, name, onEmit, 'fatal'), - error: logger.bind(0, name, onEmit, 'error'), - warn: logger.bind(0, name, onEmit, 'warn'), - debug: logger.bind(0, name, onEmit, 'debug'), - info: logger.bind(0, name, onEmit, 'info'), - log: logger.bind(0, name, onEmit, 'log'), - }; -}; \ No newline at end of file diff --git a/src/node.ts b/src/node.ts deleted file mode 100644 index 0c79ada..0000000 --- a/src/node.ts +++ /dev/null @@ -1,38 +0,0 @@ -/// - -import { format } from 'node:util' - -import { enable, diary as _diary, type LogEvent, type Diary, Reporter } from './logger'; -import * as LEVELS from './levels'; - -enable(process.env.DEBUG || 'a^'); - -function reporter(event: LogEvent) { - let label = `${LEVELS[event.level]} `; - const fn = console[event.level === 'fatal' ? 'error' : event.level]; - - if (event.name) label += `[${event.name}] `; - - let message: string; - const maybe_error = event.messages[0]; - - if ( - maybe_error instanceof Error && - typeof maybe_error.stack !== 'undefined' - ) { - const m = maybe_error.stack.split('\n'); - m.shift(); - message = `${maybe_error.message}\n${m.join('\n')}`; - } else { - message = format(...event.messages); - } - - return void fn(label + message); -} - -export const diary = (name: string, onEmit: Reporter = reporter): Diary => _diary(name, onEmit); - -const { fatal, error, warn, debug, info, log } = diary('', reporter); -export { fatal, error, warn, debug, info, log }; -export type { Diary, LogEvent, LogLevels, Reporter } from './logger'; -export { enable } from './logger'; diff --git a/src/utils.test.ts b/src/utils.test.ts deleted file mode 100644 index a99e6c9..0000000 --- a/src/utils.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { suite } from 'uvu'; -import * as assert from 'uvu/assert'; -import * as lib from './utils'; - -const sprintf = suite('sprintf'); - -sprintf('should format something basic', () => { - assert.equal(lib.sprintf('hello %s', 'world'), 'hello world'); -}); - -sprintf('should support strings', () => { - assert.equal(lib.sprintf('foo %s', { bar: 'baz' }), 'foo [object Object]'); - assert.equal(lib.sprintf('foo %s', ['bar']), 'foo bar'); - assert.equal(lib.sprintf('foo %s', ['bar', 'baz']), 'foo bar,baz'); -}); - -sprintf('should support object notation', () => { - assert.equal(lib.sprintf('foo %o', { bar: 'baz' }), 'foo {"bar":"baz"}'); - assert.equal(lib.sprintf('foo %O', { bar: 'baz' }), 'foo {"bar":"baz"}'); - assert.equal(lib.sprintf('foo %o', 'bar'), 'foo bar'); - assert.equal(lib.sprintf('foo %o', ['bar', 'baz']), 'foo ["bar","baz"]'); -}); - -sprintf('should support integers', () => { - assert.equal(lib.sprintf('foo %i', 1), 'foo 1'); - assert.equal(lib.sprintf('foo %i', 1.25), 'foo 1'); - assert.equal(lib.sprintf('foo %d', 1.25), 'foo 1'); -}); - -sprintf('should support floats', () => { - assert.equal(lib.sprintf('foo %f', 1), 'foo 1'); - assert.equal(lib.sprintf('foo %f', 1.25), 'foo 1.25'); - assert.equal(lib.sprintf('foo %f', 1.25), 'foo 1.25'); -}); - -sprintf('should work when under supplied', () => { - assert.equal(lib.sprintf('foo %s %s', 'bar'), 'foo bar undefined'); - assert.equal( - lib.sprintf('foo %s with %o', 'bar', { bar: 'baz' }), - 'foo bar with {"bar":"baz"}', - ); - // assert.equal(lib.sprintf('foo %s with %o', 'bar', {"bar":"baz"}, 'test'), 'foo bar with {"bar":"baz"} test'); - assert.equal( - lib.sprintf('foo %o %s', { bar: 'baz' }), - 'foo {"bar":"baz"} undefined', - ); -}); - -sprintf('should work, when over supplied', () => { - assert.equal(lib.sprintf('foo %s', 'bar', 'baz'), 'foo bar'); -}); - -const compare = suite('compare'); - -compare('should compare when equal', () => { - assert.equal(lib.compare('log', 'log'), 0); - assert.equal(lib.compare('error', 'error'), 0); -}); - -compare('should compare when less', () => { - assert.equal(lib.compare('error', 'fatal'), -1); - assert.equal(lib.compare('warn', 'error'), -1); -}); - -compare('should compare when more', () => { - assert.equal(lib.compare('fatal', 'error'), 1); - assert.equal(lib.compare('info', 'log'), 1); -}); - -compare('show be zero when level is _real_', () => { - // @ts-ignore - assert.equal(lib.compare('what the', 'log'), 0); - // @ts-ignore - assert.equal(lib.compare('what the', 'heck'), 0); - // @ts-ignore - assert.equal(lib.compare('log', 'heck'), 0); -}); - -sprintf.run(); -compare.run(); diff --git a/src/utils.ts b/src/utils.ts deleted file mode 100644 index 92bbd22..0000000 --- a/src/utils.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { LogLevels } from './logger'; - -export const sprintf = (message: string, ...extra: unknown[]) => - message.replace(/(\s+)(%[Oodifs](?=[^a-z0-9A-Z]|$))/g, (_, ws, pattern) => { - let v = extra.shift() as string | undefined; - - if (/[Oo]/.test(pattern) && typeof v === 'object') v = JSON.stringify(v); - else if (/[di]/.test(pattern) && v) v = v.toString().replace(/\..*$/, ''); - - return ws + v; - }); - -const LEVELS: Record = { fatal: 60, error: 50, warn: 40, info: 30, debug: 20, log: 10 } as const; - -/** - * Returns if a log level is than its comparitor. - * - * @example - * - * ```js - * compare("error", "fatal") === -1; - * // Thus error is "less-than" fatal. - * ``` - * - * @param input the level youre trying to test - * @param target the level youre wanting to compare too - */ -export const compare = (log_level: LogLevels, input: LogLevels) => { - if (!(input in LEVELS) || !(log_level in LEVELS)) return 0; - - return LEVELS[input] === LEVELS[log_level] - ? 0 - : LEVELS[input] < LEVELS[log_level] - ? 1 - : -1; -}; diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index b35b655..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "@marais/tsconfig", - "compilerOptions": { - "baseUrl": ".", - "paths": { - "diary": ["src/index.d.ts"], - "diary/*": ["src/*.d.ts"] - } - }, - "include": ["src", "test"], - "exclude": ["node_modules"] -}