@nan0web/ui 1.0.3 → 1.1.0
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 +18 -20
- package/package.json +15 -16
- package/src/App/Command/DepsCommand.js +29 -0
- package/src/App/Core/UI.js +1 -50
- package/src/App/Core/Widget.js +4 -6
- package/src/App/User/Command/Message.js +2 -43
- package/src/App/User/Command/index.js +2 -8
- package/src/App/User/UserApp.js +31 -11
- package/src/README.md.js +33 -33
- package/src/StdIn.js +12 -13
- package/src/View/View.js +7 -7
- package/src/core/Form/Form.js +8 -6
- package/src/core/Form/Input.js +13 -13
- package/src/core/Form/Message.js +6 -5
- package/src/core/InputAdapter.js +2 -2
- package/src/core/Message/Message.js +109 -19
- package/src/core/Message/OutputMessage.js +7 -7
- package/src/core/Message/index.js +3 -4
- package/src/core/Stream.js +11 -10
- package/src/core/UiAdapter.js +216 -0
- package/src/core/index.js +10 -7
- package/src/index.js +4 -4
- package/types/App/Command/DepsCommand.d.ts +16 -0
- package/types/App/Core/UI.d.ts +2 -13
- package/types/App/Core/Widget.d.ts +6 -7
- package/types/App/User/Command/Message.d.ts +2 -29
- package/types/App/User/Command/index.d.ts +2 -4
- package/types/App/User/UserApp.d.ts +14 -7
- package/types/StdIn.d.ts +13 -13
- package/types/View/View.d.ts +6 -6
- package/types/core/Form/Form.d.ts +2 -5
- package/types/core/Form/Input.d.ts +4 -4
- package/types/core/Form/Message.d.ts +5 -10
- package/types/core/Intent.d.ts +91 -0
- package/types/core/Message/Message.d.ts +58 -15
- package/types/core/Message/OutputMessage.d.ts +3 -3
- package/types/core/Message/index.d.ts +3 -4
- package/types/core/Stream.d.ts +4 -4
- package/types/core/UiAdapter.d.ts +122 -0
- package/types/core/index.d.ts +6 -5
- package/types/index.d.ts +4 -4
- package/src/App/User/Command/Options.js +0 -48
- package/src/core/Message/InputMessage.js +0 -119
package/src/View/View.js
CHANGED
|
@@ -3,13 +3,13 @@ import Frame, { FrameRenderMethod } from "../Frame/Frame.js"
|
|
|
3
3
|
import Locale from "../Locale.js"
|
|
4
4
|
import StdOut from "../StdOut.js"
|
|
5
5
|
import StdIn from "../StdIn.js"
|
|
6
|
-
import InputMessage from "../core/Message/InputMessage.js"
|
|
7
6
|
import RenderOptions from "./RenderOptions.js"
|
|
7
|
+
import UiMessage from "../core/Message/Message.js"
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* @typedef {Object} ComponentFn
|
|
11
11
|
* @property {string} name
|
|
12
|
-
* @property {(input:
|
|
12
|
+
* @property {(input: UiMessage) => Promise<any>} ask
|
|
13
13
|
* @property {Function} bind
|
|
14
14
|
*/
|
|
15
15
|
|
|
@@ -256,8 +256,8 @@ export default class View {
|
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
/**
|
|
259
|
-
* @param {
|
|
260
|
-
* @returns {Promise<
|
|
259
|
+
* @param {UiMessage} input
|
|
260
|
+
* @returns {Promise<UiMessage | null>}
|
|
261
261
|
*/
|
|
262
262
|
async ask(input) {
|
|
263
263
|
const name = input.constructor.name.replace(/Input$/, "")
|
|
@@ -268,9 +268,9 @@ export default class View {
|
|
|
268
268
|
let result = null
|
|
269
269
|
do {
|
|
270
270
|
const answer = await this.stdin.read()
|
|
271
|
-
result = /** @type {typeof
|
|
272
|
-
} while (!result.isValid && !result.
|
|
273
|
-
return result.
|
|
271
|
+
result = /** @type {typeof UiMessage} */ (input.constructor).from(answer)
|
|
272
|
+
} while (!result.isValid && !result.head.cancelled)
|
|
273
|
+
return result.head.cancelled ? null : result
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
/**
|
package/src/core/Form/Form.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import Message from "@nan0web/co"
|
|
1
2
|
import FormMessage from "./Message.js"
|
|
2
3
|
import FormInput from "./Input.js"
|
|
3
|
-
import Message from "@nan0web/co"
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Abstract form for data entry.
|
|
@@ -112,10 +112,10 @@ export default class UIForm extends FormMessage {
|
|
|
112
112
|
/**
|
|
113
113
|
* Validates the entire form.
|
|
114
114
|
*
|
|
115
|
-
* @returns {
|
|
115
|
+
* @returns {Map<string, string>} Map of validation errors, empty if valid.
|
|
116
116
|
*/
|
|
117
117
|
validate() {
|
|
118
|
-
const errors =
|
|
118
|
+
const errors = new Map()
|
|
119
119
|
let isValid = true
|
|
120
120
|
|
|
121
121
|
this.fields.forEach((field) => {
|
|
@@ -123,7 +123,7 @@ export default class UIForm extends FormMessage {
|
|
|
123
123
|
|
|
124
124
|
// Required validation based on field definition or schema
|
|
125
125
|
if (field.required && (fieldValue === '' || fieldValue === null || fieldValue === undefined)) {
|
|
126
|
-
errors
|
|
126
|
+
errors.set(field.name, 'This field is required')
|
|
127
127
|
isValid = false
|
|
128
128
|
return
|
|
129
129
|
}
|
|
@@ -132,12 +132,14 @@ export default class UIForm extends FormMessage {
|
|
|
132
132
|
const { isValid: fieldValid, errors: fieldErrors } = this.validateField(field.name, fieldValue)
|
|
133
133
|
|
|
134
134
|
if (!fieldValid) {
|
|
135
|
-
Object.
|
|
135
|
+
for (const [key, err] of Object.entries(fieldErrors)) {
|
|
136
|
+
errors.set(key, err)
|
|
137
|
+
}
|
|
136
138
|
isValid = false
|
|
137
139
|
}
|
|
138
140
|
})
|
|
139
141
|
|
|
140
|
-
return
|
|
142
|
+
return errors
|
|
141
143
|
}
|
|
142
144
|
|
|
143
145
|
/**
|
package/src/core/Form/Input.js
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
* @property {string} type - Input type (text, email, number, select, etc.).
|
|
17
17
|
* @property {boolean} required - Whether the field is required.
|
|
18
18
|
* @property {string} placeholder - Placeholder text.
|
|
19
|
-
* @property {
|
|
20
|
-
* @property {Function
|
|
19
|
+
* @property {InputOptions} options - Select options (if type is 'select').
|
|
20
|
+
* @property {Function} validation - Custom validation function.
|
|
21
21
|
* @property {*} defaultValue - Default value.
|
|
22
22
|
*/
|
|
23
23
|
export default class FormInput {
|
|
@@ -27,7 +27,7 @@ export default class FormInput {
|
|
|
27
27
|
/** @type {boolean} */ required = false
|
|
28
28
|
/** @type {string} */ placeholder = ''
|
|
29
29
|
/** @type {InputOptions} */ options = []
|
|
30
|
-
/** @type {Function
|
|
30
|
+
/** @type {Function} */ validation = () => true
|
|
31
31
|
/** @type {*} */ defaultValue = null
|
|
32
32
|
|
|
33
33
|
/**
|
|
@@ -39,7 +39,7 @@ export default class FormInput {
|
|
|
39
39
|
NUMBER: 'number',
|
|
40
40
|
SELECT: 'select',
|
|
41
41
|
CHECKBOX: 'checkbox',
|
|
42
|
-
TEXTAREA: 'textarea'
|
|
42
|
+
TEXTAREA: 'textarea',
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
@@ -52,7 +52,7 @@ export default class FormInput {
|
|
|
52
52
|
* @param {boolean} [props.required=false] - Is required.
|
|
53
53
|
* @param {string} [props.placeholder=''] - Placeholder.
|
|
54
54
|
* @param {InputOptions} [props.options=[]] - Select options or async function to retrieve data with the search and page.
|
|
55
|
-
* @param {Function} [props.validation
|
|
55
|
+
* @param {Function} [props.validation] - Custom validation.
|
|
56
56
|
* @param {*} [props.defaultValue=null] - Default value.
|
|
57
57
|
*/
|
|
58
58
|
constructor(props) {
|
|
@@ -64,7 +64,7 @@ export default class FormInput {
|
|
|
64
64
|
placeholder = this.placeholder,
|
|
65
65
|
options = [],
|
|
66
66
|
validation = this.validation,
|
|
67
|
-
defaultValue = this.defaultValue
|
|
67
|
+
defaultValue = this.defaultValue,
|
|
68
68
|
} = props
|
|
69
69
|
|
|
70
70
|
if (!name) {
|
|
@@ -86,11 +86,11 @@ export default class FormInput {
|
|
|
86
86
|
requireValidType() {
|
|
87
87
|
if (!Object.values(FormInput.TYPES).includes(this.type)) {
|
|
88
88
|
throw new TypeError([
|
|
89
|
-
|
|
90
|
-
[
|
|
91
|
-
|
|
92
|
-
...Object.values(FormInput.TYPES).map(t => ` - ${t}`)
|
|
93
|
-
].join(
|
|
89
|
+
'FormInput.type is invalid!',
|
|
90
|
+
['Provided', this.type].join(': '),
|
|
91
|
+
'Available types:',
|
|
92
|
+
...Object.values(FormInput.TYPES).map((t) => ` - ${t}`),
|
|
93
|
+
].join('\n'))
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
@@ -107,7 +107,7 @@ export default class FormInput {
|
|
|
107
107
|
required: this.required,
|
|
108
108
|
placeholder: this.placeholder,
|
|
109
109
|
options: this.options,
|
|
110
|
-
defaultValue: this.defaultValue
|
|
110
|
+
defaultValue: this.defaultValue,
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
|
|
@@ -117,7 +117,7 @@ export default class FormInput {
|
|
|
117
117
|
*/
|
|
118
118
|
static from(input) {
|
|
119
119
|
if (input instanceof FormInput) return input
|
|
120
|
-
if (
|
|
120
|
+
if (typeof input === 'string') {
|
|
121
121
|
return new FormInput({ name: input, label: input })
|
|
122
122
|
}
|
|
123
123
|
return new FormInput(input)
|
package/src/core/Form/Message.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import UiMessage from "../Message/Message.js"
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* FormMessage – specialized
|
|
4
|
+
* FormMessage – specialized UiMessage for forms.
|
|
5
|
+
* It carries form-specific data and schema for validation.
|
|
5
6
|
*
|
|
6
7
|
* @class FormMessage
|
|
7
|
-
* @extends
|
|
8
|
+
* @extends UiMessage
|
|
8
9
|
*/
|
|
9
|
-
export default class FormMessage extends
|
|
10
|
+
export default class FormMessage extends UiMessage {
|
|
10
11
|
/**
|
|
11
12
|
* Creates a FormMessage.
|
|
12
13
|
*
|
|
@@ -80,4 +81,4 @@ export default class FormMessage extends InputMessage {
|
|
|
80
81
|
|
|
81
82
|
return { isValid: Object.keys(errors).length === 0, errors }
|
|
82
83
|
}
|
|
83
|
-
}
|
|
84
|
+
}
|
package/src/core/InputAdapter.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Event from "@nan0web/event/oop"
|
|
2
|
-
import InputMessage from "./Message/InputMessage.js"
|
|
3
2
|
import CancelError from "./Error/CancelError.js"
|
|
3
|
+
import UiMessage from "./Message/Message.js"
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Abstract input adapter for UI implementations.
|
|
@@ -21,7 +21,7 @@ export default class InputAdapter extends Event {
|
|
|
21
21
|
*/
|
|
22
22
|
start() {
|
|
23
23
|
this.emit('input',
|
|
24
|
-
|
|
24
|
+
UiMessage.from({ body: "Adapter started" })
|
|
25
25
|
)
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -1,12 +1,36 @@
|
|
|
1
|
-
import { Message
|
|
1
|
+
import { Message } from "@nan0web/co"
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* @typedef {Object} MessageBodySchema
|
|
5
|
+
* @property {boolean} [required]
|
|
6
|
+
* @property {string} [help]
|
|
7
|
+
* @property {RegExp} [pattern]
|
|
8
|
+
* @property {string[]} [options]
|
|
9
|
+
* @property {*} [defaultValue]
|
|
10
|
+
* @property {Function} [validate]
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Base message class for UI communications.
|
|
15
|
+
* A message holds structured data (body) defined by a static Body class.
|
|
16
|
+
* It can represent commands, forms, alerts, or any UI unit.
|
|
17
|
+
*
|
|
18
|
+
* @class UiMessage
|
|
19
|
+
* @extends Message
|
|
5
20
|
*
|
|
6
|
-
* @
|
|
7
|
-
*
|
|
21
|
+
* @example
|
|
22
|
+
* class UserLoginMessage extends UiMessage {
|
|
23
|
+
* static Body = class {
|
|
24
|
+
* static username = { required: true, help: "Enter username" }
|
|
25
|
+
* static password = { required: true, type: "password" }
|
|
26
|
+
* constructor({ username = "", password = "" }) {
|
|
27
|
+
* this.username = username
|
|
28
|
+
* this.password = password
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
8
32
|
*/
|
|
9
|
-
class
|
|
33
|
+
export default class UiMessage extends Message {
|
|
10
34
|
static TYPES = {
|
|
11
35
|
TEXT: 'text',
|
|
12
36
|
FORM: 'form',
|
|
@@ -25,7 +49,7 @@ class UIMessage extends BaseMessage {
|
|
|
25
49
|
id = ""
|
|
26
50
|
|
|
27
51
|
/**
|
|
28
|
-
* Creates a
|
|
52
|
+
* Creates a UiMessage.
|
|
29
53
|
*
|
|
30
54
|
* @param {Object} [input={}] - Message properties.
|
|
31
55
|
*/
|
|
@@ -45,14 +69,62 @@ class UIMessage extends BaseMessage {
|
|
|
45
69
|
}
|
|
46
70
|
|
|
47
71
|
/**
|
|
48
|
-
*
|
|
72
|
+
* Checks whether the message contains any body content.
|
|
49
73
|
*
|
|
50
|
-
* @
|
|
51
|
-
* @returns {UIMessage}
|
|
74
|
+
* @returns {boolean}
|
|
52
75
|
*/
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
76
|
+
get empty() {
|
|
77
|
+
return !this.body
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Validates the message body against its schema.
|
|
82
|
+
*
|
|
83
|
+
* NOTE: The signature must exactly match `Message.validate` – it returns a
|
|
84
|
+
* `Map<string,string>` regardless of the generic type, otherwise TypeScript
|
|
85
|
+
* reports incompatibility with the base class.
|
|
86
|
+
*
|
|
87
|
+
* @param {any} [body=this.body] - Optional body to validate.
|
|
88
|
+
* @returns {Map<string,string>} Map of validation errors, empty if valid.
|
|
89
|
+
*/
|
|
90
|
+
validate(body = this.body) {
|
|
91
|
+
/** @type {any} */
|
|
92
|
+
const Class = /** @type {typeof Message} */ (this.constructor).Body
|
|
93
|
+
const result = new Map()
|
|
94
|
+
const entries = /** @type {Array<[string, MessageBodySchema]>} */ (Object.entries(Class))
|
|
95
|
+
|
|
96
|
+
for (const [field, schema] of entries) {
|
|
97
|
+
const value = body[field]
|
|
98
|
+
const fn = schema?.validate
|
|
99
|
+
if ("function" === typeof fn) {
|
|
100
|
+
const ok = fn.apply(body, [value])
|
|
101
|
+
if (ok !== true) {
|
|
102
|
+
result.set(field, String(ok))
|
|
103
|
+
continue
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const required = schema?.required ?? false
|
|
107
|
+
if (required && !value) {
|
|
108
|
+
result.set(field, "Required")
|
|
109
|
+
continue
|
|
110
|
+
}
|
|
111
|
+
if (schema?.pattern && schema.pattern instanceof RegExp) {
|
|
112
|
+
if (!schema.pattern.test(value)) {
|
|
113
|
+
result.set(field, `Does not match pattern: ${schema.pattern}`)
|
|
114
|
+
continue
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (schema?.options) {
|
|
118
|
+
if (!Array.isArray(schema.options)) {
|
|
119
|
+
throw new Error("Schema options must be an array of possible values")
|
|
120
|
+
}
|
|
121
|
+
if (!schema.options.includes(value)) {
|
|
122
|
+
result.set(field, "Enumeration must have one value")
|
|
123
|
+
continue
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return result
|
|
56
128
|
}
|
|
57
129
|
|
|
58
130
|
/**
|
|
@@ -61,17 +133,35 @@ class UIMessage extends BaseMessage {
|
|
|
61
133
|
* @returns {boolean}
|
|
62
134
|
*/
|
|
63
135
|
isValidType() {
|
|
64
|
-
return Object.values(
|
|
136
|
+
return Object.values(UiMessage.TYPES).includes(this.type)
|
|
65
137
|
}
|
|
66
138
|
|
|
67
139
|
/**
|
|
68
|
-
*
|
|
140
|
+
* Creates a UiMessage instance from plain data.
|
|
69
141
|
*
|
|
70
|
-
* @
|
|
142
|
+
* @param {Object} data - Message data.
|
|
143
|
+
* @returns {UiMessage}
|
|
71
144
|
*/
|
|
72
|
-
|
|
73
|
-
|
|
145
|
+
static from(data) {
|
|
146
|
+
if (data instanceof UiMessage) return data
|
|
147
|
+
return new this(data)
|
|
74
148
|
}
|
|
75
|
-
}
|
|
76
149
|
|
|
77
|
-
|
|
150
|
+
/**
|
|
151
|
+
* Initializes body from input using static Body schema.
|
|
152
|
+
*
|
|
153
|
+
* @param {Object} input - Input object.
|
|
154
|
+
* @param {Function} BodyClass - Static body class with defaults and schema.
|
|
155
|
+
* @returns {Object} Parsed body.
|
|
156
|
+
*/
|
|
157
|
+
static parseBody(input = {}, BodyClass) {
|
|
158
|
+
const result = {}
|
|
159
|
+
const entries = /** @type {Array<[string, MessageBodySchema]>} */ (Object.entries(BodyClass))
|
|
160
|
+
|
|
161
|
+
for (const [field, schema] of entries) {
|
|
162
|
+
const { defaultValue = undefined, ...schemaProps } = schema
|
|
163
|
+
result[field] = input[field] ?? defaultValue
|
|
164
|
+
}
|
|
165
|
+
return result
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import UiMessage from "./Message.js"
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* OutputMessage – message sent from the system to the UI.
|
|
5
5
|
*
|
|
6
6
|
* @class OutputMessage
|
|
7
|
-
* @extends
|
|
7
|
+
* @extends UiMessage
|
|
8
8
|
*/
|
|
9
|
-
export default class OutputMessage extends
|
|
9
|
+
export default class OutputMessage extends UiMessage {
|
|
10
10
|
static PRIORITY = {
|
|
11
11
|
LOW: 0,
|
|
12
12
|
NORMAL: 1,
|
|
@@ -52,9 +52,9 @@ export default class OutputMessage extends UIMessage {
|
|
|
52
52
|
this.priority = Number(priority)
|
|
53
53
|
|
|
54
54
|
if (!this.type && this.error) {
|
|
55
|
-
this.type =
|
|
55
|
+
this.type = UiMessage.TYPES.ERROR
|
|
56
56
|
} else if (!this.type) {
|
|
57
|
-
this.type =
|
|
57
|
+
this.type = UiMessage.TYPES.INFO
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -72,11 +72,11 @@ export default class OutputMessage extends UIMessage {
|
|
|
72
72
|
}
|
|
73
73
|
/** @returns {boolean} */
|
|
74
74
|
get isError() {
|
|
75
|
-
return this.error !== null || this.type ===
|
|
75
|
+
return this.error !== null || this.type === UiMessage.TYPES.ERROR
|
|
76
76
|
}
|
|
77
77
|
/** @returns {boolean} */
|
|
78
78
|
get isInfo() {
|
|
79
|
-
return this.type ===
|
|
79
|
+
return this.type === UiMessage.TYPES.INFO || this.type === UiMessage.TYPES.SUCCESS
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import InputMessage from "./InputMessage.js"
|
|
1
|
+
import UiMessage from "./Message.js"
|
|
3
2
|
import OutputMessage from "./OutputMessage.js"
|
|
4
3
|
|
|
5
|
-
export {
|
|
4
|
+
export { UiMessage, OutputMessage }
|
|
6
5
|
|
|
7
|
-
export default
|
|
6
|
+
export default UiMessage
|
package/src/core/Stream.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import StreamEntry from "./StreamEntry.js"
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Agnostic UI stream for processing progress.
|
|
4
|
+
* Agnostic UI stream for processing progress using async generators.
|
|
5
5
|
*
|
|
6
6
|
* @class UIStream
|
|
7
7
|
*/
|
|
@@ -16,6 +16,9 @@ export default class UIStream {
|
|
|
16
16
|
static createProcessor(signal, processorFn) {
|
|
17
17
|
return async function* () {
|
|
18
18
|
try {
|
|
19
|
+
if (signal.aborted) {
|
|
20
|
+
throw new DOMException('Aborted', 'AbortError')
|
|
21
|
+
}
|
|
19
22
|
const result = await processorFn()
|
|
20
23
|
yield result
|
|
21
24
|
} catch (/** @type {any} */ error) {
|
|
@@ -29,17 +32,17 @@ export default class UIStream {
|
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
/**
|
|
32
|
-
* Runs
|
|
35
|
+
* Runs an async generator with progress callbacks and abort handling.
|
|
33
36
|
*
|
|
34
37
|
* @param {AbortSignal} signal - Abort signal.
|
|
35
|
-
* @param {
|
|
38
|
+
* @param {() => AsyncGenerator<StreamEntry>} generatorFn - Function that returns an async generator.
|
|
36
39
|
* @param {Function} [onProgress] - Called with (progress, item).
|
|
37
40
|
* @param {Function} [onError] - Called with (errorMessage, item).
|
|
38
41
|
* @param {Function} [onComplete] - Called with (item) when done.
|
|
39
42
|
* @returns {Promise<void>}
|
|
40
43
|
*/
|
|
41
|
-
static async process(signal,
|
|
42
|
-
const iter =
|
|
44
|
+
static async process(signal, generatorFn, onProgress, onError, onComplete) {
|
|
45
|
+
const iter = generatorFn()
|
|
43
46
|
|
|
44
47
|
try {
|
|
45
48
|
for await (const item of iter) {
|
|
@@ -47,13 +50,11 @@ export default class UIStream {
|
|
|
47
50
|
throw new DOMException('Aborted', 'AbortError')
|
|
48
51
|
}
|
|
49
52
|
|
|
50
|
-
if (item.
|
|
51
|
-
onProgress?.(item.progress, item)
|
|
52
|
-
} else if (item.error) {
|
|
53
|
-
onError?.(item.error, item)
|
|
54
|
-
} else if (item.done) {
|
|
53
|
+
if (item.done) {
|
|
55
54
|
onComplete?.(item)
|
|
56
55
|
break
|
|
56
|
+
} else if (item.error) {
|
|
57
|
+
onError?.(item.error, item)
|
|
57
58
|
} else {
|
|
58
59
|
// Intermediate results
|
|
59
60
|
onProgress?.(null, item)
|