dff-util 1.3.9 → 1.4.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/README.md CHANGED
@@ -9,8 +9,9 @@ Select a topic below to view its reference and examples.
9
9
  | | | | | | | |
10
10
  | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
11
11
  | [AppRandomString](#general-utilities) | [AppUniqueCode](#general-utilities) | [AppCodeByType](#general-utilities) | [AppCode](#general-utilities) | [AppDaysBack](#general-utilities) | [AppAddDays](#general-utilities) | [AppUUID4](#general-utilities) |
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) |
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) | [toKebabCase](#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) | [toKebabCase](#case-conversion-and-object-mapping) | | | | |
14
15
 
15
16
  **[Http](#http)**
16
17
 
@@ -94,19 +95,20 @@ Available helpers:
94
95
  | :--- | :--- | :--- |
95
96
  | `toCamelCase` | Join words in camel case | `user_name` → `userName`; `user-name` → `userName`; `User Name` → `userName`; `USER_NAME` → `uSERNAME` |
96
97
  | `toSnakeCase` | Separate words with underscores and lowercase | `userName` → `user_name`; `UserName` → `user_name`; `user name` → `user_name`; `user-name` → `user_name` |
97
- | `toLowerCase` | Convert word separators to underscores and lowercase | `user-name` → `user_name`; `userName` → `user__name`; `UserName` → `user__name` |
98
- | `toUpperCase` | Convert word separators to underscores and uppercase | `user-name` → `USER_NAME`; `user name` → `USER_NAME`; `userName` → `USER__NAME`; `UserName` → `USER__NAME` |
98
+ | `toKebabCase` | Separate words with hyphens and lowercase | `userName` → `user-name`; `UserName` → `user-name`; `user name` → `user-name`; `user_name` → `user-name` |
99
+ | `toLowerCase` | Convert word separators to underscores and lowercase | `user-name` → `user_name`; `userName` → `user_name`; `UserName` → `user_name` |
100
+ | `toUpperCase` | Convert word separators to underscores and uppercase | `user-name` → `USER_NAME`; `user name` → `USER_NAME`; `userName` → `USER_NAME`; `UserName` → `USER_NAME` |
99
101
  | `toPascalCase` | Capitalize each separated word and join | `user_name` → `UserName`; `user name` → `UserName`; `user-name` → `UserName`; `USER_NAME` → `UserName` |
100
102
 
101
- These examples reflect the current implementation. `toCamelCase` preserves internal uppercase letters; `toLowerCase` and `toUpperCase` can produce double underscores before uppercase letters.
103
+ These examples reflect the current implementation. `toCamelCase` preserves internal uppercase letters.
102
104
 
103
105
  #### Object mapping
104
106
 
105
107
  | Function | Key conversion | Example input | Example output |
106
108
  | :--- | :--- | :--- | :--- |
107
- | `toViewMapper` | Convert keys to camel case and `_id` to `id` recursively | `{ _id: 'abc123', user_name: 'prasad', address_info: { city_name: 'hyderabad' } }` | `{ id: 'abc123', userName: 'prasad', addressInfo: { cityName: 'hyderabad' } }` |
108
- | `toEntityMapper` | Convert keys to snake case recursively; preserve `id` | `{ id: 'abc123', userName: 'prasad', addressInfo: { cityName: 'hyderabad' } }` | `{ id: 'abc123', user_name: 'prasad', address_info: { city_name: 'hyderabad' } }` |
109
- | `toSchemaMapper` | Convert top-level `id` to `_id` and keys to snake case; nested values use `toEntityMapper` | `{ id: 'abc123', userName: 'prasad', addressInfo: { cityName: 'hyderabad' } }` | `{ _id: 'abc123', user_name: 'prasad', address_info: { city_name: 'hyderabad' } }` |
109
+ | `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' } }` |
110
+ | `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' } }` |
111
+ | `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
112
 
111
113
  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
114
 
@@ -300,6 +302,67 @@ QueryCond output: [
300
302
  ]
301
303
  ```
302
304
 
305
+ ### CSV and JSON conversion
306
+
307
+ `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.
308
+
309
+ ```typescript
310
+ import { CsvToJson, JsonToCsv } from 'dff-util';
311
+
312
+ const rows = CsvToJson('name,note\n"Doe, John","He said ""hello"""');
313
+ // [{ name: 'Doe, John', note: 'He said "hello"' }]
314
+
315
+ const csv = JsonToCsv([
316
+ { name: 'Doe, John', note: 'He said "hello"' },
317
+ { name: 'k7m2x9', city: 'Hyderabad' },
318
+ ]);
319
+ // name,note,city
320
+ // "Doe, John","He said ""hello""",
321
+ // k7m2x9,,Hyderabad
322
+
323
+ const tsv = JsonToCsv([{ name: 'k7m2x9', city: 'Hyderabad' }], '\t');
324
+ const fromTsv = CsvToJson(tsv, '\t');
325
+
326
+ const multiline = CsvToJson(`name,bio
327
+ k7m2x9,"line1
328
+ line2"
329
+ n4t9pw,"a
330
+ b
331
+ c"`);
332
+ // [
333
+ // { name: 'k7m2x9', bio: 'line1\nline2' },
334
+ // { name: 'n4t9pw', bio: 'a\nb\nc' },
335
+ // ]
336
+
337
+ JsonToCsv([
338
+ { name: 'k7m2x9', note: 'line1\nline2' },
339
+ { name: 'n4t9pw', note: 'hello\r\nworld' },
340
+ ]);
341
+ // name,note
342
+ // k7m2x9,"line1
343
+ // line2"
344
+ // n4t9pw,"hello
345
+ // world"
346
+ ```
347
+
348
+ | Function | Arguments | Result |
349
+ | :--- | :--- | :--- |
350
+ | `CsvToJson` | `csv, delimiter = ','` | Array of objects; header row becomes keys |
351
+ | `JsonToCsv` | `data, delimiter = ','` | CSV text with a header row |
352
+
353
+ `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.
354
+
355
+ Every header still becomes a property on each row. `null` and `undefined` write as empty cells; an empty cell parses as `null`.
356
+
357
+ | JSON value | CSV cell | Parsed value |
358
+ | :--- | :--- | :--- |
359
+ | `null` | empty cell | `null` |
360
+ | `undefined` | empty cell | `null` |
361
+ | missing key | empty cell | `null` |
362
+ | `''` | `""` | `''` |
363
+
364
+ The delimiter may be any non-empty string except a quote or line break, including `;`, `\t`, or `||`.
365
+
303
366
  ## Http
304
367
 
305
368
  ### HTTP requests
@@ -500,22 +563,20 @@ const fileBlob = await Http.Get(
500
563
 
501
564
  ## Call Api
502
565
 
503
- These helpers are defined in `src/main/api-call.ts` and exported from `dff-util`. All three use synchronous `XMLHttpRequest`: they return values directly and throw errors synchronously. No `await` is needed. Browser XHR support and server CORS permission are required; requests block while waiting.
566
+ 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
567
 
505
568
  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
569
 
507
570
  ### Handlebars rendering (CallHbs)
508
571
 
509
- `CallHbs` renders inline templates through `https://hbs.rndpro.in`, using the same request format as the HBS service. It uses synchronous `XMLHttpRequest` and sends the template and data to that service. It returns directly, without a Promise. Standard Node.js does not provide `XMLHttpRequest`.
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).
572
+ `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
573
 
513
574
  | Call | Endpoint | Return type |
514
575
  | :--- | :--- | :--- |
515
- | `CallHbs(template, data)` | `POST /render` | `string` |
516
- | `CallHbs(template, items, true)` | `POST /multi` | `string[]` |
576
+ | `await CallHbs(template, data)` | `POST /render` | `Promise<string>` |
577
+ | `await CallHbs(template, items, true)` | `POST /multi` | `Promise<string[]>` |
517
578
 
518
- The TypeScript return type is `string | string[]`; the table shows the result for each mode.
579
+ The TypeScript return type is `Promise<string | string[]>`; the table shows the result for each mode.
519
580
 
520
581
  #### Supported inputs and rendering modes
521
582
 
@@ -524,10 +585,10 @@ Based on the `dff-hbs` service in the local `hbs` repository (`src/models.rs`, `
524
585
  | Input or mode | Type | Behavior / example |
525
586
  | :--- | :--- | :--- |
526
587
  | `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: 'Prasad' }` |
588
+ | `data` with `multi = false` (default) | `RequestBodyType` | One object used as the template context: `{ name: 'k7m2x9' }` |
528
589
  | `data` with `multi = false` | `RequestBodyType[]` | One array context; iterate using `{{#each this}}{{name}}{{/each}}` |
529
590
  | `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: 'Prasad' }, active: true }` |
591
+ | Object property values | JSON string, number, boolean, null, object, or array | Nested data is supported, for example `{ user: { name: 'k7m2x9' }, active: true }` |
531
592
  | Single result | `string` | Rendered text from `/render` |
532
593
  | Batch result | `string[]` | Rendered strings in input order from `/multi`; an empty array returns `[]` |
533
594
  | `data` is `null`/`undefined` | no request | Returns `template` unchanged; `[template]` when `multi = true` |
@@ -575,21 +636,21 @@ Quote examples show the helper value before any HTML escaping. The registered he
575
636
  ```typescript
576
637
  import { CallHbs } from 'dff-util';
577
638
 
578
- const rendered = CallHbs('Hello, {{to_pascal_case name}}!', {
579
- name: 'prasad',
639
+ const rendered = await CallHbs('Hello, {{to_pascal_case name}}!', {
640
+ name: 'k7m2x9',
580
641
  });
581
- console.log(rendered); // Hello, Prasad!
642
+ console.log(rendered); // Hello, K7m2x9!
582
643
  ```
583
644
 
584
645
  #### Render multiple data items in one request
585
646
 
586
647
  ```typescript
587
- const rendered = CallHbs(
648
+ const rendered = await CallHbs(
588
649
  'Hello, {{to_pascal_case name}}!',
589
- [{ name: 'prasad' }, { name: 'monika' }],
650
+ [{ name: 'k7m2x9' }, { name: 'n4t9pw' }],
590
651
  true,
591
652
  );
592
- console.log(rendered); // ['Hello, Prasad!', 'Hello, Monika!']
653
+ console.log(rendered); // ['Hello, K7m2x9!', 'Hello, N4t9pw!']
593
654
  ```
594
655
 
595
656
  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}}`.
@@ -600,49 +661,49 @@ Both endpoints receive JSON in the form `{ template, data }`. Templates can use
600
661
 
601
662
  ```typescript
602
663
  try {
603
- const result = CallHbs('Hello, {{name}}!', { name: 'Prasad' });
664
+ const result = await CallHbs('Hello, {{name}}!', { name: 'k7m2x9' });
604
665
  console.log(result);
605
666
  } catch (error) {
606
667
  console.error(error instanceof Error ? error.message : error);
607
668
  }
608
669
  ```
609
670
 
610
- Non-success HTTP responses throw 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.
671
+ 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.
611
672
 
612
673
  ### Language translation (CallLangText)
613
674
 
614
- `CallLangText(text, sourceLocale, targetLocale)` returns the translated text as a `string`.
675
+ `CallLangText(text, sourceLocale, targetLocale)` returns a Promise that resolves to the translated text as a `string`.
615
676
 
616
677
  ```typescript
617
678
  import { CallLangText } from 'dff-util';
618
679
 
619
680
  try {
620
- const translated = CallLangText('Hello, how are you?', 'en-US', 'te-IN');
681
+ const translated = await CallLangText('Hello, how are you?', 'en-US', 'te-IN');
621
682
  console.log(translated);
622
683
  } catch (error) {
623
684
  console.error(error instanceof Error ? error.message : error);
624
685
  }
625
686
  ```
626
687
 
627
- 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 throw.
688
+ 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.
628
689
 
629
690
  ### Currency conversion (CallCurrencyConvert)
630
691
 
631
- `CallCurrencyConvert(fromCurrency, toCurrency)` returns the exchange rate as a `number`: the target-currency value of one source-currency unit.
692
+ `CallCurrencyConvert(fromCurrency, toCurrency)` returns a Promise that resolves to the exchange rate as a `number`: the target-currency value of one source-currency unit.
632
693
 
633
694
  ```typescript
634
695
  import { CallCurrencyConvert } from 'dff-util';
635
696
 
636
697
  try {
637
- const rate = CallCurrencyConvert('INR', 'USD');
698
+ const rate = await CallCurrencyConvert('INR', 'USD');
638
699
  console.log(rate);
639
- console.log(CallCurrencyConvert('USD', 'USD')); // 1; no HTTP request
700
+ console.log(await CallCurrencyConvert('USD', 'USD')); // 1; no HTTP request
640
701
  } catch (error) {
641
702
  console.error(error instanceof Error ? error.message : error);
642
703
  }
643
704
  ```
644
705
 
645
- Uses `https://api.frankfurter.dev/v1/latest`. Missing inputs, unsuccessful HTTP responses, and missing or nonnumeric rates throw. Network and JSON-parsing errors propagate to the caller.
706
+ 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.
646
707
 
647
708
  ## Tokens and encryption
648
709
 
@@ -1167,6 +1228,7 @@ export type SessionUser = {
1167
1228
  email?: string;
1168
1229
  mobile?: string;
1169
1230
  name?: string;
1231
+ tenant?: string;
1170
1232
  roles: string[];
1171
1233
  type?: string;
1172
1234
  };
@@ -1180,6 +1242,7 @@ export type SessionInfo = {
1180
1242
  email?: string;
1181
1243
  mobile?: string;
1182
1244
  name?: string;
1245
+ tenant?: string;
1183
1246
  roles: string[];
1184
1247
  type?: string;
1185
1248
  requestId?: string;
@@ -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[] | null | undefined, 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>;
@@ -1,50 +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
- if (typeof XMLHttpRequest === 'undefined') {
8
- throw new Error(`${label} requires a browser with synchronous XMLHttpRequest support`);
9
- }
10
- const request = new XMLHttpRequest();
11
- request.open(body === undefined ? 'GET' : 'POST', url, false);
12
- if (body !== undefined)
13
- request.setRequestHeader('Content-Type', 'application/json');
14
- request.send(body === undefined ? null : JSON.stringify(body));
15
- if (request.status < 200 || request.status >= 300) {
16
- const message = request.responseText;
17
- throw new Error(`${label} failed: ${request.status} ${request.statusText}${message ? `: ${message}` : ''}`);
18
- }
19
- return request.responseText;
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(template, data, multi = false) {
23
- if (typeof template !== 'string') {
24
- throw new Error('Invalid input: template must be a string');
25
- }
26
- // With no data, no request is made: return the template unchanged,
27
- // or a single-item array when rendering in multi mode.
28
- if (data === null || data === undefined) {
29
- return multi ? [template] : template;
30
- }
31
- const isObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
32
- if (!(Array.isArray(data) ? data.every(isObject) : isObject(data))) {
33
- throw new Error('Invalid input: data must be a JSON object or an array of JSON objects');
34
- }
35
- if (multi && !Array.isArray(data)) {
36
- throw new Error('Invalid input: data must be an array for multi rendering');
37
- }
38
- const text = requestText(`https://hbs.rndpro.in/${multi ? 'multi' : 'render'}`, 'HBS rendering', { template, data });
39
- if (!multi)
40
- return text;
41
- const rendered = JSON.parse(text);
42
- if (!Array.isArray(rendered) || !rendered.every((item) => typeof item === 'string')) {
43
- throw new Error('Invalid HBS response: expected an array of strings');
44
- }
45
- return rendered;
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
+ });
46
61
  }
47
- const CallLangText = (data, source, target) => {
62
+ const CallLangText = (data, source, target) => __awaiter(void 0, void 0, void 0, function* () {
48
63
  if (!data || !source || !target) {
49
64
  throw new Error('Invalid input: data, source, or target is missing');
50
65
  }
@@ -55,7 +70,7 @@ const CallLangText = (data, source, target) => {
55
70
  }
56
71
  const encodedData = encodeURIComponent(data);
57
72
  const url = `https://lingva.ml/api/v1/${src}/${tgt}/${encodedData}`;
58
- const json = JSON.parse(requestText(url, 'Translation'));
73
+ const json = JSON.parse(yield requestText(url, 'Translation'));
59
74
  if (json.error) {
60
75
  throw new Error(`API error: ${json.error}`);
61
76
  }
@@ -63,10 +78,10 @@ const CallLangText = (data, source, target) => {
63
78
  throw new Error('Invalid translation response: expected a string');
64
79
  }
65
80
  return json.translation || '';
66
- };
81
+ });
67
82
  exports.CallLangText = CallLangText;
68
83
  /** Return the exchange rate for one unit of the source currency. */
69
- const CallCurrencyConvert = (from, to) => {
84
+ const CallCurrencyConvert = (from, to) => __awaiter(void 0, void 0, void 0, function* () {
70
85
  var _a;
71
86
  if (!from || !to) {
72
87
  throw new Error('Invalid input: from or to is missing');
@@ -74,11 +89,11 @@ const CallCurrencyConvert = (from, to) => {
74
89
  if (from === to)
75
90
  return 1;
76
91
  const url = `https://api.frankfurter.dev/v1/latest?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`;
77
- const json = JSON.parse(requestText(url, 'Currency conversion'));
92
+ const json = JSON.parse(yield requestText(url, 'Currency conversion'));
78
93
  const rate = (_a = json.rates) === null || _a === void 0 ? void 0 : _a[to];
79
94
  if (typeof rate !== 'number' || !Number.isFinite(rate)) {
80
95
  throw new Error('Invalid currency response: expected a numeric exchange rate');
81
96
  }
82
97
  return rate;
83
- };
98
+ });
84
99
  exports.CallCurrencyConvert = CallCurrencyConvert;
@@ -1,5 +1,6 @@
1
1
  export declare function toCamelCase(str: string): string;
2
2
  export declare function toSnakeCase(str: string): string;
3
+ export declare function toKebabCase(str: string): string;
3
4
  export declare function toPascalCase(str: string): string;
4
5
  export declare function toUpperCase(str: string): string;
5
6
  export declare function toLowerCase(str: string): string;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.toCamelCase = toCamelCase;
4
4
  exports.toSnakeCase = toSnakeCase;
5
+ exports.toKebabCase = toKebabCase;
5
6
  exports.toPascalCase = toPascalCase;
6
7
  exports.toUpperCase = toUpperCase;
7
8
  exports.toLowerCase = toLowerCase;
@@ -11,37 +12,43 @@ exports.toSchemaMapper = toSchemaMapper;
11
12
  const const_value_1 = require("./const-value");
12
13
  function toCamelCase(str) {
13
14
  return str
14
- .replace(/[_\s-]+(.)?/g, (_, chr) => (chr ? chr.toUpperCase() : ''))
15
- .replace(/^[A-Z]/, (chr) => chr.toLowerCase());
15
+ .replace(/[_\s-]+(.)?/g, (_, chr) => (chr ? chr.toUpperCase() : '')) // snake/kebab/space → camel
16
+ .replace(/^[A-Z]/, (chr) => chr.toLowerCase()); // PascalCase → camelCase
16
17
  }
17
18
  function toSnakeCase(str) {
18
19
  return str
19
20
  .replace(/([a-z0-9])([A-Z])/g, '$1_$2') // camelCase → camel_Case
20
- .replace(/[\s-]+/g, '_') // space/dash → underscore
21
+ .replace(/[\s-]+/g, '_') // space/kebab → underscore
21
22
  .replace(/__+/g, '_') // collapse multiple underscores
22
23
  .toLowerCase();
23
24
  }
25
+ function toKebabCase(str) {
26
+ return str
27
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2') // camelCase → camel-Case
28
+ .replace(/[_\s]+/g, '-') // snake/space → hyphen
29
+ .replace(/--+/g, '-') // collapse multiple hyphens
30
+ .toLowerCase();
31
+ }
24
32
  function toPascalCase(str) {
25
33
  return str
26
- .replace(/[_\s-]+/g, ' ')
27
- .replace(/\w+/g, (word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
28
- .replace(/\s+/g, '');
34
+ .replace(/[_\s-]+/g, ' ') // snake/kebab/space → words
35
+ .replace(/\w+/g, (word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() // capitalize each word
36
+ )
37
+ .replace(/\s+/g, ''); // join words
29
38
  }
30
- function toUpperCase(str) {
39
+ function toUnderscoreSeparated(str) {
31
40
  return str
32
- .replace(/([a-z0-9])([A-Z])/g, '$1_$2') // camelCase to snake_case
33
- .replace(/[\s-]+/g, '_') // spaces/hyphens to underscores
34
- .replace(/([A-Z]+)/g, '_$1') // handle acronyms (optional)
35
- .replace(/^_+|_+$/g, '') // trim leading/trailing _
36
- .toUpperCase();
41
+ .replace(/([a-z0-9])([A-Z])/g, '$1_$2') // camelCase → camel_Case
42
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') // XMLHttp → XML_Http
43
+ .replace(/[\s-]+/g, '_') // space/kebab → underscore
44
+ .replace(/__+/g, '_') // collapse multiple underscores
45
+ .replace(/^_+|_+$/g, ''); // trim leading/trailing _
46
+ }
47
+ function toUpperCase(str) {
48
+ return toUnderscoreSeparated(str).toUpperCase();
37
49
  }
38
50
  function toLowerCase(str) {
39
- return str
40
- .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
41
- .replace(/[\s-]+/g, '_')
42
- .replace(/([A-Z]+)/g, '_$1')
43
- .replace(/^_+|_+$/g, '')
44
- .toLowerCase();
51
+ return toUnderscoreSeparated(str).toLowerCase();
45
52
  }
46
53
  function isPlainObject(obj) {
47
54
  return Object.prototype.toString.call(obj) === '[object Object]';
@@ -3,6 +3,7 @@ export type SessionInfo = {
3
3
  email?: string;
4
4
  mobile?: string;
5
5
  name?: string;
6
+ tenant?: string;
6
7
  roles: string[];
7
8
  requestId?: string;
8
9
  type?: string;
@@ -16,6 +17,7 @@ export type SessionUser = {
16
17
  email?: string;
17
18
  mobile?: string;
18
19
  name?: string;
20
+ tenant?: string;
19
21
  roles: string[];
20
22
  type?: string;
21
23
  };
@@ -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;
@@ -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
+ }
@@ -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; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dff-util",
3
- "version": "1.3.9",
3
+ "version": "1.4.3",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "scripts": {
@@ -9,6 +9,8 @@
9
9
  "pp": "npm run build"
10
10
  },
11
11
  "keywords": [
12
+ "dff-util",
13
+ "dff-utils",
12
14
  "npm",
13
15
  "dff",
14
16
  "util"