Compare commits
	
		
			7 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 057528b0c5 | |||
| 1595aea529 | |||
| 08503de552 | |||
| a9a9b04a5a | |||
| a7b19900da | |||
| 34227e5c4b | |||
| 7e8352ed2a | 
@@ -13,3 +13,7 @@ quote_type = single
 | 
			
		||||
[*.md]
 | 
			
		||||
max_line_length = off
 | 
			
		||||
trim_trailing_whitespace = false
 | 
			
		||||
 | 
			
		||||
[*.{yaml,yml}]
 | 
			
		||||
indent_style = space
 | 
			
		||||
indent_size = 2
 | 
			
		||||
							
								
								
									
										96
									
								
								.github/workflows/build.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										96
									
								
								.github/workflows/build.yaml
									
									
									
									
										vendored
									
									
								
							@@ -11,26 +11,26 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    container: node:20-alpine
 | 
			
		||||
    steps:
 | 
			
		||||
      -   name: Clone Repository
 | 
			
		||||
          uses: ztimson/actions/clone@develop
 | 
			
		||||
      - name: Clone Repository
 | 
			
		||||
        uses: ztimson/actions/clone@develop
 | 
			
		||||
 | 
			
		||||
      -   name: Install & Build
 | 
			
		||||
          run: |
 | 
			
		||||
            npm i
 | 
			
		||||
            npm run build
 | 
			
		||||
      - name: Install & Build
 | 
			
		||||
        run: |
 | 
			
		||||
          npm i
 | 
			
		||||
          npm run build
 | 
			
		||||
 | 
			
		||||
      -   name: Test
 | 
			
		||||
          run: npm run test:coverage
 | 
			
		||||
      - name: Test
 | 
			
		||||
        run: npm run test:coverage
 | 
			
		||||
 | 
			
		||||
      -   name: Upload to Registry
 | 
			
		||||
          uses: ztimson/actions/npm/publish@develop
 | 
			
		||||
      - name: Upload to Registry
 | 
			
		||||
        uses: ztimson/actions/npm/publish@develop
 | 
			
		||||
 | 
			
		||||
      -   name: Upload to NPM
 | 
			
		||||
          uses: ztimson/actions/npm/publish@develop
 | 
			
		||||
          with:
 | 
			
		||||
              owner: ztimson
 | 
			
		||||
              registry: https://registry.npmjs.org/
 | 
			
		||||
              token: ${{secrets.NPM_TOKEN}}
 | 
			
		||||
      - name: Upload to NPM
 | 
			
		||||
        uses: ztimson/actions/npm/publish@develop
 | 
			
		||||
        with:
 | 
			
		||||
          owner: ztimson
 | 
			
		||||
          registry: https://registry.npmjs.org/
 | 
			
		||||
          token: ${{secrets.NPM_TOKEN}}
 | 
			
		||||
  tag:
 | 
			
		||||
    name: Tag Version
 | 
			
		||||
    needs: build
 | 
			
		||||
@@ -38,23 +38,53 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    container: node
 | 
			
		||||
    steps:
 | 
			
		||||
      -   name: Clone Repository
 | 
			
		||||
          uses: ztimson/actions/clone@develop
 | 
			
		||||
 | 
			
		||||
      -   name: Get Version Number
 | 
			
		||||
          run: echo "VERSION=$(cat package.json | grep version | grep -Eo ':.+' | grep -Eo '[[:alnum:]\.\/\-]+')" >> $GITHUB_ENV
 | 
			
		||||
 | 
			
		||||
      -   name: Tag Version
 | 
			
		||||
          uses: ztimson/actions/tag@develop
 | 
			
		||||
          with:
 | 
			
		||||
            tag: ${{env.VERSION}}
 | 
			
		||||
      - name: Clone Repository
 | 
			
		||||
        uses: ztimson/actions/clone@develop
 | 
			
		||||
 | 
			
		||||
      - name: Get Version Number
 | 
			
		||||
        run: echo "VERSION=$(cat package.json | grep version | grep -Eo ':.+' | grep -Eo '[[:alnum:]\.\/\-]+')" >> $GITHUB_ENV
 | 
			
		||||
 | 
			
		||||
      - name: Tag Version
 | 
			
		||||
        uses: ztimson/actions/tag@develop
 | 
			
		||||
        with:
 | 
			
		||||
          tag: ${{env.VERSION}}
 | 
			
		||||
  docs:
 | 
			
		||||
    name: Publish Documentation
 | 
			
		||||
    needs: build
 | 
			
		||||
    uses: ztimson/actions/.github/workflows/docker.yaml@develop
 | 
			
		||||
    with:
 | 
			
		||||
        name: ztimson/utils
 | 
			
		||||
        repository: ${{github.server_url}}/${{github.repository}}.git
 | 
			
		||||
        pass: ${{secrets.DEPLOY_TOKEN}}
 | 
			
		||||
    name: Publish Docs
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    container: docker:dind
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Clone Repository
 | 
			
		||||
        uses: ztimson/actions/clone@develop
 | 
			
		||||
 | 
			
		||||
      - name: Build Image
 | 
			
		||||
        run: |
 | 
			
		||||
          REGISTRY=$(echo ${{github.server_url}} | sed s%http://%% | sed s%https://%%)
 | 
			
		||||
          docker build -t "$REGISTRY/${{github.repository}}:${{github.ref_name}}" .
 | 
			
		||||
 | 
			
		||||
      - name: Build Image
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "CHECKSUM: ${{env.CHECKSUM}}"
 | 
			
		||||
          REGISTRY=$(echo ${{github.server_url}} | sed s%http://%% | sed s%https://%%)
 | 
			
		||||
          docker build -t "$REGISTRY/${{github.repository}}:${{github.ref_name}}" --build-arg LICENSE_KEY="${{secrets.LICENSE_KEY}}" --build-arg UPDATE_TOKEN="${{secrets.UPDATE_TOKEN}}" .
 | 
			
		||||
 | 
			
		||||
      - name: Publish Branch Tag
 | 
			
		||||
        run: |
 | 
			
		||||
          REGISTRY=$(echo ${{github.server_url}} | sed s%http://%% | sed s%https://%%)
 | 
			
		||||
          docker login -u ${{github.repository_owner}} -p ${{secrets.DEPLOY_TOKEN}} $REGISTRY
 | 
			
		||||
          docker push "$REGISTRY/${{github.repository}}:${{github.ref_name}}"
 | 
			
		||||
 | 
			
		||||
      - name: Publish Version Tag
 | 
			
		||||
        run: |
 | 
			
		||||
          if [ "${{github.ref_name}}" = "master" ]; then
 | 
			
		||||
            REGISTRY=$(echo ${{github.server_url}} | sed s%http://%% | sed s%https://%%)
 | 
			
		||||
            docker tag "$REGISTRY/${{github.repository}}:${{github.ref_name}}" "$REGISTRY/${{github.repository}}:${{env.VERSION}}"
 | 
			
		||||
            docker push "$REGISTRY/${{github.repository}}:${{env.VERSION}}"
 | 
			
		||||
          fi
 | 
			
		||||
 | 
			
		||||
      - name: Publish Latest Tag
 | 
			
		||||
        run: |
 | 
			
		||||
          if [ "${{github.ref_name}}" = "master" ]; then
 | 
			
		||||
            REGISTRY=$(echo ${{github.server_url}} | sed s%http://%% | sed s%https://%%)
 | 
			
		||||
            docker tag "$REGISTRY/${{github.repository}}:${{github.ref_name}}" "$REGISTRY/${{github.repository}}:latest"
 | 
			
		||||
            docker push "$REGISTRY/${{github.repository}}:latest"
 | 
			
		||||
          fi
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										101
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										101
									
								
								index.html
									
									
									
									
									
								
							@@ -1,11 +1,102 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
	<head>
 | 
			
		||||
		<title>Console Styling Playground</title>
 | 
			
		||||
	</head>
 | 
			
		||||
	<body>
 | 
			
		||||
		<script type="module">
 | 
			
		||||
			import {formatDate} from './dist/index.mjs';
 | 
			
		||||
		<h1>Open Chrome DevTools Console to see magic! (Ctrl+Shift+J or Cmd+Option+J)</h1>
 | 
			
		||||
 | 
			
		||||
			const dt = new Date('2021-03-03T09:00:00Z');
 | 
			
		||||
			const result = formatDate('Do MMMM dddd', dt, 0);
 | 
			
		||||
			console.log(result);
 | 
			
		||||
		<script>
 | 
			
		||||
			// Console Styling Playground
 | 
			
		||||
			class ConsoleStyler {
 | 
			
		||||
				// Basic badge with customizable colors
 | 
			
		||||
				badge(text, bgColor = '#007bff', textColor = 'white') {
 | 
			
		||||
					console.log(
 | 
			
		||||
						`%c ${text} `,
 | 
			
		||||
						`background-color: ${bgColor};
 | 
			
		||||
                 color: ${textColor};
 | 
			
		||||
                 border-radius: 12px;
 | 
			
		||||
                 padding: 2px 8px;
 | 
			
		||||
                 font-weight: bold;`
 | 
			
		||||
					);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Multi-style log with different sections
 | 
			
		||||
				richLog() {
 | 
			
		||||
					console.log(
 | 
			
		||||
						'%cSystem%c Operation %cDetails',
 | 
			
		||||
						'background-color: #f0f0f0; color: black; padding: 2px 5px; border-radius: 3px;',
 | 
			
		||||
						'color: blue; font-weight: bold;',
 | 
			
		||||
						'color: green;'
 | 
			
		||||
					);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Grouped logs with nested information
 | 
			
		||||
				groupedLog(title, items) {
 | 
			
		||||
					console.group(title);
 | 
			
		||||
					items.forEach(item => {
 | 
			
		||||
						console.log(item);
 | 
			
		||||
					});
 | 
			
		||||
					console.groupEnd();
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Table view for structured data
 | 
			
		||||
				tableLog(data) {
 | 
			
		||||
					console.table(data);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Performance tracking
 | 
			
		||||
				timeTrack(label, operation) {
 | 
			
		||||
					console.time(label);
 | 
			
		||||
					operation();
 | 
			
		||||
					console.timeEnd(label);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Demonstration method
 | 
			
		||||
				demo() {
 | 
			
		||||
					// Different styled badges
 | 
			
		||||
					this.badge('NEW', '#28a745');  // Green
 | 
			
		||||
					this.badge('WARNING', '#ffc107', 'black');  // Yellow
 | 
			
		||||
					this.badge('ERROR', '#dc3545');  // Red
 | 
			
		||||
					this.badge('CUSTOM', '#6f42c1');  // Purple
 | 
			
		||||
 | 
			
		||||
					// Rich multi-style log
 | 
			
		||||
					this.richLog();
 | 
			
		||||
 | 
			
		||||
					// Grouped logs
 | 
			
		||||
					this.groupedLog('User Details', [
 | 
			
		||||
						{ name: 'John Doe', age: 30, role: 'Admin' },
 | 
			
		||||
						{ name: 'Jane Smith', age: 25, role: 'User' }
 | 
			
		||||
					]);
 | 
			
		||||
 | 
			
		||||
					// Table logging
 | 
			
		||||
					this.tableLog([
 | 
			
		||||
						{ name: 'John', age: 30, active: true },
 | 
			
		||||
						{ name: 'Jane', age: 25, active: false }
 | 
			
		||||
					]);
 | 
			
		||||
 | 
			
		||||
					// Performance tracking
 | 
			
		||||
					this.timeTrack('Complex Operation', () => {
 | 
			
		||||
						let sum = 0;
 | 
			
		||||
						for(let i = 0; i < 1000000; i++) {
 | 
			
		||||
							sum += i;
 | 
			
		||||
						}
 | 
			
		||||
						console.log('Sum:', sum);
 | 
			
		||||
					});
 | 
			
		||||
 | 
			
		||||
					// Advanced: Conditional styling
 | 
			
		||||
					const logLevel = 'warn';
 | 
			
		||||
					console.log(
 | 
			
		||||
						`%c[${logLevel.toUpperCase()}]%c Detailed message`,
 | 
			
		||||
						`color: ${logLevel === 'warn' ? 'orange' : 'red'}; font-weight: bold;`,
 | 
			
		||||
						'color: black;'
 | 
			
		||||
					);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Create instance and run demo
 | 
			
		||||
			const styler = new ConsoleStyler();
 | 
			
		||||
			styler.demo();
 | 
			
		||||
		</script>
 | 
			
		||||
	</body>
 | 
			
		||||
</html>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
	"name": "@ztimson/utils",
 | 
			
		||||
	"version": "0.27.5",
 | 
			
		||||
	"version": "0.27.9",
 | 
			
		||||
	"description": "Utility library",
 | 
			
		||||
	"author": "Zak Timson",
 | 
			
		||||
	"license": "MIT",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import {Database, Table} from './database.ts';
 | 
			
		||||
import {deepCopy, includes, JSONSanitize} from './objects.ts';
 | 
			
		||||
import {JSONSanitize} from './json.ts';
 | 
			
		||||
import {deepCopy, includes} from './objects.ts';
 | 
			
		||||
 | 
			
		||||
export type CacheOptions = {
 | 
			
		||||
	/** Delete keys automatically after x amount of seconds */
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import {makeArray} from './array.ts';
 | 
			
		||||
import {ASet} from './aset.ts';
 | 
			
		||||
import {dotNotation, flattenObj, JSONSanitize} from './objects.ts';
 | 
			
		||||
import {JSONSanitize} from './json.ts';
 | 
			
		||||
import {dotNotation, flattenObj} from './objects.ts';
 | 
			
		||||
import {LETTER_LIST} from './string.ts';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import {makeArray} from './array.ts';
 | 
			
		||||
import {JSONAttemptParse} from './objects.ts';
 | 
			
		||||
import {JSONAttemptParse} from './json.ts';
 | 
			
		||||
import {PromiseProgress} from './promise-progress';
 | 
			
		||||
import {formatDate} from './time.ts';
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ export * from './files';
 | 
			
		||||
export * from './emitter';
 | 
			
		||||
export * from './errors';
 | 
			
		||||
export * from './http';
 | 
			
		||||
export * from './json';
 | 
			
		||||
export * from './jwt';
 | 
			
		||||
export * from './logger';
 | 
			
		||||
export * from './math';
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										40
									
								
								src/json.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/json.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Parse JSON but return the original string if it fails
 | 
			
		||||
 *
 | 
			
		||||
 * @param {any} json JSON string to parse
 | 
			
		||||
 * @param fallback Fallback value if unable to parse, defaults to original string
 | 
			
		||||
 * @return {string | T} Object if successful, original string otherwise
 | 
			
		||||
 */
 | 
			
		||||
export function JSONAttemptParse<T1, T2>(json: T2, fallback?: T1): T1 | T2 {
 | 
			
		||||
	try { return JSON.parse(<any>json); }
 | 
			
		||||
	catch { return fallback ?? json; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Stringifies objects & skips primitives
 | 
			
		||||
 *
 | 
			
		||||
 * @param {any} obj Object to convert to serializable value
 | 
			
		||||
 * @return {string | T} Serialized value
 | 
			
		||||
 */
 | 
			
		||||
export function JSONSerialize<T1>(obj: T1): T1 | string {
 | 
			
		||||
	if(typeof obj == 'object' && obj != null) return JSONSanitize(obj);
 | 
			
		||||
	return obj;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Convert an object to a JSON string avoiding any circular references.
 | 
			
		||||
 *
 | 
			
		||||
 * @param obj Object to convert to JSON
 | 
			
		||||
 * @param {number} space Format the JSON with spaces
 | 
			
		||||
 * @return {string} JSON string
 | 
			
		||||
 */
 | 
			
		||||
export function JSONSanitize(obj: any, space?: number): string {
 | 
			
		||||
	const cache: any[] = [];
 | 
			
		||||
	return JSON.stringify(obj, (key, value) => {
 | 
			
		||||
		if(typeof value === 'object' && value !== null) {
 | 
			
		||||
			if(cache.includes(value)) return '[Circular]';
 | 
			
		||||
			cache.push(value);
 | 
			
		||||
		}
 | 
			
		||||
		return value;
 | 
			
		||||
	}, space);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import {JSONAttemptParse} from './objects.ts';
 | 
			
		||||
import {JSONAttemptParse} from './json.ts';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Creates a JSON Web Token (JWT) using the provided payload.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import {TypedEmitter, TypedEvents} from './emitter';
 | 
			
		||||
import {JSONSanitize} from './objects.ts';
 | 
			
		||||
import {JSONSanitize} from './json.ts';
 | 
			
		||||
 | 
			
		||||
export const CliEffects = {
 | 
			
		||||
	CLEAR: "\x1b[0m",
 | 
			
		||||
@@ -42,6 +42,8 @@ export const CliBackground = {
 | 
			
		||||
	GREY: "\x1b[100m",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type LogLevels = 'debug' | 'log' | 'info' | 'warn' | 'error';
 | 
			
		||||
 | 
			
		||||
export enum LOG_LEVEL {
 | 
			
		||||
	ERROR = 0,
 | 
			
		||||
	WARN = 1,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										29
									
								
								src/misc.ts
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								src/misc.ts
									
									
									
									
									
								
							@@ -1,3 +1,4 @@
 | 
			
		||||
import {LogLevels} from './logger.ts';
 | 
			
		||||
import {PathEvent} from './path-events.ts';
 | 
			
		||||
import {md5} from './string';
 | 
			
		||||
 | 
			
		||||
@@ -14,6 +15,34 @@ export function compareVersions(target: string, vs: string): -1 | 0 | 1 {
 | 
			
		||||
		(tMajor < vMajor || tMinor < vMinor || tPatch < vPatch) ? -1 : 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Create a console object to intercept logs with optional passthrough
 | 
			
		||||
 * @param {null | {debug: Function, log: Function, info: Function, warn: Function, error: Function}} out Passthrough logs, null to silence
 | 
			
		||||
 * @param {{[K in LogLevels]?: LogLevels | "none"}} map Map log levels: {log: 'debug', warn: 'error'} = Suppress debug logs, elevate warnings
 | 
			
		||||
 * @returns {{debug: Function, log: Function, info: Function, warn: Function, error: Function, stderr: string[], stdout: string[]}}
 | 
			
		||||
 */
 | 
			
		||||
export function consoleInterceptor(
 | 
			
		||||
	out: null | {debug: Function, log: Function, info: Function, warn: Function, error: Function} = console,
 | 
			
		||||
	map?: {[K in LogLevels]?: LogLevels | 'none'}
 | 
			
		||||
): {debug: Function, log: Function, info: Function, warn: Function, error: Function, output: {debug: any[], log: any[], info: any[], warn: any[], error: any[], stderr: any[], stdout: any[]}} {
 | 
			
		||||
	const logs: any = {debug: [], log: [], info: [], warn: [], error: [], stderr: [], stdout: [],}
 | 
			
		||||
	const cWrapper = (type: 'debug' | 'log' | 'info' | 'warn' | 'error') => ((...args: any[]) => {
 | 
			
		||||
		if(out) out[type](...args);
 | 
			
		||||
		logs[type].push(...args);
 | 
			
		||||
		if(type == 'error') logs.stderr.push(...args);
 | 
			
		||||
		else logs.stdout.push(...args);
 | 
			
		||||
	});
 | 
			
		||||
	return {
 | 
			
		||||
		debug: map?.debug != 'none' ? cWrapper(map?.debug || 'debug') : () => {},
 | 
			
		||||
		log: map?.log != 'none' ? cWrapper(map?.log || 'log') : () => {},
 | 
			
		||||
		info: map?.info != 'none' ? cWrapper(map?.info || 'info') : () => {},
 | 
			
		||||
		warn: map?.warn != 'none' ? cWrapper(map?.warn || 'warn') : () => {},
 | 
			
		||||
		error: map?.error != 'none' ? cWrapper(map?.error || 'error') : () => {},
 | 
			
		||||
		output: logs
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Escape any regex special characters to avoid misinterpretation during search
 | 
			
		||||
 * @param {string} value String which should be escaped
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
export type Delta = { [key: string]: any | Delta | null };
 | 
			
		||||
import {JSONSanitize} from './json.ts';
 | 
			
		||||
 | 
			
		||||
export type Delta = { [key: string]: any | Delta | null };
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Applies deltas to `target`.
 | 
			
		||||
@@ -294,41 +295,12 @@ export function mixin(target: any, constructors: any[]) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Parse JSON but return the original string if it fails
 | 
			
		||||
 *
 | 
			
		||||
 * @param {any} json JSON string to parse
 | 
			
		||||
 * @return {string | T} Object if successful, original string otherwise
 | 
			
		||||
 * Run a map function on each property
 | 
			
		||||
 * @param obj Object that will be iterated
 | 
			
		||||
 * @param {(key: string, value: any) => any} fn Transformer function - receives key & value
 | 
			
		||||
 * @returns {{}}
 | 
			
		||||
 */
 | 
			
		||||
export function JSONAttemptParse<T1, T2>(json: T2): T1 | T2 {
 | 
			
		||||
	try { return JSON.parse(<any>json); }
 | 
			
		||||
	catch { return json; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Stringifies objects & skips primitives
 | 
			
		||||
 *
 | 
			
		||||
 * @param {any} obj Object to convert to serializable value
 | 
			
		||||
 * @return {string | T} Serialized value
 | 
			
		||||
 */
 | 
			
		||||
export function JSONSerialize<T1>(obj: T1): T1 | string {
 | 
			
		||||
	if(typeof obj == 'object' && obj != null) return JSONSanitize(obj);
 | 
			
		||||
	return obj;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Convert an object to a JSON string avoiding any circular references.
 | 
			
		||||
 *
 | 
			
		||||
 * @param obj Object to convert to JSON
 | 
			
		||||
 * @param {number} space Format the JSON with spaces
 | 
			
		||||
 * @return {string} JSON string
 | 
			
		||||
 */
 | 
			
		||||
export function JSONSanitize(obj: any, space?: number): string {
 | 
			
		||||
	const cache: any[] = [];
 | 
			
		||||
	return JSON.stringify(obj, (key, value) => {
 | 
			
		||||
		if(typeof value === 'object' && value !== null) {
 | 
			
		||||
			if(cache.includes(value)) return '[Circular]';
 | 
			
		||||
			cache.push(value);
 | 
			
		||||
		}
 | 
			
		||||
		return value;
 | 
			
		||||
	}, space);
 | 
			
		||||
export function objectMap<T>(obj: any, fn: (key: string, value: any) => any): T {
 | 
			
		||||
	return <any>Object.entries(obj).map(([key, value]: [string, any]) => [key, fn(key, value)])
 | 
			
		||||
		.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
import {dotNotation, JSONAttemptParse, JSONSerialize} from './objects.ts';
 | 
			
		||||
import {JSONAttemptParse, JSONSerialize} from './json.ts';
 | 
			
		||||
import {dotNotation} from './objects.ts';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Filters an array of objects based on a search term and optional regex checking.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										46
									
								
								src/time.ts
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								src/time.ts
									
									
									
									
									
								
							@@ -1,5 +1,3 @@
 | 
			
		||||
import {numSuffix} from './math.ts';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Like setInterval but will adjust the timeout value to account for runtime
 | 
			
		||||
 * @param {Function} cb Callback function that will be ran
 | 
			
		||||
@@ -34,6 +32,50 @@ export function dayOfYear(date: Date): number {
 | 
			
		||||
/**
 | 
			
		||||
 * Format date
 | 
			
		||||
 *
 | 
			
		||||
 * Year:
 | 
			
		||||
 * - YYYY = 2025 (4-digit year)
 | 
			
		||||
 * - YY = 25 (2-digit year)
 | 
			
		||||
 *
 | 
			
		||||
 * Month:
 | 
			
		||||
 * - MMMM = January (full month name)
 | 
			
		||||
 * - MMM = Jan (abbreviated month name)
 | 
			
		||||
 * - MM = 01 (zero-padded month number)
 | 
			
		||||
 * - M = 1 (month number)
 | 
			
		||||
 *
 | 
			
		||||
 * Day:
 | 
			
		||||
 * - DDD = 123 (day of year)
 | 
			
		||||
 * - DD = 01 (zero-padded day)
 | 
			
		||||
 * - Do = 1st (day with ordinal suffix)
 | 
			
		||||
 * - D = 1 (day number)
 | 
			
		||||
 * - dddd = Monday (full day name)
 | 
			
		||||
 * - ddd = Mon (abbreviated day name)
 | 
			
		||||
 *
 | 
			
		||||
 * Hour:
 | 
			
		||||
 * - HH = 13 (24-hour format, zero-padded)
 | 
			
		||||
 * - H = 13 (24-hour format)
 | 
			
		||||
 * - hh = 01 (12-hour format, zero-padded)
 | 
			
		||||
 * - h = 1 (12-hour format)
 | 
			
		||||
 *
 | 
			
		||||
 * Minute:
 | 
			
		||||
 * - mm = 05 (zero-padded minutes)
 | 
			
		||||
 * - m = 5 (minutes)
 | 
			
		||||
 *
 | 
			
		||||
 * Second:
 | 
			
		||||
 * - ss = 09 (zero-padded seconds)
 | 
			
		||||
 * - s = 9 (seconds)
 | 
			
		||||
 *
 | 
			
		||||
 * Millisecond:
 | 
			
		||||
 * - SSS = 123 (3-digit milliseconds)
 | 
			
		||||
 *
 | 
			
		||||
 * AM/PM:
 | 
			
		||||
 * - A = AM/PM (uppercase)
 | 
			
		||||
 * - a = am/pm (lowercase)
 | 
			
		||||
 *
 | 
			
		||||
 * Timezone:
 | 
			
		||||
 * - ZZ = +0500 (timezone offset without colon)
 | 
			
		||||
 * - Z = +05:00 (timezone offset with colon)
 | 
			
		||||
 * - z = EST (timezone abbreviation)
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} format How date string will be formatted, default: `YYYY-MM-DD H:mm A`
 | 
			
		||||
 * @param {Date | number | string} date Date or timestamp, defaults to now
 | 
			
		||||
 * @param tz Set timezone offset
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										33
									
								
								tests/json.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								tests/json.spec.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
import {JSONAttemptParse, JSONSanitize, JSONSerialize} from '../src';
 | 
			
		||||
 | 
			
		||||
describe('JSON Utilities', () => {
 | 
			
		||||
	describe('JSONAttemptParse', () => {
 | 
			
		||||
		it('parses valid JSON', () => {
 | 
			
		||||
			expect(JSONAttemptParse('{"a":1}')).toEqual({ a: 1 });
 | 
			
		||||
		});
 | 
			
		||||
		it('returns original string on error', () => {
 | 
			
		||||
			expect(JSONAttemptParse('not json')).toBe('not json');
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe('JSONSerialize', () => {
 | 
			
		||||
		it('serializes objects', () => {
 | 
			
		||||
			expect(JSONSerialize({ a: 1 })).toBe(JSON.stringify({ a: 1 }));
 | 
			
		||||
		});
 | 
			
		||||
		it('leaves primitives as is', () => {
 | 
			
		||||
			expect(JSONSerialize('test')).toBe('test');
 | 
			
		||||
			expect(JSONSerialize(123)).toBe(123);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe('JSONSanitize', () => {
 | 
			
		||||
		it('stringifies objects', () => {
 | 
			
		||||
			expect(JSONSanitize({ a: 1 })).toBe(JSON.stringify({ a: 1 }));
 | 
			
		||||
		});
 | 
			
		||||
		it('does not throw on circular refs', () => {
 | 
			
		||||
			const obj: any = {};
 | 
			
		||||
			obj.self = obj;
 | 
			
		||||
			expect(() => JSONSanitize(obj)).not.toThrow();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
import {
 | 
			
		||||
	clean, deepCopy, deepMerge, dotNotation, encodeQuery, flattenObj, formData, includes, isEqual, mixin,
 | 
			
		||||
	JSONAttemptParse, JSONSerialize, JSONSanitize
 | 
			
		||||
} from '../src';
 | 
			
		||||
 | 
			
		||||
describe('Object utilities', () => {
 | 
			
		||||
@@ -131,34 +130,4 @@ describe('Object utilities', () => {
 | 
			
		||||
			expect(c.bar()).toBe(2);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe('JSONAttemptParse', () => {
 | 
			
		||||
		it('parses valid JSON', () => {
 | 
			
		||||
			expect(JSONAttemptParse('{"a":1}')).toEqual({ a: 1 });
 | 
			
		||||
		});
 | 
			
		||||
		it('returns original string on error', () => {
 | 
			
		||||
			expect(JSONAttemptParse('not json')).toBe('not json');
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe('JSONSerialize', () => {
 | 
			
		||||
		it('serializes objects', () => {
 | 
			
		||||
			expect(JSONSerialize({ a: 1 })).toBe(JSON.stringify({ a: 1 }));
 | 
			
		||||
		});
 | 
			
		||||
		it('leaves primitives as is', () => {
 | 
			
		||||
			expect(JSONSerialize('test')).toBe('test');
 | 
			
		||||
			expect(JSONSerialize(123)).toBe(123);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe('JSONSanitize', () => {
 | 
			
		||||
		it('stringifies objects', () => {
 | 
			
		||||
			expect(JSONSanitize({ a: 1 })).toBe(JSON.stringify({ a: 1 }));
 | 
			
		||||
		});
 | 
			
		||||
		it('does not throw on circular refs', () => {
 | 
			
		||||
			const obj: any = {};
 | 
			
		||||
			obj.self = obj;
 | 
			
		||||
			expect(() => JSONSanitize(obj)).not.toThrow();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user