@richardmcquiston01/chat-and-react-core 0.5.0 → 0.6.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/LICENSE +201 -201
- package/dist/index.cjs +434 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +208 -41
- package/dist/index.d.ts +208 -41
- package/dist/index.mjs +436 -75
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import { StorageAdapter } from '@richardmcquiston01/storage-adapters';
|
|
2
|
+
export { StorageAdapter, StoreErrorCode, StoreResult, StoredValue } from '@richardmcquiston01/storage-adapters';
|
|
3
|
+
export { LocalStorageAdapterOptions, StorageChangeEvent, StorageEventTarget, WebStorageLike, localStorageAdapter } from '@richardmcquiston01/storage-adapters/browser';
|
|
4
|
+
import { ErrorLogEntry, ErrorLogAdapter as ErrorLogAdapter$1, ConsoleErrorAdapter as ConsoleErrorAdapter$1, ConsoleErrorOptions } from '@richardmcquiston01/error-log-adapter';
|
|
5
|
+
export { ErrorLogEntry, ErrorLogResult } from '@richardmcquiston01/error-log-adapter';
|
|
6
|
+
|
|
1
7
|
type AnswerMap = Record<string, string | string[]>;
|
|
2
8
|
type ChatInputType = 'text' | 'textarea' | 'dropdown' | 'radio' | 'checkbox';
|
|
3
9
|
type JsonLogicRule = Record<string, unknown>;
|
|
@@ -11,6 +17,16 @@ interface ChatQuestion {
|
|
|
11
17
|
title: string;
|
|
12
18
|
'x-chat-placeholder'?: string;
|
|
13
19
|
'x-chat-condition'?: JsonLogicRule | ChatConditionRef;
|
|
20
|
+
/**
|
|
21
|
+
* Shows a "thinking" bubble before this question appears, so the conversation
|
|
22
|
+
* feels less abrupt. A number is the delay in milliseconds (0 turns it off for
|
|
23
|
+
* this question). An object can also set the text shown instead of the dots.
|
|
24
|
+
* Overrides the `thinkingDelay` and `thinkingText` options.
|
|
25
|
+
*/
|
|
26
|
+
'x-chat-thinking'?: number | {
|
|
27
|
+
delay?: number;
|
|
28
|
+
text?: string;
|
|
29
|
+
};
|
|
14
30
|
enum?: string[];
|
|
15
31
|
items?: {
|
|
16
32
|
enum: string[];
|
|
@@ -33,13 +49,31 @@ interface ChatPage {
|
|
|
33
49
|
questions: ChatQuestion[];
|
|
34
50
|
branching: BranchingRule[];
|
|
35
51
|
}
|
|
52
|
+
/** How a bubble leaves the screen when it is hidden: fade out, shrink away ("pop"), or vanish at once. */
|
|
53
|
+
type HideAnimation = 'fade' | 'pop' | 'none';
|
|
36
54
|
interface ChatOptions {
|
|
37
55
|
userResumable: boolean;
|
|
38
56
|
localStorage: boolean;
|
|
39
57
|
autoComplete: boolean;
|
|
58
|
+
/** Show an Edit icon on answered questions so users can change their answers. */
|
|
59
|
+
allowEdit: boolean;
|
|
60
|
+
/** Milliseconds to wait before hiding an answered question bubble. 0 keeps them visible. */
|
|
61
|
+
autoHideQuestions: number;
|
|
62
|
+
/** Milliseconds to wait before hiding an answer bubble once its question is answered. 0 keeps them visible. */
|
|
63
|
+
autoHideAnswers: number;
|
|
64
|
+
/** How hidden bubbles leave the screen. */
|
|
65
|
+
hideAnimation: HideAnimation;
|
|
66
|
+
/** Milliseconds per character when question text is "typed" onto the screen. 0 shows it at once. */
|
|
67
|
+
typingSpeed: number;
|
|
68
|
+
/** Milliseconds to show a "thinking" bubble before each question. 0 turns it off. */
|
|
69
|
+
thinkingDelay: number;
|
|
70
|
+
/** Text for the thinking bubble. Empty shows animated dots. */
|
|
71
|
+
thinkingText: string;
|
|
72
|
+
/** Show an icon on the Continue button. */
|
|
73
|
+
showContinueIcon: boolean;
|
|
40
74
|
}
|
|
41
75
|
interface ChatSchema {
|
|
42
|
-
|
|
76
|
+
$schema?: string;
|
|
43
77
|
'x-chat-version': '1';
|
|
44
78
|
'x-chat-options'?: Partial<ChatOptions>;
|
|
45
79
|
'x-chat-pages': ChatPage[];
|
|
@@ -51,7 +85,23 @@ interface PageSubmitEvent {
|
|
|
51
85
|
pageId: string;
|
|
52
86
|
visitIndex: number;
|
|
53
87
|
answers: AnswerMap;
|
|
88
|
+
/** Present and true when the user re-submitted a page they had already answered. */
|
|
89
|
+
edited?: boolean;
|
|
90
|
+
}
|
|
91
|
+
/** One page the user has submitted, in the order they visited it. */
|
|
92
|
+
interface RouteEntry {
|
|
93
|
+
pageId: string;
|
|
94
|
+
answers: AnswerMap;
|
|
54
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* What happened to the form after `submitPage`:
|
|
98
|
+
* - `advanced`: a normal submit; the form moved to the next page or completed.
|
|
99
|
+
* - `edit-resumed`: an earlier page was edited and the route is unchanged, so the
|
|
100
|
+
* form is back on the page the user was on.
|
|
101
|
+
* - `edit-rerouted`: an earlier page was edited and its branching now leads
|
|
102
|
+
* somewhere else. Later pages were dropped and the form moved to the new page.
|
|
103
|
+
*/
|
|
104
|
+
type SubmitOutcome = 'advanced' | 'edit-resumed' | 'edit-rerouted';
|
|
55
105
|
interface FormCompleteEvent {
|
|
56
106
|
type: 'form:complete';
|
|
57
107
|
sessionId: string;
|
|
@@ -59,49 +109,42 @@ interface FormCompleteEvent {
|
|
|
59
109
|
}
|
|
60
110
|
type ChatEvent = PageSubmitEvent | FormCompleteEvent;
|
|
61
111
|
type ChatErrorCode = 'SCHEMA_INVALID' | 'RULE_EVAL_FAILED' | 'ADAPTER_ERROR' | 'STORAGE_UNAVAILABLE';
|
|
62
|
-
|
|
63
|
-
code: ChatErrorCode;
|
|
64
|
-
message: string;
|
|
65
|
-
cause?: unknown;
|
|
66
|
-
}
|
|
112
|
+
type ChatError = ErrorLogEntry<ChatErrorCode>;
|
|
67
113
|
interface InputAdapter {
|
|
68
114
|
transform(raw: unknown): unknown;
|
|
69
115
|
}
|
|
70
116
|
interface OutputAdapter {
|
|
71
117
|
emit(event: ChatEvent): void;
|
|
72
118
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Routes `ChatError`s to a sink. `log` returns a result instead of throwing, so the
|
|
121
|
+
* caller can check `ok`. See `@richardmcquiston01/error-log-adapter`.
|
|
122
|
+
*/
|
|
123
|
+
type ErrorLogAdapter = ErrorLogAdapter$1<ChatErrorCode>;
|
|
124
|
+
|
|
76
125
|
interface ChatControllerConfig {
|
|
77
126
|
schema: unknown;
|
|
78
127
|
inputAdapter?: InputAdapter;
|
|
79
128
|
outputAdapter?: OutputAdapter;
|
|
80
129
|
errorAdapter?: ErrorLogAdapter;
|
|
130
|
+
/**
|
|
131
|
+
* Option values that win over the schema's `x-chat-options`, for example ones
|
|
132
|
+
* read from element attributes.
|
|
133
|
+
*/
|
|
134
|
+
options?: Partial<ChatOptions>;
|
|
81
135
|
}
|
|
82
136
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
* @param raw - The raw input to validate.
|
|
87
|
-
* @returns The typed {@link ChatSchema} if valid.
|
|
88
|
-
* @throws `{ code: 'SCHEMA_INVALID', message: string }` on any validation failure.
|
|
89
|
-
*/
|
|
90
|
-
declare function parseSchema(raw: unknown): ChatSchema;
|
|
137
|
+
declare class BrowserEventAdapter implements OutputAdapter {
|
|
138
|
+
emit(event: ChatEvent): void;
|
|
139
|
+
}
|
|
91
140
|
|
|
92
|
-
/**
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
* @param rule The JSON Logic rule to evaluate
|
|
100
|
-
* @param answers The answer map to evaluate the rule against
|
|
101
|
-
* @returns The boolean result of the rule evaluation
|
|
102
|
-
* @throws {{ code: 'RULE_EVAL_FAILED', message: string, cause: unknown }} if rule evaluation fails
|
|
103
|
-
*/
|
|
104
|
-
evaluate(rule: JsonLogicRule, answers: AnswerMap): boolean;
|
|
141
|
+
/** Writes errors to `console.error`, prefixed with `[chat-and-react]` unless overridden. */
|
|
142
|
+
declare class ConsoleErrorAdapter extends ConsoleErrorAdapter$1<ChatErrorCode> {
|
|
143
|
+
constructor(options?: ConsoleErrorOptions);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
declare class IdentityInputAdapter implements InputAdapter {
|
|
147
|
+
transform(raw: unknown): unknown;
|
|
105
148
|
}
|
|
106
149
|
|
|
107
150
|
interface StorageBackend {
|
|
@@ -115,7 +158,22 @@ interface SessionProgress {
|
|
|
115
158
|
currentPageId: string;
|
|
116
159
|
answers: AnswerMap;
|
|
117
160
|
}
|
|
118
|
-
|
|
161
|
+
/**
|
|
162
|
+
* What `ChatController` needs from session storage. `SessionManager` keeps it in a
|
|
163
|
+
* synchronous Web Storage object; `AdapterSessionManager` keeps it in a `StorageAdapter`.
|
|
164
|
+
*/
|
|
165
|
+
interface SessionStore {
|
|
166
|
+
readonly isStorageAvailable: boolean;
|
|
167
|
+
getSessionId(): string;
|
|
168
|
+
saveProgress(currentPageId: string, answers: AnswerMap): void;
|
|
169
|
+
loadProgress(): SessionProgress | null;
|
|
170
|
+
saveInput(questionId: string, value: string | string[]): void;
|
|
171
|
+
loadInput(questionId: string): string | string[] | null;
|
|
172
|
+
clearAll(): void;
|
|
173
|
+
/** Stops listening for changes made elsewhere. Called when the controller is destroyed. */
|
|
174
|
+
dispose?(): void;
|
|
175
|
+
}
|
|
176
|
+
declare class SessionManager implements SessionStore {
|
|
119
177
|
private readonly storage;
|
|
120
178
|
private readonly sessionId;
|
|
121
179
|
private readonly available;
|
|
@@ -146,12 +204,18 @@ declare class ChatController {
|
|
|
146
204
|
private currentPage;
|
|
147
205
|
private allAnswers;
|
|
148
206
|
private visitIndex;
|
|
207
|
+
/** Pages submitted in this session, in visit order. Used for editing earlier answers. */
|
|
208
|
+
private route;
|
|
209
|
+
/** Index in `route` of the page being edited, or null when not editing. */
|
|
210
|
+
private editIndex;
|
|
211
|
+
/** The page the user was on before they started editing an earlier one. */
|
|
212
|
+
private resumePageId;
|
|
149
213
|
private readonly listeners;
|
|
150
214
|
/**
|
|
151
215
|
* @param config - Controller configuration including schema and optional adapters.
|
|
152
216
|
* @param sessionManager - Optional session manager for test injection.
|
|
153
217
|
*/
|
|
154
|
-
constructor(config: ChatControllerConfig, sessionManager?:
|
|
218
|
+
constructor(config: ChatControllerConfig, sessionManager?: SessionStore);
|
|
155
219
|
/**
|
|
156
220
|
* Initialises the controller: parses the schema, resolves options,
|
|
157
221
|
* restores any saved session, and positions to the first page.
|
|
@@ -170,6 +234,24 @@ declare class ChatController {
|
|
|
170
234
|
* Returns the resolved options for the current session (schema options merged with defaults).
|
|
171
235
|
*/
|
|
172
236
|
getOptions(): ChatOptions;
|
|
237
|
+
/**
|
|
238
|
+
* Returns the pages the user has submitted in this session, in visit order.
|
|
239
|
+
* A session restored from storage starts with an empty route.
|
|
240
|
+
*/
|
|
241
|
+
getRoute(): RouteEntry[];
|
|
242
|
+
/**
|
|
243
|
+
* Returns every answer given so far, for pre-filling inputs.
|
|
244
|
+
*/
|
|
245
|
+
getAnswers(): AnswerMap;
|
|
246
|
+
/**
|
|
247
|
+
* Re-opens a page the user already submitted so its answers can be changed.
|
|
248
|
+
* Call `submitPage` afterwards to save the new answers. Not available once the
|
|
249
|
+
* form is complete.
|
|
250
|
+
*
|
|
251
|
+
* @param routeIndex - Position of the page in `getRoute()`.
|
|
252
|
+
* @returns true when the page was re-opened.
|
|
253
|
+
*/
|
|
254
|
+
reopenPage(routeIndex: number): boolean;
|
|
173
255
|
/**
|
|
174
256
|
* Submits the answers for the current page and advances the session.
|
|
175
257
|
* - Accumulates answers into `allAnswers`.
|
|
@@ -177,9 +259,15 @@ declare class ChatController {
|
|
|
177
259
|
* - Resolves branching to find the next page.
|
|
178
260
|
* - Emits `form:complete` and clears session when `**FORM_COMPLETED**` is reached.
|
|
179
261
|
*
|
|
262
|
+
* When the page was re-opened with `reopenPage`, the new answers replace the
|
|
263
|
+
* old ones and `page:submit` is emitted with `edited: true`. If the page's
|
|
264
|
+
* branching still leads to the same next page, the form returns to the page the
|
|
265
|
+
* user was on. If it leads somewhere else, the pages that followed it are
|
|
266
|
+
* dropped, along with their answers, and the form continues from the new page.
|
|
267
|
+
*
|
|
180
268
|
* @param pageAnswers - Answers collected for the current page.
|
|
181
269
|
*/
|
|
182
|
-
submitPage(pageAnswers: AnswerMap):
|
|
270
|
+
submitPage(pageAnswers: AnswerMap): SubmitOutcome;
|
|
183
271
|
/**
|
|
184
272
|
* Registers an event listener for the given event type.
|
|
185
273
|
*
|
|
@@ -203,6 +291,12 @@ declare class ChatController {
|
|
|
203
291
|
* Returns true when no condition is present. On evaluation failure, logs and returns false.
|
|
204
292
|
*/
|
|
205
293
|
private isQuestionVisible;
|
|
294
|
+
/**
|
|
295
|
+
* The answers question conditions are checked against. While an earlier page is
|
|
296
|
+
* being edited, that page's own answers are left out, so its questions are
|
|
297
|
+
* shown exactly as they were the first time the page was asked.
|
|
298
|
+
*/
|
|
299
|
+
private answersForRules;
|
|
206
300
|
/**
|
|
207
301
|
* Walks the branching rules top-to-bottom. Returns the first match's destination_id.
|
|
208
302
|
* Falls back to `**FORM_COMPLETED**` if nothing matches (should not happen with valid schemas).
|
|
@@ -221,18 +315,91 @@ declare class ChatController {
|
|
|
221
315
|
* Used for unrecoverable errors (schema invalid, adapter failure on start).
|
|
222
316
|
*/
|
|
223
317
|
private handleFatal;
|
|
318
|
+
/**
|
|
319
|
+
* Sends the error to the error adapter without waiting for it. If the adapter
|
|
320
|
+
* reports a failure, rejects, or throws, that is written to `console.error` so
|
|
321
|
+
* the original error is never lost and the form keeps running.
|
|
322
|
+
*/
|
|
323
|
+
private logError;
|
|
224
324
|
}
|
|
225
325
|
|
|
226
|
-
|
|
227
|
-
|
|
326
|
+
/**
|
|
327
|
+
* Evaluates JSON Logic rules against provided answers.
|
|
328
|
+
* Wraps json-logic-js to provide type-safe rule evaluation.
|
|
329
|
+
*/
|
|
330
|
+
declare class RuleEngine {
|
|
331
|
+
/**
|
|
332
|
+
* Evaluates a JSON Logic rule against an answer map.
|
|
333
|
+
* @param rule The JSON Logic rule to evaluate
|
|
334
|
+
* @param answers The answer map to evaluate the rule against
|
|
335
|
+
* @returns The boolean result of the rule evaluation
|
|
336
|
+
* @throws {{ code: 'RULE_EVAL_FAILED', message: string, cause: unknown }} if rule evaluation fails
|
|
337
|
+
*/
|
|
338
|
+
evaluate(rule: JsonLogicRule, answers: AnswerMap): boolean;
|
|
228
339
|
}
|
|
229
340
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
341
|
+
/**
|
|
342
|
+
* Parses and validates an unknown input as a {@link ChatSchema}.
|
|
343
|
+
*
|
|
344
|
+
* @param raw - The raw input to validate.
|
|
345
|
+
* @returns The typed {@link ChatSchema} if valid.
|
|
346
|
+
* @throws `{ code: 'SCHEMA_INVALID', message: string }` on any validation failure.
|
|
347
|
+
*/
|
|
348
|
+
declare function parseSchema(raw: unknown): ChatSchema;
|
|
233
349
|
|
|
234
|
-
|
|
235
|
-
|
|
350
|
+
type InputValue = string | string[];
|
|
351
|
+
interface AdapterSessionManagerOptions {
|
|
352
|
+
/** Called when the adapter fails. The session keeps working from memory. */
|
|
353
|
+
onError?: (error: ChatError) => void;
|
|
354
|
+
/**
|
|
355
|
+
* Called when saved progress changes because of a write made elsewhere, such as
|
|
356
|
+
* another tab. `null` means the session was cleared there.
|
|
357
|
+
*/
|
|
358
|
+
onExternalChange?: (progress: SessionProgress | null) => void;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* A session store backed by a `StorageAdapter`. `ChatController` is synchronous, so
|
|
362
|
+
* the saved session is loaded into memory by `create()`, reads come from memory, and
|
|
363
|
+
* writes are saved in the background. Failures are reported through `onError`.
|
|
364
|
+
*
|
|
365
|
+
* Saves carry the revision they were based on. If another tab wrote first, the two
|
|
366
|
+
* are merged: inputs per question, and progress only when this tab saved progress
|
|
367
|
+
* itself, so a tab that only typed cannot overwrite another tab's progress.
|
|
368
|
+
*/
|
|
369
|
+
declare class AdapterSessionManager implements SessionStore {
|
|
370
|
+
private readonly adapter;
|
|
371
|
+
private readonly sessionId;
|
|
372
|
+
private readonly available;
|
|
373
|
+
private readonly options;
|
|
374
|
+
private record;
|
|
375
|
+
private revision;
|
|
376
|
+
private readonly dirtyInputs;
|
|
377
|
+
private dirtyProgress;
|
|
378
|
+
private pending;
|
|
379
|
+
private persisting;
|
|
380
|
+
private readonly signal;
|
|
381
|
+
private unsubscribe;
|
|
382
|
+
private constructor();
|
|
383
|
+
/** Loads (or creates) the session id and its saved record, then returns the manager. */
|
|
384
|
+
static create(adapter: StorageAdapter, options?: AdapterSessionManagerOptions): Promise<AdapterSessionManager>;
|
|
385
|
+
getSessionId(): string;
|
|
386
|
+
get isStorageAvailable(): boolean;
|
|
387
|
+
saveProgress(currentPageId: string, answers: AnswerMap): void;
|
|
388
|
+
loadProgress(): SessionProgress | null;
|
|
389
|
+
saveInput(questionId: string, value: InputValue): void;
|
|
390
|
+
loadInput(questionId: string): InputValue | null;
|
|
391
|
+
clearAll(): void;
|
|
392
|
+
dispose(): void;
|
|
393
|
+
/** Resolves when every save made so far has finished. Mostly for tests. */
|
|
394
|
+
flush(): Promise<void>;
|
|
395
|
+
private report;
|
|
396
|
+
private schedulePersist;
|
|
397
|
+
/** Saves until nothing is left to save. Never rejects. */
|
|
398
|
+
private drain;
|
|
399
|
+
private persistOnce;
|
|
400
|
+
/** Merges a version written elsewhere with this tab's unsaved changes. */
|
|
401
|
+
private applyRemote;
|
|
402
|
+
private removeRecord;
|
|
236
403
|
}
|
|
237
404
|
|
|
238
|
-
export { type AnswerMap, type BranchingAlways, type BranchingIf, type BranchingRule, BrowserEventAdapter, type ChatConditionRef, ChatController, type ChatControllerConfig, type ChatError, type ChatErrorCode, type ChatEvent, type ChatInputType, type ChatOptions, type ChatPage, type ChatQuestion, type ChatSchema, ConsoleErrorAdapter, type ErrorLogAdapter, type FormCompleteEvent, IdentityInputAdapter, type InputAdapter, type JsonLogicRule, type OutputAdapter, type PageSubmitEvent, RuleEngine, SessionManager, type SessionProgress, type StorageBackend, parseSchema };
|
|
405
|
+
export { AdapterSessionManager, type AdapterSessionManagerOptions, type AnswerMap, type BranchingAlways, type BranchingIf, type BranchingRule, BrowserEventAdapter, type ChatConditionRef, ChatController, type ChatControllerConfig, type ChatError, type ChatErrorCode, type ChatEvent, type ChatInputType, type ChatOptions, type ChatPage, type ChatQuestion, type ChatSchema, ConsoleErrorAdapter, type ErrorLogAdapter, type FormCompleteEvent, type HideAnimation, IdentityInputAdapter, type InputAdapter, type JsonLogicRule, type OutputAdapter, type PageSubmitEvent, type RouteEntry, RuleEngine, SessionManager, type SessionProgress, type SessionStore, type StorageBackend, type SubmitOutcome, parseSchema };
|