@sqlrooms/utils 0.0.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.
@@ -0,0 +1,42 @@
1
+ import { convertToUniqueS3ObjectName, generateUniqueName, generateUniquePath, } from './str';
2
+ test('generateUniqueName generates unique table names', () => {
3
+ expect(generateUniqueName('foo', [])).toBe('foo');
4
+ expect(generateUniqueName('foo', ['345'])).toBe('foo');
5
+ expect(generateUniqueName('foo', ['foo_1'])).toBe('foo');
6
+ expect(generateUniqueName('foo', ['foo', 'foo_1'])).toBe('foo_2');
7
+ expect(generateUniqueName('foo_1', ['foo_1'])).toBe('foo_2');
8
+ expect(generateUniqueName('foo_2', ['foo_2'])).toBe('foo_3');
9
+ expect(generateUniqueName('foo', ['foo', 'foo_1', 'foo_2'])).toBe('foo_3');
10
+ expect(generateUniqueName('')).toBe('');
11
+ expect(generateUniqueName('', [''])).toBe('_1');
12
+ expect(generateUniqueName('_', ['_'])).toBe('__1');
13
+ });
14
+ test('generateUniquePath generates unique paths', () => {
15
+ expect(generateUniquePath('/foo/bar.csv', [])).toBe('/foo/bar.csv');
16
+ expect(generateUniquePath('/foo/bar.csv', ['345'])).toBe('/foo/bar.csv');
17
+ expect(generateUniquePath('/foo/bar.csv', ['/foo/bar.csv'])).toBe('/foo/bar_1.csv');
18
+ expect(generateUniquePath('/foo/bar.csv', ['/foo/bar_1.csv'])).toBe('/foo/bar.csv');
19
+ expect(generateUniquePath('/foo/bar.csv', ['/foo/bar.csv', '/foo/bar_1.csv'])).toBe('/foo/bar_2.csv');
20
+ expect(generateUniquePath('/foo/bar.csv', [
21
+ '/foo/bar.csv',
22
+ '/foo/bar_1.csv',
23
+ '/foo/bar_2.csv',
24
+ ])).toBe('/foo/bar_3.csv');
25
+ expect(generateUniquePath('/foo/bar_1.csv', ['/foo/bar_1.csv'])).toBe('/foo/bar_2.csv');
26
+ expect(generateUniquePath('/foo/bar_2.csv', ['/foo/bar_2.csv'])).toBe('/foo/bar_3.csv');
27
+ expect(generateUniquePath('/foo', [])).toBe('/foo');
28
+ expect(generateUniquePath('/foo/bar', [])).toBe('/foo/bar');
29
+ expect(generateUniquePath('/foo/bar', ['/foo/bar'])).toBe('/foo/bar_1');
30
+ expect(generateUniquePath('/', [])).toBe('/');
31
+ expect(generateUniquePath('', [])).toBe('');
32
+ expect(generateUniquePath('', [''])).toBe('_1');
33
+ expect(generateUniquePath('_', ['_'])).toBe('__1');
34
+ });
35
+ test('convertToUniqueS3ObjectName works correctly', () => {
36
+ expect(convertToUniqueS3ObjectName('/flows.csv', [])).toBe('_flows.csv');
37
+ expect(convertToUniqueS3ObjectName('&flows.csv', [])).toBe('_flows.csv');
38
+ expect(convertToUniqueS3ObjectName('@flows.csv', [])).toBe('_flows.csv');
39
+ expect(convertToUniqueS3ObjectName('/flows.csv', [])).toBe('_flows.csv');
40
+ expect(convertToUniqueS3ObjectName('flows.csv', ['flows.csv'])).toBe('flows_1.csv');
41
+ expect(convertToUniqueS3ObjectName('flows.csv', ['flows.csv', 'flows_1.csv'])).toBe('flows_2.csv');
42
+ });
@@ -0,0 +1 @@
1
+ {"program":{"fileNames":["../../../node_modules/.pnpm/typescript@5.5.4/node_modules/typescript/lib/lib.es5.d.ts","../../../node_modules/.pnpm/typescript@5.5.4/node_modules/typescript/lib/lib.es2015.d.ts","../../../node_modules/.pnpm/typescript@5.5.4/node_modules/typescript/lib/lib.es2016.d.ts","../../../node_modules/.pnpm/typescript@5.5.4/node_modules/typescript/lib/lib.dom.d.ts","../../../node_modules/.pnpm/typescript@5.5.4/node_modules/typescript/lib/lib.dom.iterable.d.ts","../../../node_modules/.pnpm/typescript@5.5.4/node_modules/typescript/lib/lib.es2015.core.d.ts","../../../node_modules/.pnpm/typescript@5.5.4/node_modules/typescript/lib/lib.es2015.collection.d.ts","../../../node_modules/.pnpm/typescript@5.5.4/node_modules/typescript/lib/lib.es2015.generator.d.ts","../../../node_modules/.pnpm/typescript@5.5.4/node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../../node_modules/.pnpm/typescript@5.5.4/node_modules/typescript/lib/lib.es2015.promise.d.ts","../../../node_modules/.pnpm/typescript@5.5.4/node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../../node_modules/.pnpm/typescript@5.5.4/node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../../node_modules/.pnpm/typescript@5.5.4/node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../../node_modules/.pnpm/typescript@5.5.4/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../../node_modules/.pnpm/typescript@5.5.4/node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../../node_modules/.pnpm/typescript@5.5.4/node_modules/typescript/lib/lib.es2016.intl.d.ts","../../../node_modules/.pnpm/typescript@5.5.4/node_modules/typescript/lib/lib.decorators.d.ts","../../../node_modules/.pnpm/typescript@5.5.4/node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../../node_modules/.pnpm/@types+react@18.3.18/node_modules/@types/react/global.d.ts","../../../node_modules/.pnpm/csstype@3.1.3/node_modules/csstype/index.d.ts","../../../node_modules/.pnpm/@types+prop-types@15.7.14/node_modules/@types/prop-types/index.d.ts","../../../node_modules/.pnpm/@types+react@18.3.18/node_modules/@types/react/index.d.ts","../../../node_modules/.pnpm/@types+react@18.3.18/node_modules/@types/react/jsx-runtime.d.ts","../../../node_modules/.pnpm/@types+d3-color@3.1.3/node_modules/@types/d3-color/index.d.ts","../src/color.ts","../../../node_modules/.pnpm/@types+d3-format@3.0.4/node_modules/@types/d3-format/index.d.ts","../src/format.ts","../../../node_modules/.pnpm/@types+d3-time-format@4.0.3/node_modules/@types/d3-time-format/index.d.ts","../../../node_modules/.pnpm/@types+seedrandom@3.0.8/node_modules/@types/seedrandom/index.d.ts","../../../node_modules/.pnpm/dayjs@1.11.13/node_modules/dayjs/locale/types.d.ts","../../../node_modules/.pnpm/dayjs@1.11.13/node_modules/dayjs/locale/index.d.ts","../../../node_modules/.pnpm/dayjs@1.11.13/node_modules/dayjs/index.d.ts","../../../node_modules/.pnpm/dayjs@1.11.13/node_modules/dayjs/plugin/relativetime.d.ts","../src/helpers.ts","../src/xhr.ts","../src/storage.ts","../src/str.ts","../src/index.ts","../src/str.test.ts","../../../node_modules/.pnpm/@jest+expect-utils@29.7.0/node_modules/@jest/expect-utils/build/index.d.ts","../../../node_modules/.pnpm/chalk@4.1.2/node_modules/chalk/index.d.ts","../../../node_modules/.pnpm/@sinclair+typebox@0.27.8/node_modules/@sinclair/typebox/typebox.d.ts","../../../node_modules/.pnpm/@jest+schemas@29.6.3/node_modules/@jest/schemas/build/index.d.ts","../../../node_modules/.pnpm/pretty-format@29.7.0/node_modules/pretty-format/build/index.d.ts","../../../node_modules/.pnpm/jest-diff@29.7.0/node_modules/jest-diff/build/index.d.ts","../../../node_modules/.pnpm/jest-matcher-utils@29.7.0/node_modules/jest-matcher-utils/build/index.d.ts","../../../node_modules/.pnpm/expect@29.7.0/node_modules/expect/build/index.d.ts","../../../node_modules/.pnpm/@types+jest@29.5.14/node_modules/@types/jest/index.d.ts","../../../node_modules/.pnpm/@types+mocha@10.0.10/node_modules/@types/mocha/index.d.ts","../../../node_modules/.pnpm/@types+react-dom@18.3.5_@types+react@18.3.18/node_modules/@types/react-dom/index.d.ts"],"fileInfos":[{"version":"44e584d4f6444f58791784f1d530875970993129442a847597db702a073ca68c","affectsGlobalScope":true},"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75",{"version":"4af6b0c727b7a2896463d512fafd23634229adf69ac7c00e2ae15a09cb084fad","affectsGlobalScope":true},{"version":"9c00a480825408b6a24c63c1b71362232927247595d7c97659bc24dc68ae0757","affectsGlobalScope":true},{"version":"6920e1448680767498a0b77c6a00a8e77d14d62c3da8967b171f1ddffa3c18e4","affectsGlobalScope":true},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true},{"version":"4443e68b35f3332f753eacc66a04ac1d2053b8b035a0e0ac1d455392b5e243b3","affectsGlobalScope":true},{"version":"bc47685641087c015972a3f072480889f0d6c65515f12bd85222f49a98952ed7","affectsGlobalScope":true},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true},{"version":"93495ff27b8746f55d19fcbcdbaccc99fd95f19d057aed1bd2c0cafe1335fbf0","affectsGlobalScope":true},{"version":"6fc23bb8c3965964be8c597310a2878b53a0306edb71d4b5a4dfe760186bcc01","affectsGlobalScope":true},{"version":"ea011c76963fb15ef1cdd7ce6a6808b46322c527de2077b6cfdf23ae6f5f9ec7","affectsGlobalScope":true},{"version":"33358442698bb565130f52ba79bfd3d4d484ac85fe33f3cb1759c54d18201393","affectsGlobalScope":true},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true},{"version":"36a2e4c9a67439aca5f91bb304611d5ae6e20d420503e96c230cf8fcdc948d94","affectsGlobalScope":true},"8a8eb4ebffd85e589a1cc7c178e291626c359543403d58c9cd22b81fab5b1fb9","65ff5a0aefd7817a03c1ad04fee85c9cdd3ec415cc3c9efec85d8008d4d5e4ee",{"version":"b89c2ddec6bd955e8721d41e24ca667de06882338d88b183c2cdc1f41f4c5a34","affectsGlobalScope":true},"42c169fb8c2d42f4f668c624a9a11e719d5d07dacbebb63cbcf7ef365b0a75b3","6fc1a4f64372593767a9b7b774e9b3b92bf04e8785c3f9ea98973aa9f4bbe490","e33a7456f02ff96e9ee751532f9ff26541070229f240ec0e8776c4410590bee5","189266dd5f90a981910c70d7dfa05e2bca901a4f8a2680d7030c3abbfb5b1e23","fc1450f026c1e9019adfa69b8cf6cb593b2f8e39a9a0a509626c171b155e1df2","dd8936160e41420264a9d5fade0ff95cc92cab56032a84c74a46b4c38e43121e","bb5d24e66d38263b1bda38d0f9b7a72aa2a9de2f7dfd840132a2e372bffd95d8",{"version":"73a0ee6395819b063df4b148211985f2e1442945c1a057204cf4cf6281760dc3","affectsGlobalScope":true},"d05d8c67116dceafc62e691c47ac89f8f10cf7313cd1b2fb4fe801c2bf1bb1a7","3c5bb5207df7095882400323d692957e90ec17323ccff5fd5f29a1ecf3b165d0","f684f2969931de8fb9a5164f8c8f51aaea4025f4eede98406a17642a605c2842","1f4390e16ab10668dc316a909d1b1bc42479e06ee4fc129b3c6e6948ac104d92","c13a38d11c837670628513f9de2d2c25272be5574ec1fd61fbe60ad02699aa05","8faa2cfac01b22e2328f7474d30b110ee2ec16ef2cabd1ae3f838e0bb8dc80fa","f1f981926e310e6c6936769f0f8b9b7e7994fd703c75d38589b25d0f47d49a49","fc51df5c5b5ae7f838f977732152b66984adeacdc14f4e55e144a4a5230af4e9","724777dc2b82b308dc37481e4b77b09ec8fa696227547712e01aaacd3d20b7a0","cdcc132f207d097d7d3aa75615ab9a2e71d6a478162dde8b67f88ea19f3e54de","0d14fa22c41fdc7277e6f71473b20ebc07f40f00e38875142335d5b63cdfc9d2","c085e9aa62d1ae1375794c1fb927a445fa105fed891a7e24edbb1c3300f7384a","f315e1e65a1f80992f0509e84e4ae2df15ecd9ef73df975f7c98813b71e4c8da","5b9586e9b0b6322e5bfbd2c29bd3b8e21ab9d871f82346cb71020e3d84bae73e","3e70a7e67c2cb16f8cd49097360c0309fe9d1e3210ff9222e9dac1f8df9d4fb6","ab68d2a3e3e8767c3fba8f80de099a1cfc18c0de79e42cb02ae66e22dfe14a66","d96cc6598148bf1a98fb2e8dcf01c63a4b3558bdaec6ef35e087fd0562eb40ec",{"version":"f8db4fea512ab759b2223b90ecbbe7dae919c02f8ce95ec03f7fb1cf757cfbeb","affectsGlobalScope":true},{"version":"29f72ec1289ae3aeda78bf14b38086d3d803262ac13904b400422941a26a3636","affectsGlobalScope":true},"17ed71200119e86ccef2d96b73b02ce8854b76ad6bd21b5021d4269bec527b5f"],"root":[25,27,[34,39]],"options":{"allowJs":true,"downlevelIteration":true,"esModuleInterop":true,"jsx":4,"module":99,"noImplicitAny":true,"outDir":"./","skipLibCheck":true,"strict":true,"target":99},"fileIdsList":[[42],[44,47],[22],[19,20,21],[31],[30],[32],[40,46],[44],[41,45],[43],[23,24],[23,26],[23,28,29,32,33],[23,25,27,34,35,36,37],[23,35],[23,37],[23]],"referencedMap":[[43,1],[48,2],[50,3],[22,4],[23,3],[32,5],[31,6],[33,7],[47,8],[45,9],[46,10],[44,11],[25,12],[27,13],[34,14],[38,15],[36,16],[39,17],[37,18],[35,18]]},"version":"5.5.4"}
package/dist/xhr.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ export declare const postData: ({ url, data, }: {
2
+ url: string;
3
+ data?: Record<string, any>;
4
+ }) => Promise<any>;
5
+ export type ProgressInfo = {
6
+ loaded: number;
7
+ total: number;
8
+ ratio: number;
9
+ };
10
+ export declare function downloadFile(url: string, opts?: {
11
+ method?: string;
12
+ headers?: Record<string, string>;
13
+ onProgress?: (info: ProgressInfo) => void;
14
+ }): Promise<Uint8Array>;
15
+ export declare function uploadFile(url: string, content: File | Blob | FormData, opts?: {
16
+ method?: string;
17
+ headers?: Record<string, string>;
18
+ onProgress?: (info: ProgressInfo) => void;
19
+ }): Promise<Response>;
20
+ //# sourceMappingURL=xhr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xhr.d.ts","sourceRoot":"","sources":["../src/xhr.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,QAAQ,mBAGlB;IACD,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC5B,iBAoBA,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAC,CAAC;AAK1E,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,IAAI,GAAE;IACJ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;CACtC,GACL,OAAO,CAAC,UAAU,CAAC,CAsCrB;AAID,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,IAAI,GAAG,IAAI,GAAG,QAAQ,EAC/B,IAAI,GAAE;IACJ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;CACtC,GACL,OAAO,CAAC,QAAQ,CAAC,CAiCnB"}
package/dist/xhr.js ADDED
@@ -0,0 +1,94 @@
1
+ export const postData = async ({ url, data, }) => {
2
+ const res = await fetch(url, {
3
+ method: 'POST',
4
+ headers: new Headers({ 'Content-Type': 'application/json' }),
5
+ credentials: 'same-origin',
6
+ body: JSON.stringify(data),
7
+ });
8
+ if (!res.ok) {
9
+ console.error('Error in postData', { url, data, res });
10
+ let message = res.statusText;
11
+ try {
12
+ message = (await res.json()).error.message;
13
+ console.error(message);
14
+ }
15
+ catch {
16
+ // ignore
17
+ }
18
+ throw new Error(message);
19
+ }
20
+ return res.json();
21
+ };
22
+ // TODO: use range requests to download in chunks
23
+ // https://github.com/duckdb/duckdb-wasm/blob/d9ea9c919b6301e7c6dc8a9b3fd527e86f69a38e/packages/duckdb-wasm/src/bindings/runtime_browser.ts#L307
24
+ export async function downloadFile(url, opts = {}) {
25
+ const { method = 'GET', headers = {}, onProgress } = opts;
26
+ return await new Promise((resolve, reject) => {
27
+ const xhr = new XMLHttpRequest();
28
+ // https://www.html5rocks.com/en/tutorials/file/xhr2/#toc-bin-data
29
+ xhr.open(method, url, true);
30
+ xhr.responseType = 'arraybuffer';
31
+ Object.keys(headers).map((key) => {
32
+ if (headers[key]) {
33
+ xhr.setRequestHeader(key, headers[key]);
34
+ }
35
+ });
36
+ xhr.onload = () => resolve(new Uint8Array(xhr.response));
37
+ xhr.onreadystatechange = () => {
38
+ if (xhr.readyState === XMLHttpRequest.DONE) {
39
+ if (xhr.status >= 200 && xhr.status < 300) {
40
+ // already handled by onload
41
+ }
42
+ else {
43
+ reject({ status: xhr.status, error: `File download failed` });
44
+ }
45
+ }
46
+ };
47
+ xhr.onerror = () => reject({ status: xhr.status, error: `File download failed` });
48
+ if (onProgress) {
49
+ xhr.onprogress = (event) => {
50
+ const { lengthComputable, loaded, total } = event;
51
+ if (lengthComputable) {
52
+ onProgress({ loaded, total, ratio: total ? loaded / total : 0 });
53
+ }
54
+ };
55
+ }
56
+ xhr.send(null);
57
+ });
58
+ }
59
+ // TODO: upload in chunks https://www.html5rocks.com/en/tutorials/file/xhr2/
60
+ export async function uploadFile(url, content, opts = {}) {
61
+ const { method = 'POST', headers = {}, onProgress } = opts;
62
+ return await new Promise((resolve, reject) => {
63
+ const xhr = new XMLHttpRequest();
64
+ xhr.open(method, url, true);
65
+ Object.keys(headers).map((key) => {
66
+ if (headers[key]) {
67
+ xhr.setRequestHeader(key, headers[key]);
68
+ }
69
+ });
70
+ xhr.onreadystatechange = () => {
71
+ if (xhr.readyState === XMLHttpRequest.DONE) {
72
+ if (xhr.status >= 200 && xhr.status < 300) {
73
+ resolve(xhr.response);
74
+ }
75
+ else {
76
+ reject({
77
+ status: xhr.status,
78
+ error: xhr.responseText,
79
+ });
80
+ }
81
+ }
82
+ };
83
+ xhr.onerror = () => reject({ status: xhr.status, error: xhr.responseText });
84
+ if (onProgress) {
85
+ xhr.upload.onprogress = (event) => {
86
+ const { lengthComputable, loaded, total } = event;
87
+ if (lengthComputable) {
88
+ onProgress({ loaded, total, ratio: total ? loaded / total : 0 });
89
+ }
90
+ };
91
+ }
92
+ xhr.send(content);
93
+ });
94
+ }
@@ -0,0 +1,4 @@
1
+ import {config} from '@sqlrooms/eslint-config/react-internal';
2
+
3
+ /** @type {import("eslint").Linter.Config} */
4
+ export default config;
package/jest.config.ms ADDED
@@ -0,0 +1,8 @@
1
+ // See https://github.com/bakeruk/modern-typescript-monorepo-example/blob/main/package.json
2
+ /** @type {import('jest').Config} */
3
+ module.exports = {
4
+ transform: {
5
+ '^.+\\.(t|j)sx?$': '@swc/jest',
6
+ },
7
+ testPathIgnorePatterns: ['/dist/'],
8
+ };
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@sqlrooms/utils",
3
+ "version": "0.0.0",
4
+ "main": "dist/index.js",
5
+ "types": "src/index.ts",
6
+ "module": "dist/index.js",
7
+ "type": "module",
8
+ "private": false,
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "scripts": {
13
+ "dev": "tsc -w",
14
+ "build": "tsc",
15
+ "_test": "jest",
16
+ "_test:watch": "jest --watch",
17
+ "lint": "eslint ."
18
+ },
19
+ "dependencies": {
20
+ "d3-color": "^3.1.0",
21
+ "d3-format": "^3.1.0",
22
+ "d3-time-format": "^4.1.0",
23
+ "seedrandom": "^3.0.5"
24
+ },
25
+ "peerDependencies": {
26
+ "dayjs": "*",
27
+ "zod": "*"
28
+ },
29
+ "devDependencies": {
30
+ "@types/d3-color": "^3.1.3",
31
+ "@types/d3-format": "^3.0.4",
32
+ "@types/d3-time-format": "^4.0.3",
33
+ "@types/seedrandom": "^3.0.8"
34
+ },
35
+ "gitHead": "4b0c709542475e4f95db0b2a8405ecadcf2ec186"
36
+ }
package/src/color.ts ADDED
@@ -0,0 +1,11 @@
1
+ import {color as d3color} from 'd3-color';
2
+
3
+ export function opacifyHex(hexCode: string, opacity: number): string {
4
+ const c = d3color(hexCode);
5
+ if (!c) {
6
+ console.warn('Invalid color: ', hexCode);
7
+ return `rgba(255, 255, 255, ${opacity})`;
8
+ }
9
+ const col = c.rgb();
10
+ return `rgba(${col.r}, ${col.g}, ${col.b}, ${opacity})`;
11
+ }
package/src/format.ts ADDED
@@ -0,0 +1,12 @@
1
+ import * as d3Format from 'd3-format';
2
+
3
+ export const formatCount = d3Format.format(',.0f');
4
+ export const formatCount4 = d3Format.format('.4~s');
5
+ export const formatCountShort = d3Format.format(',.0s');
6
+
7
+ export function shorten(str: string, maxLength = 10): string {
8
+ if (str.length <= maxLength) {
9
+ return str;
10
+ }
11
+ return `${str.substring(0, maxLength - 3)}…`;
12
+ }
package/src/helpers.ts ADDED
@@ -0,0 +1,68 @@
1
+ import {timeFormat} from 'd3-time-format';
2
+ import {alea} from 'seedrandom';
3
+
4
+ import dayjs, {ConfigType} from 'dayjs';
5
+ import relativeTime from 'dayjs/plugin/relativeTime';
6
+
7
+ dayjs.extend(relativeTime);
8
+
9
+ export const toDateTime = (secs: number) => {
10
+ const t = new Date('1970-01-01T00:30:00Z'); // Unix epoch start.
11
+ t.setSeconds(secs);
12
+ return t;
13
+ };
14
+
15
+ export function genRandomStr(length: number, seed?: string) {
16
+ const rnd = seed ? alea(seed) : Math.random;
17
+ return Array.from(
18
+ (function* () {
19
+ for (let i = 0; i < length; i++) {
20
+ const v = Math.floor(rnd() * (26 * 2 + 10));
21
+ if (v < 26) {
22
+ yield String.fromCharCode(v + 65); // 'A' - 'Z'
23
+ } else if (v < 52) {
24
+ yield String.fromCharCode(v + 71); // 'a' - 'z'
25
+ } else {
26
+ yield String.fromCharCode(v + 48); // '0' - '9'
27
+ }
28
+ }
29
+ })(),
30
+ ).join('');
31
+ }
32
+
33
+ export const NUMBER_FORMAT = new Intl.NumberFormat('en-US', {
34
+ minimumFractionDigits: 0,
35
+ });
36
+
37
+ export const formatNumber = (n: number) => NUMBER_FORMAT.format(n);
38
+
39
+ const TIME_OF_DAY_FORMAT = timeFormat('%I:%M %p');
40
+ const DATE_TIME_FORMAT = timeFormat('%a %Y-%m-%d %I:%M %p');
41
+ const DATE_FORMAT = timeFormat('%Y-%m-%d');
42
+
43
+ export const formatDateTime = (d: Date | number | bigint) => {
44
+ const date = d instanceof Date ? d : new Date(Number(d));
45
+ // return date.toISOString();
46
+ return DATE_TIME_FORMAT(date);
47
+ };
48
+
49
+ export const formatDate = (d: Date | number | bigint) => {
50
+ const date = d instanceof Date ? d : new Date(Number(d));
51
+ return DATE_FORMAT(date);
52
+ };
53
+
54
+ export const formatTimeOfDay = (d: Date | number | bigint) => {
55
+ const date = d instanceof Date ? d : new Date(Number(d));
56
+ return TIME_OF_DAY_FORMAT(date);
57
+ };
58
+
59
+ export const formatTimeRelative = (d: ConfigType) => {
60
+ return dayjs().to(d);
61
+ };
62
+
63
+ export const getErrorMessageForDisplay = (e: any) => {
64
+ let msg = e instanceof Error ? e.message : String(e);
65
+ msg = msg.replace(/Query failed: Error: /, '');
66
+ const firstNl = msg.indexOf('\n');
67
+ return firstNl >= 0 ? msg.substring(0, firstNl) : msg;
68
+ };
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export * from './color';
2
+ export * from './format';
3
+ export * from './helpers';
4
+ export * from './storage';
5
+ export * from './str';
6
+ export * from './xhr';
package/src/storage.ts ADDED
@@ -0,0 +1,14 @@
1
+ import {postData} from './xhr';
2
+
3
+ // TODO: this should live in app
4
+ export async function getSignedFileUrl(
5
+ params:
6
+ | {fname: string; upload?: boolean}
7
+ | {projectId: string; fname: string; upload?: boolean; password?: string},
8
+ ) {
9
+ const {url: locationsUrl} = await postData({
10
+ url: '/api/gen-signed-url',
11
+ data: params,
12
+ });
13
+ return locationsUrl;
14
+ }
@@ -0,0 +1,65 @@
1
+ import {
2
+ convertToUniqueS3ObjectName,
3
+ generateUniqueName,
4
+ generateUniquePath,
5
+ } from './str';
6
+
7
+ test('generateUniqueName generates unique table names', () => {
8
+ expect(generateUniqueName('foo', [])).toBe('foo');
9
+ expect(generateUniqueName('foo', ['345'])).toBe('foo');
10
+ expect(generateUniqueName('foo', ['foo_1'])).toBe('foo');
11
+ expect(generateUniqueName('foo', ['foo', 'foo_1'])).toBe('foo_2');
12
+ expect(generateUniqueName('foo_1', ['foo_1'])).toBe('foo_2');
13
+ expect(generateUniqueName('foo_2', ['foo_2'])).toBe('foo_3');
14
+ expect(generateUniqueName('foo', ['foo', 'foo_1', 'foo_2'])).toBe('foo_3');
15
+ expect(generateUniqueName('')).toBe('');
16
+ expect(generateUniqueName('', [''])).toBe('_1');
17
+ expect(generateUniqueName('_', ['_'])).toBe('__1');
18
+ });
19
+
20
+ test('generateUniquePath generates unique paths', () => {
21
+ expect(generateUniquePath('/foo/bar.csv', [])).toBe('/foo/bar.csv');
22
+ expect(generateUniquePath('/foo/bar.csv', ['345'])).toBe('/foo/bar.csv');
23
+ expect(generateUniquePath('/foo/bar.csv', ['/foo/bar.csv'])).toBe(
24
+ '/foo/bar_1.csv',
25
+ );
26
+ expect(generateUniquePath('/foo/bar.csv', ['/foo/bar_1.csv'])).toBe(
27
+ '/foo/bar.csv',
28
+ );
29
+ expect(
30
+ generateUniquePath('/foo/bar.csv', ['/foo/bar.csv', '/foo/bar_1.csv']),
31
+ ).toBe('/foo/bar_2.csv');
32
+ expect(
33
+ generateUniquePath('/foo/bar.csv', [
34
+ '/foo/bar.csv',
35
+ '/foo/bar_1.csv',
36
+ '/foo/bar_2.csv',
37
+ ]),
38
+ ).toBe('/foo/bar_3.csv');
39
+ expect(generateUniquePath('/foo/bar_1.csv', ['/foo/bar_1.csv'])).toBe(
40
+ '/foo/bar_2.csv',
41
+ );
42
+ expect(generateUniquePath('/foo/bar_2.csv', ['/foo/bar_2.csv'])).toBe(
43
+ '/foo/bar_3.csv',
44
+ );
45
+ expect(generateUniquePath('/foo', [])).toBe('/foo');
46
+ expect(generateUniquePath('/foo/bar', [])).toBe('/foo/bar');
47
+ expect(generateUniquePath('/foo/bar', ['/foo/bar'])).toBe('/foo/bar_1');
48
+ expect(generateUniquePath('/', [])).toBe('/');
49
+ expect(generateUniquePath('', [])).toBe('');
50
+ expect(generateUniquePath('', [''])).toBe('_1');
51
+ expect(generateUniquePath('_', ['_'])).toBe('__1');
52
+ });
53
+
54
+ test('convertToUniqueS3ObjectName works correctly', () => {
55
+ expect(convertToUniqueS3ObjectName('/flows.csv', [])).toBe('_flows.csv');
56
+ expect(convertToUniqueS3ObjectName('&flows.csv', [])).toBe('_flows.csv');
57
+ expect(convertToUniqueS3ObjectName('@flows.csv', [])).toBe('_flows.csv');
58
+ expect(convertToUniqueS3ObjectName('/flows.csv', [])).toBe('_flows.csv');
59
+ expect(convertToUniqueS3ObjectName('flows.csv', ['flows.csv'])).toBe(
60
+ 'flows_1.csv',
61
+ );
62
+ expect(
63
+ convertToUniqueS3ObjectName('flows.csv', ['flows.csv', 'flows_1.csv']),
64
+ ).toBe('flows_2.csv');
65
+ });
package/src/str.ts ADDED
@@ -0,0 +1,166 @@
1
+ export function formatBytes(bytes: number): string {
2
+ if (bytes === 0) return '0 Bytes';
3
+
4
+ const k = 1024;
5
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
6
+
7
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
8
+
9
+ let sizeValue = bytes / Math.pow(k, i);
10
+ // Use floor to check if there's a non-zero fractional part, format accordingly
11
+ sizeValue =
12
+ sizeValue != Math.floor(sizeValue)
13
+ ? parseFloat(sizeValue.toFixed(2))
14
+ : Math.floor(sizeValue);
15
+
16
+ return sizeValue + ' ' + sizes[i];
17
+ }
18
+
19
+ export function splitFilePath(filePath: string): {
20
+ dir: string;
21
+ name: string;
22
+ ext: string;
23
+ } {
24
+ const pathParts = filePath.split('/');
25
+ const file = pathParts.pop() || '';
26
+
27
+ const dotIndex = file.lastIndexOf('.');
28
+ if (dotIndex === -1 || dotIndex === 0)
29
+ return {dir: pathParts.join('/'), name: file, ext: ''};
30
+
31
+ const name = file.substring(0, dotIndex);
32
+ const ext = file.substring(dotIndex + 1);
33
+
34
+ return {dir: pathParts.join('/'), name, ext};
35
+ }
36
+
37
+ export function convertToUniqueColumnOrTableName(
38
+ filename: string,
39
+ existingTables?: string[],
40
+ ): string {
41
+ // Remove file extension
42
+ const base = filename.replace(/\.[^/.]+$/, '');
43
+
44
+ // Replace any invalid character with underscore, and convert to lowercase
45
+ let tableName = base.replace(/[^a-z0-9_]/gi, '_');
46
+
47
+ // If the first character is a number, prepend an underscore
48
+ if (/^\d/.test(tableName)) {
49
+ tableName = '_' + tableName;
50
+ }
51
+
52
+ // Truncate to the max length 63
53
+ if (tableName.length > 63) {
54
+ tableName = tableName.substring(0, 63);
55
+ }
56
+
57
+ tableName = generateUniqueName(tableName, existingTables);
58
+
59
+ return tableName;
60
+ }
61
+
62
+ export function generateUniqueName(name: string, usedNames?: string[]) {
63
+ const usedNamesLower = usedNames?.map((n) => n.toLowerCase());
64
+
65
+ // If tableName exists in the list
66
+ if (usedNamesLower?.includes(name.toLowerCase())) {
67
+ let baseName: string | undefined = name;
68
+ let i = 0;
69
+
70
+ // If tableName ends with `_${i}` pattern, update the baseTableName and i
71
+ const matched = name.match(/^(.+)_(\d+)$/);
72
+ if (matched) {
73
+ baseName = matched[1];
74
+ i = Number(matched[2]);
75
+ }
76
+
77
+ do {
78
+ i++;
79
+ name = `${baseName}_${i}`;
80
+ } while (usedNamesLower.includes(name.toLowerCase()));
81
+ }
82
+
83
+ return name;
84
+ }
85
+
86
+ export function generateUniquePath(
87
+ filePath: string,
88
+ existingPaths: string[],
89
+ ): string {
90
+ let nextPath = filePath;
91
+ if (existingPaths?.includes(filePath)) {
92
+ const {dir, name, ext} = splitFilePath(filePath);
93
+
94
+ let i = 0;
95
+ let baseName: string | undefined = name;
96
+ const matched = name.match(/^(.+)_(\d+)$/);
97
+ if (matched) {
98
+ baseName = matched[1];
99
+ i = Number(matched[2]);
100
+ }
101
+
102
+ do {
103
+ i++;
104
+ const fname = `${baseName}_${i}${ext ? `.${ext}` : ''}`;
105
+ nextPath = `${dir}${dir ? '/' : ''}${fname}`;
106
+ } while (existingPaths.includes(nextPath));
107
+ }
108
+
109
+ return nextPath;
110
+ }
111
+
112
+ export function convertToUniqueS3ObjectName(
113
+ str: string,
114
+ existingObjects?: string[],
115
+ ): string {
116
+ let rv = str
117
+ .trim() // Remove leading and trailing white spaces
118
+ .replace(/[^\w\s-\.]/g, '_') // Replace special characters with underscores
119
+ .replace(/\s+/g, '_') // Replace consecutive spaces with a single underscore
120
+ // .replace(/_+/g, '_') // Remove consecutive underscores
121
+ // .replace(/^_/, '') // Remove leading underscores
122
+ // .replace(/_$/, '') // Remove trailing underscores
123
+ .slice(0, 255); // Truncate the string if it exceeds 255 characters
124
+
125
+ if (existingObjects?.length) {
126
+ rv = generateUniquePath(rv, existingObjects);
127
+ }
128
+
129
+ return rv;
130
+ }
131
+
132
+ export function convertToUniqueS3FolderPath(
133
+ str: string,
134
+ existingObjects?: string[],
135
+ ): string {
136
+ let next = convertToUniqueS3ObjectName(str, existingObjects);
137
+ if (!next.endsWith('/')) next += '/'; // Add trailing slash if not present
138
+ return next;
139
+ // return (
140
+ // str
141
+ // .trim() // Remove leading and trailing white spaces
142
+ // .replace(/\/+/g, '/') // Replace consecutive slashes with a single slash
143
+ // .replace(/[^\w\s-\/]/g, '_') // Replace special characters with underscores
144
+ // .replace(/\s+/g, '_') // Replace consecutive spaces with a single underscore
145
+ // .replace(/^\//, '') + // Remove leading slash
146
+ // (str.endsWith('/') ? '' : '/')
147
+ // );
148
+ }
149
+
150
+ export function camelCaseToTitle(camelCase: string): string {
151
+ // Split the string into words on the camelCase boundaries
152
+ const words = camelCase.match(/^[a-z]+|[A-Z][a-z]*/g);
153
+
154
+ // If words are found, transform them and join into a title string
155
+ if (words) {
156
+ return words
157
+ .map((word) => {
158
+ // Capitalize the first letter of each word
159
+ return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
160
+ })
161
+ .join(' '); // Join the words with space
162
+ }
163
+
164
+ // If no words were found, just capitalize the whole string
165
+ return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
166
+ }