From 355aee7df5c709a50549fe5c00ff692dc6bc3578 Mon Sep 17 00:00:00 2001 From: Marais Rossouw Date: Sat, 1 Jun 2024 15:02:44 +1000 Subject: [PATCH] feat: is now deno --- .github/logo_dark.svg | 95 +++++ .github/logo_light.svg | 95 +++++ .github/workflows/ci.yml | 41 +-- .gitignore | 22 +- TEMP/bench.ts | 18 + TEMP/migration.md | 6 + TEMP/migration.ts | 42 +++ bench/index.ts | 112 ------ bench/package.json | 11 - bench/pnpm-lock.yaml | 328 ----------------- bundt.config.ts | 25 -- deno.json | 53 +++ examples/_journey.ts | 71 ++++ examples/example.ts | 80 +++++ examples/stream.ts | 28 ++ lib/mod.test.ts | 71 ++++ lib/mod.ts | 21 ++ lib/output.console.test.ts | 73 ++++ lib/output.console.ts | 47 +++ lib/stream.test.ts | 26 ++ lib/stream.ts | 14 + lib/using.test.ts | 46 +++ lib/using.ts | 20 ++ lib/utils.test.ts | 109 ++++++ lib/utils.ts | 35 ++ package.json | 72 ---- pnpm-lock.yaml | 699 ------------------------------------- readme.md | 185 ++-------- scripts/build.ts | 83 +++++ shots/logo.png | Bin 49475 -> 0 bytes src/generic.ts | 24 -- src/index.test.ts | 161 --------- src/json.test.ts | 48 --- src/json.ts | 25 -- src/levels.ts | 6 - src/logger.ts | 87 ----- src/node.ts | 38 -- src/utils.test.ts | 80 ----- src/utils.ts | 36 -- tsconfig.json | 12 - 40 files changed, 1091 insertions(+), 1954 deletions(-) create mode 100644 .github/logo_dark.svg create mode 100644 .github/logo_light.svg create mode 100644 TEMP/bench.ts create mode 100644 TEMP/migration.md create mode 100644 TEMP/migration.ts delete mode 100644 bench/index.ts delete mode 100644 bench/package.json delete mode 100644 bench/pnpm-lock.yaml delete mode 100644 bundt.config.ts create mode 100644 deno.json create mode 100644 examples/_journey.ts create mode 100644 examples/example.ts create mode 100644 examples/stream.ts create mode 100644 lib/mod.test.ts create mode 100644 lib/mod.ts create mode 100644 lib/output.console.test.ts create mode 100644 lib/output.console.ts create mode 100644 lib/stream.test.ts create mode 100644 lib/stream.ts create mode 100644 lib/using.test.ts create mode 100644 lib/using.ts create mode 100644 lib/utils.test.ts create mode 100644 lib/utils.ts delete mode 100644 package.json delete mode 100644 pnpm-lock.yaml create mode 100644 scripts/build.ts delete mode 100644 shots/logo.png delete mode 100644 src/generic.ts delete mode 100644 src/index.test.ts delete mode 100644 src/json.test.ts delete mode 100644 src/json.ts delete mode 100644 src/levels.ts delete mode 100644 src/logger.ts delete mode 100644 src/node.ts delete mode 100644 src/utils.test.ts delete mode 100644 src/utils.ts delete mode 100644 tsconfig.json 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..0633593 --- /dev/null +++ b/deno.json @@ -0,0 +1,53 @@ +{ + "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" + ], + "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 871f5090d3a48235b90066eb33523740d367f758..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49475 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 Tt*kNUze4GOI=oQM%>Taty_U4; 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"] -}