diff --git a/package.json b/package.json index 3f4e713..68c6c31 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "zeed", "type": "module", - "version": "0.20.3", + "version": "0.20.4", "description": "🌱 Simple foundation library", "author": { "name": "Dirk Holtwick", diff --git a/src/common/data/object.spec.ts b/src/common/data/object.spec.ts index 4df183c..687a5d0 100644 --- a/src/common/data/object.spec.ts +++ b/src/common/data/object.spec.ts @@ -178,7 +178,11 @@ describe('objectPlain', () => { fn.z = 6 class Klass { - name = 'test' + name: string + + constructor(name?: string) { + this.name = name ?? 'test' + } } const obj = { @@ -197,33 +201,51 @@ describe('objectPlain', () => { ['b', 2], [true, 'bool'], ]), + date: new Date(Date.UTC(2024, 0, 1, 12)), weakMap: new WeakMap(), uint8: new Uint8Array([1, 2, 3]), uint16: new Uint16Array([1, 2, 3]), c: 2, d: [3, 4, 5], Klass, - newKlass: new Klass(), + newKlass: new Klass('testNewKlass'), }, } + const result = objectPlain(obj, { keepAsIs: o => o instanceof Uint8Array, errorTrace: false, + transformer(obj) { + if (obj instanceof Date) + return { __timestamp: obj.getTime() } + }, }) expect(result).toMatchInlineSnapshot(` Object { "a": 1, "b": Object { - "Klass": Object {}, + "Klass": Object { + "__class": "Function", + }, "c": 2, "d": Array [ 3, 4, 5, ], - "err": "Error: err", + "date": Object { + "__timestamp": 1704110400000, + }, + "err": Object { + "__class": "Error", + "cause": undefined, + "message": "err", + "name": "Error", + "stack": undefined, + }, "fn": Object { + "__class": "Function", "z": 6, }, "inf": Infinity, @@ -234,9 +256,13 @@ describe('objectPlain', () => { }, "nan": NaN, "newKlass": Object { - "name": "test", + "__class": "Klass", + "name": "testNewKlass", + }, + "rx": Object { + "__class": "RegExp", + "source": "/.*?.test/gim", }, - "rx": "/.*?.test/gim", "set": Array [ 1, Object { @@ -254,7 +280,9 @@ describe('objectPlain', () => { 2, 3, ], - "weakMap": Object {}, + "weakMap": Object { + "__class": "WeakMap", + }, "x": "Symbol(x)", "y": 123n, }, diff --git a/src/common/data/object.ts b/src/common/data/object.ts index 45e3290..1695beb 100644 --- a/src/common/data/object.ts +++ b/src/common/data/object.ts @@ -61,6 +61,7 @@ export function objectPlain(obj: any, opt?: { errorTrace?: boolean filter?: (value: any) => boolean keepAsIs?: (value: any) => boolean + transformer?: (value: any) => any | undefined }): any { const { maxDepth = 99, @@ -69,6 +70,7 @@ export function objectPlain(obj: any, opt?: { errorTrace = true, filter = () => true, keepAsIs = () => false, + transformer = () => undefined, } = opt ?? {} const cycle: any = [] @@ -91,11 +93,25 @@ export function objectPlain(obj: any, opt?: { cycle.push(obj) - if (obj instanceof Date) - return obj.toISOString() + if (transformer) { + const result = transformer(obj) + if (result !== undefined) + return result + } + + if (obj instanceof Date) { + return { + __class: 'Date', + value: obj.toISOString(), + } + } - if (obj instanceof RegExp) - return obj.toString() + if (obj instanceof RegExp) { + return { + __class: 'RegExp', + source: obj.toString(), + } + } if (obj instanceof Map) obj = Object.fromEntries(obj) @@ -103,8 +119,21 @@ export function objectPlain(obj: any, opt?: { if (obj instanceof Set || isBinaryArray(obj)) obj = Array.from(obj as any) - if (obj instanceof Error) - return `${obj.name || 'Error'}: ${obj.message}${errorTrace ? `\n${obj.stack}` : ''}` + if (obj instanceof Error) { + return { + __class: 'Error', + name: obj.name, + message: obj.message, + stack: errorTrace ? obj.stack : undefined, + cause: obj.cause ? String(obj.cause) : undefined, + } + } + // return `${obj.name || 'Error'}: ${obj.message}${errorTrace ? `\n${obj.stack}` : ''}` + + /* if (obj instanceof Element) { + const attrs = obj.getAttributeNames().map(name => `${name}="${String(obj.getAttribute(name))}"`).join(' ') + return `<${[obj.tagName.toLocaleLowerCase(), ...attrs].join(' ')}}>` + } */ if (Array.isArray(obj)) { return obj @@ -112,7 +141,21 @@ export function objectPlain(obj: any, opt?: { .map(o => handleObject(o, depth + 1)) } - // if (isObject(obj) || isFunction(obj)) { + // For class objects just dump the first level of primitives + const objName = obj?.constructor?.name + if (objName && objName !== 'Object') { + const nobj: any = { + __class: objName, + // __code: obj.toString ? obj.toString() : undefined, + } + for (const k in obj) { + if (Object.hasOwn(obj, k) && isPrimitive(obj[k])) + nobj[k] = obj[k] + } + return nobj + } + + // Plain objects are recursively dumped const nobj: any = {} for (const key in obj) { const value = obj[key] @@ -120,7 +163,6 @@ export function objectPlain(obj: any, opt?: { nobj[key] = handleObject(value, depth + 1) } return nobj - // } // return undefined } diff --git a/src/common/network.ts b/src/common/network.ts index 0a207d3..7e4a27e 100644 --- a/src/common/network.ts +++ b/src/common/network.ts @@ -9,6 +9,8 @@ import { encodeQuery } from './data/url' import { DefaultLogger } from './log' import type { Json } from './types' +// TODO: Abort signal https://codedrivendevelopment.com/posts/everything-about-abort-signal-timeout + interface fetchOptionType { /** Returns the cache mode associated with request, which is a string indicating how the request will interact with the browser's cache when fetching. */ cache?: RequestCache