freesail 0.0.1 → 0.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 +190 -5
- package/docs/A2UX_Protocol.md +183 -0
- package/docs/Agents.md +218 -0
- package/docs/Architecture.md +285 -0
- package/docs/CatalogReference.md +377 -0
- package/docs/GettingStarted.md +230 -0
- package/examples/demo/package.json +21 -0
- package/examples/demo/public/index.html +381 -0
- package/examples/demo/server.js +253 -0
- package/package.json +38 -5
- package/packages/core/package.json +48 -0
- package/packages/core/src/functions.ts +403 -0
- package/packages/core/src/index.ts +214 -0
- package/packages/core/src/parser.ts +270 -0
- package/packages/core/src/protocol.ts +254 -0
- package/packages/core/src/store.ts +452 -0
- package/packages/core/src/transport.ts +439 -0
- package/packages/core/src/types.ts +209 -0
- package/packages/core/tsconfig.json +10 -0
- package/packages/lit-ui/package.json +44 -0
- package/packages/lit-ui/src/catalogs/standard/catalog.json +405 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Badge.ts +96 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Button.ts +147 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Card.ts +78 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Checkbox.ts +94 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Column.ts +66 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Divider.ts +59 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Image.ts +54 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Input.ts +125 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Progress.ts +79 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Row.ts +68 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Select.ts +110 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Spacer.ts +37 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Spinner.ts +76 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Text.ts +86 -0
- package/packages/lit-ui/src/catalogs/standard/elements/index.ts +18 -0
- package/packages/lit-ui/src/catalogs/standard/index.ts +17 -0
- package/packages/lit-ui/src/index.ts +84 -0
- package/packages/lit-ui/src/renderer.ts +211 -0
- package/packages/lit-ui/src/types.ts +49 -0
- package/packages/lit-ui/src/utils/define-props.ts +157 -0
- package/packages/lit-ui/src/utils/index.ts +2 -0
- package/packages/lit-ui/src/utils/registry.ts +139 -0
- package/packages/lit-ui/tsconfig.json +11 -0
- package/packages/server/package.json +61 -0
- package/packages/server/src/adapters/index.ts +5 -0
- package/packages/server/src/adapters/langchain.ts +175 -0
- package/packages/server/src/adapters/openai.ts +209 -0
- package/packages/server/src/catalog-loader.ts +311 -0
- package/packages/server/src/index.ts +142 -0
- package/packages/server/src/stream.ts +329 -0
- package/packages/server/tsconfig.json +11 -0
- package/tsconfig.base.json +23 -0
- package/index.js +0 -3
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard Functions Library
|
|
3
|
+
*
|
|
4
|
+
* A2UI Standard Functions for client-side data logic.
|
|
5
|
+
* Used in template expressions like ${formatCurrency(price, 'USD')}.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// Currency Formatting
|
|
10
|
+
// =============================================================================
|
|
11
|
+
|
|
12
|
+
export interface CurrencyOptions {
|
|
13
|
+
locale?: string;
|
|
14
|
+
minimumFractionDigits?: number;
|
|
15
|
+
maximumFractionDigits?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Format a number as currency
|
|
20
|
+
*/
|
|
21
|
+
export function formatCurrency(
|
|
22
|
+
value: number,
|
|
23
|
+
currency: string = 'USD',
|
|
24
|
+
options: CurrencyOptions = {}
|
|
25
|
+
): string {
|
|
26
|
+
const {
|
|
27
|
+
locale = 'en-US',
|
|
28
|
+
minimumFractionDigits = 2,
|
|
29
|
+
maximumFractionDigits = 2,
|
|
30
|
+
} = options;
|
|
31
|
+
|
|
32
|
+
return new Intl.NumberFormat(locale, {
|
|
33
|
+
style: 'currency',
|
|
34
|
+
currency,
|
|
35
|
+
minimumFractionDigits,
|
|
36
|
+
maximumFractionDigits,
|
|
37
|
+
}).format(value);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// =============================================================================
|
|
41
|
+
// Number Formatting
|
|
42
|
+
// =============================================================================
|
|
43
|
+
|
|
44
|
+
export interface NumberOptions {
|
|
45
|
+
locale?: string;
|
|
46
|
+
minimumFractionDigits?: number;
|
|
47
|
+
maximumFractionDigits?: number;
|
|
48
|
+
useGrouping?: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Format a number with locale-specific formatting
|
|
53
|
+
*/
|
|
54
|
+
export function formatNumber(
|
|
55
|
+
value: number,
|
|
56
|
+
options: NumberOptions = {}
|
|
57
|
+
): string {
|
|
58
|
+
const {
|
|
59
|
+
locale = 'en-US',
|
|
60
|
+
minimumFractionDigits = 0,
|
|
61
|
+
maximumFractionDigits = 2,
|
|
62
|
+
useGrouping = true,
|
|
63
|
+
} = options;
|
|
64
|
+
|
|
65
|
+
return new Intl.NumberFormat(locale, {
|
|
66
|
+
minimumFractionDigits,
|
|
67
|
+
maximumFractionDigits,
|
|
68
|
+
useGrouping,
|
|
69
|
+
}).format(value);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Format a number as a percentage
|
|
74
|
+
*/
|
|
75
|
+
export function formatPercent(
|
|
76
|
+
value: number,
|
|
77
|
+
options: { locale?: string; decimals?: number } = {}
|
|
78
|
+
): string {
|
|
79
|
+
const { locale = 'en-US', decimals = 0 } = options;
|
|
80
|
+
|
|
81
|
+
return new Intl.NumberFormat(locale, {
|
|
82
|
+
style: 'percent',
|
|
83
|
+
minimumFractionDigits: decimals,
|
|
84
|
+
maximumFractionDigits: decimals,
|
|
85
|
+
}).format(value);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Format bytes into human-readable size
|
|
90
|
+
*/
|
|
91
|
+
export function formatBytes(bytes: number, decimals: number = 2): string {
|
|
92
|
+
if (bytes === 0) return '0 Bytes';
|
|
93
|
+
|
|
94
|
+
const k = 1024;
|
|
95
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
|
96
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
97
|
+
|
|
98
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))} ${sizes[i]}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// =============================================================================
|
|
102
|
+
// Date Formatting
|
|
103
|
+
// =============================================================================
|
|
104
|
+
|
|
105
|
+
export type DateFormatStyle = 'short' | 'medium' | 'long' | 'full';
|
|
106
|
+
|
|
107
|
+
export interface DateOptions {
|
|
108
|
+
locale?: string;
|
|
109
|
+
dateStyle?: DateFormatStyle;
|
|
110
|
+
timeStyle?: DateFormatStyle;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Format a date/time value
|
|
115
|
+
*/
|
|
116
|
+
export function formatDate(
|
|
117
|
+
value: Date | string | number,
|
|
118
|
+
options: DateOptions = {}
|
|
119
|
+
): string {
|
|
120
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
121
|
+
const { locale = 'en-US', dateStyle = 'medium', timeStyle } = options;
|
|
122
|
+
|
|
123
|
+
return new Intl.DateTimeFormat(locale, {
|
|
124
|
+
dateStyle,
|
|
125
|
+
timeStyle,
|
|
126
|
+
} as Intl.DateTimeFormatOptions).format(date);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Format a date as relative time (e.g., "2 hours ago")
|
|
131
|
+
*/
|
|
132
|
+
export function formatRelativeTime(
|
|
133
|
+
value: Date | string | number,
|
|
134
|
+
locale: string = 'en-US'
|
|
135
|
+
): string {
|
|
136
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
137
|
+
const now = new Date();
|
|
138
|
+
const diffMs = date.getTime() - now.getTime();
|
|
139
|
+
const diffSecs = Math.round(diffMs / 1000);
|
|
140
|
+
const diffMins = Math.round(diffSecs / 60);
|
|
141
|
+
const diffHours = Math.round(diffMins / 60);
|
|
142
|
+
const diffDays = Math.round(diffHours / 24);
|
|
143
|
+
|
|
144
|
+
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
|
|
145
|
+
|
|
146
|
+
if (Math.abs(diffSecs) < 60) {
|
|
147
|
+
return rtf.format(diffSecs, 'second');
|
|
148
|
+
} else if (Math.abs(diffMins) < 60) {
|
|
149
|
+
return rtf.format(diffMins, 'minute');
|
|
150
|
+
} else if (Math.abs(diffHours) < 24) {
|
|
151
|
+
return rtf.format(diffHours, 'hour');
|
|
152
|
+
} else {
|
|
153
|
+
return rtf.format(diffDays, 'day');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// =============================================================================
|
|
158
|
+
// String Formatting
|
|
159
|
+
// =============================================================================
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Pluralize a word based on count
|
|
163
|
+
*/
|
|
164
|
+
export function pluralize(
|
|
165
|
+
count: number,
|
|
166
|
+
singular: string,
|
|
167
|
+
plural?: string
|
|
168
|
+
): string {
|
|
169
|
+
if (count === 1) return singular;
|
|
170
|
+
return plural ?? `${singular}s`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Truncate text with ellipsis
|
|
175
|
+
*/
|
|
176
|
+
export function truncate(
|
|
177
|
+
text: string,
|
|
178
|
+
maxLength: number,
|
|
179
|
+
suffix: string = '...'
|
|
180
|
+
): string {
|
|
181
|
+
if (text.length <= maxLength) return text;
|
|
182
|
+
return text.slice(0, maxLength - suffix.length) + suffix;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Capitalize the first letter of a string
|
|
187
|
+
*/
|
|
188
|
+
export function capitalize(text: string): string {
|
|
189
|
+
if (!text) return '';
|
|
190
|
+
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Convert a string to title case
|
|
195
|
+
*/
|
|
196
|
+
export function titleCase(text: string): string {
|
|
197
|
+
return text
|
|
198
|
+
.toLowerCase()
|
|
199
|
+
.split(' ')
|
|
200
|
+
.map(word => capitalize(word))
|
|
201
|
+
.join(' ');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Format a string using named placeholders
|
|
206
|
+
*/
|
|
207
|
+
export function format(
|
|
208
|
+
template: string,
|
|
209
|
+
values: Record<string, unknown>
|
|
210
|
+
): string {
|
|
211
|
+
return template.replace(/\{(\w+)\}/g, (_, key) => {
|
|
212
|
+
return key in values ? String(values[key]) : `{${key}}`;
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// =============================================================================
|
|
217
|
+
// Regex Functions
|
|
218
|
+
// =============================================================================
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Test if a string matches a pattern
|
|
222
|
+
*/
|
|
223
|
+
export function regexTest(
|
|
224
|
+
text: string,
|
|
225
|
+
pattern: string,
|
|
226
|
+
flags: string = ''
|
|
227
|
+
): boolean {
|
|
228
|
+
const regex = new RegExp(pattern, flags);
|
|
229
|
+
return regex.test(text);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Extract matches from a string
|
|
234
|
+
*/
|
|
235
|
+
export function regexMatch(
|
|
236
|
+
text: string,
|
|
237
|
+
pattern: string,
|
|
238
|
+
flags: string = ''
|
|
239
|
+
): string[] {
|
|
240
|
+
const regex = new RegExp(pattern, flags);
|
|
241
|
+
const match = text.match(regex);
|
|
242
|
+
return match ? Array.from(match) : [];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Replace text using regex
|
|
247
|
+
*/
|
|
248
|
+
export function regexReplace(
|
|
249
|
+
text: string,
|
|
250
|
+
pattern: string,
|
|
251
|
+
replacement: string,
|
|
252
|
+
flags: string = 'g'
|
|
253
|
+
): string {
|
|
254
|
+
const regex = new RegExp(pattern, flags);
|
|
255
|
+
return text.replace(regex, replacement);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// =============================================================================
|
|
259
|
+
// Validation Functions
|
|
260
|
+
// =============================================================================
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Check if a value is empty
|
|
264
|
+
*/
|
|
265
|
+
export function isEmpty(value: unknown): boolean {
|
|
266
|
+
if (value === null || value === undefined) return true;
|
|
267
|
+
if (typeof value === 'string') return value.trim() === '';
|
|
268
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
269
|
+
if (typeof value === 'object') return Object.keys(value).length === 0;
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Check if a string is a valid email
|
|
275
|
+
*/
|
|
276
|
+
export function isEmail(value: string): boolean {
|
|
277
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
278
|
+
return emailRegex.test(value);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Check if a string is a valid URL
|
|
283
|
+
*/
|
|
284
|
+
export function isUrl(value: string): boolean {
|
|
285
|
+
try {
|
|
286
|
+
new URL(value);
|
|
287
|
+
return true;
|
|
288
|
+
} catch {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// =============================================================================
|
|
294
|
+
// Array Functions
|
|
295
|
+
// =============================================================================
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Get the first N items from an array
|
|
299
|
+
*/
|
|
300
|
+
export function take<T>(array: T[], count: number): T[] {
|
|
301
|
+
return array.slice(0, count);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Get the last N items from an array
|
|
306
|
+
*/
|
|
307
|
+
export function takeLast<T>(array: T[], count: number): T[] {
|
|
308
|
+
return array.slice(-count);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Join array items with a separator
|
|
313
|
+
*/
|
|
314
|
+
export function join(
|
|
315
|
+
array: unknown[],
|
|
316
|
+
separator: string = ', ',
|
|
317
|
+
lastSeparator?: string
|
|
318
|
+
): string {
|
|
319
|
+
if (array.length === 0) return '';
|
|
320
|
+
if (array.length === 1) return String(array[0]);
|
|
321
|
+
|
|
322
|
+
if (lastSeparator) {
|
|
323
|
+
const items = array.map(String);
|
|
324
|
+
const last = items.pop();
|
|
325
|
+
return `${items.join(separator)}${lastSeparator}${last}`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return array.map(String).join(separator);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// =============================================================================
|
|
332
|
+
// Conditional Functions
|
|
333
|
+
// =============================================================================
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Return value based on condition
|
|
337
|
+
*/
|
|
338
|
+
export function ifElse<T>(
|
|
339
|
+
condition: boolean,
|
|
340
|
+
trueValue: T,
|
|
341
|
+
falseValue: T
|
|
342
|
+
): T {
|
|
343
|
+
return condition ? trueValue : falseValue;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Return first non-null/undefined value
|
|
348
|
+
*/
|
|
349
|
+
export function coalesce<T>(...values: (T | null | undefined)[]): T | undefined {
|
|
350
|
+
for (const value of values) {
|
|
351
|
+
if (value !== null && value !== undefined) return value;
|
|
352
|
+
}
|
|
353
|
+
return undefined;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// =============================================================================
|
|
357
|
+
// Function Registry
|
|
358
|
+
// =============================================================================
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Standard function registry for template expression evaluation
|
|
362
|
+
*/
|
|
363
|
+
export const standardFunctions = {
|
|
364
|
+
// Currency
|
|
365
|
+
formatCurrency,
|
|
366
|
+
|
|
367
|
+
// Numbers
|
|
368
|
+
formatNumber,
|
|
369
|
+
formatPercent,
|
|
370
|
+
formatBytes,
|
|
371
|
+
|
|
372
|
+
// Dates
|
|
373
|
+
formatDate,
|
|
374
|
+
formatRelativeTime,
|
|
375
|
+
|
|
376
|
+
// Strings
|
|
377
|
+
pluralize,
|
|
378
|
+
truncate,
|
|
379
|
+
capitalize,
|
|
380
|
+
titleCase,
|
|
381
|
+
format,
|
|
382
|
+
|
|
383
|
+
// Regex
|
|
384
|
+
regexTest,
|
|
385
|
+
regexMatch,
|
|
386
|
+
regexReplace,
|
|
387
|
+
|
|
388
|
+
// Validation
|
|
389
|
+
isEmpty,
|
|
390
|
+
isEmail,
|
|
391
|
+
isUrl,
|
|
392
|
+
|
|
393
|
+
// Arrays
|
|
394
|
+
take,
|
|
395
|
+
takeLast,
|
|
396
|
+
join,
|
|
397
|
+
|
|
398
|
+
// Conditionals
|
|
399
|
+
ifElse,
|
|
400
|
+
coalesce,
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
export type StandardFunctions = typeof standardFunctions;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Freesail Core SDK
|
|
3
|
+
*
|
|
4
|
+
* The brain of the Freesail SDK - handles A2UX protocol,
|
|
5
|
+
* transport, state management, and standard functions.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Types
|
|
9
|
+
export type {
|
|
10
|
+
A2UXComponent,
|
|
11
|
+
A2UXMessage,
|
|
12
|
+
ServerToClientMessage,
|
|
13
|
+
ClientToServerMessage,
|
|
14
|
+
CreateSurfaceMessage,
|
|
15
|
+
UpdateComponentsMessage,
|
|
16
|
+
UpdateDataModelMessage,
|
|
17
|
+
DeleteSurfaceMessage,
|
|
18
|
+
WatchSurfaceMessage,
|
|
19
|
+
UnwatchSurfaceMessage,
|
|
20
|
+
UserActionMessage,
|
|
21
|
+
WatchSurfaceResponseMessage,
|
|
22
|
+
} from './types.js';
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
isCreateSurface,
|
|
26
|
+
isUpdateComponents,
|
|
27
|
+
isUpdateDataModel,
|
|
28
|
+
isDeleteSurface,
|
|
29
|
+
isWatchSurface,
|
|
30
|
+
isUnwatchSurface,
|
|
31
|
+
isUserAction,
|
|
32
|
+
isWatchSurfaceResponse,
|
|
33
|
+
getMessageType,
|
|
34
|
+
getSurfaceId,
|
|
35
|
+
} from './types.js';
|
|
36
|
+
|
|
37
|
+
// Protocol Handler
|
|
38
|
+
export type {
|
|
39
|
+
ProtocolEventMap,
|
|
40
|
+
ProtocolEventHandler,
|
|
41
|
+
ProtocolHandlerOptions,
|
|
42
|
+
} from './protocol.js';
|
|
43
|
+
|
|
44
|
+
export { A2UXProtocolHandler } from './protocol.js';
|
|
45
|
+
|
|
46
|
+
// Parser
|
|
47
|
+
export type {
|
|
48
|
+
ParseResult,
|
|
49
|
+
ParserOptions,
|
|
50
|
+
} from './parser.js';
|
|
51
|
+
|
|
52
|
+
export {
|
|
53
|
+
JSONStreamParser,
|
|
54
|
+
SSEParser,
|
|
55
|
+
parseTemplateExpression,
|
|
56
|
+
} from './parser.js';
|
|
57
|
+
|
|
58
|
+
// Transport
|
|
59
|
+
export type {
|
|
60
|
+
TransportOptions,
|
|
61
|
+
TransportEventMap,
|
|
62
|
+
TransportEventHandler,
|
|
63
|
+
ConnectionState,
|
|
64
|
+
} from './transport.js';
|
|
65
|
+
|
|
66
|
+
export { FreesailTransport } from './transport.js';
|
|
67
|
+
|
|
68
|
+
// Store
|
|
69
|
+
export type {
|
|
70
|
+
Surface,
|
|
71
|
+
StoreEventMap,
|
|
72
|
+
StoreEventHandler,
|
|
73
|
+
} from './store.js';
|
|
74
|
+
|
|
75
|
+
export { SurfaceStore } from './store.js';
|
|
76
|
+
|
|
77
|
+
// Functions
|
|
78
|
+
export {
|
|
79
|
+
formatCurrency,
|
|
80
|
+
formatNumber,
|
|
81
|
+
formatPercent,
|
|
82
|
+
formatBytes,
|
|
83
|
+
formatDate,
|
|
84
|
+
formatRelativeTime,
|
|
85
|
+
pluralize,
|
|
86
|
+
truncate,
|
|
87
|
+
capitalize,
|
|
88
|
+
titleCase,
|
|
89
|
+
format,
|
|
90
|
+
regexTest,
|
|
91
|
+
regexMatch,
|
|
92
|
+
regexReplace,
|
|
93
|
+
isEmpty,
|
|
94
|
+
isEmail,
|
|
95
|
+
isUrl,
|
|
96
|
+
take,
|
|
97
|
+
takeLast,
|
|
98
|
+
join,
|
|
99
|
+
ifElse,
|
|
100
|
+
coalesce,
|
|
101
|
+
standardFunctions,
|
|
102
|
+
} from './functions.js';
|
|
103
|
+
|
|
104
|
+
export type { StandardFunctions, CurrencyOptions, NumberOptions, DateOptions } from './functions.js';
|
|
105
|
+
|
|
106
|
+
// =============================================================================
|
|
107
|
+
// Convenience: Freesail Client
|
|
108
|
+
// =============================================================================
|
|
109
|
+
|
|
110
|
+
import { FreesailTransport, type TransportOptions } from './transport.js';
|
|
111
|
+
import { A2UXProtocolHandler, type ProtocolHandlerOptions } from './protocol.js';
|
|
112
|
+
import { SurfaceStore } from './store.js';
|
|
113
|
+
import type { A2UXMessage, UserActionMessage, WatchSurfaceResponseMessage } from './types.js';
|
|
114
|
+
|
|
115
|
+
export interface FreesailClientOptions extends TransportOptions {
|
|
116
|
+
/** Protocol handler options */
|
|
117
|
+
protocol?: Omit<ProtocolHandlerOptions, 'store'>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* FreesailClient is the main entry point for client applications.
|
|
122
|
+
* It combines transport, protocol handling, and state management.
|
|
123
|
+
*/
|
|
124
|
+
export class FreesailClient {
|
|
125
|
+
private transport: FreesailTransport;
|
|
126
|
+
private protocol: A2UXProtocolHandler;
|
|
127
|
+
private store: SurfaceStore;
|
|
128
|
+
|
|
129
|
+
constructor(options: FreesailClientOptions) {
|
|
130
|
+
this.store = new SurfaceStore();
|
|
131
|
+
|
|
132
|
+
this.protocol = new A2UXProtocolHandler({
|
|
133
|
+
...options.protocol,
|
|
134
|
+
store: this.store,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
this.transport = new FreesailTransport(options);
|
|
138
|
+
|
|
139
|
+
// Wire up transport messages to protocol handler
|
|
140
|
+
this.transport.on('message', (message: A2UXMessage) => {
|
|
141
|
+
this.protocol.processMessage(message);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Wire up watch callbacks to transport
|
|
145
|
+
this.store.on('watchCallback', (response: WatchSurfaceResponseMessage) => {
|
|
146
|
+
this.transport.sendWatchResponse(response);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Connect to the server
|
|
152
|
+
*/
|
|
153
|
+
async connect(): Promise<void> {
|
|
154
|
+
await this.transport.connect();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Disconnect from the server
|
|
159
|
+
*/
|
|
160
|
+
disconnect(): void {
|
|
161
|
+
this.transport.disconnect();
|
|
162
|
+
this.protocol.destroy();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Send a user action
|
|
167
|
+
*/
|
|
168
|
+
async sendAction(
|
|
169
|
+
surfaceId: string,
|
|
170
|
+
action: string,
|
|
171
|
+
context?: Record<string, unknown>
|
|
172
|
+
): Promise<void> {
|
|
173
|
+
// Use full context from store if not provided
|
|
174
|
+
const fullContext = context ?? this.store.getDataModel(surfaceId) ?? {};
|
|
175
|
+
|
|
176
|
+
const message: UserActionMessage = {
|
|
177
|
+
userAction: {
|
|
178
|
+
surfaceId,
|
|
179
|
+
action,
|
|
180
|
+
context: fullContext,
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
await this.transport.sendAction(message);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get the surface store
|
|
189
|
+
*/
|
|
190
|
+
getStore(): SurfaceStore {
|
|
191
|
+
return this.store;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get the protocol handler
|
|
196
|
+
*/
|
|
197
|
+
getProtocol(): A2UXProtocolHandler {
|
|
198
|
+
return this.protocol;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get the transport
|
|
203
|
+
*/
|
|
204
|
+
getTransport(): FreesailTransport {
|
|
205
|
+
return this.transport;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Check if connected
|
|
210
|
+
*/
|
|
211
|
+
get isConnected(): boolean {
|
|
212
|
+
return this.transport.isConnected;
|
|
213
|
+
}
|
|
214
|
+
}
|