@mhmdhammoud/meritt-utils 1.6.2 → 1.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ReleaseNotes.md +17 -1
- package/dist/__tests__/elastic-transport.test.js +13 -0
- package/dist/__tests__/logger.test.js +21 -0
- package/dist/lib/elastic-transport.js +3 -1
- package/dist/lib/logger.js +4 -3
- package/dist/types/logger.d.ts +2 -1
- package/package.json +9 -3
- package/.github/workflows/npm-publish.yml +0 -84
- package/.github/workflows/push.yml +0 -83
- package/.husky/pre-commit +0 -62
- package/.prettierrc +0 -8
- package/.prettierrc.json +0 -8
- package/ISSUES.md +0 -0
- package/eslint.config.mjs +0 -64
- package/example.env +0 -6
- package/jest.config.js +0 -13
- package/src/__tests__/colorful.test.ts +0 -77
- package/src/__tests__/elastic-transport.test.ts +0 -94
- package/src/__tests__/formatter.test.ts +0 -96
- package/src/__tests__/logger.test.ts +0 -122
- package/src/env.d.ts +0 -9
- package/src/index.ts +0 -1
- package/src/lib/colorful.ts +0 -167
- package/src/lib/cypto.ts +0 -296
- package/src/lib/elastic-transport.ts +0 -356
- package/src/lib/formatter.ts +0 -125
- package/src/lib/imagefull.ts +0 -41
- package/src/lib/index.ts +0 -12
- package/src/lib/logger.ts +0 -600
- package/src/lib/pdf.ts +0 -25
- package/src/lib/trace-store.ts +0 -46
- package/src/types/index.ts +0 -1
- package/src/types/logger.ts +0 -66
- package/src/utilities/axios.ts +0 -4
- package/src/utilities/index.ts +0 -1
- package/tsconfig.json +0 -16
|
@@ -1,356 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Elasticsearch transport for Pino with connection lifecycle resilience.
|
|
3
|
-
*
|
|
4
|
-
* Based on pino-elasticsearch with a fix for GitHub issue #140:
|
|
5
|
-
* When maxRetries are exceeded and Elasticsearch nodes are DEAD, the bulk helper
|
|
6
|
-
* destroys the splitter stream, causing logs to stop permanently until restart.
|
|
7
|
-
*
|
|
8
|
-
* This implementation overrides splitter.destroy to BOTH resurrect the connection
|
|
9
|
-
* pool AND reinitialize the bulk handler, so logging continues after ES recovers.
|
|
10
|
-
*
|
|
11
|
-
* @see https://github.com/pinojs/pino-elasticsearch/issues/140
|
|
12
|
-
* @see https://github.com/pinojs/pino-elasticsearch/issues/72
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
16
|
-
const split = require('split2') as (
|
|
17
|
-
fn: (line: string) => unknown,
|
|
18
|
-
opts?: { autoDestroy?: boolean }
|
|
19
|
-
) => NodeJS.ReadWriteStream
|
|
20
|
-
import { Client } from '@elastic/elasticsearch'
|
|
21
|
-
import type { ClientOptions } from '@elastic/elasticsearch'
|
|
22
|
-
|
|
23
|
-
export interface ElasticTransportOptions extends Pick<
|
|
24
|
-
ClientOptions,
|
|
25
|
-
| 'node'
|
|
26
|
-
| 'auth'
|
|
27
|
-
| 'cloud'
|
|
28
|
-
| 'caFingerprint'
|
|
29
|
-
| 'Connection'
|
|
30
|
-
| 'ConnectionPool'
|
|
31
|
-
| 'maxRetries'
|
|
32
|
-
| 'requestTimeout'
|
|
33
|
-
> {
|
|
34
|
-
sniffOnConnectionFault?: boolean
|
|
35
|
-
index?: string | ((logTime: string) => string)
|
|
36
|
-
flushBytes?: number
|
|
37
|
-
'flush-bytes'?: number
|
|
38
|
-
flushInterval?: number
|
|
39
|
-
'flush-interval'?: number
|
|
40
|
-
esVersion?: number
|
|
41
|
-
'es-version'?: number
|
|
42
|
-
rejectUnauthorized?: boolean
|
|
43
|
-
tls?: ClientOptions['tls']
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface LogDocument {
|
|
47
|
-
time?: string
|
|
48
|
-
'@timestamp'?: string
|
|
49
|
-
[k: string]: unknown
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface ElasticTransportStream extends NodeJS.ReadWriteStream {
|
|
53
|
-
flush: () => Promise<void>
|
|
54
|
-
close: () => Promise<void>
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const createTimeoutError = (timeoutMs: number): Error =>
|
|
58
|
-
new Error(`Elasticsearch bulk request timed out after ${timeoutMs}ms`)
|
|
59
|
-
|
|
60
|
-
function setDateTimeString(value: unknown): string {
|
|
61
|
-
if (value !== null && typeof value === 'object' && 'time' in value) {
|
|
62
|
-
const t = (value as { time: unknown }).time
|
|
63
|
-
if (
|
|
64
|
-
(typeof t === 'string' && t.length > 0) ||
|
|
65
|
-
(typeof t === 'number' && t >= 0)
|
|
66
|
-
) {
|
|
67
|
-
return new Date(t).toISOString()
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return new Date().toISOString()
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function getIndexName(
|
|
74
|
-
index: string | ((logTime: string) => string),
|
|
75
|
-
time: string
|
|
76
|
-
): string {
|
|
77
|
-
if (typeof index === 'function') {
|
|
78
|
-
return index(time)
|
|
79
|
-
}
|
|
80
|
-
return index.replace('%{DATE}', time.substring(0, 10))
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function createBulkSender(
|
|
84
|
-
opts: ElasticTransportOptions,
|
|
85
|
-
client: Client,
|
|
86
|
-
splitter: NodeJS.ReadWriteStream
|
|
87
|
-
): {
|
|
88
|
-
add: (doc: unknown) => void
|
|
89
|
-
flush: () => Promise<void>
|
|
90
|
-
close: () => Promise<void>
|
|
91
|
-
} {
|
|
92
|
-
const esVersion = Number(opts.esVersion ?? opts['es-version'] ?? 7)
|
|
93
|
-
const index = opts.index ?? 'pino'
|
|
94
|
-
const buildIndexName = typeof index === 'function' ? index : null
|
|
95
|
-
const opType = esVersion >= 7 ? undefined : undefined
|
|
96
|
-
const flushBytes = opts.flushBytes ?? opts['flush-bytes'] ?? 1000
|
|
97
|
-
const flushInterval = opts.flushInterval ?? opts['flush-interval'] ?? 3000
|
|
98
|
-
const requestTimeout = opts.requestTimeout ?? 30000
|
|
99
|
-
const bulkWatchdogTimeout = requestTimeout + 5000
|
|
100
|
-
|
|
101
|
-
let buffer: unknown[] = []
|
|
102
|
-
let bufferedBytes = 0
|
|
103
|
-
let timer: NodeJS.Timeout | undefined
|
|
104
|
-
let isFlushing = false
|
|
105
|
-
let flushAgain = false
|
|
106
|
-
|
|
107
|
-
const indexName = (time = new Date().toISOString()) =>
|
|
108
|
-
buildIndexName ? buildIndexName(time) : getIndexName(index as string, time)
|
|
109
|
-
|
|
110
|
-
const clearFlushTimer = () => {
|
|
111
|
-
if (timer) {
|
|
112
|
-
clearTimeout(timer)
|
|
113
|
-
timer = undefined
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const scheduleFlush = () => {
|
|
118
|
-
if (timer || buffer.length === 0) {
|
|
119
|
-
return
|
|
120
|
-
}
|
|
121
|
-
timer = setTimeout(() => {
|
|
122
|
-
timer = undefined
|
|
123
|
-
void flush()
|
|
124
|
-
}, flushInterval)
|
|
125
|
-
timer.unref?.()
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const buildOperation = (doc: unknown): [Record<string, unknown>, unknown] => {
|
|
129
|
-
try {
|
|
130
|
-
const d = doc as LogDocument
|
|
131
|
-
const date = d.time ?? d['@timestamp'] ?? new Date().toISOString()
|
|
132
|
-
if (opType === 'create') {
|
|
133
|
-
d['@timestamp'] = date
|
|
134
|
-
}
|
|
135
|
-
return [
|
|
136
|
-
{
|
|
137
|
-
index: {
|
|
138
|
-
_index: indexName(date),
|
|
139
|
-
op_type: opType,
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
doc,
|
|
143
|
-
]
|
|
144
|
-
} catch {
|
|
145
|
-
return [
|
|
146
|
-
{
|
|
147
|
-
index: {
|
|
148
|
-
_index: indexName(),
|
|
149
|
-
op_type: opType,
|
|
150
|
-
},
|
|
151
|
-
},
|
|
152
|
-
doc,
|
|
153
|
-
]
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const emitDroppedDocument = (doc: unknown, cause?: unknown) => {
|
|
158
|
-
const error = new Error('Dropped document') as Error & {
|
|
159
|
-
document: unknown
|
|
160
|
-
cause?: unknown
|
|
161
|
-
}
|
|
162
|
-
error.document = doc
|
|
163
|
-
error.cause = cause
|
|
164
|
-
splitter.emit('insertError', error)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const emitBulkError = (err: unknown) => {
|
|
168
|
-
// Do not emit the standard stream "error" event for retryable bulk
|
|
169
|
-
// failures. Some stream consumers treat it as terminal, which can leave
|
|
170
|
-
// Pino writing into a poisoned stream while the process continues running.
|
|
171
|
-
splitter.emit('bulkError', err)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const bulkWithWatchdog = async (
|
|
175
|
-
operations: unknown[]
|
|
176
|
-
): Promise<{
|
|
177
|
-
errors?: boolean
|
|
178
|
-
items?: Array<Record<string, { error?: unknown }>>
|
|
179
|
-
}> => {
|
|
180
|
-
let timeout: NodeJS.Timeout | undefined
|
|
181
|
-
const bulkPromise = client.bulk({
|
|
182
|
-
operations,
|
|
183
|
-
refresh: false,
|
|
184
|
-
timeout: `${requestTimeout}ms`,
|
|
185
|
-
}) as Promise<{
|
|
186
|
-
errors?: boolean
|
|
187
|
-
items?: Array<Record<string, { error?: unknown }>>
|
|
188
|
-
}>
|
|
189
|
-
|
|
190
|
-
try {
|
|
191
|
-
return await Promise.race([
|
|
192
|
-
bulkPromise,
|
|
193
|
-
new Promise<never>((_, reject) => {
|
|
194
|
-
timeout = setTimeout(() => {
|
|
195
|
-
reject(createTimeoutError(bulkWatchdogTimeout))
|
|
196
|
-
}, bulkWatchdogTimeout)
|
|
197
|
-
timeout.unref?.()
|
|
198
|
-
}),
|
|
199
|
-
])
|
|
200
|
-
} finally {
|
|
201
|
-
if (timeout) {
|
|
202
|
-
clearTimeout(timeout)
|
|
203
|
-
}
|
|
204
|
-
// If the watchdog wins, the original request may reject later. Consume
|
|
205
|
-
// that rejection so a stale request cannot crash the app.
|
|
206
|
-
bulkPromise.catch(() => undefined)
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const flush = async (): Promise<void> => {
|
|
211
|
-
if (isFlushing) {
|
|
212
|
-
flushAgain = true
|
|
213
|
-
return
|
|
214
|
-
}
|
|
215
|
-
clearFlushTimer()
|
|
216
|
-
if (buffer.length === 0) {
|
|
217
|
-
return
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
isFlushing = true
|
|
221
|
-
const batch = buffer
|
|
222
|
-
buffer = []
|
|
223
|
-
bufferedBytes = 0
|
|
224
|
-
|
|
225
|
-
try {
|
|
226
|
-
const operations = batch.flatMap(buildOperation)
|
|
227
|
-
const body = await bulkWithWatchdog(operations)
|
|
228
|
-
if (body.errors && Array.isArray(body.items)) {
|
|
229
|
-
body.items.forEach((item, index) => {
|
|
230
|
-
const result = Object.values(item)[0]
|
|
231
|
-
if (result?.error) {
|
|
232
|
-
emitDroppedDocument(batch[index], result.error)
|
|
233
|
-
}
|
|
234
|
-
})
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
splitter.emit('insert', {
|
|
238
|
-
successful: batch.length,
|
|
239
|
-
failed: body.errors
|
|
240
|
-
? (body.items?.filter((item) => Object.values(item)[0]?.error)
|
|
241
|
-
.length ?? 0)
|
|
242
|
-
: 0,
|
|
243
|
-
})
|
|
244
|
-
} catch (err) {
|
|
245
|
-
emitBulkError(err)
|
|
246
|
-
// Drop the failed batch instead of wedging the stream. The next log line
|
|
247
|
-
// creates a fresh bulk request and can recover without a process restart.
|
|
248
|
-
batch.forEach((doc) => emitDroppedDocument(doc, err))
|
|
249
|
-
} finally {
|
|
250
|
-
isFlushing = false
|
|
251
|
-
if (flushAgain || buffer.length > 0) {
|
|
252
|
-
flushAgain = false
|
|
253
|
-
scheduleFlush()
|
|
254
|
-
if (bufferedBytes >= flushBytes) {
|
|
255
|
-
void flush()
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
return {
|
|
262
|
-
add(doc: unknown) {
|
|
263
|
-
buffer.push(doc)
|
|
264
|
-
bufferedBytes += Buffer.byteLength(JSON.stringify(doc))
|
|
265
|
-
if (bufferedBytes >= flushBytes) {
|
|
266
|
-
void flush()
|
|
267
|
-
return
|
|
268
|
-
}
|
|
269
|
-
scheduleFlush()
|
|
270
|
-
},
|
|
271
|
-
flush,
|
|
272
|
-
async close() {
|
|
273
|
-
clearFlushTimer()
|
|
274
|
-
await flush()
|
|
275
|
-
},
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
export const createElasticTransport = (
|
|
280
|
-
opts: ElasticTransportOptions = {}
|
|
281
|
-
): ElasticTransportStream => {
|
|
282
|
-
const splitter = split(
|
|
283
|
-
function (this: NodeJS.ReadWriteStream, line: string) {
|
|
284
|
-
let value: unknown
|
|
285
|
-
|
|
286
|
-
try {
|
|
287
|
-
value = JSON.parse(line) as unknown
|
|
288
|
-
} catch (error) {
|
|
289
|
-
this.emit('unknown', line, error)
|
|
290
|
-
return
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
if (typeof value === 'boolean') {
|
|
294
|
-
this.emit('unknown', line, 'Boolean value ignored')
|
|
295
|
-
return
|
|
296
|
-
}
|
|
297
|
-
if (value === null) {
|
|
298
|
-
this.emit('unknown', line, 'Null value ignored')
|
|
299
|
-
return
|
|
300
|
-
}
|
|
301
|
-
if (typeof value !== 'object') {
|
|
302
|
-
value = { data: value, time: setDateTimeString(value) }
|
|
303
|
-
} else {
|
|
304
|
-
const obj = value as Record<string, unknown>
|
|
305
|
-
if (obj['@timestamp'] === undefined) {
|
|
306
|
-
;(obj as LogDocument).time = setDateTimeString(obj)
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
return value
|
|
310
|
-
},
|
|
311
|
-
{ autoDestroy: true }
|
|
312
|
-
)
|
|
313
|
-
|
|
314
|
-
const clientOpts: ClientOptions = {
|
|
315
|
-
node: opts.node,
|
|
316
|
-
auth: opts.auth,
|
|
317
|
-
cloud: opts.cloud,
|
|
318
|
-
tls: { rejectUnauthorized: opts.rejectUnauthorized, ...opts.tls },
|
|
319
|
-
maxRetries: opts.maxRetries,
|
|
320
|
-
requestTimeout: opts.requestTimeout,
|
|
321
|
-
sniffOnConnectionFault: opts.sniffOnConnectionFault,
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
if (opts.caFingerprint) {
|
|
325
|
-
clientOpts.caFingerprint = opts.caFingerprint
|
|
326
|
-
}
|
|
327
|
-
if (opts.Connection) {
|
|
328
|
-
clientOpts.Connection = opts.Connection
|
|
329
|
-
}
|
|
330
|
-
if (opts.ConnectionPool) {
|
|
331
|
-
clientOpts.ConnectionPool = opts.ConnectionPool
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const client = new Client(clientOpts)
|
|
335
|
-
const bulkSender = createBulkSender(opts, client, splitter)
|
|
336
|
-
|
|
337
|
-
splitter.on('data', (doc) => {
|
|
338
|
-
bulkSender.add(doc)
|
|
339
|
-
})
|
|
340
|
-
splitter.on('finish', () => {
|
|
341
|
-
void bulkSender.close()
|
|
342
|
-
})
|
|
343
|
-
|
|
344
|
-
const splitterWithDestroy = splitter as ElasticTransportStream & {
|
|
345
|
-
destroy: (err?: Error) => void
|
|
346
|
-
}
|
|
347
|
-
splitterWithDestroy.flush = bulkSender.flush
|
|
348
|
-
splitterWithDestroy.close = bulkSender.close
|
|
349
|
-
const originalDestroy = splitterWithDestroy.destroy.bind(splitterWithDestroy)
|
|
350
|
-
splitterWithDestroy.destroy = function (err?: Error) {
|
|
351
|
-
void bulkSender.close()
|
|
352
|
-
originalDestroy(err)
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
return splitterWithDestroy
|
|
356
|
-
}
|
package/src/lib/formatter.ts
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Author : Mhmd Hammoud https://github.com/mhmdhammoud
|
|
3
|
-
Date : 2023-06-17
|
|
4
|
-
Description : String manipulation
|
|
5
|
-
Version : 1.0
|
|
6
|
-
*/
|
|
7
|
-
class Formatter {
|
|
8
|
-
/**
|
|
9
|
-
* @remarks Normalizes input by removing underscores and hyphens and capitalizing the first letter of the sentence.
|
|
10
|
-
* @param stringToBeChanged - to be formatted without underscores and hyphens
|
|
11
|
-
* @param withWhiteSpacing - Determines if white spacing is needed in the final string or not (default: true)
|
|
12
|
-
* @returns string
|
|
13
|
-
* @example
|
|
14
|
-
* ```typescript
|
|
15
|
-
* formatter.toUpperFirst('hello world')
|
|
16
|
-
* // => 'Hello-World'
|
|
17
|
-
* ```
|
|
18
|
-
* */
|
|
19
|
-
toUpperFirst = (str: string | number): string => {
|
|
20
|
-
if (typeof str !== 'string') throw new Error('Provide a valid string')
|
|
21
|
-
|
|
22
|
-
const formattedString = str
|
|
23
|
-
.toString()
|
|
24
|
-
.replace(/[^a-zA-Z0-9]+/g, '-')
|
|
25
|
-
.trim()
|
|
26
|
-
.toLowerCase()
|
|
27
|
-
|
|
28
|
-
return formattedString.charAt(0).toUpperCase() + formattedString.slice(1)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* @remarks normalizes input to supported path and file name format.
|
|
33
|
-
* Changes camelCase strings to kebab-case, replaces spaces with dash and keeps underscores. *
|
|
34
|
-
* @param str - String needed to be modified
|
|
35
|
-
* @returns formatted string
|
|
36
|
-
*/
|
|
37
|
-
camelToKebab = (str: string | number): string => {
|
|
38
|
-
if (typeof str !== 'string') {
|
|
39
|
-
throw new Error('Invalid input. Please provide a valid string.')
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const STRING_DASHERIZE_REGEXP = /\s/g
|
|
43
|
-
const STRING_DECAMELIZE_REGEXP = /([a-z\d])([A-Z])/g
|
|
44
|
-
|
|
45
|
-
return str
|
|
46
|
-
.replace(STRING_DECAMELIZE_REGEXP, '$1-$2')
|
|
47
|
-
.toLowerCase()
|
|
48
|
-
.replace(STRING_DASHERIZE_REGEXP, '-')
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* @remarks obfuscates email address
|
|
53
|
-
* @param email - email address to be obfuscated
|
|
54
|
-
* @returns obfuscated email address
|
|
55
|
-
* @example
|
|
56
|
-
* ```typescript
|
|
57
|
-
* formatter.obfuscate('johndoe@email.com') // => jo*****@gmail.com
|
|
58
|
-
* ```
|
|
59
|
-
* */
|
|
60
|
-
obfuscate = (email: string) => {
|
|
61
|
-
if (typeof email !== 'string') throw new Error('Provide a valid string')
|
|
62
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
63
|
-
if (!emailRegex.test(email))
|
|
64
|
-
throw new Error('Provide a valid email address')
|
|
65
|
-
const separatorIndex = email.indexOf('@')
|
|
66
|
-
if (separatorIndex < 3) {
|
|
67
|
-
// 'ab@gmail.com' -> '**@gmail.com'
|
|
68
|
-
return (
|
|
69
|
-
email.slice(0, separatorIndex).replace(/./g, '*') +
|
|
70
|
-
email.slice(separatorIndex)
|
|
71
|
-
)
|
|
72
|
-
}
|
|
73
|
-
// 'test42@gmail.com' -> 'te****@gmail.com'
|
|
74
|
-
return (
|
|
75
|
-
email.slice(0, 2) +
|
|
76
|
-
email.slice(2, separatorIndex).replace(/./g, '*') +
|
|
77
|
-
email.slice(separatorIndex)
|
|
78
|
-
)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Capitalizes the first letter of each word in a sentence and removes non-alphanumeric characters.
|
|
83
|
-
* @param sentence - The input sentence to be processed.
|
|
84
|
-
* @returns A new sentence with the first letter of each word capitalized and non-alphanumeric characters removed.
|
|
85
|
-
* @example
|
|
86
|
-
* ```typescript
|
|
87
|
-
* const sentence = 'this is34354345a---sentence'
|
|
88
|
-
* const newSentence = ToUpperTitle(sentence)
|
|
89
|
-
* console.log(newSentence) // 'This Is A Sentence'
|
|
90
|
-
* ```
|
|
91
|
-
*/
|
|
92
|
-
toUpperTitle = (sentence: string | number): string => {
|
|
93
|
-
if (typeof sentence !== 'string') throw new Error('Provide a valid string')
|
|
94
|
-
if (!sentence) throw new Error('Provide a valid string')
|
|
95
|
-
|
|
96
|
-
const sanitizedSentence = sentence.replace(/[^a-zA-Z\s]+/g, ' ') // Exclude numbers from the character class
|
|
97
|
-
const words = sanitizedSentence.split(/\s+/)
|
|
98
|
-
const capitalizedWords = words.map((word) => {
|
|
99
|
-
return word.charAt(0).toUpperCase() + word.slice(1)
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
return capitalizedWords.join(' ')
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* @remarks Generates a slug from a given string
|
|
107
|
-
* @param title - string to be converted to slug
|
|
108
|
-
* @returns slug
|
|
109
|
-
* @example
|
|
110
|
-
* ```typescript
|
|
111
|
-
* formatter.slugify('Hello World') // => hello-world
|
|
112
|
-
* ```
|
|
113
|
-
* */
|
|
114
|
-
slugify = (title: string | number): string => {
|
|
115
|
-
if (typeof title !== 'string') throw new Error('Provide a valid string')
|
|
116
|
-
|
|
117
|
-
return title
|
|
118
|
-
.replace(/[^\w\s]/g, '') // Remove non-alphanumeric characters
|
|
119
|
-
.replace(/\s+/g, '-') // Replace consecutive spaces with a single dash
|
|
120
|
-
.toLowerCase()
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const formatter = new Formatter()
|
|
125
|
-
export default formatter
|
package/src/lib/imagefull.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import imagesLoaded from 'imagesloaded'
|
|
2
|
-
/*
|
|
3
|
-
Author : Mustafa Halabi https://github.com/mustafahalabi
|
|
4
|
-
Date : 2024-01-29
|
|
5
|
-
Description : Loads images and returns a promise
|
|
6
|
-
Version : 1.0
|
|
7
|
-
*/
|
|
8
|
-
class ImageFull {
|
|
9
|
-
/**
|
|
10
|
-
* @remarks
|
|
11
|
-
* Receives either a className or an id or an element and returns a promise that resolves when all images are loaded
|
|
12
|
-
* @param selector - either a className or an id or an element that contains images
|
|
13
|
-
* @example
|
|
14
|
-
* ```ts
|
|
15
|
-
* .image-container
|
|
16
|
-
* #image-container
|
|
17
|
-
* img //as an element
|
|
18
|
-
*
|
|
19
|
-
* Imagefull.preloadImages('.image-container').then((event) => {
|
|
20
|
-
* do something
|
|
21
|
-
* })
|
|
22
|
-
* ```
|
|
23
|
-
*
|
|
24
|
-
* @returns Promise<ImagesLoaded | undefined>
|
|
25
|
-
*
|
|
26
|
-
*/
|
|
27
|
-
preloadImages = (selector: string) => {
|
|
28
|
-
return new Promise((resolve) => {
|
|
29
|
-
imagesLoaded(
|
|
30
|
-
document.querySelectorAll(selector),
|
|
31
|
-
{ background: true },
|
|
32
|
-
(event) => {
|
|
33
|
-
resolve(event)
|
|
34
|
-
}
|
|
35
|
-
)
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const imagefull = new ImageFull()
|
|
41
|
-
export default imagefull
|
package/src/lib/index.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export { default as Crypto } from './cypto'
|
|
2
|
-
export { default as Formatter } from './formatter'
|
|
3
|
-
export { default as Pdf } from './formatter'
|
|
4
|
-
export { default as Colorful } from './colorful'
|
|
5
|
-
export { default as ImageFull } from './imagefull'
|
|
6
|
-
export { default as Logger } from './logger'
|
|
7
|
-
export {
|
|
8
|
-
runWithTrace,
|
|
9
|
-
runWithTraceSync,
|
|
10
|
-
getTraceContext,
|
|
11
|
-
type TraceContext,
|
|
12
|
-
} from './trace-store'
|