Compare commits
	
		
			7 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 057528b0c5 | |||
| 1595aea529 | |||
| 08503de552 | |||
| a9a9b04a5a | |||
| a7b19900da | |||
| 34227e5c4b | |||
| 7e8352ed2a | 
@@ -13,3 +13,7 @@ quote_type = single
 | 
				
			|||||||
[*.md]
 | 
					[*.md]
 | 
				
			||||||
max_line_length = off
 | 
					max_line_length = off
 | 
				
			||||||
trim_trailing_whitespace = false
 | 
					trim_trailing_whitespace = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[*.{yaml,yml}]
 | 
				
			||||||
 | 
					indent_style = space
 | 
				
			||||||
 | 
					indent_size = 2
 | 
				
			||||||
							
								
								
									
										48
									
								
								.github/workflows/build.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										48
									
								
								.github/workflows/build.yaml
									
									
									
									
										vendored
									
									
								
							@@ -48,13 +48,43 @@ jobs:
 | 
				
			|||||||
        uses: ztimson/actions/tag@develop
 | 
					        uses: ztimson/actions/tag@develop
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          tag: ${{env.VERSION}}
 | 
					          tag: ${{env.VERSION}}
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  docs:
 | 
					  docs:
 | 
				
			||||||
    name: Publish Documentation
 | 
					    name: Publish Docs
 | 
				
			||||||
    needs: build
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    uses: ztimson/actions/.github/workflows/docker.yaml@develop
 | 
					    container: docker:dind
 | 
				
			||||||
    with:
 | 
					    steps:
 | 
				
			||||||
        name: ztimson/utils
 | 
					      - name: Clone Repository
 | 
				
			||||||
        repository: ${{github.server_url}}/${{github.repository}}.git
 | 
					        uses: ztimson/actions/clone@develop
 | 
				
			||||||
        pass: ${{secrets.DEPLOY_TOKEN}}
 | 
					
 | 
				
			||||||
 | 
					      - 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>
 | 
					<html>
 | 
				
			||||||
 | 
						<head>
 | 
				
			||||||
 | 
							<title>Console Styling Playground</title>
 | 
				
			||||||
 | 
						</head>
 | 
				
			||||||
	<body>
 | 
						<body>
 | 
				
			||||||
		<script type="module">
 | 
							<h1>Open Chrome DevTools Console to see magic! (Ctrl+Shift+J or Cmd+Option+J)</h1>
 | 
				
			||||||
			import {formatDate} from './dist/index.mjs';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const dt = new Date('2021-03-03T09:00:00Z');
 | 
							<script>
 | 
				
			||||||
			const result = formatDate('Do MMMM dddd', dt, 0);
 | 
								// Console Styling Playground
 | 
				
			||||||
			console.log(result);
 | 
								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>
 | 
							</script>
 | 
				
			||||||
	</body>
 | 
						</body>
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
	"name": "@ztimson/utils",
 | 
						"name": "@ztimson/utils",
 | 
				
			||||||
	"version": "0.27.5",
 | 
						"version": "0.27.9",
 | 
				
			||||||
	"description": "Utility library",
 | 
						"description": "Utility library",
 | 
				
			||||||
	"author": "Zak Timson",
 | 
						"author": "Zak Timson",
 | 
				
			||||||
	"license": "MIT",
 | 
						"license": "MIT",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
import {Database, Table} from './database.ts';
 | 
					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 = {
 | 
					export type CacheOptions = {
 | 
				
			||||||
	/** Delete keys automatically after x amount of seconds */
 | 
						/** Delete keys automatically after x amount of seconds */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
import {makeArray} from './array.ts';
 | 
					import {makeArray} from './array.ts';
 | 
				
			||||||
import {ASet} from './aset.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';
 | 
					import {LETTER_LIST} from './string.ts';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import {makeArray} from './array.ts';
 | 
					import {makeArray} from './array.ts';
 | 
				
			||||||
import {JSONAttemptParse} from './objects.ts';
 | 
					import {JSONAttemptParse} from './json.ts';
 | 
				
			||||||
import {PromiseProgress} from './promise-progress';
 | 
					import {PromiseProgress} from './promise-progress';
 | 
				
			||||||
import {formatDate} from './time.ts';
 | 
					import {formatDate} from './time.ts';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ export * from './files';
 | 
				
			|||||||
export * from './emitter';
 | 
					export * from './emitter';
 | 
				
			||||||
export * from './errors';
 | 
					export * from './errors';
 | 
				
			||||||
export * from './http';
 | 
					export * from './http';
 | 
				
			||||||
 | 
					export * from './json';
 | 
				
			||||||
export * from './jwt';
 | 
					export * from './jwt';
 | 
				
			||||||
export * from './logger';
 | 
					export * from './logger';
 | 
				
			||||||
export * from './math';
 | 
					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.
 | 
					 * Creates a JSON Web Token (JWT) using the provided payload.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import {TypedEmitter, TypedEvents} from './emitter';
 | 
					import {TypedEmitter, TypedEvents} from './emitter';
 | 
				
			||||||
import {JSONSanitize} from './objects.ts';
 | 
					import {JSONSanitize} from './json.ts';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const CliEffects = {
 | 
					export const CliEffects = {
 | 
				
			||||||
	CLEAR: "\x1b[0m",
 | 
						CLEAR: "\x1b[0m",
 | 
				
			||||||
@@ -42,6 +42,8 @@ export const CliBackground = {
 | 
				
			|||||||
	GREY: "\x1b[100m",
 | 
						GREY: "\x1b[100m",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type LogLevels = 'debug' | 'log' | 'info' | 'warn' | 'error';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export enum LOG_LEVEL {
 | 
					export enum LOG_LEVEL {
 | 
				
			||||||
	ERROR = 0,
 | 
						ERROR = 0,
 | 
				
			||||||
	WARN = 1,
 | 
						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 {PathEvent} from './path-events.ts';
 | 
				
			||||||
import {md5} from './string';
 | 
					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;
 | 
							(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
 | 
					 * Escape any regex special characters to avoid misinterpretation during search
 | 
				
			||||||
 * @param {string} value String which should be escaped
 | 
					 * @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`.
 | 
					 * 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
 | 
					 * Run a map function on each property
 | 
				
			||||||
 *
 | 
					 * @param obj Object that will be iterated
 | 
				
			||||||
 * @param {any} json JSON string to parse
 | 
					 * @param {(key: string, value: any) => any} fn Transformer function - receives key & value
 | 
				
			||||||
 * @return {string | T} Object if successful, original string otherwise
 | 
					 * @returns {{}}
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function JSONAttemptParse<T1, T2>(json: T2): T1 | T2 {
 | 
					export function objectMap<T>(obj: any, fn: (key: string, value: any) => any): T {
 | 
				
			||||||
	try { return JSON.parse(<any>json); }
 | 
						return <any>Object.entries(obj).map(([key, value]: [string, any]) => [key, fn(key, value)])
 | 
				
			||||||
	catch { return json; }
 | 
							.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * 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,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.
 | 
					 * 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
 | 
					 * Like setInterval but will adjust the timeout value to account for runtime
 | 
				
			||||||
 * @param {Function} cb Callback function that will be ran
 | 
					 * @param {Function} cb Callback function that will be ran
 | 
				
			||||||
@@ -34,6 +32,50 @@ export function dayOfYear(date: Date): number {
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * Format date
 | 
					 * 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 {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 {Date | number | string} date Date or timestamp, defaults to now
 | 
				
			||||||
 * @param tz Set timezone offset
 | 
					 * @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 {
 | 
					import {
 | 
				
			||||||
	clean, deepCopy, deepMerge, dotNotation, encodeQuery, flattenObj, formData, includes, isEqual, mixin,
 | 
						clean, deepCopy, deepMerge, dotNotation, encodeQuery, flattenObj, formData, includes, isEqual, mixin,
 | 
				
			||||||
	JSONAttemptParse, JSONSerialize, JSONSanitize
 | 
					 | 
				
			||||||
} from '../src';
 | 
					} from '../src';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('Object utilities', () => {
 | 
					describe('Object utilities', () => {
 | 
				
			||||||
@@ -131,34 +130,4 @@ describe('Object utilities', () => {
 | 
				
			|||||||
			expect(c.bar()).toBe(2);
 | 
								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