Skip to content

Commit 58aba3e

Browse files
authored
Merge pull request #13 from crashmax-dev/feat/logger
feat(packages): add `@stenodb/logger`
2 parents 3c0fd92 + 0fb4731 commit 58aba3e

36 files changed

+532
-112
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@
44
.env
55
.DS_Store
66
**/database
7+
**/logs
8+
**/temp

examples/with-logger/index.html

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>with-logger</title>
7+
</head>
8+
<body>
9+
<div id="app"></div>
10+
<script type="module" src="/src/index.ts"></script>
11+
</body>
12+
</html>

examples/with-logger/package.json

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "with-logger",
3+
"private": true,
4+
"version": "0.0.0",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite",
8+
"build": "tsc && vite build",
9+
"preview": "vite preview"
10+
},
11+
"devDependencies": {
12+
"typescript": "4.9.5",
13+
"vite": "4.1.1"
14+
},
15+
"dependencies": {
16+
"@stenodb/browser": "workspace:*",
17+
"@stenodb/logger": "workspace:*",
18+
"@zero-dependency/dom": "0.12.0",
19+
"class-transformer": "0.5.1",
20+
"reflect-metadata": "0.1.13"
21+
}
22+
}

examples/with-logger/src/entities.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Type } from 'class-transformer'
2+
3+
export class Users {
4+
@Type(() => User)
5+
users: User[]
6+
7+
constructor(...users: User[]) {
8+
this.users = users
9+
}
10+
11+
addUser(user: User): void {
12+
this.users.push(user)
13+
}
14+
15+
getLastUserId(): number {
16+
return this.users!.at(-1)!.id
17+
}
18+
}
19+
20+
export class User {
21+
id: number
22+
username: string
23+
24+
constructor(id: number, username: string) {
25+
this.id = id
26+
this.username = username
27+
}
28+
29+
changeUsername(username: string): void {
30+
this.username = username
31+
}
32+
}

examples/with-logger/src/index.ts

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import 'reflect-metadata'
2+
import { el } from '@zero-dependency/dom'
3+
import { User } from './entities.js'
4+
import { storage } from './storage.js'
5+
6+
const app = document.querySelector('#app')!
7+
8+
const userIdInput = el('input', {
9+
type: 'number',
10+
name: 'id',
11+
disabled: true
12+
})
13+
14+
const usernameInput = el('input', {
15+
type: 'text',
16+
name: 'username',
17+
placeholder: 'Username'
18+
})
19+
20+
const submitButton = el(
21+
'button',
22+
{
23+
type: 'submit'
24+
},
25+
'Submit'
26+
)
27+
28+
const resetButton = el(
29+
'button',
30+
{
31+
onclick: () => {
32+
storage.reset()
33+
render()
34+
}
35+
},
36+
'Reset'
37+
)
38+
39+
const form = el(
40+
'form',
41+
{
42+
onsubmit: (event: Event) => {
43+
event.preventDefault()
44+
const formData = new FormData(form)
45+
const username = formData.get('username')?.toString()
46+
if (!username) {
47+
usernameInput.focus()
48+
return
49+
}
50+
const user = new User(storage.data!.getLastUserId() + 1, username)
51+
storage.data!.addUser(user)
52+
storage.write()
53+
render()
54+
}
55+
},
56+
userIdInput,
57+
usernameInput,
58+
submitButton,
59+
resetButton
60+
)
61+
62+
const storagePreview = el('pre')
63+
64+
function render() {
65+
form.reset()
66+
usernameInput.focus()
67+
userIdInput.value = storage.data!.getLastUserId().toString()
68+
storagePreview.textContent = JSON.stringify(storage.data, null, 2)
69+
}
70+
71+
render()
72+
app.append(form, storagePreview)

examples/with-logger/src/storage.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { BrowserProvider, LocalStorage } from '@stenodb/browser'
2+
import { createLogger } from '@stenodb/logger'
3+
import { User, Users } from './entities.js'
4+
5+
const initialData = new Users(new User(1, 'John'))
6+
const adapter = new LocalStorage('users', Users, initialData)
7+
const provider = new BrowserProvider({ logger: createLogger() })
8+
9+
export const storage = provider.create(adapter)
10+
storage.read()
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/// <reference types="vite/client" />

examples/with-logger/tsconfig.json

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ESNext",
4+
"useDefineForClassFields": true,
5+
"module": "ESNext",
6+
"lib": ["ESNext", "DOM"],
7+
"moduleResolution": "Node",
8+
"strict": true,
9+
"resolveJsonModule": true,
10+
"isolatedModules": true,
11+
"esModuleInterop": true,
12+
"noEmit": true,
13+
"noUnusedLocals": true,
14+
"noUnusedParameters": true,
15+
"noImplicitReturns": true,
16+
"skipLibCheck": true,
17+
"strictPropertyInitialization": false,
18+
"experimentalDecorators": true,
19+
"emitDecoratorMetadata": true
20+
},
21+
"include": ["src"]
22+
}

examples/with-node/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"build": "del-cli dist && tsc"
99
},
1010
"dependencies": {
11+
"@stenodb/logger": "workspace:*",
1112
"@stenodb/node": "workspace:*",
1213
"class-transformer": "0.5.1",
1314
"reflect-metadata": "0.1.13"

examples/with-node/src/index.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
import 'reflect-metadata'
22
import { dirname, resolve } from 'node:path'
33
import { fileURLToPath } from 'node:url'
4+
import { createLogger } from '@stenodb/logger'
5+
import { createRotation } from '@stenodb/logger/rotation'
46
import { AsyncAdapter, NodeProvider } from '@stenodb/node'
57
import { Post, User, Users } from './entities.js'
68

79
const path = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'database')
10+
const logger = createLogger({
11+
rotation: createRotation({
12+
path: 'logs',
13+
size: '10M', // rotate every 10 MegaBytes written
14+
interval: '1d', // rotate daily
15+
compress: 'gzip' // compress rotated files
16+
})
17+
})
18+
819
const initialData = new Users(new User('John Doe'))
920
const adapter = new AsyncAdapter('users', Users, initialData)
10-
const provider = new NodeProvider(path)
21+
const provider = new NodeProvider(path, { logger })
1122

1223
const database = provider.createAsync(adapter)
1324
await database.read()

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"nodemon": "2.0.20",
2323
"ts-node": "10.9.1",
2424
"tsx": "3.12.3",
25-
"turbo": "1.7.3",
25+
"turbo": "1.7.4",
2626
"typescript": "4.9.5"
2727
},
2828
"engines": {

packages/browser/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@
4949
"prepublishOnly": "pnpm build"
5050
},
5151
"dependencies": {
52-
"@stenodb/utils": "workspace:3.0.0",
53-
"class-transformer": "0.5.1"
52+
"@stenodb/logger": "workspace:3.0.0",
53+
"@stenodb/utils": "workspace:3.0.0"
5454
},
5555
"peerDependencies": {
5656
"class-transformer": ">=0.5.0"

packages/browser/src/adapter/WebStorage.ts

+16-14
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
import { parseData } from '@stenodb/utils'
2-
import { plainToClass } from 'class-transformer'
1+
import { dataTransformer, entityTransformer } from '@stenodb/utils'
32
import type { Steno } from '../types.js'
3+
import type { BaseLogger } from '@stenodb/logger'
4+
import type { DataTransformer, EntityTransformer } from '@stenodb/utils'
45

56
export class BrowserStorage<T> {
67
name: string
78
storage: Storage
8-
entity: Steno.Entity<T>
9+
logger: BaseLogger | undefined
10+
11+
entityTransformer: EntityTransformer<T>
12+
dataTransformer: DataTransformer<T>
913

1014
data: T | null = null
1115
initialData: T | null = null
@@ -18,34 +22,32 @@ export class BrowserStorage<T> {
1822
) {
1923
this.name = name
2024
this.storage = storage
21-
this.entity = entity
25+
this.entityTransformer = entityTransformer(entity)
26+
this.dataTransformer = dataTransformer(this.entityTransformer)
2227

2328
if (initialData) {
2429
this.initialData = initialData
2530
}
2631
}
2732

28-
plainData(data: T | string | null = this.data): T | null {
29-
if (!data) return null
30-
31-
const parsedData =
32-
typeof data === 'string' ? parseData<T>(data).toJSON() : data
33-
34-
return plainToClass(this.entity, parsedData)
33+
registerLogger(logger: BaseLogger): void {
34+
this.logger = logger
3535
}
3636

3737
read(): void {
3838
const data = this.storage.getItem(this.name)
39-
this.data = data ? this.plainData(data) : null
39+
this.data = this.dataTransformer.toJSON(data)
40+
this.logger?.info('Read data:', this.data)
4041
}
4142

4243
write(): void {
43-
this.storage.setItem(this.name, parseData(this.data).toString())
44+
this.storage.setItem(this.name, this.dataTransformer.toString(this.data))
45+
this.logger?.info('Write data:', this.data)
4446
}
4547

4648
reset(): void {
4749
if (!this.initialData) return
48-
this.data = plainToClass(this.entity, this.initialData)
50+
this.data = this.entityTransformer(this.initialData)
4951
this.write()
5052
}
5153

Original file line numberDiff line numberDiff line change
@@ -1,8 +1,24 @@
11
import { StorageProvider } from './StorageProvider.js'
22
import type { Steno } from '../types.js'
3+
import type { CreateLogger } from '@stenodb/logger'
34

45
export class BrowserProvider {
6+
#options: Steno.BrowserProviderOptions | undefined
7+
#logger: CreateLogger
8+
9+
constructor(options?: Steno.BrowserProviderOptions) {
10+
this.#options = options
11+
this.#logger = options?.logger
12+
}
13+
14+
private registerAdapterModules<T>(adapter: Steno.BrowserAdapter<T>) {
15+
if (!this.#logger) return
16+
const logger = this.#logger(adapter.name)
17+
adapter.registerLogger(logger)
18+
}
19+
520
create<T>(adapter: Steno.BrowserAdapter<T>): StorageProvider<T> {
21+
this.registerAdapterModules(adapter)
622
return new StorageProvider(adapter)
723
}
824
}

packages/browser/src/provider/StorageProvider.ts

-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ export class StorageProvider<T> extends BaseProvider<T> {
1414

1515
if (!this.data) {
1616
this.reset()
17-
} else {
18-
this.data = this.#adapter.plainData()
1917
}
2018

2119
return this.data

packages/browser/src/types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import type { LocalStorage } from './adapter/LocalStorage.js'
22
import type { SessionStorage } from './adapter/SessionStorage.js'
33
import type { StorageProvider } from './provider/StorageProvider.js'
4+
import type { CreateLogger } from '@stenodb/logger'
45
import type { ClassConstructor } from 'class-transformer'
56

67
export namespace Steno {
78
export type Entity<T> = ClassConstructor<T>
89
export type BrowserAdapter<T> = LocalStorage<T> | SessionStorage<T>
910
export type BrowserProvider<T> = StorageProvider<T>
11+
12+
export interface BrowserProviderOptions {
13+
logger?: CreateLogger
14+
}
1015
}

packages/logger/package.json

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "@stenodb/logger",
3+
"version": "3.0.0",
4+
"type": "module",
5+
"files": [
6+
"dist"
7+
],
8+
"types": "dist",
9+
"typesVersions": {
10+
"*": {
11+
"rotation": [
12+
"./dist/rotation.d.ts"
13+
]
14+
}
15+
},
16+
"exports": {
17+
".": "./dist/index.js",
18+
"./rotation": "./dist/rotation.js"
19+
},
20+
"scripts": {
21+
"dev": "tsc --watch",
22+
"build": "del-cli dist && tsc",
23+
"prepublishOnly": "pnpm build"
24+
},
25+
"devDependencies": {
26+
"@types/node": "18.11.19"
27+
},
28+
"engines": {
29+
"node": ">=14.16"
30+
},
31+
"dependencies": {
32+
"rotating-file-stream": "3.0.4",
33+
"tslog": "4.7.2"
34+
}
35+
}

packages/logger/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './logger.js'
2+
export * from './types.js'

0 commit comments

Comments
 (0)