@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.
Files changed (107) hide show
  1. package/.ctirc +2 -1
  2. package/.turbo/turbo-build.log +18 -13
  3. package/.turbo/turbo-test.log +76 -23
  4. package/CHANGELOG.md +12 -0
  5. package/README.md +1 -1
  6. package/demo/{cache-demo-clean.ts → worker-demo.ts} +1 -1
  7. package/dist/bootstrap/controls.d.ts +5 -2
  8. package/dist/bootstrap/controls.d.ts.map +1 -1
  9. package/dist/bootstrap/controls.js +1 -0
  10. package/dist/bootstrap/controls.js.map +1 -1
  11. package/dist/bootstrap/controls.stories.d.ts +9 -0
  12. package/dist/bootstrap/controls.stories.d.ts.map +1 -0
  13. package/dist/bootstrap/error.stories.d.ts +8 -0
  14. package/dist/bootstrap/error.stories.d.ts.map +1 -0
  15. package/dist/bootstrap/field.stories.d.ts +8 -0
  16. package/dist/bootstrap/field.stories.d.ts.map +1 -0
  17. package/dist/bootstrap/globalLoading.stories.d.ts +7 -0
  18. package/dist/bootstrap/globalLoading.stories.d.ts.map +1 -0
  19. package/dist/bootstrap/index.js +2 -1
  20. package/dist/bootstrap/loading.stories.d.ts +9 -0
  21. package/dist/bootstrap/loading.stories.d.ts.map +1 -0
  22. package/dist/bootstrap/screen.stories.d.ts +14 -0
  23. package/dist/bootstrap/screen.stories.d.ts.map +1 -0
  24. package/dist/bootstrap/toaster.stories.d.ts +8 -0
  25. package/dist/bootstrap/toaster.stories.d.ts.map +1 -0
  26. package/dist/bootstrap/useModal.stories.d.ts +9 -0
  27. package/dist/bootstrap/useModal.stories.d.ts.map +1 -0
  28. package/dist/core/cache.d.ts +7 -8
  29. package/dist/core/cache.d.ts.map +1 -1
  30. package/dist/core/cache.js +7 -4
  31. package/dist/core/cache.js.map +1 -1
  32. package/dist/core/cache.test.d.ts +2 -0
  33. package/dist/core/cache.test.d.ts.map +1 -0
  34. package/dist/core/files.d.ts +3 -0
  35. package/dist/core/files.d.ts.map +1 -0
  36. package/dist/core/files.js +27 -0
  37. package/dist/core/files.js.map +1 -0
  38. package/dist/core/index.d.ts +4 -0
  39. package/dist/core/index.d.ts.map +1 -1
  40. package/dist/core/index.js +14 -2
  41. package/dist/core/index.js.map +1 -1
  42. package/dist/core/ls.d.ts +6 -0
  43. package/dist/core/ls.d.ts.map +1 -0
  44. package/dist/core/ls.js +12 -0
  45. package/dist/core/ls.js.map +1 -0
  46. package/dist/core/worker.d.ts +23 -9
  47. package/dist/core/worker.d.ts.map +1 -1
  48. package/dist/core/worker.js +93 -90
  49. package/dist/core/worker.js.map +1 -1
  50. package/dist/core/worker.test.d.ts +2 -0
  51. package/dist/core/worker.test.d.ts.map +1 -0
  52. package/dist/core/workerUtils.d.ts +3 -0
  53. package/dist/core/workerUtils.d.ts.map +1 -0
  54. package/dist/core/workerUtils.js +31 -0
  55. package/dist/core/workerUtils.js.map +1 -0
  56. package/dist/core/wrpc.d.ts +45 -0
  57. package/dist/core/wrpc.d.ts.map +1 -0
  58. package/dist/core/wrpc.fixture.d.ts +29 -0
  59. package/dist/core/wrpc.fixture.d.ts.map +1 -0
  60. package/dist/core/wrpc.js +242 -0
  61. package/dist/core/wrpc.js.map +1 -0
  62. package/dist/core/wrpc.test.d.ts +1 -0
  63. package/dist/core/wrpc.test.d.ts.map +1 -0
  64. package/dist/mui/formControlFieldset.stories.d.ts +7 -0
  65. package/dist/mui/formControlFieldset.stories.d.ts.map +1 -0
  66. package/dist/react/form/form.d.ts +7 -6
  67. package/dist/react/form/form.d.ts.map +1 -1
  68. package/dist/react/form/form.js +14 -20
  69. package/dist/react/form/form.js.map +1 -1
  70. package/dist/react/index.d.ts +1 -0
  71. package/dist/react/index.d.ts.map +1 -1
  72. package/dist/react/index.js +8 -2
  73. package/dist/react/index.js.map +1 -1
  74. package/dist/react/keyboard.d.ts +11 -0
  75. package/dist/react/keyboard.d.ts.map +1 -0
  76. package/dist/react/keyboard.js +38 -0
  77. package/dist/react/keyboard.js.map +1 -0
  78. package/package.json +5 -2
  79. package/src/bootstrap/controls.stories.tsx +2 -2
  80. package/src/bootstrap/controls.tsx +3 -3
  81. package/src/bootstrap/error.stories.tsx +3 -3
  82. package/src/bootstrap/field.stories.tsx +2 -2
  83. package/src/bootstrap/globalLoading.stories.tsx +2 -2
  84. package/src/bootstrap/loading.stories.tsx +2 -2
  85. package/src/bootstrap/screen.stories.tsx +2 -2
  86. package/src/bootstrap/toaster.stories.tsx +2 -2
  87. package/src/bootstrap/useModal.stories.tsx +3 -3
  88. package/src/core/README.md +91 -0
  89. package/src/core/cache.ts +12 -12
  90. package/src/core/files.ts +25 -0
  91. package/src/core/index.ts +4 -0
  92. package/src/core/ls.ts +11 -0
  93. package/src/core/worker.test.ts +197 -0
  94. package/src/core/worker.ts +106 -93
  95. package/src/core/workerUtils.ts +32 -0
  96. package/src/core/wrpc.fixture.ts +72 -0
  97. package/src/core/wrpc.test.ts +225 -0
  98. package/src/core/wrpc.ts +359 -0
  99. package/src/mui/formControlFieldset.stories.tsx +1 -1
  100. package/src/react/form/form.tsx +53 -33
  101. package/src/react/index.ts +1 -0
  102. package/src/react/keyboard.tsx +49 -0
  103. package/src/tailwind/README.md +8 -0
  104. package/tsconfig.json +2 -1
  105. package/vite.config.ts +10 -3
  106. package/vite.exports.ts +3 -3
  107. package/demo/cache-demo.ts +0 -343
@@ -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 function create<S extends ZodObject>(schema: S) {
27
- const getShape = () =>
28
- (schema as z.ZodObject<any>).shape || (schema as z.ZodEffects<z.ZodObject<any>>).sourceType().shape;
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
- if (!getShape()) {
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
- //FIXME Context?
77
- function Field({
78
- children,
79
- name,
80
- errors,
81
- hint,
82
- }: {
83
- children?: any;
84
- name: string;
85
- errors?: Validation<S>['errors'];
86
- hint?: string;
87
- }) {
88
- const { description } = getShape()[name];
89
-
90
- return (
91
- <div className="form-row">
92
- {description && <label htmlFor={name}>{description}</label>}
93
- {children}
94
- {hint && <Hint>{hint}</Hint>}
95
- {errors?.[name]?.map((e: string) => (
96
- <Hint error key={e}>
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 { register, validate, Field, validationError };
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 }) {
@@ -1,4 +1,5 @@
1
1
  export * from './form';
2
2
  export * from './apiCall';
3
+ export * from './keyboard';
3
4
  export * from './useFetch';
4
5
  export * from './useFetcher';
@@ -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
+ };
@@ -0,0 +1,8 @@
1
+ # Usage
2
+
3
+ https://tailwindcss.com/docs/detecting-classes-in-source-files#explicitly-registering-sources
4
+
5
+ ```css
6
+ @import 'tailwindcss';
7
+ @source "../node_modules/@kirill.konshin/utils/tailwind";
8
+ ```
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": ["**/*.test.ts", "**/*.stories.ts", "**/*.stories.tsx"]
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: true,
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);
@@ -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
- });