@kirill.konshin/utils 0.0.14 → 0.0.16
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/.ctirc +2 -1
- package/.turbo/turbo-build.log +18 -13
- package/.turbo/turbo-test.log +76 -23
- package/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/demo/{cache-demo-clean.ts → worker-demo.ts} +1 -1
- package/dist/bootstrap/controls.d.ts +5 -2
- package/dist/bootstrap/controls.d.ts.map +1 -1
- package/dist/bootstrap/controls.js +1 -0
- package/dist/bootstrap/controls.js.map +1 -1
- package/dist/bootstrap/controls.stories.d.ts +9 -0
- package/dist/bootstrap/controls.stories.d.ts.map +1 -0
- package/dist/bootstrap/error.stories.d.ts +8 -0
- package/dist/bootstrap/error.stories.d.ts.map +1 -0
- package/dist/bootstrap/field.stories.d.ts +8 -0
- package/dist/bootstrap/field.stories.d.ts.map +1 -0
- package/dist/bootstrap/globalLoading.stories.d.ts +7 -0
- package/dist/bootstrap/globalLoading.stories.d.ts.map +1 -0
- package/dist/bootstrap/index.js +2 -1
- package/dist/bootstrap/loading.stories.d.ts +9 -0
- package/dist/bootstrap/loading.stories.d.ts.map +1 -0
- package/dist/bootstrap/screen.stories.d.ts +14 -0
- package/dist/bootstrap/screen.stories.d.ts.map +1 -0
- package/dist/bootstrap/toaster.stories.d.ts +8 -0
- package/dist/bootstrap/toaster.stories.d.ts.map +1 -0
- package/dist/bootstrap/useModal.stories.d.ts +9 -0
- package/dist/bootstrap/useModal.stories.d.ts.map +1 -0
- package/dist/core/cache.d.ts +7 -8
- package/dist/core/cache.d.ts.map +1 -1
- package/dist/core/cache.js +7 -4
- package/dist/core/cache.js.map +1 -1
- package/dist/core/cache.test.d.ts +2 -0
- package/dist/core/cache.test.d.ts.map +1 -0
- package/dist/core/files.d.ts +3 -0
- package/dist/core/files.d.ts.map +1 -0
- package/dist/core/files.js +27 -0
- package/dist/core/files.js.map +1 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +14 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/ls.d.ts +6 -0
- package/dist/core/ls.d.ts.map +1 -0
- package/dist/core/ls.js +12 -0
- package/dist/core/ls.js.map +1 -0
- package/dist/core/worker.d.ts +23 -9
- package/dist/core/worker.d.ts.map +1 -1
- package/dist/core/worker.js +93 -90
- package/dist/core/worker.js.map +1 -1
- package/dist/core/worker.test.d.ts +2 -0
- package/dist/core/worker.test.d.ts.map +1 -0
- package/dist/core/workerUtils.d.ts +3 -0
- package/dist/core/workerUtils.d.ts.map +1 -0
- package/dist/core/workerUtils.js +31 -0
- package/dist/core/workerUtils.js.map +1 -0
- package/dist/core/wrpc.d.ts +45 -0
- package/dist/core/wrpc.d.ts.map +1 -0
- package/dist/core/wrpc.fixture.d.ts +29 -0
- package/dist/core/wrpc.fixture.d.ts.map +1 -0
- package/dist/core/wrpc.js +242 -0
- package/dist/core/wrpc.js.map +1 -0
- package/dist/core/wrpc.test.d.ts +1 -0
- package/dist/core/wrpc.test.d.ts.map +1 -0
- package/dist/mui/formControlFieldset.stories.d.ts +7 -0
- package/dist/mui/formControlFieldset.stories.d.ts.map +1 -0
- package/dist/react/form/form.d.ts +7 -6
- package/dist/react/form/form.d.ts.map +1 -1
- package/dist/react/form/form.js +14 -20
- package/dist/react/form/form.js.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +8 -2
- package/dist/react/index.js.map +1 -1
- package/dist/react/keyboard.d.ts +11 -0
- package/dist/react/keyboard.d.ts.map +1 -0
- package/dist/react/keyboard.js +38 -0
- package/dist/react/keyboard.js.map +1 -0
- package/package.json +5 -2
- package/src/bootstrap/controls.stories.tsx +2 -2
- package/src/bootstrap/controls.tsx +3 -3
- package/src/bootstrap/error.stories.tsx +3 -3
- package/src/bootstrap/field.stories.tsx +2 -2
- package/src/bootstrap/globalLoading.stories.tsx +2 -2
- package/src/bootstrap/loading.stories.tsx +2 -2
- package/src/bootstrap/screen.stories.tsx +2 -2
- package/src/bootstrap/toaster.stories.tsx +2 -2
- package/src/bootstrap/useModal.stories.tsx +3 -3
- package/src/core/README.md +91 -0
- package/src/core/cache.ts +12 -12
- package/src/core/files.ts +25 -0
- package/src/core/index.ts +4 -0
- package/src/core/ls.ts +11 -0
- package/src/core/worker.test.ts +197 -0
- package/src/core/worker.ts +106 -93
- package/src/core/workerUtils.ts +32 -0
- package/src/core/wrpc.fixture.ts +72 -0
- package/src/core/wrpc.test.ts +225 -0
- package/src/core/wrpc.ts +359 -0
- package/src/mui/formControlFieldset.stories.tsx +1 -1
- package/src/react/form/form.tsx +53 -33
- package/src/react/index.ts +1 -0
- package/src/react/keyboard.tsx +49 -0
- package/src/tailwind/README.md +8 -0
- package/tsconfig.json +2 -1
- package/vite.config.ts +10 -3
- package/vite.exports.ts +3 -3
- package/demo/cache-demo.ts +0 -343
package/src/react/form/form.tsx
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { typeToFlattenedError, z, SafeParseReturnType, TypeOf } from 'zod';
|
|
2
|
+
import { createContext, useContext, useMemo } from 'react';
|
|
3
|
+
import clsx from 'clsx';
|
|
2
4
|
|
|
3
5
|
const nonEmpty = 'This field cannot be empty';
|
|
4
6
|
|
|
@@ -23,16 +25,25 @@ export type Validation<S extends ZodObject> =
|
|
|
23
25
|
errors?: Errors<S>;
|
|
24
26
|
};
|
|
25
27
|
|
|
26
|
-
export
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
export const FormContext = createContext<{
|
|
29
|
+
schema: ZodObject;
|
|
30
|
+
}>(null as never);
|
|
31
|
+
|
|
32
|
+
export const Form = <S extends ZodObject>({ schema, children }: { schema: S; children: any }) => {
|
|
33
|
+
const value = useMemo(() => ({ schema }), [schema]);
|
|
34
|
+
return <FormContext.Provider value={value}>{children}</FormContext.Provider>;
|
|
35
|
+
};
|
|
29
36
|
|
|
30
|
-
|
|
37
|
+
const getShape = <S extends ZodObject>(schema: S) =>
|
|
38
|
+
(schema as z.ZodObject<any>).shape || (schema as z.ZodEffects<z.ZodObject<any>>).sourceType().shape;
|
|
39
|
+
|
|
40
|
+
export function create<S extends ZodObject>(schema: S) {
|
|
41
|
+
if (!getShape(schema)) {
|
|
31
42
|
throw new Error('Invalid schema: only z.object() or z.object().refine() are supported');
|
|
32
43
|
}
|
|
33
44
|
|
|
34
45
|
function register(name: keyof TypeOf<S>, data?: MaybeTypeOf<S>, errors?: Errors<S>, mui: boolean = false) {
|
|
35
|
-
const field = getShape()[name];
|
|
46
|
+
const field = getShape(schema)[name];
|
|
36
47
|
return {
|
|
37
48
|
name,
|
|
38
49
|
id: name,
|
|
@@ -73,35 +84,44 @@ export function create<S extends ZodObject>(schema: S) {
|
|
|
73
84
|
return { success: true, data };
|
|
74
85
|
}
|
|
75
86
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
{e}
|
|
98
|
-
</Hint>
|
|
99
|
-
))}
|
|
100
|
-
</div>
|
|
101
|
-
);
|
|
102
|
-
}
|
|
87
|
+
return { register, validate, validationError };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function Field<S extends ZodObject>({
|
|
91
|
+
children,
|
|
92
|
+
name,
|
|
93
|
+
errors,
|
|
94
|
+
hint,
|
|
95
|
+
className,
|
|
96
|
+
labelProps,
|
|
97
|
+
...props
|
|
98
|
+
}: {
|
|
99
|
+
children?: any;
|
|
100
|
+
name: keyof TypeOf<S>;
|
|
101
|
+
errors?: Validation<S>['errors'];
|
|
102
|
+
hint?: string;
|
|
103
|
+
className?: string;
|
|
104
|
+
labelProps?: any;
|
|
105
|
+
}) {
|
|
106
|
+
const { schema } = useContext(FormContext);
|
|
107
|
+
const { description } = getShape(schema)[name];
|
|
103
108
|
|
|
104
|
-
return
|
|
109
|
+
return (
|
|
110
|
+
<div {...props} className={clsx('form-row', className)}>
|
|
111
|
+
{description && (
|
|
112
|
+
<label {...labelProps} htmlFor={name}>
|
|
113
|
+
{description}
|
|
114
|
+
</label>
|
|
115
|
+
)}
|
|
116
|
+
{children}
|
|
117
|
+
{hint && <Hint>{hint}</Hint>}
|
|
118
|
+
{errors?.[name]?.map((e: string) => (
|
|
119
|
+
<Hint error key={e}>
|
|
120
|
+
{e}
|
|
121
|
+
</Hint>
|
|
122
|
+
))}
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
105
125
|
}
|
|
106
126
|
|
|
107
127
|
export function Hint({ children, error }: { children: any; error?: boolean }) {
|
package/src/react/index.ts
CHANGED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, createContext, useMemo, useState, Dispatch, SetStateAction, useContext } from 'react';
|
|
4
|
+
|
|
5
|
+
const isCtrlOrMeta = (e: KeyboardEvent) => e.metaKey || e.ctrlKey;
|
|
6
|
+
|
|
7
|
+
const EVENT = 'keydown';
|
|
8
|
+
|
|
9
|
+
export type Hotkeys = Record<KeyboardEvent['code'], (e: KeyboardEvent) => void>;
|
|
10
|
+
|
|
11
|
+
export const HotkeysContext = createContext<{
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
setEnabled: Dispatch<SetStateAction<boolean>>;
|
|
14
|
+
}>(null as never);
|
|
15
|
+
|
|
16
|
+
export const HotkeysProvider = ({ children }) => {
|
|
17
|
+
const [enabled, setEnabled] = useState(true);
|
|
18
|
+
|
|
19
|
+
const control = useMemo(() => ({ enabled, setEnabled }), [enabled, setEnabled]);
|
|
20
|
+
|
|
21
|
+
return <HotkeysContext.Provider value={control}>{children}</HotkeysContext.Provider>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const useHotkeys = (hotkeys: Hotkeys) => {
|
|
25
|
+
const { enabled } = useContext(HotkeysContext);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (typeof document === 'undefined' || !enabled) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const listeners = (e: KeyboardEvent) => {
|
|
33
|
+
if (!isCtrlOrMeta(e)) return;
|
|
34
|
+
|
|
35
|
+
for (const [code, callback] of Object.entries(hotkeys)) {
|
|
36
|
+
if (e.code === code) {
|
|
37
|
+
callback(e);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
window.addEventListener(EVENT, listeners, true);
|
|
44
|
+
|
|
45
|
+
return () => {
|
|
46
|
+
window.removeEventListener(EVENT, listeners);
|
|
47
|
+
};
|
|
48
|
+
}, [hotkeys, enabled]);
|
|
49
|
+
};
|
package/tsconfig.json
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"incremental": false,
|
|
10
10
|
"allowJs": true,
|
|
11
11
|
/* Build-specific options */
|
|
12
|
+
"module": "esnext",
|
|
12
13
|
"moduleResolution": "bundler",
|
|
13
14
|
"allowImportingTsExtensions": true,
|
|
14
15
|
"isolatedModules": true,
|
|
@@ -17,5 +18,5 @@
|
|
|
17
18
|
"jsx": "react-jsx"
|
|
18
19
|
},
|
|
19
20
|
"include": ["src"],
|
|
20
|
-
"exclude": [
|
|
21
|
+
"exclude": []
|
|
21
22
|
}
|
package/vite.config.ts
CHANGED
|
@@ -7,12 +7,14 @@ import { fixExports, formats, entry, external, distDir, generateIndex } from './
|
|
|
7
7
|
// https://rbardini.com/how-to-build-ts-library-with-vite/
|
|
8
8
|
// https://dev.to/receter/how-to-create-a-react-component-library-using-vites-library-mode-4lma
|
|
9
9
|
|
|
10
|
+
const isWatch = process.argv.includes('--watch'); // https://github.com/vitejs/vite/discussions/7565#discussioncomment-2939256
|
|
11
|
+
|
|
10
12
|
export default defineConfig({
|
|
11
13
|
build: {
|
|
12
14
|
ssr: true,
|
|
13
15
|
sourcemap: true,
|
|
14
16
|
outDir: distDir,
|
|
15
|
-
emptyOutDir:
|
|
17
|
+
emptyOutDir: !isWatch,
|
|
16
18
|
// target: 'esnext',
|
|
17
19
|
lib: {
|
|
18
20
|
entry,
|
|
@@ -39,11 +41,16 @@ export default defineConfig({
|
|
|
39
41
|
{
|
|
40
42
|
name: 'Generate Index & Exports',
|
|
41
43
|
async buildStart() {
|
|
42
|
-
await generateIndex();
|
|
44
|
+
if (!isWatch) await generateIndex();
|
|
43
45
|
},
|
|
44
46
|
async closeBundle() {
|
|
45
|
-
await fixExports();
|
|
47
|
+
if (!isWatch) await fixExports();
|
|
46
48
|
},
|
|
47
49
|
},
|
|
48
50
|
],
|
|
51
|
+
server: {
|
|
52
|
+
watch: {
|
|
53
|
+
ignored: ['**/package.json', '**/index.ts'],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
49
56
|
});
|
package/vite.exports.ts
CHANGED
|
@@ -21,8 +21,8 @@ export const formats = {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
export const entryGlob = 'src/*/index.ts';
|
|
24
|
-
export const excludeGlob = 'src/**/*.{stories,test}.{ts,tsx}';
|
|
25
|
-
export const includeGlob = 'src/**/!(*.stories|*.test).{ts,tsx}';
|
|
24
|
+
export const excludeGlob = 'src/**/*.{stories,test,fixture}.{ts,tsx}';
|
|
25
|
+
export const includeGlob = 'src/**/!(*.stories|*.test,fixture).{ts,tsx}';
|
|
26
26
|
export const foldersGlob = 'src/*/';
|
|
27
27
|
|
|
28
28
|
export const external = [
|
|
@@ -104,7 +104,7 @@ export async function fixExports() {
|
|
|
104
104
|
|
|
105
105
|
// Write
|
|
106
106
|
|
|
107
|
-
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
107
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
108
108
|
|
|
109
109
|
console.log('Updated package.json with exports');
|
|
110
110
|
// console.log(pkg);
|
package/demo/cache-demo.ts
DELETED
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
/* eslint-disable */
|
|
2
|
-
|
|
3
|
-
import { z, ZodType } from 'zod';
|
|
4
|
-
|
|
5
|
-
export type EventType = 'request' | 'response';
|
|
6
|
-
|
|
7
|
-
type MethodReturn<R, M extends keyof R> = R[M] extends (...args: any[]) => any ? Awaited<ReturnType<R[M]>> : never;
|
|
8
|
-
|
|
9
|
-
type MethodArg<R, M extends keyof R> = R[M] extends (context: any, input: infer I) => any ? I : never;
|
|
10
|
-
|
|
11
|
-
type Data<R, M extends keyof R, T extends EventType> = T extends 'response' ? MethodReturn<R, M> : MethodArg<R, M>;
|
|
12
|
-
|
|
13
|
-
// can only be used to create actual contexts, carries the context data
|
|
14
|
-
class ContextSeed<R, M extends keyof R, T extends EventType> {
|
|
15
|
-
constructor(
|
|
16
|
-
public responders: R,
|
|
17
|
-
public message: M,
|
|
18
|
-
public type: T,
|
|
19
|
-
public id: string,
|
|
20
|
-
) {}
|
|
21
|
-
|
|
22
|
-
req<M extends keyof R>(message: M) {
|
|
23
|
-
return new RequestContext<R, M>(this.responders, message, 'request', this.id);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
res<M extends keyof R>(message: M) {
|
|
27
|
-
return new ResponseContext<R, M>(this.responders, message, 'response', this.id);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// base class with basic non-direction specific methods
|
|
32
|
-
class Context<R, M extends keyof R, T extends EventType> extends ContextSeed<R, M, T> {
|
|
33
|
-
send(data: Data<R, M, T>): this {
|
|
34
|
-
return this;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
listen(
|
|
38
|
-
callback: (
|
|
39
|
-
data: Data<R, M, T>,
|
|
40
|
-
self: this,
|
|
41
|
-
event: MessageEvent<{ data: Data<R, M, T>; context: Pick<typeof this, 'message' | 'type' | 'id'> }>,
|
|
42
|
-
) => void,
|
|
43
|
-
): () => void {
|
|
44
|
-
return () => {};
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
class ResponseContext<R, M extends keyof R> extends Context<R, M, 'response'> {
|
|
49
|
-
send(data: MethodReturn<R, M>): this {
|
|
50
|
-
return this;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
class RequestContext<R, M extends keyof R> extends Context<R, M, 'request'> {
|
|
55
|
-
send(data: MethodArg<R, M>): this {
|
|
56
|
-
return this;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
fetch(data: MethodArg<R, M>, callback?: (context: RequestContext<R, M>) => void): Promise<MethodReturn<R, M>> {
|
|
60
|
-
return Promise.resolve(null as any);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
class Dialog<R> {
|
|
65
|
-
constructor(public responders: R) {}
|
|
66
|
-
|
|
67
|
-
req<M extends keyof R>(message: M) {
|
|
68
|
-
return new RequestContext<R, M>(this.responders, message, 'request', crypto.randomUUID());
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
res<M extends keyof R>(message: M) {
|
|
72
|
-
return new ResponseContext<R, M>(this.responders, message, 'response', crypto.randomUUID());
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
77
|
-
|
|
78
|
-
// Worker
|
|
79
|
-
|
|
80
|
-
class RespondersBase<R> {
|
|
81
|
-
create<
|
|
82
|
-
M extends keyof R,
|
|
83
|
-
S extends ZodType<any, any, any>,
|
|
84
|
-
F extends (context: Context<R, M, 'response'>, input: z.infer<S>) => any,
|
|
85
|
-
>(
|
|
86
|
-
message: M, // <--- this is needed just to tell TS what message this is
|
|
87
|
-
schema: S,
|
|
88
|
-
responder: F,
|
|
89
|
-
): F {
|
|
90
|
-
return ((context, input) => {
|
|
91
|
-
schema.parse(input);
|
|
92
|
-
return responder(context, input);
|
|
93
|
-
}) as any;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Works perfectly
|
|
99
|
-
*/
|
|
100
|
-
class Responders extends RespondersBase<Responders> {
|
|
101
|
-
encode = this.create(
|
|
102
|
-
'encode',
|
|
103
|
-
z.object({
|
|
104
|
-
file: z.instanceof(File),
|
|
105
|
-
}),
|
|
106
|
-
async (context, { file }) => {
|
|
107
|
-
// send extra data within same message
|
|
108
|
-
context.listen((data, ctx) => {
|
|
109
|
-
data.file;
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
// send extra data within same message
|
|
113
|
-
context.send({ bitmap: await createImageBitmap(new File([], 'test')) }); // ✅ Valid
|
|
114
|
-
context.send({ junk: 'xxx' }); // ❌ Bad & should be red
|
|
115
|
-
|
|
116
|
-
// send data within different message
|
|
117
|
-
context.res('progress').send({ progress: 0 }); // ✅ Valid
|
|
118
|
-
context.res('progress').send({ junk: 'xxx' }); // ❌ Bad & should be red
|
|
119
|
-
|
|
120
|
-
// request data
|
|
121
|
-
context.req('progress').send({ total: 0 }); // ✅ Valid
|
|
122
|
-
context.req('progress').send({ junk: 'xxx' }); // ❌ Bad & should be red
|
|
123
|
-
|
|
124
|
-
// bad names
|
|
125
|
-
context.req('junk'); // ❌ Bad & should be red
|
|
126
|
-
context.res('junk'); // ❌ Bad & should be red
|
|
127
|
-
|
|
128
|
-
return { bitmap: await createImageBitmap(file) };
|
|
129
|
-
},
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
progress = this.create(
|
|
133
|
-
'progress',
|
|
134
|
-
z.object({
|
|
135
|
-
total: z.number(),
|
|
136
|
-
}),
|
|
137
|
-
(context, { total }: { total: number }) => {
|
|
138
|
-
return { progress: total / 2 };
|
|
139
|
-
},
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Main
|
|
144
|
-
|
|
145
|
-
// import type { Responders } from './worker';
|
|
146
|
-
|
|
147
|
-
const dialog = new Dialog({} as Responders);
|
|
148
|
-
|
|
149
|
-
dialog.res('progress').send({ progress: 0 }); // ✅ Valid
|
|
150
|
-
|
|
151
|
-
dialog
|
|
152
|
-
.req('encode')
|
|
153
|
-
.fetch({ file: new File([], 'test') }, (context) => {
|
|
154
|
-
context.res('progress').listen((data, ctx) => {
|
|
155
|
-
// get periodical updates while operation is running
|
|
156
|
-
console.log(data.progress); // ✅ Valid
|
|
157
|
-
console.log(data.junk); // ❌ Bad & should be red
|
|
158
|
-
|
|
159
|
-
// could be an abort message
|
|
160
|
-
ctx.res('progress').send({ progress: 0 }); // ✅ Valid
|
|
161
|
-
ctx.req('progress').send({ total: 0 }); // ✅ Valid
|
|
162
|
-
ctx.req('progress').send({ junk: 'xxx' }); // ❌ Bad & should be red
|
|
163
|
-
});
|
|
164
|
-
})
|
|
165
|
-
.then((res) => {
|
|
166
|
-
console.log(res.bitmap);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
dialog.req('junk'); // ❌ Bad & should be red
|
|
170
|
-
|
|
171
|
-
dialog.req('encode').fetch({ junk: new File([], 'test') }); // ❌ Bad & should be red
|
|
172
|
-
|
|
173
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
174
|
-
|
|
175
|
-
type ResSchema = Record<string, [ZodType, ZodType]>;
|
|
176
|
-
type ResSchemaFinal<S extends ResSchema> = { [K in keyof S]: [K, S[K][0], S[K][1]] };
|
|
177
|
-
|
|
178
|
-
const createSchema = <S extends ResSchema>(s: S): ResSchemaFinal<S> => {
|
|
179
|
-
return null as any;
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* CREATE function and object
|
|
184
|
-
*
|
|
185
|
-
* Validation works, but navigation does not, plus it's not valid code to use responders2 before creation
|
|
186
|
-
*/
|
|
187
|
-
function create<
|
|
188
|
-
S extends ResSchemaFinal<ResSchema>,
|
|
189
|
-
M extends keyof S,
|
|
190
|
-
F extends (key: S[M][0], input: S[M][1]) => S[M][2],
|
|
191
|
-
>(schema: S, message: M, responder: F): F {
|
|
192
|
-
// context: Context<S, M, 'response'>
|
|
193
|
-
return responder as any;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const schema = createSchema({
|
|
197
|
-
encode: [
|
|
198
|
-
z.object({
|
|
199
|
-
file: z.instanceof(File),
|
|
200
|
-
}),
|
|
201
|
-
z.object({
|
|
202
|
-
bitmap: z.instanceof(ImageBitmap),
|
|
203
|
-
}),
|
|
204
|
-
],
|
|
205
|
-
preview: [
|
|
206
|
-
z.object({
|
|
207
|
-
total: z.number(),
|
|
208
|
-
}),
|
|
209
|
-
z.object({
|
|
210
|
-
progress: z.number(),
|
|
211
|
-
}),
|
|
212
|
-
],
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
const responders2 = {
|
|
216
|
-
encode: create(schema, 'encode', async (context, { file }: { file: File }) => {
|
|
217
|
-
// send extra data within same message
|
|
218
|
-
context.listen((data, ctx) => {
|
|
219
|
-
data.file;
|
|
220
|
-
data.bitmap;
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
// send extra data within same message
|
|
224
|
-
context.send({ bitmap: await createImageBitmap(new File([], 'test')) }); // ✅ Valid
|
|
225
|
-
context.send({ junk: 'xxx' }); // ❌ Bad & should be red
|
|
226
|
-
|
|
227
|
-
// send data within different message
|
|
228
|
-
context.res('progress').send({ progress: 0 }); // ✅ Valid
|
|
229
|
-
context.res('progress').send({ junk: 'xxx' }); // ❌ Bad & should be red
|
|
230
|
-
|
|
231
|
-
// request data
|
|
232
|
-
context.req('progress').send({ total: 0 }); // ✅ Valid
|
|
233
|
-
context.req('progress').send({ junk: 'xxx' }); // ❌ Bad & should be red
|
|
234
|
-
|
|
235
|
-
// bad names
|
|
236
|
-
context.req('junk'); // ❌ Bad & should be red
|
|
237
|
-
context.res('junk'); // ❌ Bad & should be red
|
|
238
|
-
|
|
239
|
-
return { bitmap: await createImageBitmap(file) };
|
|
240
|
-
}),
|
|
241
|
-
progress: create(schema, 'progress', (context, { total }) => {
|
|
242
|
-
return { progress: total / 2 };
|
|
243
|
-
}),
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
247
|
-
|
|
248
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
249
|
-
|
|
250
|
-
type RespondersType<R extends Record<string, (...args: any[]) => any>> = {
|
|
251
|
-
[K in keyof R]: (context: Context<R, K, 'response'>, input: Parameters<R[K]>[0]) => ReturnType<R[K]>;
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* TYPE DEF and object
|
|
256
|
-
* Works fine but navigation points to type and not the function
|
|
257
|
-
*/
|
|
258
|
-
const responders: RespondersType<{
|
|
259
|
-
encode: (input: { file: File }) => Promise<{ bitmap: ImageBitmap }>;
|
|
260
|
-
progress: (input: { total: number }) => { progress: number };
|
|
261
|
-
}> = {
|
|
262
|
-
async encode(context, { file }) {
|
|
263
|
-
// send extra data within same message
|
|
264
|
-
context.listen((data, ctx) => {
|
|
265
|
-
data.file;
|
|
266
|
-
data.bitmap;
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
// send extra data within same message
|
|
270
|
-
context.send({ bitmap: await createImageBitmap(new File([], 'test')) }); // ✅ Valid
|
|
271
|
-
context.send({ junk: 'xxx' }); // ❌ Bad & should be red
|
|
272
|
-
|
|
273
|
-
// send data within different message
|
|
274
|
-
context.res('progress').send({ progress: 0 }); // ✅ Valid
|
|
275
|
-
context.res('progress').send({ junk: 'xxx' }); // ❌ Bad & should be red
|
|
276
|
-
|
|
277
|
-
// request data
|
|
278
|
-
context.req('progress').send({ total: 0 }); // ✅ Valid
|
|
279
|
-
context.req('progress').send({ junk: 'xxx' }); // ❌ Bad & should be red
|
|
280
|
-
|
|
281
|
-
// bad names
|
|
282
|
-
context.req('junk'); // ❌ Bad & should be red
|
|
283
|
-
context.res('junk'); // ❌ Bad & should be red
|
|
284
|
-
|
|
285
|
-
return { bitmap: await createImageBitmap(file) };
|
|
286
|
-
},
|
|
287
|
-
|
|
288
|
-
progress(context, { total }) {
|
|
289
|
-
return { progress: total / 2 };
|
|
290
|
-
},
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
294
|
-
|
|
295
|
-
export type RespondersMap<R extends Record<string, (...args: any[]) => any>> = {
|
|
296
|
-
[K in keyof R]: (key: K, input: Parameters<R[K]>[1]) => Awaited<ReturnType<R[K]>>;
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Broken completely
|
|
301
|
-
*/
|
|
302
|
-
const createObject = <R extends Record<string, (...args: any[]) => any>>(
|
|
303
|
-
creator: (
|
|
304
|
-
create: <K extends keyof R>(key: K, responder: (context: Context<R, K, any>, input: any) => any) => R[K],
|
|
305
|
-
) => R,
|
|
306
|
-
): {
|
|
307
|
-
[K in keyof R]: (key: K, input: Parameters<R[K]>[1]) => Awaited<ReturnType<R[K]>>;
|
|
308
|
-
} => {
|
|
309
|
-
const obj = {} as any;
|
|
310
|
-
|
|
311
|
-
const create = (key: any, fn: any) => {
|
|
312
|
-
obj[key] = function (key, input) {
|
|
313
|
-
return fn.call(this, key, input);
|
|
314
|
-
};
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
creator(create as any);
|
|
318
|
-
|
|
319
|
-
return obj;
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
const obj = createObject((create) => {
|
|
323
|
-
return {
|
|
324
|
-
encode: create('encode', (c, file: File) => {
|
|
325
|
-
c.res('progress').send({ progress: 0 });
|
|
326
|
-
return { bitmap: createImageBitmap(file) };
|
|
327
|
-
}),
|
|
328
|
-
progress: create('progress', (c, total: number) => {
|
|
329
|
-
return { progress: total / 2 };
|
|
330
|
-
}),
|
|
331
|
-
};
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
// Usage example
|
|
335
|
-
obj.encode('xxxx', new File([], 'test')).then((result) => {
|
|
336
|
-
console.log(result.bitmap); // ImageBitmap
|
|
337
|
-
console.log(result.bitmapxxx); // ImageBitmap
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
obj.progress('xxxx', 'xxx').then((result) => {
|
|
341
|
-
console.log(result.progress); // 50
|
|
342
|
-
console.log(result.progressxxx); // 50
|
|
343
|
-
});
|