feat(core): Introduce native implementations for Base64, random generation and normalization; remove runtime plugin dependencies; update tests, docs and package metadata

This commit is contained in:
2025-09-12 18:57:31 +00:00
parent 6a7570de7b
commit 1f3e170a88
17 changed files with 7585 additions and 4795 deletions

View File

@@ -1,8 +1,8 @@
/**
* autocreated commitinfo by @pushrocks/commitinfo
* autocreated commitinfo by @push.rocks/commitinfo
*/
export const commitinfo = {
name: '@push.rocks/smartstring',
version: '4.0.15',
description: 'handle strings in smart ways. TypeScript ready.'
version: '4.1.0',
description: 'A library for handling strings in smart ways, including manipulation and encoding, with TypeScript support.'
}

View File

@@ -1,10 +1,89 @@
import * as plugins from './smartstring.plugins.js';
/**
* the type for base 64
*/
export type TStringInputType = 'string' | 'base64' | 'base64uri';
/**
* Cross-platform base64 implementation
* Works in both Node.js and browser environments
*/
const universalBase64 = {
encode: (str: string): string => {
if (typeof Buffer !== 'undefined') {
// Node.js environment
return Buffer.from(str, 'utf8').toString('base64');
} else if (typeof btoa !== 'undefined') {
// Browser environment
// Handle Unicode properly
const utf8Bytes = new TextEncoder().encode(str);
const binaryString = Array.from(utf8Bytes, byte => String.fromCharCode(byte)).join('');
return btoa(binaryString);
} else {
// Fallback pure JS implementation
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
const bytes = new TextEncoder().encode(str);
let result = '';
let i = 0;
while (i < bytes.length) {
const a = bytes[i++];
const b = i < bytes.length ? bytes[i++] : 0;
const c = i < bytes.length ? bytes[i++] : 0;
const bitmap = (a << 16) | (b << 8) | c;
result += chars.charAt((bitmap >> 18) & 63);
result += chars.charAt((bitmap >> 12) & 63);
result += i - 2 < bytes.length ? chars.charAt((bitmap >> 6) & 63) : '=';
result += i - 1 < bytes.length ? chars.charAt(bitmap & 63) : '=';
}
return result;
}
},
decode: (str: string): string => {
// Handle base64uri by converting back to standard base64
const base64String = str
.replace(/-/g, '+')
.replace(/_/g, '/')
.padEnd(str.length + ((4 - (str.length % 4)) % 4), '=');
if (typeof Buffer !== 'undefined') {
// Node.js environment
return Buffer.from(base64String, 'base64').toString('utf8');
} else if (typeof atob !== 'undefined') {
// Browser environment
const binaryString = atob(base64String);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return new TextDecoder().decode(bytes);
} else {
// Fallback pure JS implementation
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
let bytes: number[] = [];
let i = 0;
while (i < base64String.length) {
const encoded1 = chars.indexOf(base64String.charAt(i++));
const encoded2 = chars.indexOf(base64String.charAt(i++));
const encoded3 = chars.indexOf(base64String.charAt(i++));
const encoded4 = chars.indexOf(base64String.charAt(i++));
const bitmap = (encoded1 << 18) | (encoded2 << 12) | (encoded3 << 6) | encoded4;
bytes.push((bitmap >> 16) & 255);
if (encoded3 !== 64) bytes.push((bitmap >> 8) & 255);
if (encoded4 !== 64) bytes.push(bitmap & 255);
}
return new TextDecoder().decode(new Uint8Array(bytes));
}
}
};
/**
* handle base64 strings
*/
@@ -50,21 +129,24 @@ export let base64 = {
* encodes the string
*/
encode: (stringArg: string) => {
return plugins.jsBase64.encode(stringArg);
return universalBase64.encode(stringArg);
},
/**
* encodes a stringArg to base64 uri style
*/
encodeUri: (stringArg: string) => {
return plugins.jsBase64.encodeURI(stringArg);
return universalBase64.encode(stringArg)
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
},
/**
* decodes a base64 encoded string
*/
decode: (stringArg: string) => {
return plugins.jsBase64.decode(stringArg);
return universalBase64.decode(stringArg);
},
/**

View File

@@ -1,5 +1,63 @@
import * as plugins from './smartstring.plugins.js';
/**
* Cross-platform random number generator
* Uses crypto.getRandomValues in browser and Math.random as fallback
*/
const getRandomInt = (min: number, max: number): number => {
if (typeof globalThis !== 'undefined' && globalThis.crypto && globalThis.crypto.getRandomValues) {
// Browser environment with crypto API
const range = max - min;
const array = new Uint32Array(1);
globalThis.crypto.getRandomValues(array);
return min + (array[0] % range);
} else {
// Fallback to Math.random for environments without crypto
return Math.floor(Math.random() * (max - min)) + min;
}
};
/**
* Custom implementation of randomatic pattern-based string generator
* Pattern characters:
* A - Uppercase letter
* a - Lowercase letter
* 0 - Number (0-9)
* ! - Special character
* * - Any character (A, a, 0, or !)
*/
const customRandomatic = (pattern: string, length?: number, options?: any): string => {
const charSets = {
'A': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'a': 'abcdefghijklmnopqrstuvwxyz',
'0': '0123456789',
'!': '!@#$%^&*()_+-=[]{}|;:,.<>?',
'*': 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?'
};
// If length is provided, repeat the pattern to match length
let actualPattern = pattern;
if (length && length > pattern.length) {
actualPattern = pattern.repeat(Math.ceil(length / pattern.length)).slice(0, length);
} else if (length) {
actualPattern = pattern.slice(0, length);
}
let result = '';
for (const char of actualPattern) {
if (charSets[char]) {
const charSet = charSets[char];
const randomIndex = getRandomInt(0, charSet.length);
result += charSet[randomIndex];
} else {
// If not a pattern character, use it literally
result += char;
}
}
return result;
};
/**
* creates a random string
*
@@ -17,7 +75,7 @@ export const createRandomString = (
lengthArg?: number,
optionsArg?: any
): string => {
return plugins.randomatic(patternArg, lengthArg, optionsArg);
return customRandomatic(patternArg, lengthArg, optionsArg);
};
/**

View File

@@ -1,5 +1,3 @@
import * as plugins from './smartstring.plugins.js';
export class Domain {
public fullName: string;
public level1: string;
@@ -14,14 +12,14 @@ export class Domain {
public domainName;
public subDomain;
public port;
public nodeParsedUrl: plugins.url.UrlWithStringQuery;
public nodeParsedUrl: URL;
constructor(domainStringArg: string) {
// lets do the node standard stuff first
this.protocol = this._protocolRegex(domainStringArg);
if (!this.protocol) {
domainStringArg = `https://${domainStringArg}`;
}
this.nodeParsedUrl = plugins.url.parse(domainStringArg);
this.nodeParsedUrl = new URL(domainStringArg);
this.port = this.nodeParsedUrl.port;
// lets do the rest after

View File

@@ -1,5 +1,3 @@
import * as plugins from './smartstring.plugins.js';
/**
* replaces all occurences of something in a string
* @param stringArg
@@ -10,6 +8,38 @@ export const replaceAll = (stringArg: string, searchPattern: string, replacement
return stringArg.replace(new RegExp(searchPattern, 'g'), replacementString);
};
/**
* Custom implementation of strip-indent
* Removes the minimum indentation from all lines
*/
const stripIndent = (str: string): string => {
const lines = str.split('\n');
// Find the minimum indentation (ignoring empty lines)
let minIndent = Infinity;
for (const line of lines) {
if (line.trim().length > 0) {
const match = line.match(/^(\s*)/);
if (match) {
minIndent = Math.min(minIndent, match[1].length);
}
}
}
// If no indentation found, return original string
if (minIndent === Infinity || minIndent === 0) {
return str;
}
// Remove the minimum indentation from all lines
return lines.map(line => {
if (line.length >= minIndent) {
return line.slice(minIndent);
}
return line;
}).join('\n');
};
export interface INormalizeOptions {
stripLeadingTrailingEmptyLines?: boolean;
stripAllEmptyLines?: boolean;
@@ -27,7 +57,7 @@ export const standard = (stringArg: string, options?: INormalizeOptions): string
let result = stringArg;
if (!options || options.stripIndent) {
result = plugins.stripIndent(result); // fix indention
result = stripIndent(result); // fix indention
}
if (!options || options.normalizeNewline) {

View File

@@ -1,17 +1,4 @@
// node native
import * as smartenv from '@push.rocks/smartenv';
const smartenvInstance = new smartenv.Smartenv();
// @push.rocks ecosystem
import * as isounique from '@push.rocks/isounique';
export { isounique };
import * as url from 'url';
export { url };
// third party
import { Base64 as jsBase64 } from 'js-base64';
import stripIndent from 'strip-indent';
import randomatic from 'randomatic';
export { jsBase64, stripIndent, randomatic };