dff-util 1.3.8 → 1.4.0
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/README.md +89 -28
- package/dist/main/api-call.d.ts +3 -3
- package/dist/main/api-call.js +59 -39
- package/dist/main/csv.d.ts +5 -0
- package/dist/main/csv.js +200 -0
- package/dist/main/util.d.ts +1 -0
- package/dist/main/util.js +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ Select a topic below to view its reference and examples.
|
|
|
11
11
|
| [AppRandomString](#general-utilities) | [AppUniqueCode](#general-utilities) | [AppCodeByType](#general-utilities) | [AppCode](#general-utilities) | [AppDaysBack](#general-utilities) | [AppAddDays](#general-utilities) | [AppUUID4](#general-utilities) |
|
|
12
12
|
| [TruncateText](#text-and-date-formatting) | [DateTime24HrFormat](#text-and-date-formatting) | [DateTime12HrFormat](#text-and-date-formatting) | [DateAndTime](#text-and-date-formatting) | [TimeAgo](#text-and-date-formatting) | [toCamelCase](#case-conversion-and-object-mapping) | [toSnakeCase](#case-conversion-and-object-mapping) |
|
|
13
13
|
| [toLowerCase](#case-conversion-and-object-mapping) | [toUpperCase](#case-conversion-and-object-mapping) | [toPascalCase](#case-conversion-and-object-mapping) | [toViewMapper](#case-conversion-and-object-mapping) | [toEntityMapper](#case-conversion-and-object-mapping) | [toSchemaMapper](#case-conversion-and-object-mapping) | [QueryCond](#query-conditions-querycond) |
|
|
14
|
+
| [CsvToJson](#csv-and-json-conversion) | [JsonToCsv](#csv-and-json-conversion) | | | | | |
|
|
14
15
|
|
|
15
16
|
**[Http](#http)**
|
|
16
17
|
|
|
@@ -104,9 +105,9 @@ These examples reflect the current implementation. `toCamelCase` preserves inter
|
|
|
104
105
|
|
|
105
106
|
| Function | Key conversion | Example input | Example output |
|
|
106
107
|
| :--- | :--- | :--- | :--- |
|
|
107
|
-
| `toViewMapper` | Convert keys to camel case and `_id` to `id` recursively | `{ _id: 'abc123', user_name: '
|
|
108
|
-
| `toEntityMapper` | Convert keys to snake case recursively; preserve `id` | `{ id: 'abc123', userName: '
|
|
109
|
-
| `toSchemaMapper` | Convert top-level `id` to `_id` and keys to snake case; nested values use `toEntityMapper` | `{ id: 'abc123', userName: '
|
|
108
|
+
| `toViewMapper` | Convert keys to camel case and `_id` to `id` recursively | `{ _id: 'abc123', user_name: 'k7m2x9', address_info: { city_name: 'hyderabad' } }` | `{ id: 'abc123', userName: 'k7m2x9', addressInfo: { cityName: 'hyderabad' } }` |
|
|
109
|
+
| `toEntityMapper` | Convert keys to snake case recursively; preserve `id` | `{ id: 'abc123', userName: 'k7m2x9', addressInfo: { cityName: 'hyderabad' } }` | `{ id: 'abc123', user_name: 'k7m2x9', address_info: { city_name: 'hyderabad' } }` |
|
|
110
|
+
| `toSchemaMapper` | Convert top-level `id` to `_id` and keys to snake case; nested values use `toEntityMapper` | `{ id: 'abc123', userName: 'k7m2x9', addressInfo: { cityName: 'hyderabad' } }` | `{ _id: 'abc123', user_name: 'k7m2x9', address_info: { city_name: 'hyderabad' } }` |
|
|
110
111
|
|
|
111
112
|
All three preserve language maps whose keys are recognized language codes. Primitive and non-plain-object values are returned unchanged. `toViewMapper` and `toEntityMapper` process array items recursively; `toSchemaMapper` uses `toEntityMapper` for array items, so their `id` keys remain `id`.
|
|
112
113
|
|
|
@@ -300,6 +301,67 @@ QueryCond output: [
|
|
|
300
301
|
]
|
|
301
302
|
```
|
|
302
303
|
|
|
304
|
+
### CSV and JSON conversion
|
|
305
|
+
|
|
306
|
+
`CsvToJson` and `JsonToCsv` convert between CSV text and JSON objects. The delimiter is optional and defaults to `,`. Fields that contain the delimiter, double quotes, or line breaks are quoted; quotes inside a field are escaped by doubling (`""`), following RFC 4180.
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
import { CsvToJson, JsonToCsv } from 'dff-util';
|
|
310
|
+
|
|
311
|
+
const rows = CsvToJson('name,note\n"Doe, John","He said ""hello"""');
|
|
312
|
+
// [{ name: 'Doe, John', note: 'He said "hello"' }]
|
|
313
|
+
|
|
314
|
+
const csv = JsonToCsv([
|
|
315
|
+
{ name: 'Doe, John', note: 'He said "hello"' },
|
|
316
|
+
{ name: 'k7m2x9', city: 'Hyderabad' },
|
|
317
|
+
]);
|
|
318
|
+
// name,note,city
|
|
319
|
+
// "Doe, John","He said ""hello""",
|
|
320
|
+
// k7m2x9,,Hyderabad
|
|
321
|
+
|
|
322
|
+
const tsv = JsonToCsv([{ name: 'k7m2x9', city: 'Hyderabad' }], '\t');
|
|
323
|
+
const fromTsv = CsvToJson(tsv, '\t');
|
|
324
|
+
|
|
325
|
+
const multiline = CsvToJson(`name,bio
|
|
326
|
+
k7m2x9,"line1
|
|
327
|
+
line2"
|
|
328
|
+
n4t9pw,"a
|
|
329
|
+
b
|
|
330
|
+
c"`);
|
|
331
|
+
// [
|
|
332
|
+
// { name: 'k7m2x9', bio: 'line1\nline2' },
|
|
333
|
+
// { name: 'n4t9pw', bio: 'a\nb\nc' },
|
|
334
|
+
// ]
|
|
335
|
+
|
|
336
|
+
JsonToCsv([
|
|
337
|
+
{ name: 'k7m2x9', note: 'line1\nline2' },
|
|
338
|
+
{ name: 'n4t9pw', note: 'hello\r\nworld' },
|
|
339
|
+
]);
|
|
340
|
+
// name,note
|
|
341
|
+
// k7m2x9,"line1
|
|
342
|
+
// line2"
|
|
343
|
+
// n4t9pw,"hello
|
|
344
|
+
// world"
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
| Function | Arguments | Result |
|
|
348
|
+
| :--- | :--- | :--- |
|
|
349
|
+
| `CsvToJson` | `csv, delimiter = ','` | Array of objects; header row becomes keys |
|
|
350
|
+
| `JsonToCsv` | `data, delimiter = ','` | CSV text with a header row |
|
|
351
|
+
|
|
352
|
+
`JsonToCsv` accepts an object, an array of objects, or a JSON string of either. Nested objects and arrays are written with `JSON.stringify`. An empty array, `null`, or `undefined` input returns `''`. `CsvToJson` also accepts `null` or `undefined` and returns `[]`. Blank lines are skipped when they cannot be a data row. A leading BOM is stripped. `\n` and `\r\n` are accepted. Unterminated quoted fields throw.
|
|
353
|
+
|
|
354
|
+
Every header still becomes a property on each row. `null` and `undefined` write as empty cells; an empty cell parses as `null`.
|
|
355
|
+
|
|
356
|
+
| JSON value | CSV cell | Parsed value |
|
|
357
|
+
| :--- | :--- | :--- |
|
|
358
|
+
| `null` | empty cell | `null` |
|
|
359
|
+
| `undefined` | empty cell | `null` |
|
|
360
|
+
| missing key | empty cell | `null` |
|
|
361
|
+
| `''` | `""` | `''` |
|
|
362
|
+
|
|
363
|
+
The delimiter may be any non-empty string except a quote or line break, including `;`, `\t`, or `||`.
|
|
364
|
+
|
|
303
365
|
## Http
|
|
304
366
|
|
|
305
367
|
### HTTP requests
|
|
@@ -500,22 +562,20 @@ const fileBlob = await Http.Get(
|
|
|
500
562
|
|
|
501
563
|
## Call Api
|
|
502
564
|
|
|
503
|
-
These helpers are defined in `src/main/api-call.ts` and exported from `dff-util`. All three use
|
|
565
|
+
These helpers are defined in `src/main/api-call.ts` and exported from `dff-util`. All three use `fetch` and return Promises. Use `await` (or `.then`) in an async function or a module that supports top-level await. A runtime with `fetch` is required (browsers and Node.js 18+). In the browser, the remote service must permit your origin through CORS.
|
|
504
566
|
|
|
505
567
|
The former names `Hbs`, `LangText`, and `CurrencyConvert` are now `CallHbs`, `CallLangText`, and `CallCurrencyConvert`. Import them from `dff-util` or `main/api-call` instead of `main/util`.
|
|
506
568
|
|
|
507
569
|
### Handlebars rendering (CallHbs)
|
|
508
570
|
|
|
509
|
-
`CallHbs` renders inline templates through `https://hbs.rndpro.in`, using the same request format as the HBS service. It
|
|
510
|
-
|
|
511
|
-
Synchronous requests block the browser while waiting and are deprecated on the main thread. The service must permit your browser origin and JSON POST requests through CORS. See [synchronous XHR limitations](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest_API/Synchronous_and_Asynchronous_Requests).
|
|
571
|
+
`CallHbs` renders inline templates through `https://hbs.rndpro.in`, using the same request format as the HBS service. It POSTs JSON `{ template, data }` with `Content-Type: application/json` and returns a Promise. The service must permit your origin and JSON POST requests through CORS.
|
|
512
572
|
|
|
513
573
|
| Call | Endpoint | Return type |
|
|
514
574
|
| :--- | :--- | :--- |
|
|
515
|
-
| `CallHbs(template, data)` | `POST /render` | `string
|
|
516
|
-
| `CallHbs(template, items, true)` | `POST /multi` | `string[]
|
|
575
|
+
| `await CallHbs(template, data)` | `POST /render` | `Promise<string>` |
|
|
576
|
+
| `await CallHbs(template, items, true)` | `POST /multi` | `Promise<string[]>` |
|
|
517
577
|
|
|
518
|
-
The TypeScript return type is `string | string[]
|
|
578
|
+
The TypeScript return type is `Promise<string | string[]>`; the table shows the result for each mode.
|
|
519
579
|
|
|
520
580
|
#### Supported inputs and rendering modes
|
|
521
581
|
|
|
@@ -524,12 +584,13 @@ Based on the `dff-hbs` service in the local `hbs` repository (`src/models.rs`, `
|
|
|
524
584
|
| Input or mode | Type | Behavior / example |
|
|
525
585
|
| :--- | :--- | :--- |
|
|
526
586
|
| `template` | `string` | Inline Handlebars text, such as `Hello, {{name}}!`; not a registered template name |
|
|
527
|
-
| `data` with `multi = false` (default) | `RequestBodyType` | One object used as the template context: `{ name: '
|
|
587
|
+
| `data` with `multi = false` (default) | `RequestBodyType` | One object used as the template context: `{ name: 'k7m2x9' }` |
|
|
528
588
|
| `data` with `multi = false` | `RequestBodyType[]` | One array context; iterate using `{{#each this}}{{name}}{{/each}}` |
|
|
529
589
|
| `data` with `multi = true` | `RequestBodyType[]` | Render the same template for each object in one `/multi` request |
|
|
530
|
-
| Object property values | JSON string, number, boolean, null, object, or array | Nested data is supported, for example `{ user: { name: '
|
|
590
|
+
| Object property values | JSON string, number, boolean, null, object, or array | Nested data is supported, for example `{ user: { name: 'k7m2x9' }, active: true }` |
|
|
531
591
|
| Single result | `string` | Rendered text from `/render` |
|
|
532
592
|
| Batch result | `string[]` | Rendered strings in input order from `/multi`; an empty array returns `[]` |
|
|
593
|
+
| `data` is `null`/`undefined` | no request | Returns `template` unchanged; `[template]` when `multi = true` |
|
|
533
594
|
|
|
534
595
|
#### Template helper reference
|
|
535
596
|
|
|
@@ -574,74 +635,74 @@ Quote examples show the helper value before any HTML escaping. The registered he
|
|
|
574
635
|
```typescript
|
|
575
636
|
import { CallHbs } from 'dff-util';
|
|
576
637
|
|
|
577
|
-
const rendered = CallHbs('Hello, {{to_pascal_case name}}!', {
|
|
578
|
-
name: '
|
|
638
|
+
const rendered = await CallHbs('Hello, {{to_pascal_case name}}!', {
|
|
639
|
+
name: 'k7m2x9',
|
|
579
640
|
});
|
|
580
|
-
console.log(rendered); // Hello,
|
|
641
|
+
console.log(rendered); // Hello, K7m2x9!
|
|
581
642
|
```
|
|
582
643
|
|
|
583
644
|
#### Render multiple data items in one request
|
|
584
645
|
|
|
585
646
|
```typescript
|
|
586
|
-
const rendered = CallHbs(
|
|
647
|
+
const rendered = await CallHbs(
|
|
587
648
|
'Hello, {{to_pascal_case name}}!',
|
|
588
|
-
[{ name: '
|
|
649
|
+
[{ name: 'k7m2x9' }, { name: 'n4t9pw' }],
|
|
589
650
|
true,
|
|
590
651
|
);
|
|
591
|
-
console.log(rendered); // ['Hello,
|
|
652
|
+
console.log(rendered); // ['Hello, K7m2x9!', 'Hello, N4t9pw!']
|
|
592
653
|
```
|
|
593
654
|
|
|
594
655
|
The third argument defaults to `false`. Set it to `true` to render the same template once per array item, preserving order. An empty batch returns `[]`. With `false`, an array is treated as a single template context, for example with `{{#each this}}`.
|
|
595
656
|
|
|
596
|
-
Both endpoints receive JSON in the form `{ template, data }`. Templates can use the string helpers and `switch` helper registered by HBS. Data must be a JSON object or an array of JSON objects; batch rendering requires an array of objects. Empty templates are allowed. Omitting `multi` is equivalent to passing `false`.
|
|
657
|
+
Both endpoints receive JSON in the form `{ template, data }`. Templates can use the string helpers and `switch` helper registered by HBS. Data must be a JSON object or an array of JSON objects; batch rendering requires an array of objects. If `data` is `null` or `undefined`, no request is made and the template is returned unchanged, or as `[template]` when `multi = true`. Empty templates are allowed. Omitting `multi` is equivalent to passing `false`.
|
|
597
658
|
|
|
598
659
|
#### Handle failures
|
|
599
660
|
|
|
600
661
|
```typescript
|
|
601
662
|
try {
|
|
602
|
-
const result = CallHbs('Hello, {{name}}!', { name: '
|
|
663
|
+
const result = await CallHbs('Hello, {{name}}!', { name: 'k7m2x9' });
|
|
603
664
|
console.log(result);
|
|
604
665
|
} catch (error) {
|
|
605
666
|
console.error(error instanceof Error ? error.message : error);
|
|
606
667
|
}
|
|
607
668
|
```
|
|
608
669
|
|
|
609
|
-
Non-success HTTP responses
|
|
670
|
+
Non-success HTTP responses reject with an error containing the status and response text. The service returns `400` for invalid requests and `500` for rendering failures; a failing batch reports the item index and rejects the entire batch. Network, CORS, and JSON-parsing errors propagate to the caller. Successful batch responses are checked to ensure they contain an array of strings.
|
|
610
671
|
|
|
611
672
|
### Language translation (CallLangText)
|
|
612
673
|
|
|
613
|
-
`CallLangText(text, sourceLocale, targetLocale)` returns the translated text as a `string`.
|
|
674
|
+
`CallLangText(text, sourceLocale, targetLocale)` returns a Promise that resolves to the translated text as a `string`.
|
|
614
675
|
|
|
615
676
|
```typescript
|
|
616
677
|
import { CallLangText } from 'dff-util';
|
|
617
678
|
|
|
618
679
|
try {
|
|
619
|
-
const translated = CallLangText('Hello, how are you?', 'en-US', 'te-IN');
|
|
680
|
+
const translated = await CallLangText('Hello, how are you?', 'en-US', 'te-IN');
|
|
620
681
|
console.log(translated);
|
|
621
682
|
} catch (error) {
|
|
622
683
|
console.error(error instanceof Error ? error.message : error);
|
|
623
684
|
}
|
|
624
685
|
```
|
|
625
686
|
|
|
626
|
-
Uses `https://lingva.ml/api/v1`, taking the two-letter language prefix from each locale. Text is URL-encoded. Empty inputs, invalid language codes, unsuccessful HTTP responses, and service-reported errors
|
|
687
|
+
Uses `https://lingva.ml/api/v1`, taking the two-letter language prefix from each locale. Text is URL-encoded. Empty inputs, invalid language codes, unsuccessful HTTP responses, and service-reported errors reject.
|
|
627
688
|
|
|
628
689
|
### Currency conversion (CallCurrencyConvert)
|
|
629
690
|
|
|
630
|
-
`CallCurrencyConvert(fromCurrency, toCurrency)` returns the exchange rate as a `number`: the target-currency value of one source-currency unit.
|
|
691
|
+
`CallCurrencyConvert(fromCurrency, toCurrency)` returns a Promise that resolves to the exchange rate as a `number`: the target-currency value of one source-currency unit.
|
|
631
692
|
|
|
632
693
|
```typescript
|
|
633
694
|
import { CallCurrencyConvert } from 'dff-util';
|
|
634
695
|
|
|
635
696
|
try {
|
|
636
|
-
const rate = CallCurrencyConvert('INR', 'USD');
|
|
697
|
+
const rate = await CallCurrencyConvert('INR', 'USD');
|
|
637
698
|
console.log(rate);
|
|
638
|
-
console.log(CallCurrencyConvert('USD', 'USD')); // 1; no HTTP request
|
|
699
|
+
console.log(await CallCurrencyConvert('USD', 'USD')); // 1; no HTTP request
|
|
639
700
|
} catch (error) {
|
|
640
701
|
console.error(error instanceof Error ? error.message : error);
|
|
641
702
|
}
|
|
642
703
|
```
|
|
643
704
|
|
|
644
|
-
Uses `https://api.frankfurter.dev/v1/latest`. Missing inputs, unsuccessful HTTP responses, and missing or nonnumeric rates
|
|
705
|
+
Uses `https://api.frankfurter.dev/v1/latest`. Missing inputs, unsuccessful HTTP responses, and missing or nonnumeric rates reject. Network and JSON-parsing errors propagate to the caller.
|
|
645
706
|
|
|
646
707
|
## Tokens and encryption
|
|
647
708
|
|
package/dist/main/api-call.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RequestBodyType } from './const-type';
|
|
2
2
|
/** Render an inline Handlebars template using the hosted HBS service. */
|
|
3
|
-
export declare function CallHbs(template: string, data: RequestBodyType | RequestBodyType[], multi?: boolean): string | string[]
|
|
4
|
-
export declare const CallLangText: (data: string, source: string, target: string) => string
|
|
3
|
+
export declare function CallHbs(template: string, data: RequestBodyType | RequestBodyType[] | null | undefined, multi?: boolean): Promise<string | string[]>;
|
|
4
|
+
export declare const CallLangText: (data: string, source: string, target: string) => Promise<string>;
|
|
5
5
|
/** Return the exchange rate for one unit of the source currency. */
|
|
6
|
-
export declare const CallCurrencyConvert: (from: string, to: string) => number
|
|
6
|
+
export declare const CallCurrencyConvert: (from: string, to: string) => Promise<number>;
|
package/dist/main/api-call.js
CHANGED
|
@@ -1,45 +1,65 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
2
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
12
|
exports.CallCurrencyConvert = exports.CallLangText = void 0;
|
|
4
13
|
exports.CallHbs = CallHbs;
|
|
5
|
-
// All API calls are synchronous and require XMLHttpRequest support.
|
|
6
14
|
function requestText(url, label, body) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
16
|
+
if (typeof fetch === 'undefined') {
|
|
17
|
+
throw new Error(`${label} requires a runtime with fetch support`);
|
|
18
|
+
}
|
|
19
|
+
const init = body === undefined
|
|
20
|
+
? { method: 'GET' }
|
|
21
|
+
: {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers: { 'Content-Type': 'application/json' },
|
|
24
|
+
body: JSON.stringify(body),
|
|
25
|
+
};
|
|
26
|
+
const response = yield fetch(url, init);
|
|
27
|
+
const text = yield response.text();
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
throw new Error(`${label} failed: ${response.status} ${response.statusText}${text ? `: ${text}` : ''}`);
|
|
30
|
+
}
|
|
31
|
+
return text;
|
|
32
|
+
});
|
|
20
33
|
}
|
|
21
34
|
/** Render an inline Handlebars template using the hosted HBS service. */
|
|
22
|
-
function CallHbs(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
function CallHbs(template_1, data_1) {
|
|
36
|
+
return __awaiter(this, arguments, void 0, function* (template, data, multi = false) {
|
|
37
|
+
if (typeof template !== 'string') {
|
|
38
|
+
throw new Error('Invalid input: template must be a string');
|
|
39
|
+
}
|
|
40
|
+
// With no data, no request is made: return the template unchanged,
|
|
41
|
+
// or a single-item array when rendering in multi mode.
|
|
42
|
+
if (data === null || data === undefined) {
|
|
43
|
+
return multi ? [template] : template;
|
|
44
|
+
}
|
|
45
|
+
const isObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
46
|
+
if (!(Array.isArray(data) ? data.every(isObject) : isObject(data))) {
|
|
47
|
+
throw new Error('Invalid input: data must be a JSON object or an array of JSON objects');
|
|
48
|
+
}
|
|
49
|
+
if (multi && !Array.isArray(data)) {
|
|
50
|
+
throw new Error('Invalid input: data must be an array for multi rendering');
|
|
51
|
+
}
|
|
52
|
+
const text = yield requestText(`https://hbs.rndpro.in/${multi ? 'multi' : 'render'}`, 'HBS rendering', { template, data });
|
|
53
|
+
if (!multi)
|
|
54
|
+
return text;
|
|
55
|
+
const rendered = JSON.parse(text);
|
|
56
|
+
if (!Array.isArray(rendered) || !rendered.every((item) => typeof item === 'string')) {
|
|
57
|
+
throw new Error('Invalid HBS response: expected an array of strings');
|
|
58
|
+
}
|
|
59
|
+
return rendered;
|
|
60
|
+
});
|
|
41
61
|
}
|
|
42
|
-
const CallLangText = (data, source, target) => {
|
|
62
|
+
const CallLangText = (data, source, target) => __awaiter(void 0, void 0, void 0, function* () {
|
|
43
63
|
if (!data || !source || !target) {
|
|
44
64
|
throw new Error('Invalid input: data, source, or target is missing');
|
|
45
65
|
}
|
|
@@ -50,7 +70,7 @@ const CallLangText = (data, source, target) => {
|
|
|
50
70
|
}
|
|
51
71
|
const encodedData = encodeURIComponent(data);
|
|
52
72
|
const url = `https://lingva.ml/api/v1/${src}/${tgt}/${encodedData}`;
|
|
53
|
-
const json = JSON.parse(requestText(url, 'Translation'));
|
|
73
|
+
const json = JSON.parse(yield requestText(url, 'Translation'));
|
|
54
74
|
if (json.error) {
|
|
55
75
|
throw new Error(`API error: ${json.error}`);
|
|
56
76
|
}
|
|
@@ -58,10 +78,10 @@ const CallLangText = (data, source, target) => {
|
|
|
58
78
|
throw new Error('Invalid translation response: expected a string');
|
|
59
79
|
}
|
|
60
80
|
return json.translation || '';
|
|
61
|
-
};
|
|
81
|
+
});
|
|
62
82
|
exports.CallLangText = CallLangText;
|
|
63
83
|
/** Return the exchange rate for one unit of the source currency. */
|
|
64
|
-
const CallCurrencyConvert = (from, to) => {
|
|
84
|
+
const CallCurrencyConvert = (from, to) => __awaiter(void 0, void 0, void 0, function* () {
|
|
65
85
|
var _a;
|
|
66
86
|
if (!from || !to) {
|
|
67
87
|
throw new Error('Invalid input: from or to is missing');
|
|
@@ -69,11 +89,11 @@ const CallCurrencyConvert = (from, to) => {
|
|
|
69
89
|
if (from === to)
|
|
70
90
|
return 1;
|
|
71
91
|
const url = `https://api.frankfurter.dev/v1/latest?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`;
|
|
72
|
-
const json = JSON.parse(requestText(url, 'Currency conversion'));
|
|
92
|
+
const json = JSON.parse(yield requestText(url, 'Currency conversion'));
|
|
73
93
|
const rate = (_a = json.rates) === null || _a === void 0 ? void 0 : _a[to];
|
|
74
94
|
if (typeof rate !== 'number' || !Number.isFinite(rate)) {
|
|
75
95
|
throw new Error('Invalid currency response: expected a numeric exchange rate');
|
|
76
96
|
}
|
|
77
97
|
return rate;
|
|
78
|
-
};
|
|
98
|
+
});
|
|
79
99
|
exports.CallCurrencyConvert = CallCurrencyConvert;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type CsvCell = string | null;
|
|
2
|
+
/** Parse CSV text into an array of objects. The first row is used as header keys. */
|
|
3
|
+
export declare function CsvToJson(csv: string | null | undefined, delimiter?: string): Record<string, CsvCell>[];
|
|
4
|
+
/** Serialize JSON objects as CSV text. Nested objects and arrays are JSON-stringified. */
|
|
5
|
+
export declare function JsonToCsv(data: string | Record<string, unknown> | Record<string, unknown>[] | null | undefined, delimiter?: string): string;
|
package/dist/main/csv.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CsvToJson = CsvToJson;
|
|
4
|
+
exports.JsonToCsv = JsonToCsv;
|
|
5
|
+
function normalizeDelimiter(delimiter) {
|
|
6
|
+
if (typeof delimiter !== 'string' || delimiter.length === 0) {
|
|
7
|
+
throw new Error('Invalid input: delimiter must be a non-empty string');
|
|
8
|
+
}
|
|
9
|
+
if (delimiter.includes('"') || delimiter.includes('\n') || delimiter.includes('\r')) {
|
|
10
|
+
throw new Error('Invalid input: delimiter cannot contain quotes or line breaks');
|
|
11
|
+
}
|
|
12
|
+
return delimiter;
|
|
13
|
+
}
|
|
14
|
+
function isPlainObject(value) {
|
|
15
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
function needsQuotes(value, delimiter) {
|
|
18
|
+
return (value === '' ||
|
|
19
|
+
value.includes('"') ||
|
|
20
|
+
value.includes('\n') ||
|
|
21
|
+
value.includes('\r') ||
|
|
22
|
+
value.includes(delimiter));
|
|
23
|
+
}
|
|
24
|
+
function encodeField(value, delimiter) {
|
|
25
|
+
if (needsQuotes(value, delimiter)) {
|
|
26
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
function encodeValue(value, delimiter) {
|
|
31
|
+
if (value === undefined || value === null)
|
|
32
|
+
return '';
|
|
33
|
+
if (typeof value === 'string')
|
|
34
|
+
return encodeField(value, delimiter);
|
|
35
|
+
if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
|
|
36
|
+
return String(value);
|
|
37
|
+
}
|
|
38
|
+
if (value instanceof Date)
|
|
39
|
+
return encodeField(value.toISOString(), delimiter);
|
|
40
|
+
if (typeof value === 'object')
|
|
41
|
+
return encodeField(JSON.stringify(value), delimiter);
|
|
42
|
+
return encodeField(String(value), delimiter);
|
|
43
|
+
}
|
|
44
|
+
function decodeCell(field) {
|
|
45
|
+
if (!field)
|
|
46
|
+
return null;
|
|
47
|
+
if (field.quoted)
|
|
48
|
+
return field.value;
|
|
49
|
+
if (field.value === '')
|
|
50
|
+
return null;
|
|
51
|
+
return field.value;
|
|
52
|
+
}
|
|
53
|
+
function parseCsvRows(input, delimiter) {
|
|
54
|
+
if (input.charCodeAt(0) === 0xfeff)
|
|
55
|
+
input = input.slice(1);
|
|
56
|
+
if (input.length === 0)
|
|
57
|
+
return [];
|
|
58
|
+
const rows = [];
|
|
59
|
+
let row = [];
|
|
60
|
+
let field = '';
|
|
61
|
+
let quoted = false;
|
|
62
|
+
let inQuotes = false;
|
|
63
|
+
const dLen = delimiter.length;
|
|
64
|
+
const pushField = () => {
|
|
65
|
+
row.push({ value: field, quoted });
|
|
66
|
+
field = '';
|
|
67
|
+
quoted = false;
|
|
68
|
+
};
|
|
69
|
+
for (let i = 0; i < input.length; i++) {
|
|
70
|
+
const ch = input[i];
|
|
71
|
+
if (inQuotes) {
|
|
72
|
+
if (ch === '"') {
|
|
73
|
+
if (input[i + 1] === '"') {
|
|
74
|
+
field += '"';
|
|
75
|
+
i++;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
inQuotes = false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
field += ch;
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (ch === '"' && field === '') {
|
|
87
|
+
inQuotes = true;
|
|
88
|
+
quoted = true;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (input.startsWith(delimiter, i)) {
|
|
92
|
+
pushField();
|
|
93
|
+
i += dLen - 1;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (ch === '\n' || ch === '\r') {
|
|
97
|
+
pushField();
|
|
98
|
+
rows.push(row);
|
|
99
|
+
row = [];
|
|
100
|
+
if (ch === '\r' && input[i + 1] === '\n')
|
|
101
|
+
i++;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
field += ch;
|
|
105
|
+
}
|
|
106
|
+
if (inQuotes) {
|
|
107
|
+
throw new Error('Invalid CSV: unterminated quoted field');
|
|
108
|
+
}
|
|
109
|
+
if (!(input.endsWith('\n') || input.endsWith('\r'))) {
|
|
110
|
+
pushField();
|
|
111
|
+
rows.push(row);
|
|
112
|
+
}
|
|
113
|
+
return rows;
|
|
114
|
+
}
|
|
115
|
+
function isBlankLine(row, columnCount) {
|
|
116
|
+
return row.length === 1 && row[0].value === '' && !row[0].quoted && columnCount > 1;
|
|
117
|
+
}
|
|
118
|
+
function isEmptyRow(row) {
|
|
119
|
+
return row.every((cell) => cell.value === '' && !cell.quoted);
|
|
120
|
+
}
|
|
121
|
+
/** Parse CSV text into an array of objects. The first row is used as header keys. */
|
|
122
|
+
function CsvToJson(csv, delimiter = ',') {
|
|
123
|
+
if (csv === null || csv === undefined)
|
|
124
|
+
return [];
|
|
125
|
+
if (typeof csv !== 'string') {
|
|
126
|
+
throw new Error('Invalid input: csv must be a string');
|
|
127
|
+
}
|
|
128
|
+
const rows = parseCsvRows(csv, normalizeDelimiter(delimiter));
|
|
129
|
+
if (rows.length === 0 || (rows.length === 1 && isEmptyRow(rows[0])))
|
|
130
|
+
return [];
|
|
131
|
+
const headers = rows[0].map((cell) => cell.value);
|
|
132
|
+
const records = [];
|
|
133
|
+
for (let r = 1; r < rows.length; r++) {
|
|
134
|
+
const row = rows[r];
|
|
135
|
+
if (isBlankLine(row, headers.length))
|
|
136
|
+
continue;
|
|
137
|
+
const record = {};
|
|
138
|
+
for (let c = 0; c < headers.length; c++) {
|
|
139
|
+
const key = headers[c];
|
|
140
|
+
if (key === '')
|
|
141
|
+
continue;
|
|
142
|
+
record[key] = decodeCell(row[c]);
|
|
143
|
+
}
|
|
144
|
+
records.push(record);
|
|
145
|
+
}
|
|
146
|
+
return records;
|
|
147
|
+
}
|
|
148
|
+
/** Serialize JSON objects as CSV text. Nested objects and arrays are JSON-stringified. */
|
|
149
|
+
function JsonToCsv(data, delimiter = ',') {
|
|
150
|
+
const delim = normalizeDelimiter(delimiter);
|
|
151
|
+
let records = data;
|
|
152
|
+
if (typeof records === 'string') {
|
|
153
|
+
if (records.trim() === '')
|
|
154
|
+
return '';
|
|
155
|
+
try {
|
|
156
|
+
records = JSON.parse(records);
|
|
157
|
+
}
|
|
158
|
+
catch (_a) {
|
|
159
|
+
throw new Error('Invalid input: data must be valid JSON');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (records === null || records === undefined)
|
|
163
|
+
return '';
|
|
164
|
+
if (!Array.isArray(records)) {
|
|
165
|
+
if (!isPlainObject(records)) {
|
|
166
|
+
throw new Error('Invalid input: data must be an object or an array of objects');
|
|
167
|
+
}
|
|
168
|
+
records = [records];
|
|
169
|
+
}
|
|
170
|
+
const rows = records;
|
|
171
|
+
if (rows.length === 0)
|
|
172
|
+
return '';
|
|
173
|
+
if (!rows.every((row) => row === null || row === undefined || isPlainObject(row))) {
|
|
174
|
+
throw new Error('Invalid input: data must be an object or an array of objects');
|
|
175
|
+
}
|
|
176
|
+
const keys = [];
|
|
177
|
+
const seen = new Set();
|
|
178
|
+
for (const record of rows) {
|
|
179
|
+
if (!isPlainObject(record))
|
|
180
|
+
continue;
|
|
181
|
+
for (const key of Object.keys(record)) {
|
|
182
|
+
if (!seen.has(key)) {
|
|
183
|
+
seen.add(key);
|
|
184
|
+
keys.push(key);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (keys.length === 0)
|
|
189
|
+
return '';
|
|
190
|
+
const lines = [keys.map((key) => encodeField(key, delim)).join(delim)];
|
|
191
|
+
for (const record of rows) {
|
|
192
|
+
if (record === null || record === undefined) {
|
|
193
|
+
lines.push(keys.map(() => '').join(delim));
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const obj = record;
|
|
197
|
+
lines.push(keys.map((key) => encodeValue(obj[key], delim)).join(delim));
|
|
198
|
+
}
|
|
199
|
+
return lines.join('\n');
|
|
200
|
+
}
|
package/dist/main/util.d.ts
CHANGED
|
@@ -19,3 +19,4 @@ export declare function DecodeURL(input: string): string;
|
|
|
19
19
|
export declare function SafeEncode(input: string): string;
|
|
20
20
|
export declare function SafeDecode(encoded: string): string;
|
|
21
21
|
export { QueryCond, type QueryCondItem } from './query-cond';
|
|
22
|
+
export { CsvToJson, JsonToCsv, type CsvCell } from './csv';
|
package/dist/main/util.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.QueryCond = exports.LangCountryCode = exports.TimeAgo = exports.DateAndTime = exports.DateTime12HrFormat = exports.DateTime24HrFormat = exports.TruncateText = exports.AppUUID4 = exports.AppAddDays = exports.AppDaysBack = exports.AppCode = exports.AppCodeByType = exports.AppUniqueCode = exports.AppRandomString = void 0;
|
|
3
|
+
exports.JsonToCsv = exports.CsvToJson = exports.QueryCond = exports.LangCountryCode = exports.TimeAgo = exports.DateAndTime = exports.DateTime12HrFormat = exports.DateTime24HrFormat = exports.TruncateText = exports.AppUUID4 = exports.AppAddDays = exports.AppDaysBack = exports.AppCode = exports.AppCodeByType = exports.AppUniqueCode = exports.AppRandomString = void 0;
|
|
4
4
|
exports.EncodeBase64 = EncodeBase64;
|
|
5
5
|
exports.DecodeBase64 = DecodeBase64;
|
|
6
6
|
exports.EncodeURL = EncodeURL;
|
|
@@ -197,3 +197,6 @@ function SafeDecode(encoded) {
|
|
|
197
197
|
}
|
|
198
198
|
var query_cond_1 = require("./query-cond");
|
|
199
199
|
Object.defineProperty(exports, "QueryCond", { enumerable: true, get: function () { return query_cond_1.QueryCond; } });
|
|
200
|
+
var csv_1 = require("./csv");
|
|
201
|
+
Object.defineProperty(exports, "CsvToJson", { enumerable: true, get: function () { return csv_1.CsvToJson; } });
|
|
202
|
+
Object.defineProperty(exports, "JsonToCsv", { enumerable: true, get: function () { return csv_1.JsonToCsv; } });
|