Skip to content

Commit

Permalink
Merge branch 'release/release/0.15.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
holtwick committed Nov 29, 2023
2 parents 6947b26 + 6d78ce0 commit dec9a85
Show file tree
Hide file tree
Showing 10 changed files with 125 additions and 140 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2021 Dirk Holtwick
Copyright (c) 2021-2023 Dirk Holtwick

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
35 changes: 19 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

> Plant the "zeed" for your next Typescript project and let it grow with this useful lib, providing basic functionalities handy in most projects.
- Strict TypeScript
- No dependencies and lightweight
- Covered by tests
- Universal for node.js and browsers
- Modern ESM, fallback to CommonJS
- Strict **TypeScript**
- **Zero dependencies** and lightweight, **tree-shakable**
- **Universal** for browsers, as well as node, deno and bun
- Modern **ESM**, fallback to CommonJS available
- Mostly covered by **tests**

Get started like this:

Expand Down Expand Up @@ -253,28 +253,27 @@ deepMerge({ a: { b: 1 } }, { c: 3, a: { d: 4 } }) // {a:{b:1, d:4}, c:4}

## Disposer

`useDisposer` will simplify cleaning up objects. You just need to `track` a function or and object with `dispose` method to be called for cleanup. This can also be nested. A simple example is a timer:
`useDispose` will simplify cleaning up objects. You just need to `add` a function or and object with `dispose` method to be called for cleanup. This can also be nested. A simple example is a timer:

```ts
function disposableTimer() {
const timeout = setTimeout(() => console.log('hello world'), 1000)
return () => clearTimeout(timeout)
}

const disposer = useDisposer()
const dispose = useDispose()

const obj = disposableTimer()
disposer.track(obj)
dispose.add(obj)

// or

const untrackTimer = disposer.track(disposableTimer())
const untrackTimer = dispose.add(disposableTimer())
untrackTimer() // dispose single object by return value of .track

// then later one of these
// then later dispose all

disposer.untrack(obj) // dispose single object
untrackTimer() // dispose single object by return value of .track
disposer.dispose() // will dispose all tracked elements
dispose()
```

You can also `untrack` single entries. Entries are untracked LIFO. Disposers can also return a Promise and therefore `await` async disposals.
Expand All @@ -285,18 +284,22 @@ The disposer itself is also a call to dispose i.e. for convenience you can add i
class DisposeExample {
// the trick is to assign to member `dispose`, will be both
// the destructor and the registration point for disposables
dispose = useDisposer()
dispose = useDispose()

constructor() {
this.dispose.track(disposableTimer())
this.dispose.add(disposableTimer())
}
}

const obj = new DisposeExample()
// ...
obj.dispose()
obj.dispose() // or async via `await obj.dispose()`
```

## Much more...

...browse the source!

## Related and Links

Related projects:
Expand Down
21 changes: 0 additions & 21 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,3 @@ export default antfu(
},
},
)

// "@typescript-eslint/no-unsafe-argument": 0,
// "@typescript-eslint/no-unsafe-assignment": 0,
// "@typescript-eslint/no-unsafe-call": 0,
// "@typescript-eslint/no-unsafe-member-access": 0,
// "@typescript-eslint/no-unsafe-return": 0,
// "@typescript-eslint/require-await": 0,
// "@typescript-eslint/restrict-template-expressions": 0,
// "@typescript-eslint/no-misused-promises": 0,
// "jsdoc/require-jsdoc": "off",
// "jsdoc/require-param-type": "off",
// "jsdoc/require-param-description": "off",
// "jsdoc/tag-lines": "off",
// "jsdoc/check-values": "off",
// "jsdoc/check-tag-names": "off",
// "jsdoc/no-undefined-types": "off",
// "jsdoc/require-param": "off",
// "jsdoc/require-returns": "off",
// "jsdoc/require-returns-type": "off",
// "jsdoc/require-throws": "off",
// "jsdoc/require-yields": "off"
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "zeed",
"type": "module",
"version": "0.14.3",
"version": "0.15.1",
"description": "🌱 Simple foundation library",
"author": {
"name": "Dirk Holtwick",
Expand Down
8 changes: 8 additions & 0 deletions src/common/dispose-defer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export function useDispose(config?: string | UseDisposeConfig | LoggerInterface)
const name = opt?.name
const log = opt?.log ?? DefaultLogger('zeed:dispose')

let disposed = 0

const tracked: Disposer[] = []

function untrack(disposable: Disposer): Promise<void> | void {
Expand All @@ -65,6 +67,9 @@ export function useDispose(config?: string | UseDisposeConfig | LoggerInterface)
function dispose(strictSync = false): Promise<any> | void {
if (name)
log.debug(`dispose "${name}": ${tracked.length} entries`)

disposed += 1

const promises: any[] = []
while (tracked.length > 0) {
const fn = tracked[0]
Expand All @@ -87,6 +92,9 @@ export function useDispose(config?: string | UseDisposeConfig | LoggerInterface)
}

return Object.assign(dispose, {
/** Counter that incremends, each time dispose has been called */
disposed,

add: track,
remove: untrack,

Expand Down
2 changes: 2 additions & 0 deletions src/common/exec/progress.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Inspired by https://developer.apple.com/documentation/foundation/progress

import { useDispose } from '../dispose-defer'
import { arrayRemoveElement } from '../data'
import { Emitter } from '../msg'
import { uname } from '../uuid'
Expand Down Expand Up @@ -32,6 +33,7 @@ export class Progress extends Emitter<{
private _resetWhenFinished = true
private _children: Progress[] = []

dispose = useDispose()
name: string

constructor(opt: ProgressOptions = {}) {
Expand Down
8 changes: 4 additions & 4 deletions src/common/msg/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ export abstract class Channel extends Emitter<{
abstract isConnected?: boolean
abstract postMessage(data: any): void

/** @deprecated use .dispose() */
close() {
void this.dispose()
}
// /** @deprecated use .dispose() */
// close() {
// void this.dispose()
// }
}

/** Very basic channel demonstrating local communication */
Expand Down
91 changes: 90 additions & 1 deletion src/common/msg/emitter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import { vi } from 'vitest'
import { detect } from '../platform'
import { sleep, waitOn } from '../exec/promise'
import { Emitter, getGlobalEmitter, lazyListener } from './emitter'
import { getSecureRandomIfPossible } from '../data/math'
import { Emitter, getGlobalEmitter } from './emitter'

const platform = detect()

Expand All @@ -14,6 +15,94 @@ declare global {
}
}

// For debugging

interface LazyEvent {
key: string
obj: any
}

export function lazyListener(
emitter: any,
listenerKey?: string,
): (key?: string, skipUnmatched?: boolean) => Promise<any> {
const name = Math.round(getSecureRandomIfPossible() * 100)

const events: LazyEvent[] = []
let lazyResolve: (() => void) | undefined

const incoming = (key: string, obj: any) => {
const ev = { key, obj }
// debug(name, " lazy push", ev)
events.push(ev)
lazyResolve && lazyResolve()
}

if (listenerKey) {
if (emitter.on) {
emitter.on(listenerKey, (obj: any) => {
incoming(listenerKey, obj)
})
}
else if (emitter.addEventListener) {
emitter.addEventListener(listenerKey, (obj: any) => {
incoming(listenerKey, obj)
})
}
else {
emitter.log.error(name, 'Cannot listen to key')
}
}
else {
if (emitter.onAny) {
emitter.onAny((key: string, obj: any) => {
incoming(key, obj)
})
}
else {
emitter.log.error(name, 'cannot listen to all for', emitter)
}
}

const on = (key?: string, skipUnmatched = true): Promise<any> => {
return new Promise((resolve, reject) => {
if (!key) {
key = listenerKey
if (!key) {
if (events.length) {
// no key specified? just take the first one!
key = events[0].key
}
}
}
// debug(name, "lazy resolve on2", key, skipUnmatched, events)
lazyResolve = () => {
// debug(name, "lazy resolve", key, listenerKey, events)
while (events.length > 0) {
const ev = events.shift() as LazyEvent
// debug(name, " lazy analyze", ev)
if (ev.key === key) {
lazyResolve = undefined
resolve(ev.obj)
}
else {
if (skipUnmatched) {
// log.warn(name, `Unhandled event ${key} with value: ${ev.obj}`)
continue
}
reject(new Error(`Expected ${key}, but found ${ev.key} with value=${ev.obj}`))
// log.error(name, `Unhandled event ${key} with value: ${ev.obj}`)
}
break
}
}
lazyResolve()
})
}

return on
}

describe('emitter', () => {
it('should emit', async () => {
expect.assertions(4)
Expand Down
93 changes: 0 additions & 93 deletions src/common/msg/emitter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
// (C)opyright 2021-07-15 Dirk Holtwick, holtwick.it. All rights reserved.

import { getSecureRandomIfPossible } from '../data/math'
import { useDispose } from '../dispose-defer'
import type { DisposerFunction } from '../dispose-types'
import { promisify } from '../exec/promise'
import { getGlobalContext } from '../global'
Expand Down Expand Up @@ -43,9 +41,6 @@ export class Emitter<

_logEmitter = DefaultLogger('zeed:emitter', 'warn')

/** Unused, but here for historical reasons */
dispose = useDispose()

call: RemoteListener = new Proxy<RemoteListener>({} as any, {
get:
(target: any, name: any) =>
Expand Down Expand Up @@ -165,91 +160,3 @@ export function getGlobalEmitter(): Emitter<ZeedGlobalEmitter> {
}
return emitter as any
}

// For debugging

interface LazyEvent {
key: string
obj: any
}

export function lazyListener(
emitter: any,
listenerKey?: string,
): (key?: string, skipUnmatched?: boolean) => Promise<any> {
const name = Math.round(getSecureRandomIfPossible() * 100)

const events: LazyEvent[] = []
let lazyResolve: (() => void) | undefined

const incoming = (key: string, obj: any) => {
const ev = { key, obj }
// debug(name, " lazy push", ev)
events.push(ev)
lazyResolve && lazyResolve()
}

if (listenerKey) {
if (emitter.on) {
emitter.on(listenerKey, (obj: any) => {
incoming(listenerKey, obj)
})
}
else if (emitter.addEventListener) {
emitter.addEventListener(listenerKey, (obj: any) => {
incoming(listenerKey, obj)
})
}
else {
emitter.log.error(name, 'Cannot listen to key')
}
}
else {
if (emitter.onAny) {
emitter.onAny((key: string, obj: any) => {
incoming(key, obj)
})
}
else {
emitter.log.error(name, 'cannot listen to all for', emitter)
}
}

const on = (key?: string, skipUnmatched = true): Promise<any> => {
return new Promise((resolve, reject) => {
if (!key) {
key = listenerKey
if (!key) {
if (events.length) {
// no key specified? just take the first one!
key = events[0].key
}
}
}
// debug(name, "lazy resolve on2", key, skipUnmatched, events)
lazyResolve = () => {
// debug(name, "lazy resolve", key, listenerKey, events)
while (events.length > 0) {
const ev = events.shift() as LazyEvent
// debug(name, " lazy analyze", ev)
if (ev.key === key) {
lazyResolve = undefined
resolve(ev.obj)
}
else {
if (skipUnmatched) {
// log.warn(name, `Unhandled event ${key} with value: ${ev.obj}`)
continue
}
reject(new Error(`Expected ${key}, but found ${ev.key} with value=${ev.obj}`))
// log.error(name, `Unhandled event ${key} with value: ${ev.obj}`)
}
break
}
}
lazyResolve()
})
}

return on
}
3 changes: 0 additions & 3 deletions tsconfig.eslint.json

This file was deleted.

0 comments on commit dec9a85

Please sign in to comment.