@ptx-showcase/utils 0.3.0 → 0.4.0-beta.1

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
@@ -58,13 +58,13 @@ import { buildSortParam } from '@ptx-showcase/utils'
58
58
  // from subpath
59
59
  import { buildSortParam } from '@ptx-showcase/utils/build-sort-param'
60
60
 
61
- buildSortParam('name') // → 'name'
62
- buildSortParam('name', 'name') // → '-name'
63
- buildSortParam('name', '-name') // → ''
61
+ buildSortParam('name') // → 'name'
62
+ buildSortParam('name', 'name') // → '-name'
63
+ buildSortParam('name', '-name') // → ''
64
64
 
65
65
  // combining with existing sort
66
- buildSortParam('age', 'name') // → 'age,name'
67
- buildSortParam('age', 'age,name') // → '-age,name'
66
+ buildSortParam('age', 'name') // → 'age,name'
67
+ buildSortParam('age', 'age,name') // → '-age,name'
68
68
  ```
69
69
 
70
70
  ---
@@ -96,7 +96,7 @@ deleteCookie('token')
96
96
  Default options:
97
97
 
98
98
  | Option | Value |
99
- |------------|------------|
99
+ | ---------- | ---------- |
100
100
  | `expires` | 1 hour |
101
101
  | `secure` | `true` |
102
102
  | `sameSite` | `'Strict'` |
@@ -126,16 +126,50 @@ await downloadBlob(blob, { hash: 12345 })
126
126
 
127
127
  ---
128
128
 
129
+ ### handleApiError
130
+
131
+ Parses backend error responses (including `ky`'s `HTTPError`) and reports human-readable messages through whatever notification system your project uses (`vue-sonner`, `react-hot-toast`, `console.error`, etc).
132
+
133
+ The utility is UI-agnostic: call `initHandleApiError` once at app startup with a `notify` function, then call `handleApiError` from anywhere without touching the notification library again. Without initialization, messages fall back to `console.error`.
134
+
135
+ ```ts
136
+ // from root
137
+ import { handleApiError, initHandleApiError } from '@ptx-showcase/utils'
138
+
139
+ // from subpath
140
+ import { handleApiError, initHandleApiError } from '@ptx-showcase/utils/handle-api-error'
141
+
142
+ // Vue project (e.g. main.ts), using vue-sonner
143
+ import { toast } from 'vue-sonner'
144
+
145
+ initHandleApiError({ notify: (message) => toast.error(message) })
146
+
147
+ // React project (e.g. app entry), using react-hot-toast
148
+ import toast from 'react-hot-toast'
149
+
150
+ initHandleApiError({ notify: (message) => toast.error(message) })
151
+
152
+ // anywhere in either project
153
+ try {
154
+ await api.get('users')
155
+ } catch (error) {
156
+ handleApiError(error)
157
+ }
158
+ ```
159
+
160
+ ---
161
+
129
162
  ## Available modules
130
163
 
131
- | Subpath | Exports |
132
- |---------------------------------------------|------------------------------------------------------|
133
- | `@ptx-showcase/utils` | all utilities |
134
- | `@ptx-showcase/utils/build-query-params` | `buildQueryParams` |
135
- | `@ptx-showcase/utils/build-sort-param` | `buildSortParam` |
136
- | `@ptx-showcase/utils/cookies` | `setCookie`, `getCookie`, `deleteCookie`, `defaultCookieOptions` |
137
- | `@ptx-showcase/utils/download-file` | `downloadBlob` |
138
- | `@ptx-showcase/utils/types` | `TableColumnConfig` |
164
+ | Subpath | Exports |
165
+ | ---------------------------------------- | ---------------------------------------------------------------- |
166
+ | `@ptx-showcase/utils` | all utilities |
167
+ | `@ptx-showcase/utils/build-query-params` | `buildQueryParams` |
168
+ | `@ptx-showcase/utils/build-sort-param` | `buildSortParam` |
169
+ | `@ptx-showcase/utils/cookies` | `setCookie`, `getCookie`, `deleteCookie`, `defaultCookieOptions` |
170
+ | `@ptx-showcase/utils/download-file` | `downloadBlob` |
171
+ | `@ptx-showcase/utils/handle-api-error` | `handleApiError`, `initHandleApiError` |
172
+ | `@ptx-showcase/utils/types` | `TableColumnConfig` |
139
173
 
140
174
  ---
141
175
 
@@ -0,0 +1,38 @@
1
+ import { isHTTPError as e } from "ky";
2
+ //#region src/handle-api-error/constants/handle-api-error.constant.ts
3
+ var t = "Something went wrong", n = /* @__PURE__ */ new Set([
4
+ "message",
5
+ "detail",
6
+ "error",
7
+ "msg"
8
+ ]), r = (e) => typeof e == "object" && !!e && !Array.isArray(e), i = (e) => typeof e == "string" && e.trim() ? e : null, a = (e, t) => typeof e == "string" ? e.trim() ? [t ? `${t}: ${e}` : e] : [] : Array.isArray(e) ? e.flatMap((e) => a(e, t)) : r(e) ? Object.entries(e).flatMap(([e, t]) => a(t, e === "non_field_errors" ? void 0 : e)) : [], o = (e) => {
9
+ if (!e) return [];
10
+ if (typeof e == "string") return [e];
11
+ if (Array.isArray(e)) return e.filter((e) => typeof e == "string" && !!e.trim());
12
+ let t = i(e.detail) ?? i(e.message) ?? i(e.error) ?? i(e.msg);
13
+ return [...Object.entries(e).flatMap(([e, t]) => n.has(e) ? [] : e === "non_field_errors" || e === "errors" ? a(t) : a(t, e)), ...t ? [t] : []];
14
+ }, s = (e) => {
15
+ console.error(e);
16
+ }, c = (e) => {
17
+ s = e;
18
+ }, l = () => s, u = (e) => {
19
+ c(e.notify);
20
+ }, d = (n) => {
21
+ let r = l();
22
+ if (e(n)) {
23
+ let e = n.data, i = [...new Set(o(e))];
24
+ if (i.length > 0) {
25
+ i.forEach((e) => r(e));
26
+ return;
27
+ }
28
+ r(t);
29
+ return;
30
+ }
31
+ if (n instanceof Error) {
32
+ r(n.message || "Something went wrong");
33
+ return;
34
+ }
35
+ r(t);
36
+ };
37
+ //#endregion
38
+ export { u as n, d as t };
@@ -0,0 +1 @@
1
+ let e=require("ky");var t=`Something went wrong`,n=new Set([`message`,`detail`,`error`,`msg`]),r=e=>typeof e==`object`&&!!e&&!Array.isArray(e),i=e=>typeof e==`string`&&e.trim()?e:null,a=(e,t)=>typeof e==`string`?e.trim()?[t?`${t}: ${e}`:e]:[]:Array.isArray(e)?e.flatMap(e=>a(e,t)):r(e)?Object.entries(e).flatMap(([e,t])=>a(t,e===`non_field_errors`?void 0:e)):[],o=e=>{if(!e)return[];if(typeof e==`string`)return[e];if(Array.isArray(e))return e.filter(e=>typeof e==`string`&&!!e.trim());let t=i(e.detail)??i(e.message)??i(e.error)??i(e.msg);return[...Object.entries(e).flatMap(([e,t])=>n.has(e)?[]:e===`non_field_errors`||e===`errors`?a(t):a(t,e)),...t?[t]:[]]},s=e=>{console.error(e)},c=e=>{s=e},l=()=>s,u=e=>{c(e.notify)},d=n=>{let r=l();if((0,e.isHTTPError)(n)){let e=n.data,i=[...new Set(o(e))];if(i.length>0){i.forEach(e=>r(e));return}r(t);return}if(n instanceof Error){r(n.message||`Something went wrong`);return}r(t)};Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return d}});
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../chunks/cookies-BayERMbH.cjs");exports.defaultCookieOptions=e.t,exports.deleteCookie=e.n,exports.getCookie=e.r,exports.setCookie=e.i;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require("js-cookie");c=s(c,1);var l={expires:1/24,secure:!0,sameSite:`Strict`},u=(e,t,n)=>{c.default.set(e,t,{...l,...n})},d=e=>c.default.get(e),f=(e,t)=>{c.default.remove(e,t)};exports.defaultCookieOptions=l,exports.deleteCookie=f,exports.getCookie=d,exports.setCookie=u;
@@ -0,0 +1,2 @@
1
+ export declare const DEFAULT_ERROR_MESSAGE: "Something went wrong";
2
+ export declare const SKIPPED_KEYS: Set<string>;
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../chunks/handle-api-error-D714W9rp.cjs");exports.handleApiError=e.t,exports.initHandleApiError=e.n;
@@ -0,0 +1,3 @@
1
+ import { HandleApiErrorConfig } from './types/handle-api-error.type';
2
+ export declare const initHandleApiError: (config: HandleApiErrorConfig) => void;
3
+ export declare const handleApiError: (error: unknown) => void;
@@ -0,0 +1,2 @@
1
+ import { n as e, t } from "../chunks/handle-api-error-CMd_LxPS.esm";
2
+ export { t as handleApiError, e as initHandleApiError };
@@ -0,0 +1,5 @@
1
+ import { ApiErrorData, ApiErrorPrimitive } from '../types/handle-api-error.type';
2
+ export declare const isPlainObject: (value: unknown) => value is Record<string, unknown>;
3
+ export declare const toMessage: (value: unknown) => string | null;
4
+ export declare const extractMessages: (value: unknown, parentKey?: string) => string[];
5
+ export declare const getHttpErrorMessages: (errorData: ApiErrorData | ApiErrorPrimitive | undefined) => string[];
@@ -0,0 +1,3 @@
1
+ import { NotifyFn } from '../types/handle-api-error.type';
2
+ export declare const setNotify: (fn: NotifyFn) => void;
3
+ export declare const getNotify: () => NotifyFn;
@@ -0,0 +1,14 @@
1
+ export type NotifyFn = (message: string) => void;
2
+ export interface HandleApiErrorConfig {
3
+ notify: NotifyFn;
4
+ }
5
+ export type ApiErrorPrimitive = string | string[];
6
+ export interface ApiErrorData {
7
+ detail?: string;
8
+ message?: string;
9
+ error?: string;
10
+ msg?: string;
11
+ non_field_errors?: string[];
12
+ errors?: unknown;
13
+ [key: string]: unknown;
14
+ }
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./chunks/cookies-BayERMbH.cjs"),t=require("./build-query-params/index.cjs"),n=require("./clean-object/index.cjs"),r=require("./build-sort-param/index.cjs"),i=require("./download-file/index.cjs"),a=require("./normalize-input/index.cjs");exports.buildQueryParams=t.buildQueryParams,exports.buildSortParam=r.buildSortParam,exports.cleanObject=n.cleanObject,exports.defaultCookieOptions=e.t,exports.deleteCookie=e.n,exports.downloadBlob=i.downloadBlob,exports.getCookie=e.r,exports.normalizeDecimalInput=a.normalizeDecimalInput,exports.normalizePhoneNumberInput=a.normalizePhoneNumberInput,exports.setCookie=e.i;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./build-query-params/index.cjs"),t=require("./clean-object/index.cjs"),n=require("./cookies/index.cjs"),r=require("./build-sort-param/index.cjs"),i=require("./download-file/index.cjs"),a=require("./chunks/handle-api-error-D714W9rp.cjs"),o=require("./normalize-input/index.cjs");exports.buildQueryParams=e.buildQueryParams,exports.buildSortParam=r.buildSortParam,exports.cleanObject=t.cleanObject,exports.defaultCookieOptions=n.defaultCookieOptions,exports.deleteCookie=n.deleteCookie,exports.downloadBlob=i.downloadBlob,exports.getCookie=n.getCookie,exports.handleApiError=a.t,exports.initHandleApiError=a.n,exports.normalizeDecimalInput=o.normalizeDecimalInput,exports.normalizePhoneNumberInput=o.normalizePhoneNumberInput,exports.setCookie=n.setCookie;
package/dist/index.d.ts CHANGED
@@ -4,4 +4,5 @@ export * from './cookies';
4
4
  export type * from './types';
5
5
  export * from './build-sort-param';
6
6
  export * from './download-file';
7
+ export * from './handle-api-error';
7
8
  export * from './normalize-input';
package/dist/index.js CHANGED
@@ -3,5 +3,6 @@ import { cleanObject as t } from "./clean-object/index.js";
3
3
  import { defaultCookieOptions as n, deleteCookie as r, getCookie as i, setCookie as a } from "./cookies/index.js";
4
4
  import { buildSortParam as o } from "./build-sort-param/index.js";
5
5
  import { downloadBlob as s } from "./download-file/index.js";
6
- import { normalizeDecimalInput as c, normalizePhoneNumberInput as l } from "./normalize-input/index.js";
7
- export { e as buildQueryParams, o as buildSortParam, t as cleanObject, n as defaultCookieOptions, r as deleteCookie, s as downloadBlob, i as getCookie, c as normalizeDecimalInput, l as normalizePhoneNumberInput, a as setCookie };
6
+ import { n as c, t as l } from "./chunks/handle-api-error-CMd_LxPS.esm";
7
+ import { normalizeDecimalInput as u, normalizePhoneNumberInput as d } from "./normalize-input/index.js";
8
+ export { e as buildQueryParams, o as buildSortParam, t as cleanObject, n as defaultCookieOptions, r as deleteCookie, s as downloadBlob, i as getCookie, l as handleApiError, c as initHandleApiError, u as normalizeDecimalInput, d as normalizePhoneNumberInput, a as setCookie };
package/package.json CHANGED
@@ -2,13 +2,13 @@
2
2
  "name": "@ptx-showcase/utils",
3
3
  "author": "ptx-showcase",
4
4
  "description": "Shared utilities for PTX Showcase projects",
5
- "version": "0.3.0",
5
+ "version": "0.4.0-beta.1",
6
6
  "private": false,
7
7
  "type": "module",
8
8
  "license": "MIT",
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "https://github.com/gibPerviy/ptx-showcase-utils"
11
+ "url": "git+https://github.com/gibPerviy/ptx-showcase-utils.git"
12
12
  },
13
13
  "homepage": "https://github.com/gibPerviy/ptx-showcase-utils#readme",
14
14
  "bugs": {
@@ -56,6 +56,11 @@
56
56
  "require": "./dist/download-file/index.cjs",
57
57
  "types": "./dist/download-file/index.d.ts"
58
58
  },
59
+ "./handle-api-error": {
60
+ "import": "./dist/handle-api-error/index.js",
61
+ "require": "./dist/handle-api-error/index.cjs",
62
+ "types": "./dist/handle-api-error/index.d.ts"
63
+ },
59
64
  "./normalize-input": {
60
65
  "import": "./dist/normalize-input/index.js",
61
66
  "require": "./dist/normalize-input/index.cjs",
@@ -71,24 +76,31 @@
71
76
  "dev": "vite",
72
77
  "test": "vitest",
73
78
  "build": "vite build",
79
+ "lint": "oxlint",
80
+ "lint:fix": "oxlint --fix",
81
+ "fmt": "oxfmt",
82
+ "fmt:check": "oxfmt --check",
83
+ "pre:commit": "oxfmt && oxlint && vite build",
74
84
  "release": "bumpp"
75
85
  },
76
86
  "publishConfig": {
77
87
  "access": "public"
78
88
  },
79
89
  "devDependencies": {
80
- "@types/node": "^25.9.2",
90
+ "@types/node": "^26.1.1",
81
91
  "bumpp": "^11.1.0",
82
- "oxlint": "^1.68.0",
92
+ "oxfmt": "^0.58.0",
93
+ "oxlint": "^1.73.0",
83
94
  "typescript": "~6.0.3",
84
- "vite": "^8.0.16",
85
- "vite-plugin-dts": "^5.0.2",
86
- "vitest": "^4.1.8"
95
+ "vite": "^8.1.4",
96
+ "vite-plugin-dts": "^5.0.3",
97
+ "vitest": "^4.1.10"
87
98
  },
88
99
  "dependencies": {
89
100
  "@types/file-saver": "^2.0.7",
90
101
  "@types/js-cookie": "^3.0.6",
91
102
  "file-saver": "^2.0.5",
92
- "js-cookie": "^3.0.8"
103
+ "js-cookie": "^3.0.8",
104
+ "ky": "^2.0.2"
93
105
  }
94
106
  }
@@ -1 +0,0 @@
1
- var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require("js-cookie");c=s(c,1);var l={expires:1/24,secure:!0,sameSite:`Strict`},u=(e,t,n)=>{c.default.set(e,t,{...l,...n})},d=e=>c.default.get(e),f=(e,t)=>{c.default.remove(e,t)};Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return f}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return d}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return l}});