commit 5dedaab09211e7625a9a9645499455cd6006b56d Author: Jonathan Bernard Date: Tue Feb 1 21:07:15 2022 -0600 Initial commit: extracted from LiveBudget. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb0a735 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +.*.sw? diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..53367d8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,51 @@ +{ + "name": "@jdbernard/vue-common", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@jdbernard/logging": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@jdbernard/logging/-/logging-1.1.3.tgz", + "integrity": "sha512-JS+kk/O+rg5TVHf+Fg9o4s0Al4nwkwd0vsrPxawUJbgjeC6GPwYi/fQHMRYl5XY2zvy+o3EoEC9o+4LIfJx/6A==", + "requires": { + "axios": "^0.19.2" + } + }, + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "requires": { + "follow-redirects": "1.5.10" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1534013 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "@jdbernard/vue-common", + "version": "1.0.0", + "description": "Extra stuff I always use when building Vue applications.", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "/dist" + ], + "scripts": { + "build": "tsc --build tsconfig.json", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://git.jdb-software.com/jdb/vue-common.git" + }, + "keywords": [ + "vuejs", + "vue", + "state", + "util" + ], + "author": "Jonathan Bernard ", + "license": "MIT", + "devDependencies": { + "typescript": "^4.5.5" + }, + "dependencies": { + "@jdbernard/logging": "^1.1.3" + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..7b061d7 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,3 @@ +export * from './state'; +export * from './util'; +export * from './sorting'; diff --git a/src/sorting.ts b/src/sorting.ts new file mode 100644 index 0000000..105bbdc --- /dev/null +++ b/src/sorting.ts @@ -0,0 +1,23 @@ +export type Comparator = (a: T, b: T) => number; + +export function reverse(cmp: Comparator): Comparator { + return (a, b) => cmp(a, b) * -1; +} + +export function sorted(arr: Readonly, cmp: Comparator): T[] { + return arr.slice().sort(cmp); +} + +export function compose(...cmps: Comparator[]): Comparator { + return (a: T, b: T): number => { + let val: number; + for (let i = 0; i < cmps.length; i++) { + const cmp = cmps[i]; + val = cmp(a, b); + if (val !== 0) { + return val; + } + } + return 0; + }; +} diff --git a/src/state.ts b/src/state.ts new file mode 100644 index 0000000..f099719 --- /dev/null +++ b/src/state.ts @@ -0,0 +1,30 @@ +/** + * Fetchable addresses the reality that most of our resources exist in three + * distinct possible states. + * - not loaded: it may or may not exist, but I don't have a copy of it + * - not found: I've asked and know definitively that this does not exist + * - value present + */ +export type Fetchable = 'loading' | 'not loaded' | 'not found' | T; + +export function hasValue(f: Fetchable): f is T { + return f !== 'not found' && f !== 'not loaded' && f !== 'loading'; +} + +export function valueOf(f: Fetchable): T | null { + return !hasValue(f) ? null : f; +} + +export function withValue(f: Fetchable, fun: (val: T) => V): V | null { + return !hasValue(f) ? null : fun(f); +} + +export function use(f: Fetchable, fun: (val: T) => V): Fetchable { + if (!hasValue(f)) { + return f; + } else { + return fun(f); + } +} + + diff --git a/src/util.ts b/src/util.ts new file mode 100644 index 0000000..7730051 --- /dev/null +++ b/src/util.ts @@ -0,0 +1,94 @@ +import { Logger } from '@jdbernard/logging'; + +export interface EditableItemState { + value: T; + editing?: boolean; + saving: boolean; + error: boolean; +} + +export function asOptional( + fun: () => T, + logger: Logger | null = null +): T | null { + try { + return fun(); + } catch (err: any) { + logger?.warn(err); + return null; + } +} + +export function batch(arr: T[], batchSize: number): T[][] { + const result: T[][] = []; + let curBatch: T[] = []; + + for (let i = 0; i < arr.length; i++) { + if (i > 0 && i % batchSize === 0) { + result.push(curBatch); + curBatch = []; + } + + curBatch.push(arr[i]); + } + + result.push(curBatch); + return result; +} + +export function clone(obj: T): T { + return JSON.parse(JSON.stringify(obj)); +} + +export function notNullOrUndef(val: T | undefined | null): val is T { + return val !== undefined && val !== null; +} + +export function newLoggedError(logger: Logger, msg: string): Error { + const err = new Error(msg); + logger.error(msg); + return err; +} + +export function requireDefined( + logger: Logger, + name: string, + val: T | null | undefined +): T { + if (val === null || val === undefined) { + throw newLoggedError( + logger, + `${name} must be defined and non-null but is not` + ); + } + return val; +} + +/* I wish I could write this, but don't know how to make TypeScript smart + * enough to infer types correctly against keys. I'ld like, for example, to be + * able to use this as: + * + * const { api, auth } = requireDefined(logger, { + * api: possiblyGetApi(), + * auth: possiblyGetAuth() + * }); + * + * But as written, it will infer the types of both api and auth as + * AuthService | LiveBudgetApiClient + */ +/* +export function requireDefined(logger: Logger, thingsThatMustBeDefined: Record): Record { + const retVal: Record = {}; + Object.entries(thingsThatMustBeDefined).map(([k, v]) => { + if (v === null || v === undefined) { + throw newLoggedError( + logger, + `${k} must be defined and non-null but is not` + ); + } else { + retVal[k] = v; + } + }); + return retVal; +} +*/ diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..76be4ff --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "declaration": true, + "outDir": "./dist", + "strict": true + }, + "include": [ + "src/**/*" + ] +}