@signal24/vue-foundation 4.7.2 → 4.7.4

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/package.json CHANGED
@@ -1,12 +1,9 @@
1
1
  {
2
2
  "name": "@signal24/vue-foundation",
3
3
  "type": "module",
4
- "version": "4.7.2",
4
+ "version": "4.7.4",
5
5
  "description": "Common components, directives, and helpers for Vue 3 apps",
6
6
  "module": "./dist/vue-foundation.es.js",
7
- "bin": {
8
- "vf-generate-openapi-client": "./dist/src/vite-plugins/vite-openapi-plugin.cli.js"
9
- },
10
7
  "exports": {
11
8
  ".": {
12
9
  "import": "./dist/vue-foundation.es.js",
@@ -24,7 +21,7 @@
24
21
  "typings": "./dist/src/index.d.ts",
25
22
  "scripts": {
26
23
  "dev": "vite",
27
- "build": "rm -rf dist && vite build && vue-tsc --declaration --emitDeclarationOnly -p tsconfig.app.json && tsc -p tsconfig.vite-plugins.json && find dist -name '*.tsbuildinfo' -delete && chmod +x dist/src/vite-plugins/vite-openapi-plugin.cli.js",
24
+ "build": "rm -rf dist && vite build && vue-tsc --declaration --emitDeclarationOnly -p tsconfig.app.json && tsc -p tsconfig.vite-plugins.json && find dist -name '*.tsbuildinfo' -delete",
28
25
  "build:watch": "fswatch -o src | xargs -n1 -I{} yarn build",
29
26
  "preview": "vite preview",
30
27
  "test:types": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
@@ -46,6 +43,7 @@
46
43
  "devDependencies": {
47
44
  "@nabla/vite-plugin-eslint": "^1.5.0",
48
45
  "@rushstack/eslint-patch": "^1.3.2",
46
+ "@signal24/openapi-client-codegen": "^1.0.4",
49
47
  "@tsconfig/node18": "^18.2.0",
50
48
  "@types/jsdom": "^21.1.1",
51
49
  "@types/lodash": "^4.14.196",
@@ -63,7 +61,6 @@
63
61
  "eslint-plugin-unused-imports": "^3.0.0",
64
62
  "eslint-plugin-vue": "^9.16.1",
65
63
  "jsdom": "^22.1.0",
66
- "openapi-typescript-codegen": "^0.25.0",
67
64
  "prettier": "^3.0.0",
68
65
  "sass": "^1.64.2",
69
66
  "start-server-and-test": "^2.0.0",
@@ -50,8 +50,8 @@ const FormMaskState = Symbol('FormMaskState');
50
50
  interface IFormMaskState {
51
51
  [FormMaskState]?: {
52
52
  disabledElements: HTMLElement[];
53
- waitButton: HTMLElement;
54
- buttonHtml: string;
53
+ waitButton?: HTMLElement;
54
+ buttonHtml?: string;
55
55
  };
56
56
  }
57
57
  type FormMaskElement = Element & IFormMaskState;
@@ -62,10 +62,13 @@ export function maskForm(formOrCmp: Element | AnyComponentPublicInstance, button
62
62
 
63
63
  const buttonEl = (
64
64
  buttonSelector instanceof Element ? buttonSelector : form.querySelectorAll(buttonSelector ?? 'button:not([disabled]):not([type="button"])')[0]
65
- ) as HTMLElement;
66
- const originalButtonHtml = buttonEl.tagName === 'INPUT' ? (buttonEl as HTMLInputElement).value : buttonEl.innerHTML;
67
- buttonEl.setAttribute('disabled', 'disabled');
68
- buttonEl.innerText = buttonText ?? 'Please wait...';
65
+ ) as HTMLElement | undefined;
66
+ let originalButtonHtml: string | undefined;
67
+ if (buttonEl) {
68
+ originalButtonHtml = buttonEl.tagName === 'INPUT' ? (buttonEl as HTMLInputElement).value : buttonEl.innerHTML;
69
+ buttonEl.setAttribute('disabled', 'disabled');
70
+ buttonEl.innerText = buttonText ?? 'Please wait...';
71
+ }
69
72
 
70
73
  const inputsQR = form.querySelectorAll('input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled])');
71
74
  const inputs = [...inputsQR] as HTMLElement[];
@@ -89,8 +92,11 @@ export function unmaskForm(formOrCmp: Element | AnyComponentPublicInstance) {
89
92
  form.classList.remove('vf-masked');
90
93
 
91
94
  state.disabledElements.forEach(el => el.removeAttribute('disabled'));
92
- state.waitButton.innerHTML = state.buttonHtml;
93
- state.waitButton.removeAttribute('disabled');
95
+
96
+ if (state.waitButton) {
97
+ state.waitButton.innerHTML = state.buttonHtml!;
98
+ state.waitButton.removeAttribute('disabled');
99
+ }
94
100
 
95
101
  delete (form as FormMaskElement)[FormMaskState];
96
102
  }
@@ -1,130 +1,16 @@
1
- import { UserError } from './error';
1
+ import { installOpenApiClientInterceptors, isOpenApiError } from '@signal24/openapi-client-codegen/helpers';
2
2
 
3
- interface IRequestOptions {
4
- readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH';
5
- readonly url: string;
6
- readonly path?: Record<string, any>;
7
- readonly cookies?: Record<string, any>;
8
- readonly headers?: Record<string, any>;
9
- readonly query?: Record<string, any>;
10
- readonly formData?: Record<string, any>;
11
- readonly body?: any;
12
- readonly mediaType?: string;
13
- readonly responseHeader?: string;
14
- readonly errors?: Record<number, string>;
15
- }
16
-
17
- interface IBaseHttpRequest {
18
- request<T>(options: IRequestOptions): ICancelablePromise<T>;
19
- }
20
-
21
- export interface IApiClient {
22
- request: IBaseHttpRequest;
23
- }
24
-
25
- export interface IApiError extends Error {
26
- status: number;
27
- statusText: string;
28
- body: any;
29
- }
30
-
31
- export declare class ICancelablePromise<T = any> {
32
- constructor(executor: (resolve: (value: any) => void, reject: (reason: any) => void, onCancel: (cancel: () => void) => void) => void);
33
- then<TResult1 = any, TResult2 = never>(
34
- onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
35
- onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
36
- ): Promise<TResult1 | TResult2>;
37
- catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
38
- finally(onfinally?: (() => void) | undefined | null): Promise<T>;
39
- cancel(): void;
40
- }
41
-
42
- interface IWrappedApiClientOptions<P extends ICancelablePromise = ICancelablePromise, Arguments extends unknown[] = any[]> {
43
- apiClient: IApiClient;
44
- wrapper?: (options: IRequestOptions, fn: (options: IRequestOptions) => P) => P;
45
- onRequest?: (options: IRequestOptions) => IRequestOptions;
46
- onError?: (err: Error, options: IRequestOptions) => Error | null | void;
47
- afterRequest?: (options: IRequestOptions) => void;
48
- CancelablePromise: new (...arguments_: Arguments) => P;
49
- }
3
+ import { UserError } from '.';
50
4
 
51
- export function isApiError(err: any): err is IApiError {
52
- return err instanceof Error && 'status' in err && 'body' in err;
53
- }
54
-
55
- export function installApiClientInterceptors({ apiClient, wrapper, onRequest, onError, afterRequest, CancelablePromise }: IWrappedApiClientOptions) {
56
- const originalRequest = apiClient.request.request.bind(apiClient.request);
57
- const resolvedWrapper = wrapper ?? ((options, fn) => fn(options));
58
- apiClient.request.request = (options: IRequestOptions) => {
59
- return resolvedWrapper(options, options => {
60
- options = rewriteOptionsForFileUpload(options);
61
-
62
- if (onRequest) {
63
- options = onRequest(options);
5
+ export function installApiClientInterceptors(clientOptions: Parameters<typeof installOpenApiClientInterceptors>[0]) {
6
+ installOpenApiClientInterceptors({
7
+ ...clientOptions,
8
+ onError(err, options) {
9
+ if (isOpenApiError(err) && err.status === 422 && typeof err.body === 'object' && 'error' in err.body) {
10
+ err = new UserError(err.body.error);
64
11
  }
65
12
 
66
- return new CancelablePromise((resolve: (value: any) => void, reject: (err: any) => void, onCancel: (handler: () => void) => void) => {
67
- const promise = originalRequest(options);
68
- onCancel(promise.cancel);
69
- promise
70
- .then(resolve)
71
- .catch(err => {
72
- if (isApiError(err) && typeof err.body === 'object' && 'error' in err.body) {
73
- if (err.status === 422) {
74
- return reject(new UserError(err.body.error));
75
- }
76
-
77
- err.message = `${err.body.error} (${err.status})`;
78
- }
79
- if (onError) {
80
- const handlerResult = onError(err, options);
81
- if (handlerResult === null) {
82
- return;
83
- }
84
- if (handlerResult instanceof Error) {
85
- return reject(handlerResult);
86
- }
87
- }
88
- reject(err);
89
- })
90
- .finally(() => afterRequest?.(options));
91
- });
92
- });
93
- };
94
- }
95
-
96
- export class FileUploadRequest {
97
- constructor(blob: Blob) {
98
- this.blob = blob;
99
- }
100
-
101
- validator = null;
102
- lastModifiedDate = null;
103
- size = 0;
104
- path = '';
105
- name = '';
106
- type = '';
107
- blob!: Blob;
108
- }
109
-
110
- function rewriteOptionsForFileUpload(options: IRequestOptions): IRequestOptions {
111
- const hasFileUpload = typeof options.body === 'object' && Object.values(options.body).some(v => v instanceof FileUploadRequest);
112
- if (!hasFileUpload) return options;
113
-
114
- const formData: Record<string, any> = {};
115
- const jsonBody: Record<string, any> = {};
116
- for (const [key, value] of Object.entries(options.body)) {
117
- if (value instanceof FileUploadRequest) {
118
- formData[key] = value.blob;
119
- } else {
120
- jsonBody[key] = value;
13
+ clientOptions.onError?.(err, options);
121
14
  }
122
- }
123
- formData._payload = new Blob([JSON.stringify(jsonBody)], { type: 'application/json' });
124
-
125
- return {
126
- ...options,
127
- body: undefined,
128
- formData
129
- };
15
+ });
130
16
  }
@@ -1,48 +1,4 @@
1
- import { createHash } from 'node:crypto';
2
- import { copyFileSync, existsSync, readFileSync, watch } from 'node:fs';
3
- import { rm } from 'node:fs/promises';
4
-
5
- import * as OpenAPI from 'openapi-typescript-codegen';
6
-
7
- const DEFAULT_OUT_PATH = './src/openapi-client-generated';
8
-
9
- let generatedHash: string | null = null;
10
- let generatorMap: Record<string, string> = {};
11
- let overridesMap: Record<string, string> | null = null;
12
- let overridesInverseMap: Record<string, string> | null = null;
13
-
14
- export function loadOpenapiConfig() {
15
- loadGeneratorMap();
16
- loadOverridesMap();
17
- }
18
-
19
- function loadGeneratorMap() {
20
- if (!existsSync('./openapi-specs.json')) {
21
- console.error('openapi-specs.json not found. Cannot generate OpenAPI client.');
22
- return;
23
- }
24
-
25
- try {
26
- const specsContent = readFileSync('./openapi-specs.json', 'utf8');
27
- generatorMap = JSON.parse(specsContent);
28
- } catch (e) {
29
- console.error('Failed to load openapi-specs.json:', e);
30
- }
31
- }
32
-
33
- function loadOverridesMap() {
34
- if (!existsSync('./openapi-specs.dev.json')) {
35
- return;
36
- }
37
-
38
- try {
39
- const overridesContent = readFileSync('./openapi-specs.dev.json', 'utf8');
40
- overridesMap = JSON.parse(overridesContent);
41
- overridesInverseMap = Object.fromEntries(Object.entries(overridesMap!).map(([k, v]) => [v, k]));
42
- } catch (e) {
43
- console.error('Failed to load openapi-specs.dev.json:', e);
44
- }
45
- }
1
+ import { createWatchfulOpenapiClientGenerators } from '@signal24/openapi-client-codegen/generator';
46
2
 
47
3
  export function openapiClientGeneratorPlugin(): {
48
4
  name: string;
@@ -50,7 +6,7 @@ export function openapiClientGeneratorPlugin(): {
50
6
  buildStart(): void;
51
7
  closeBundle(): void;
52
8
  } {
53
- let generators: ReturnType<typeof createWatchfulGenerators> | null = null;
9
+ let generators: ReturnType<typeof createWatchfulOpenapiClientGenerators> | null = null;
54
10
 
55
11
  return {
56
12
  name: 'openapi-types-generator',
@@ -59,7 +15,7 @@ export function openapiClientGeneratorPlugin(): {
59
15
  buildStart() {
60
16
  // apply a slight delay so any output doesn't get pushed off screen
61
17
  setTimeout(() => {
62
- generators = createWatchfulGenerators();
18
+ generators = createWatchfulOpenapiClientGenerators();
63
19
  }, 250);
64
20
  },
65
21
 
@@ -72,74 +28,3 @@ export function openapiClientGeneratorPlugin(): {
72
28
  }
73
29
  };
74
30
  }
75
-
76
- function createWatchfulGenerators() {
77
- loadOpenapiConfig();
78
- return Object.entries(generatorMap).map(([openapiYamlPath, outPath]) => createWatchfulGenerator(openapiYamlPath, outPath));
79
- }
80
-
81
- function createWatchfulGenerator(openapiYamlPath: string, outPath: string) {
82
- const resolvedPath = overridesMap?.[openapiYamlPath] ?? openapiYamlPath;
83
-
84
- if (!existsSync(resolvedPath)) {
85
- console.log(`OpenAPI YAML file not found: ${resolvedPath}`);
86
- return null;
87
- }
88
-
89
- const watcher = watch(resolvedPath);
90
- watcher.on('change', () => {
91
- // give the writes a moment to settle
92
- setTimeout(() => generateOpenapiClient(resolvedPath, outPath), 100);
93
- });
94
-
95
- generateOpenapiClient(resolvedPath, outPath);
96
-
97
- return {
98
- close() {
99
- watcher.close();
100
- }
101
- };
102
- }
103
-
104
- export async function generateConfiguredOpenapiClients() {
105
- loadOpenapiConfig();
106
- for (const [openapiYamlPath, outPath] of Object.entries(generatorMap)) {
107
- const resolvedPath = overridesMap?.[openapiYamlPath] ?? openapiYamlPath;
108
- await generateOpenapiClient(resolvedPath, outPath);
109
- }
110
- }
111
-
112
- export async function generateOpenapiClient(openapiYamlPath: string, outPath: string = DEFAULT_OUT_PATH) {
113
- const yaml = readFileSync(openapiYamlPath, 'utf8');
114
- const hash = createHash('sha256').update(yaml).digest('hex');
115
-
116
- if (hash === generatedHash) {
117
- return;
118
- }
119
-
120
- generatedHash = hash;
121
-
122
- try {
123
- try {
124
- await rm(outPath, { recursive: true });
125
- } catch (e) {
126
- // ignore
127
- }
128
-
129
- await OpenAPI.generate({
130
- input: openapiYamlPath,
131
- output: outPath,
132
- clientName: 'ApiClient',
133
- useOptions: true,
134
- useUnionTypes: true
135
- });
136
-
137
- if (overridesInverseMap?.[openapiYamlPath]) {
138
- copyFileSync(openapiYamlPath, overridesInverseMap[openapiYamlPath]);
139
- }
140
-
141
- console.log(`[${new Date().toISOString()}] Generated client from ${openapiYamlPath} to ${outPath}/`);
142
- } catch (err) {
143
- console.error(`[${new Date().toISOString()}] Error generating client from ${openapiYamlPath}:`, err);
144
- }
145
- }
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env node
2
- import { existsSync } from 'fs';
3
- import { generateConfiguredOpenapiClients, generateOpenapiClient } from './vite-openapi-plugin.js';
4
- if (process.argv[2]) {
5
- if (process.argv[2] === '--help') {
6
- throw new Error('Usage: vf-generate-openapi-client [<openapi-yaml-path> [<openapi-output-path>]]');
7
- }
8
- if (!existsSync(process.argv[2])) {
9
- throw new Error(`OpenAPI YAML file not found: ${process.argv[2]}`);
10
- }
11
- await generateOpenapiClient(process.argv[2], process.argv[3]);
12
- }
13
- else {
14
- generateConfiguredOpenapiClients();
15
- }
@@ -1,19 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { existsSync } from 'fs';
4
-
5
- import { generateConfiguredOpenapiClients, generateOpenapiClient } from './vite-openapi-plugin.js';
6
-
7
- if (process.argv[2]) {
8
- if (process.argv[2] === '--help') {
9
- throw new Error('Usage: vf-generate-openapi-client [<openapi-yaml-path> [<openapi-output-path>]]');
10
- }
11
-
12
- if (!existsSync(process.argv[2])) {
13
- throw new Error(`OpenAPI YAML file not found: ${process.argv[2]}`);
14
- }
15
-
16
- await generateOpenapiClient(process.argv[2], process.argv[3]);
17
- } else {
18
- generateConfiguredOpenapiClients();
19
- }