Skip to content

Commit

Permalink
Merge branch 'release/v0.20.8'
Browse files Browse the repository at this point in the history
  • Loading branch information
holtwick committed Jun 21, 2024
2 parents 72a4d2b + d565fad commit f17a4ff
Show file tree
Hide file tree
Showing 12 changed files with 168 additions and 44 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ _archive
.npmrc*
lib
docs
_sandbox
20 changes: 10 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "zeed",
"type": "module",
"version": "0.20.7",
"version": "0.20.8",
"description": "🌱 Simple foundation library",
"author": {
"name": "Dirk Holtwick",
Expand Down Expand Up @@ -65,16 +65,16 @@
"watch": "nr build -- --watch src"
},
"devDependencies": {
"@antfu/eslint-config": "^2.16.1",
"@antfu/eslint-config": "^2.21.1",
"@antfu/ni": "^0.21.12",
"@types/node": "^20.12.8",
"@vitest/coverage-v8": "^1.5.3",
"esbuild": "^0.20.2",
"eslint": "^9.1.1",
"tsup": "^8.0.2",
"@types/node": "^20.14.7",
"@vitest/coverage-v8": "^1.6.0",
"esbuild": "^0.21.5",
"eslint": "<9",
"tsup": "^8.1.0",
"typedoc": "^0.25.13",
"typescript": "^5.4.5",
"vite": "^5.2.10",
"vitest": "^1.5.3"
"typescript": "^5.5.2",
"vite": "^5.3.1",
"vitest": "^1.6.0"
}
}
24 changes: 22 additions & 2 deletions src/common/data/object.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import exp from 'node:constants'
import { useDispose } from '../dispose-defer'
import { Emitter } from '../msg/emitter'
import { isNotNull } from './is'
import { isBoolean, isNotNull } from './is'
import { objectInclusivePick, objectMap, objectMergeDisposable, objectOmit, objectPick, objectPlain } from './object'

describe('object.spec', () => {
Expand Down Expand Up @@ -100,6 +99,23 @@ describe('objectPlain', () => {
expect(result).toEqual(obj)
})

it('should handle bool', async () => {
const x = objectPlain({ test: true, fdsf: { fsdafs: false } }, {
transformer: (obj) => {
if (isBoolean(obj))
return +obj
},
})
expect(x).toMatchInlineSnapshot(`
Object {
"fdsf": Object {
"fsdafs": 0,
},
"test": 1,
}
`)
})

it('should handle circular references', () => {
const obj: any = {
a: 1,
Expand Down Expand Up @@ -192,6 +208,7 @@ describe('objectPlain', () => {
x: Symbol('x'),
y: BigInt(123),
fn,
bool: true,
nan: Number.NaN,
inf: Number.POSITIVE_INFINITY,
err: new Error('err'),
Expand Down Expand Up @@ -219,6 +236,8 @@ describe('objectPlain', () => {
transformer(obj) {
if (obj instanceof Date)
return { __timestamp: obj.getTime() }
if (isBoolean(obj))
return +obj
},
})

Expand All @@ -229,6 +248,7 @@ describe('objectPlain', () => {
"Klass": Object {
"__class": "Function",
},
"bool": 1,
"c": 2,
"d": Array [
3,
Expand Down
54 changes: 33 additions & 21 deletions src/common/dispose-defer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,19 @@ describe('dispose', () => {
expect(stack).toEqual(['c', 'b', 'a'])
})

it('should dispose sync using using', async () => {
const stack: string[] = []
function helper() {
using dispose = useDispose()
dispose.add(() => stack.push('a'))
dispose.add(() => stack.push('b'))
dispose.add(() => stack.push('c'))
expect(stack).toEqual([])
}
helper()
expect(stack).toEqual(['c', 'b', 'a'])
})

it('should dispose sync 2', async () => {
const stack: string[] = []
const dispose = useDispose()
Expand All @@ -124,25 +137,24 @@ describe('dispose', () => {
})

// TODO future
// it("should use using", async () => {
// class TempFile implements Disposable {

// constructor(path: string) {
// console.log('constructor')
// }

// [Symbol.dispose]() {
// console.log('dispose')
// }
// }

// function fn() {
// using f = new TempFile('abc')
// console.log('fn return')
// }

// console.log('fn before')
// fn()
// console.log('fn after')
// })
it('should use using', async () => {
class TempFile implements Disposable {
constructor(path: string) {
log('constructor')
}

[Symbol.dispose]() {
log('dispose')
}
}

function fn() {
using f = new TempFile('abc')
log('fn return')
}

log('fn before')
fn()
log('fn after')
})
})
19 changes: 19 additions & 0 deletions src/common/dispose-defer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ import { isPromise } from './exec/promise'
import { DefaultLogger } from './log'
import type { LoggerInterface } from './log/log-base'

// function polyfillUsing() {
// try {
// // @ts-expect-error just a polyfill
// Symbol.dispose ??= Symbol('Symbol.dispose')
// // @ts-expect-error just a polyfill
// Symbol.asyncDispose ??= Symbol('Symbol.asyncDispose')
// }
// catch (err) { }
// }

/** Different kinds of implementations have grown, this should unify them */
function callDisposer(disposable: Disposer): Promise<void> | void {
let result
Expand Down Expand Up @@ -116,6 +126,15 @@ export function useDispose(config?: string | UseDisposeConfig | LoggerInterface)
isDisposed() {
return tracked.length <= 0
},

[Symbol.dispose]() {
return dispose()
},

async [Symbol.asyncDispose]() {
return await dispose()
},

})
}

Expand Down
6 changes: 6 additions & 0 deletions src/common/dispose-types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
// https://blog.hediet.de/post/the_disposable_pattern_in_typescript
// todo adopt for `using` https://www.totaltypescript.com/typescript-5-2-new-keyword-using

// export interface DisposerFunctionBase {
// (): any
// [Symbol.dispose]: () => void
// }

export type DisposerFunction = () => any | Promise<any>

/** @deprecated conflicts with `using` feature */
export type Disposer = DisposerFunction | {
dispose: DisposerFunction // | Promise<unknown>

// [Symbol.dispose]:
// cleanup?: DisposerFunction | Promise<unknown> // deprecated, but used often in my old code
}
20 changes: 20 additions & 0 deletions src/common/dispose-utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,26 @@ describe('useTimeout', () => {
expect(emitter.off).toBeCalledWith(eventName, fn, ...args)
})

it('should add event listener using "on" method if available using using', () => {
const emitter = {
on: jest.fn(),
off: jest.fn(),
}
const eventName = 'click'
const fn = jest.fn()
const args = [1, 2, 3]

function helper() {
using _ = useEventListener(emitter, eventName, fn, ...args) as any
expect(emitter.on).toBeCalledWith(eventName, fn, ...args)
expect(emitter.off).not.toBeCalled()
}

helper()

expect(emitter.off).toBeCalledWith(eventName, fn, ...args)
})

it('should add event listener using "addEventListener" method if "on" method is not available', () => {
const emitter = {
addEventListener: jest.fn(),
Expand Down
30 changes: 23 additions & 7 deletions src/common/dispose-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import type { LoggerInterface } from './log/log-base'

export type TimerExecFunction = () => void | Promise<void>

export const noopDisposer: () => DisposerFunction = () => {
const dispose = () => {}
dispose[Symbol.dispose] = dispose
return dispose
}

/**
* Executes a function after a specified timeout and returns a disposer function
* that can be used to cancel the timeout.
Expand All @@ -16,12 +22,14 @@ export type TimerExecFunction = () => void | Promise<void>
*/
export function useTimeout(fn: TimerExecFunction, timeout = 0): DisposerFunction {
let timeoutHandle: any = setTimeout(fn, timeout)
return () => {
const dispose = () => {
if (timeoutHandle) {
clearTimeout(timeoutHandle)
timeoutHandle = undefined
}
}
dispose[Symbol.dispose] = dispose
return dispose
}

/**
Expand All @@ -34,12 +42,14 @@ export function useTimeout(fn: TimerExecFunction, timeout = 0): DisposerFunction
*/
export function useInterval(fn: TimerExecFunction, interval: number): DisposerFunction {
let intervalHandle: any = setInterval(fn, interval)
return () => {
const dispose = () => {
if (intervalHandle) {
clearInterval(intervalHandle)
intervalHandle = undefined
}
}
dispose[Symbol.dispose] = dispose
return dispose
}

/** The interval starts only, when the function is finished. */
Expand All @@ -56,13 +66,15 @@ export function useIntervalPause(fn: TimerExecFunction, interval: number, immedi

void loop(immediately)

return () => {
const dispose = () => {
if (intervalHandle) {
stop = true
clearInterval(intervalHandle)
intervalHandle = undefined
}
}
dispose[Symbol.dispose] = dispose
return dispose
}

export function useEventListener(
Expand All @@ -72,17 +84,19 @@ export function useEventListener(
...args: any[]
): DisposerFunction {
if (emitter == null)
return () => { }
return noopDisposer()
if (emitter.on)
emitter.on(eventName, fn, ...args)
else if (emitter.addEventListener)
emitter.addEventListener(eventName, fn, ...args)
return () => {
const dispose = () => {
if (emitter.off)
emitter.off(eventName, fn, ...args)
else if (emitter.removeEventListener)
emitter.removeEventListener(eventName, fn, ...args)
}
dispose[Symbol.dispose] = dispose
return dispose
}

export function useEventListenerOnce(
Expand All @@ -92,17 +106,19 @@ export function useEventListenerOnce(
...args: any[]
): DisposerFunction {
if (emitter == null)
return () => { }
return noopDisposer()
if (emitter.on)
emitter.once(eventName, fn, ...args)
else if (emitter.addEventListener)
emitter.addEventListener(eventName, fn, ...args)
return () => {
const dispose = () => {
if (emitter.off)
emitter.off(eventName, fn, ...args)
else if (emitter.removeEventListener)
emitter.removeEventListener(eventName, fn, ...args)
}
dispose[Symbol.dispose] = dispose
return dispose
}

/** Like useDispose but with shorthands for emitter and timers */
Expand Down
31 changes: 29 additions & 2 deletions src/node/files-async.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,43 @@
import { readdir, stat } from 'node:fs/promises'
import { join, resolve } from 'node:path'
import process from 'node:process'
import type { Stats } from 'node:fs'
import { isHiddenPath } from './fs'
import { globToRegExp } from './glob'

interface StatsBase {
isFile: () => boolean
isDirectory: () => boolean
isBlockDevice: () => boolean
isCharacterDevice: () => boolean
isSymbolicLink: () => boolean
isFIFO: () => boolean
isSocket: () => boolean
dev: number
ino: number
mode: number
nlink: number
uid: number
gid: number
rdev: number
size: number
blksize: number
blocks: number
atimeMs: number
mtimeMs: number
ctimeMs: number
birthtimeMs: number
atime: Date
mtime: Date
ctime: Date
birthtime: Date
}

/**
* Retrieves the file system stats for the specified path asynchronously.
* @param path - The path to the file or directory.
* @returns A Promise that resolves to the file system stats (Stats) or undefined if an error occurs.
*/
export async function getStatAsync(path: string): Promise<Stats | undefined> {
export async function getStatAsync(path: string): Promise<StatsBase | undefined> {
try {
return await stat(path)
}
Expand Down
Loading

0 comments on commit f17a4ff

Please sign in to comment.