Initial commit: extracted from LiveBudget.
This commit is contained in:
commit
5dedaab092
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.*.sw?
|
51
package-lock.json
generated
Normal file
51
package-lock.json
generated
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
32
package.json
Normal file
32
package.json
Normal file
@ -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 <jonathan@jdbernard.com>",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"typescript": "^4.5.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jdbernard/logging": "^1.1.3"
|
||||
}
|
||||
}
|
3
src/index.ts
Normal file
3
src/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './state';
|
||||
export * from './util';
|
||||
export * from './sorting';
|
23
src/sorting.ts
Normal file
23
src/sorting.ts
Normal file
@ -0,0 +1,23 @@
|
||||
export type Comparator<T> = (a: T, b: T) => number;
|
||||
|
||||
export function reverse<T>(cmp: Comparator<T>): Comparator<T> {
|
||||
return (a, b) => cmp(a, b) * -1;
|
||||
}
|
||||
|
||||
export function sorted<T>(arr: Readonly<T[]>, cmp: Comparator<T>): T[] {
|
||||
return arr.slice().sort(cmp);
|
||||
}
|
||||
|
||||
export function compose<T>(...cmps: Comparator<T>[]): Comparator<T> {
|
||||
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;
|
||||
};
|
||||
}
|
30
src/state.ts
Normal file
30
src/state.ts
Normal file
@ -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<T> = 'loading' | 'not loaded' | 'not found' | T;
|
||||
|
||||
export function hasValue<T>(f: Fetchable<T>): f is T {
|
||||
return f !== 'not found' && f !== 'not loaded' && f !== 'loading';
|
||||
}
|
||||
|
||||
export function valueOf<T>(f: Fetchable<T>): T | null {
|
||||
return !hasValue(f) ? null : f;
|
||||
}
|
||||
|
||||
export function withValue<T, V>(f: Fetchable<T>, fun: (val: T) => V): V | null {
|
||||
return !hasValue(f) ? null : fun(f);
|
||||
}
|
||||
|
||||
export function use<T, V>(f: Fetchable<T>, fun: (val: T) => V): Fetchable<V> {
|
||||
if (!hasValue(f)) {
|
||||
return f;
|
||||
} else {
|
||||
return fun(f);
|
||||
}
|
||||
}
|
||||
|
||||
|
94
src/util.ts
Normal file
94
src/util.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import { Logger } from '@jdbernard/logging';
|
||||
|
||||
export interface EditableItemState<T> {
|
||||
value: T;
|
||||
editing?: boolean;
|
||||
saving: boolean;
|
||||
error: boolean;
|
||||
}
|
||||
|
||||
export function asOptional<T>(
|
||||
fun: () => T,
|
||||
logger: Logger | null = null
|
||||
): T | null {
|
||||
try {
|
||||
return fun();
|
||||
} catch (err: any) {
|
||||
logger?.warn(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function batch<T>(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<T>(obj: T): T {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
export function notNullOrUndef<T>(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<T>(
|
||||
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<T>(logger: Logger, thingsThatMustBeDefined: Record<string, T | null>): Record<string, T> {
|
||||
const retVal: Record<string, T> = {};
|
||||
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;
|
||||
}
|
||||
*/
|
12
tsconfig.json
Normal file
12
tsconfig.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"strict": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user